From b252db845e19603faf528cf93fe0c44757a27430 Mon Sep 17 00:00:00 2001 From: James Munns Date: Fri, 5 Dec 2025 14:28:47 +0100 Subject: Move --- CODE_OF_CONDUCT.md | 132 - CONTRIBUTING.md | 48 - Cargo.lock | 918 ------- Cargo.toml | 54 - Embed.toml | 28 - LICENSE | 22 - README.md | 13 - SECURITY.md | 66 - SW-Content-Register.txt | 78 - defmt.x | 6 - deny.toml | 241 -- embassy-mcxa/CODE_OF_CONDUCT.md | 132 + embassy-mcxa/CONTRIBUTING.md | 48 + embassy-mcxa/Cargo.lock | 918 +++++++ embassy-mcxa/Cargo.toml | 54 + embassy-mcxa/Embed.toml | 28 + embassy-mcxa/LICENSE | 22 + embassy-mcxa/README.md | 13 + embassy-mcxa/SECURITY.md | 66 + embassy-mcxa/SW-Content-Register.txt | 78 + embassy-mcxa/defmt.x | 6 + embassy-mcxa/deny.toml | 241 ++ embassy-mcxa/ram.ld | 109 + embassy-mcxa/run.sh | 93 + embassy-mcxa/rustfmt.toml | 3 + embassy-mcxa/src/adc.rs | 409 +++ embassy-mcxa/src/clkout.rs | 169 ++ embassy-mcxa/src/clocks/config.rs | 204 ++ embassy-mcxa/src/clocks/mod.rs | 950 +++++++ embassy-mcxa/src/clocks/periph_helpers.rs | 506 ++++ embassy-mcxa/src/config.rs | 27 + embassy-mcxa/src/dma.rs | 2594 ++++++++++++++++++++ embassy-mcxa/src/gpio.rs | 1062 ++++++++ embassy-mcxa/src/i2c/controller.rs | 455 ++++ embassy-mcxa/src/i2c/mod.rs | 171 ++ embassy-mcxa/src/interrupt.rs | 546 ++++ embassy-mcxa/src/lib.rs | 484 ++++ embassy-mcxa/src/lpuart/buffered.rs | 780 ++++++ embassy-mcxa/src/lpuart/mod.rs | 1733 +++++++++++++ embassy-mcxa/src/ostimer.rs | 745 ++++++ embassy-mcxa/src/pins.rs | 33 + embassy-mcxa/src/rtc.rs | 299 +++ embassy-mcxa/supply-chain/README.md | 149 ++ embassy-mcxa/supply-chain/audits.toml | 349 +++ embassy-mcxa/supply-chain/config.toml | 141 ++ embassy-mcxa/supply-chain/imports.lock | 523 ++++ embassy-mcxa/tools/run_and_attach_rtt.sh | 24 + embassy-mcxa/tools/run_jlink_noblock.sh | 76 + examples/.cargo/config.toml | 17 - examples/.gitignore | 1 - examples/Cargo.lock | 1555 ------------ examples/Cargo.toml | 26 - examples/build.rs | 20 - examples/mcxa/.cargo/config.toml | 17 + examples/mcxa/.gitignore | 1 + examples/mcxa/Cargo.lock | 1555 ++++++++++++ examples/mcxa/Cargo.toml | 26 + examples/mcxa/build.rs | 20 + examples/mcxa/memory.x | 5 + examples/mcxa/src/bin/adc_interrupt.rs | 84 + examples/mcxa/src/bin/adc_polling.rs | 68 + examples/mcxa/src/bin/blinky.rs | 36 + examples/mcxa/src/bin/button.rs | 23 + examples/mcxa/src/bin/button_async.rs | 29 + examples/mcxa/src/bin/clkout.rs | 69 + examples/mcxa/src/bin/dma_channel_link.rs | 372 +++ examples/mcxa/src/bin/dma_interleave_transfer.rs | 215 ++ examples/mcxa/src/bin/dma_mem_to_mem.rs | 229 ++ examples/mcxa/src/bin/dma_memset.rs | 218 ++ examples/mcxa/src/bin/dma_ping_pong_transfer.rs | 376 +++ examples/mcxa/src/bin/dma_scatter_gather.rs | 262 ++ .../mcxa/src/bin/dma_scatter_gather_builder.rs | 231 ++ examples/mcxa/src/bin/dma_wrap_transfer.rs | 222 ++ examples/mcxa/src/bin/hello.rs | 119 + examples/mcxa/src/bin/i2c-blocking.rs | 31 + examples/mcxa/src/bin/i2c-scan-blocking.rs | 41 + examples/mcxa/src/bin/lpuart_buffered.rs | 62 + examples/mcxa/src/bin/lpuart_dma.rs | 81 + examples/mcxa/src/bin/lpuart_polling.rs | 47 + examples/mcxa/src/bin/lpuart_ring_buffer.rs | 130 + examples/mcxa/src/bin/rtc_alarm.rs | 74 + examples/mcxa/src/lib.rs | 16 + examples/memory.x | 5 - examples/src/bin/adc_interrupt.rs | 84 - examples/src/bin/adc_polling.rs | 68 - examples/src/bin/blinky.rs | 36 - examples/src/bin/button.rs | 23 - examples/src/bin/button_async.rs | 29 - examples/src/bin/clkout.rs | 69 - examples/src/bin/dma_channel_link.rs | 372 --- examples/src/bin/dma_interleave_transfer.rs | 215 -- examples/src/bin/dma_mem_to_mem.rs | 229 -- examples/src/bin/dma_memset.rs | 218 -- examples/src/bin/dma_ping_pong_transfer.rs | 376 --- examples/src/bin/dma_scatter_gather.rs | 262 -- examples/src/bin/dma_scatter_gather_builder.rs | 231 -- examples/src/bin/dma_wrap_transfer.rs | 222 -- examples/src/bin/hello.rs | 119 - examples/src/bin/i2c-blocking.rs | 31 - examples/src/bin/i2c-scan-blocking.rs | 41 - examples/src/bin/lpuart_buffered.rs | 62 - examples/src/bin/lpuart_dma.rs | 81 - examples/src/bin/lpuart_polling.rs | 47 - examples/src/bin/lpuart_ring_buffer.rs | 130 - examples/src/bin/rtc_alarm.rs | 74 - examples/src/lib.rs | 16 - ram.ld | 109 - run.sh | 93 - rustfmt.toml | 3 - src/adc.rs | 409 --- src/clkout.rs | 169 -- src/clocks/config.rs | 204 -- src/clocks/mod.rs | 950 ------- src/clocks/periph_helpers.rs | 506 ---- src/config.rs | 27 - src/dma.rs | 2594 -------------------- src/gpio.rs | 1062 -------- src/i2c/controller.rs | 455 ---- src/i2c/mod.rs | 171 -- src/interrupt.rs | 546 ---- src/lib.rs | 484 ---- src/lpuart/buffered.rs | 780 ------ src/lpuart/mod.rs | 1733 ------------- src/ostimer.rs | 745 ------ src/pins.rs | 33 - src/rtc.rs | 299 --- supply-chain/README.md | 149 -- supply-chain/audits.toml | 349 --- supply-chain/config.toml | 141 -- supply-chain/imports.lock | 523 ---- tools/run_and_attach_rtt.sh | 24 - tools/run_jlink_noblock.sh | 76 - 132 files changed, 18899 insertions(+), 18899 deletions(-) delete mode 100644 CODE_OF_CONDUCT.md delete mode 100644 CONTRIBUTING.md delete mode 100644 Cargo.lock delete mode 100644 Cargo.toml delete mode 100644 Embed.toml delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 SECURITY.md delete mode 100644 SW-Content-Register.txt delete mode 100644 defmt.x delete mode 100644 deny.toml create mode 100644 embassy-mcxa/CODE_OF_CONDUCT.md create mode 100644 embassy-mcxa/CONTRIBUTING.md create mode 100644 embassy-mcxa/Cargo.lock create mode 100644 embassy-mcxa/Cargo.toml create mode 100644 embassy-mcxa/Embed.toml create mode 100644 embassy-mcxa/LICENSE create mode 100644 embassy-mcxa/README.md create mode 100644 embassy-mcxa/SECURITY.md create mode 100644 embassy-mcxa/SW-Content-Register.txt create mode 100644 embassy-mcxa/defmt.x create mode 100644 embassy-mcxa/deny.toml create mode 100644 embassy-mcxa/ram.ld create mode 100644 embassy-mcxa/run.sh create mode 100644 embassy-mcxa/rustfmt.toml create mode 100644 embassy-mcxa/src/adc.rs create mode 100644 embassy-mcxa/src/clkout.rs create mode 100644 embassy-mcxa/src/clocks/config.rs create mode 100644 embassy-mcxa/src/clocks/mod.rs create mode 100644 embassy-mcxa/src/clocks/periph_helpers.rs create mode 100644 embassy-mcxa/src/config.rs create mode 100644 embassy-mcxa/src/dma.rs create mode 100644 embassy-mcxa/src/gpio.rs create mode 100644 embassy-mcxa/src/i2c/controller.rs create mode 100644 embassy-mcxa/src/i2c/mod.rs create mode 100644 embassy-mcxa/src/interrupt.rs create mode 100644 embassy-mcxa/src/lib.rs create mode 100644 embassy-mcxa/src/lpuart/buffered.rs create mode 100644 embassy-mcxa/src/lpuart/mod.rs create mode 100644 embassy-mcxa/src/ostimer.rs create mode 100644 embassy-mcxa/src/pins.rs create mode 100644 embassy-mcxa/src/rtc.rs create mode 100644 embassy-mcxa/supply-chain/README.md create mode 100644 embassy-mcxa/supply-chain/audits.toml create mode 100644 embassy-mcxa/supply-chain/config.toml create mode 100644 embassy-mcxa/supply-chain/imports.lock create mode 100644 embassy-mcxa/tools/run_and_attach_rtt.sh create mode 100644 embassy-mcxa/tools/run_jlink_noblock.sh delete mode 100644 examples/.cargo/config.toml delete mode 100644 examples/.gitignore delete mode 100644 examples/Cargo.lock delete mode 100644 examples/Cargo.toml delete mode 100644 examples/build.rs create mode 100644 examples/mcxa/.cargo/config.toml create mode 100644 examples/mcxa/.gitignore create mode 100644 examples/mcxa/Cargo.lock create mode 100644 examples/mcxa/Cargo.toml create mode 100644 examples/mcxa/build.rs create mode 100644 examples/mcxa/memory.x create mode 100644 examples/mcxa/src/bin/adc_interrupt.rs create mode 100644 examples/mcxa/src/bin/adc_polling.rs create mode 100644 examples/mcxa/src/bin/blinky.rs create mode 100644 examples/mcxa/src/bin/button.rs create mode 100644 examples/mcxa/src/bin/button_async.rs create mode 100644 examples/mcxa/src/bin/clkout.rs create mode 100644 examples/mcxa/src/bin/dma_channel_link.rs create mode 100644 examples/mcxa/src/bin/dma_interleave_transfer.rs create mode 100644 examples/mcxa/src/bin/dma_mem_to_mem.rs create mode 100644 examples/mcxa/src/bin/dma_memset.rs create mode 100644 examples/mcxa/src/bin/dma_ping_pong_transfer.rs create mode 100644 examples/mcxa/src/bin/dma_scatter_gather.rs create mode 100644 examples/mcxa/src/bin/dma_scatter_gather_builder.rs create mode 100644 examples/mcxa/src/bin/dma_wrap_transfer.rs create mode 100644 examples/mcxa/src/bin/hello.rs create mode 100644 examples/mcxa/src/bin/i2c-blocking.rs create mode 100644 examples/mcxa/src/bin/i2c-scan-blocking.rs create mode 100644 examples/mcxa/src/bin/lpuart_buffered.rs create mode 100644 examples/mcxa/src/bin/lpuart_dma.rs create mode 100644 examples/mcxa/src/bin/lpuart_polling.rs create mode 100644 examples/mcxa/src/bin/lpuart_ring_buffer.rs create mode 100644 examples/mcxa/src/bin/rtc_alarm.rs create mode 100644 examples/mcxa/src/lib.rs delete mode 100644 examples/memory.x delete mode 100644 examples/src/bin/adc_interrupt.rs delete mode 100644 examples/src/bin/adc_polling.rs delete mode 100644 examples/src/bin/blinky.rs delete mode 100644 examples/src/bin/button.rs delete mode 100644 examples/src/bin/button_async.rs delete mode 100644 examples/src/bin/clkout.rs delete mode 100644 examples/src/bin/dma_channel_link.rs delete mode 100644 examples/src/bin/dma_interleave_transfer.rs delete mode 100644 examples/src/bin/dma_mem_to_mem.rs delete mode 100644 examples/src/bin/dma_memset.rs delete mode 100644 examples/src/bin/dma_ping_pong_transfer.rs delete mode 100644 examples/src/bin/dma_scatter_gather.rs delete mode 100644 examples/src/bin/dma_scatter_gather_builder.rs delete mode 100644 examples/src/bin/dma_wrap_transfer.rs delete mode 100644 examples/src/bin/hello.rs delete mode 100644 examples/src/bin/i2c-blocking.rs delete mode 100644 examples/src/bin/i2c-scan-blocking.rs delete mode 100644 examples/src/bin/lpuart_buffered.rs delete mode 100644 examples/src/bin/lpuart_dma.rs delete mode 100644 examples/src/bin/lpuart_polling.rs delete mode 100644 examples/src/bin/lpuart_ring_buffer.rs delete mode 100644 examples/src/bin/rtc_alarm.rs delete mode 100644 examples/src/lib.rs delete mode 100644 ram.ld delete mode 100644 run.sh delete mode 100644 rustfmt.toml delete mode 100644 src/adc.rs delete mode 100644 src/clkout.rs delete mode 100644 src/clocks/config.rs delete mode 100644 src/clocks/mod.rs delete mode 100644 src/clocks/periph_helpers.rs delete mode 100644 src/config.rs delete mode 100644 src/dma.rs delete mode 100644 src/gpio.rs delete mode 100644 src/i2c/controller.rs delete mode 100644 src/i2c/mod.rs delete mode 100644 src/interrupt.rs delete mode 100644 src/lib.rs delete mode 100644 src/lpuart/buffered.rs delete mode 100644 src/lpuart/mod.rs delete mode 100644 src/ostimer.rs delete mode 100644 src/pins.rs delete mode 100644 src/rtc.rs delete mode 100644 supply-chain/README.md delete mode 100644 supply-chain/audits.toml delete mode 100644 supply-chain/config.toml delete mode 100644 supply-chain/imports.lock delete mode 100644 tools/run_and_attach_rtt.sh delete mode 100644 tools/run_jlink_noblock.sh diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 54a673e04..000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,132 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual -identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall - community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or advances of - any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, - without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -odpadmin@microsoft.com. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.1, available at -[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at -[https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 7c8289a58..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,48 +0,0 @@ -# Contributing to Open Device Partnership - -The Open Device Partnership project welcomes your suggestions and contributions! Before opening your first issue or pull request, please review our -[Code of Conduct](CODE_OF_CONDUCT.md) to understand how our community interacts in an inclusive and respectful manner. - -## Contribution Licensing - -Most of our code is distributed under the terms of the [MIT license](LICENSE), and when you contribute code that you wrote to our repositories, -you agree that you are contributing under those same terms. In addition, by submitting your contributions you are indicating that -you have the right to submit those contributions under those terms. - -## Other Contribution Information - -If you wish to contribute code or documentation authored by others, or using the terms of any other license, please indicate that clearly in your -pull request so that the project team can discuss the situation with you. - -# Contribution Guideline - -* For any new HAL driver added, please add corresponding test in the examples -* Format the code with `cargo fmt`. Or better yet, enable format on save in your IDE for rust source files. -* Use meaningful commit messages. See [this blogpost](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) - -# PR Etiquette - -* Create a draft PR first -* Make sure that your branch has `.github` folder and all the code linting/sanity check workflows are passing in your draft PR before sending it out to code reviewers. - -# Careful Use of `Unsafe` - -Working with embedded, using of `unsafe` is a necessity. However, please wrap unsafe code with safe interfaces to prevent `unsafe` keyword being sprinkled everywhere. - -# RFC Draft PR - -If you want feedback on your design or HAL driver early, please create a draft PR with title prefix `RFC:`. - -# Branch Naming Scheme - -For now, we're not using forks. Eventually a personal fork will be required for any PRs to limit the amount of people with merge access to the main branch. Until that happens, please use meaningful branch names like this `user_alias/feature` and avoid sending PRs from branches containing prefixes such as "wip", "test", etc. Prior to sending a PR, please rename the branch. - -# Clean Commit History - -We disabled squashing of commit and would like to maintain a clean commit history. So please reorganize your commits with the following items: - * Each commit builds successfully without warning from `rustc` or `clippy` - * Miscellaneous commits to fix typos + formatting are squashed - -# Regressions - -When reporting a regression, please ensure that you use `git bisect` to find the first offending commit, as that will help us finding the culprit a lot faster. diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 747a69c08..000000000 --- a/Cargo.lock +++ /dev/null @@ -1,918 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "bare-metal" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "bitfield" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "cc" -version = "1.2.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cordyceps" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" -dependencies = [ - "loom", - "tracing", -] - -[[package]] -name = "cortex-m" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" -dependencies = [ - "bare-metal", - "bitfield", - "critical-section", - "embedded-hal 0.2.7", - "volatile-register", -] - -[[package]] -name = "cortex-m-rt" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" -dependencies = [ - "cortex-m-rt-macros", -] - -[[package]] -name = "cortex-m-rt-macros" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - -[[package]] -name = "defmt" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" -dependencies = [ - "bitflags", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" -dependencies = [ - "defmt-parser", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "defmt-parser" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" -dependencies = [ - "thiserror", -] - -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - -[[package]] -name = "embassy-embedded-hal" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "554e3e840696f54b4c9afcf28a0f24da431c927f4151040020416e7393d6d0d8" -dependencies = [ - "embassy-futures", - "embassy-hal-internal", - "embassy-sync", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "embedded-storage", - "embedded-storage-async", - "nb 1.1.0", -] - -[[package]] -name = "embassy-futures" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" - -[[package]] -name = "embassy-hal-internal" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" -dependencies = [ - "cortex-m", - "critical-section", - "num-traits", -] - -[[package]] -name = "embassy-mcxa" -version = "0.1.0" -dependencies = [ - "cortex-m", - "cortex-m-rt", - "critical-section", - "defmt", - "embassy-embedded-hal", - "embassy-hal-internal", - "embassy-sync", - "embassy-time", - "embassy-time-driver", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "embedded-hal-nb", - "embedded-io", - "embedded-io-async", - "heapless", - "maitake-sync", - "mcxa-pac", - "nb 1.1.0", - "paste", -] - -[[package]] -name = "embassy-sync" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" -dependencies = [ - "cfg-if", - "critical-section", - "embedded-io-async", - "futures-core", - "futures-sink", - "heapless", -] - -[[package]] -name = "embassy-time" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" -dependencies = [ - "cfg-if", - "critical-section", - "document-features", - "embassy-time-driver", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "futures-core", -] - -[[package]] -name = "embassy-time-driver" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" -dependencies = [ - "document-features", -] - -[[package]] -name = "embedded-hal" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" -dependencies = [ - "nb 0.1.3", - "void", -] - -[[package]] -name = "embedded-hal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" - -[[package]] -name = "embedded-hal-async" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" -dependencies = [ - "embedded-hal 1.0.0", -] - -[[package]] -name = "embedded-hal-nb" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" -dependencies = [ - "embedded-hal 1.0.0", - "nb 1.1.0", -] - -[[package]] -name = "embedded-io" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" - -[[package]] -name = "embedded-io-async" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" -dependencies = [ - "embedded-io", -] - -[[package]] -name = "embedded-storage" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" - -[[package]] -name = "embedded-storage-async" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1763775e2323b7d5f0aa6090657f5e21cfa02ede71f5dc40eead06d64dcd15cc" -dependencies = [ - "embedded-storage", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "generator" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows", -] - -[[package]] -name = "hash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" -dependencies = [ - "byteorder", -] - -[[package]] -name = "heapless" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" -dependencies = [ - "hash32", - "stable_deref_trait", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.177" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" - -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - -[[package]] -name = "log" -version = "0.4.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" - -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "maitake-sync" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "748f86d9befd480b602c3bebc9ef30dbf2f3dfc8acc4a73d07b90f0117e6de3f" -dependencies = [ - "cordyceps", - "critical-section", - "loom", - "mutex-traits", - "mycelium-bitfield", - "pin-project", - "portable-atomic", - "tracing", -] - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "mcxa-pac" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/mcxa-pac#e18dfb52500ca77b8d8326662b966a80251182ca" -dependencies = [ - "cortex-m", - "cortex-m-rt", - "critical-section", - "defmt", - "vcell", -] - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "mutex-traits" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3929f2b5633d29cf7b6624992e5f3c1e9334f1193423e12d17be4faf678cde3f" - -[[package]] -name = "mycelium-bitfield" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc" - -[[package]] -name = "nb" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" -dependencies = [ - "nb 1.1.0", -] - -[[package]] -name = "nb" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pin-project" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "portable-atomic" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" -dependencies = [ - "critical-section", -] - -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "regex-automata" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "syn" -version = "2.0.110" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "vcell" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" - -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - -[[package]] -name = "volatile-register" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" -dependencies = [ - "vcell", -] - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core", - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index f86b92c32..000000000 --- a/Cargo.toml +++ /dev/null @@ -1,54 +0,0 @@ -[package] -name = "embassy-mcxa" -version = "0.1.0" -edition = "2021" -license = "MIT OR Apache-2.0" -description = "Embassy Hardware Abstraction Layer (HAL) for NXP MCXA series of MCUs" -keywords = ["embedded", "hal", "nxp", "mcxa", "embassy"] -categories = ["embedded", "hardware-support", "no-std"] - -[dependencies] -cortex-m = { version = "0.7", features = ["critical-section-single-core"] } -# If you would like "device" to be an optional feature, please open an issue. -cortex-m-rt = { version = "0.7", features = ["device"] } -critical-section = "1.2.0" -defmt = { version = "1.0", optional = true } -embassy-embedded-hal = "0.5.0" -embassy-hal-internal = { version = "0.3.0", features = ["cortex-m", "prio-bits-3"] } -embassy-sync = "0.7.2" -embedded-hal-1 = { package = "embedded-hal", version = "1.0" } -embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } -embedded-hal-async = { version = "1.0" } -embedded-hal-nb = { version = "1.0" } -embedded-io = "0.6" -embedded-io-async = { version = "0.6.1" } -heapless = "0.8" -mcxa-pac = { git = "https://github.com/OpenDevicePartnership/mcxa-pac", features = ["rt", "critical-section"], version = "0.1.0" } -nb = "1.1.0" -paste = "1.0.15" -maitake-sync = { version = "0.2.2", default-features = false, features = ["critical-section", "no-cache-pad"] } - -# `time` dependencies -embassy-time = { version = "0.5.0", optional = true } -embassy-time-driver = { version = "0.2.1", optional = true } - -[features] -default = [] - -# Base defmt feature enables core + panic handler -# Use with one logger feature: defmt-rtt (preferred) or defmt-uart (fallback) -defmt = ["dep:defmt", "mcxa-pac/defmt"] - -unstable-pac = [] - -# Embassy time -time = [ - "dep:embassy-time", - "dep:embassy-time-driver", -] - -[lints.rust] -# The `rt` cfg is checked by embassy_hal_internal::interrupt_mod! macro -# even though it's defined in the macro's crate, not ours. This suppresses -# the "unexpected cfg condition value" warning. -unexpected_cfgs = { level = "warn", check-cfg = ["cfg(feature, values(\"rt\"))"] } diff --git a/Embed.toml b/Embed.toml deleted file mode 100644 index 40218c8bf..000000000 --- a/Embed.toml +++ /dev/null @@ -1,28 +0,0 @@ -[default.probe] -# Use the first available probe -protocol = "Swd" -speed = 1000 - -[default.flashing] -# Attach-only: don't flash (we already loaded to RAM) -enabled = false - -[default.reset] -# Don't reset; keep running app started by run_jlink_noblock.sh -enabled = false - -[default.general] -# The chip name of the target -chip = "MCXA276" - -[default.gdb] -# Whether or not a GDB server should be opened after loading -enabled = false - -[default.rtt] -# Enable RTT for debugging output -enabled = true - -# Increase timeout for RTT CB discovery -up_channels = [ { channel = 0, mode = "BlockIfFull" } ] -timeout = 5000 diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 479657c89..000000000 --- a/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -MIT License - -Copyright (c) 2025 OEMWCSE - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/README.md b/README.md deleted file mode 100644 index 6e7d61c0e..000000000 --- a/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Embassy MCXA256 HAL - -[![check](https://github.com/OpenDevicePartnership/embassy-mcxa/actions/workflows/check.yml/badge.svg)](https://github.com/OpenDevicePartnership/embassy-mcxa/actions/workflows/check.yml) -[![no-std](https://github.com/OpenDevicePartnership/embassy-mcxa/actions/workflows/nostd.yml/badge.svg)](https://github.com/OpenDevicePartnership/embassy-mcxa/actions/workflows/nostd.yml) -[![rolling](https://github.com/OpenDevicePartnership/embassy-mcxa/actions/workflows/rolling.yml/badge.svg)](https://github.com/OpenDevicePartnership/embassy-mcxa/actions/workflows/rolling.yml) - -A Hardware Abstraction Layer (HAL) for the NXP MCXA256 microcontroller -using the Embassy async framework. This HAL provides safe, idiomatic -Rust interfaces for GPIO, UART, and OSTIMER peripherals. - -## License - -This project is licensed under MIT OR Apache-2.0. diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 5357b8824..000000000 --- a/SECURITY.md +++ /dev/null @@ -1,66 +0,0 @@ -# Vulnerability Disclosure and Embargo Policy - -The Open Device Partnership project welcomes the responsible disclosure of vulnerabilities. - -## Initial Contact - -All security bugs in Open Device Partnership should be reported to the security team. -To do so, please reach out in the form of a -[Github Security Advisory](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities). - -You will be invited to join this private area to discuss specifics. Doing so -allows us to start with a high level of confidentiality and relax it if the -issue is less critical, moving to work on the fix in the open. - -Your initial contact will be acknowledged within 48 hours, and you’ll receive -a more detailed response within 96 hours indicating the next steps in handling -your report. - -After the initial reply to your report, the security team will endeavor to -keep you informed of the progress being made towards a fix and full -announcement. As recommended by -[RFPolicy](https://dl.packetstormsecurity.net/papers/general/rfpolicy-2.0.txt), -these updates will be sent at least every five working days. - -## Disclosure Policy - -The Open Device Partnership project has a 5 step disclosure process. - -1. Contact is established, a private channel created, and the security report - is received and is assigned a primary handler. This person will coordinate - the fix and release process. -2. The problem is confirmed and a list of all affected versions is determined. - If an embargo is needed (see below), details of the embargo are decided. -3. Code is audited to find any potential similar problems. -4. Fixes are prepared for all releases which are still under maintenance. In - case of embargo, these fixes are not committed to the public repository but - rather held in a private fork pending the announcement. -5. The changes are pushed to the public repository and new builds are deployed. - -This process can take some time, especially when coordination is required -with maintainers of other projects. Every effort will be made to handle the bug -in as timely a manner as possible, however it is important that we follow the -release process above to ensure that the disclosure is handled in a consistent -manner. - -## Embargoes - -While the Open Device Partnership project aims to follow the highest standards of -transparency and openness, handling some security issues may pose such an -immediate threat to various stakeholders and require coordination between -various actors that it cannot be made immediately public. - -In this case, security issues will fall under an embargo. - -An embargo can be called for in various cases: - -- when disclosing the issue without simultaneously providing a mitigation - would seriously endanger users, -- when producing a fix requires coordinating between multiple actors (such as - upstream or downstream/dependency projects), or simply -- when proper analysis of the issue and its ramifications demands time. - -If we determine that an issue you report requires an embargo, we will discuss -this with you and try to find a reasonable expiry date (aka “embargo -completion date”), as well as who should be included in the list of -need-to-know people. diff --git a/SW-Content-Register.txt b/SW-Content-Register.txt deleted file mode 100644 index 09f879c2f..000000000 --- a/SW-Content-Register.txt +++ /dev/null @@ -1,78 +0,0 @@ -Release Name: Embassy MCXA276 HAL -Release Version: 0.1.0 -Package License: MIT (see ./License.txt) -Note: The crate is dual-licensed “MIT OR Apache-2.0” per Cargo.toml; choosing MIT satisfies the dual-license terms. - -Scope of this Register -This file documents software content that is part of this repository and, for transparency, lists major non-vendored Rust dependencies that are resolved from crates.io during builds. Components that are not present in the repository (e.g., external crates) are listed under “Non‑vendored Rust dependencies”. - -Repository Components (included in this release) - -embassy_mcxa276_hal Name: Embassy MCXA276 HAL (library + examples) - Version: 0.1.0 - Outgoing License: MIT - License File: ./License.txt - Format: Rust source code, examples, scripts, linker scripts - Description: Hardware Abstraction Layer for NXP MCXA276 using the Embassy async framework. Implements drivers and helpers for: - - LPUART2 (UART), OSTIMER0, RTC0, ADC1, GPIO - - Embassy integration (executors, time) - Location: ./src, ./examples, ./build.rs, ./memory.x, ./ram.ld, ./defmt.x, ./.cargo/config.toml, ./run.sh, ./tools/ - Origin: OEMWCSE (MIT) - URL: https://bitbucket.sw.nxp.com/scm/oemwcse/mcxa_rust.git - -mcxa276_pac Name: MCXA276 Peripheral Access Crate (PAC) - Version: 0.1.0 - Outgoing License: MIT OR Apache-2.0 - License File: (see crate metadata) - Format: Rust source code (auto-generated PAC) - Description: Auto-generated register mappings for MCXA276 peripherals (svd2rust). Includes device.x and interrupt vectors. - Location: External (git): https://github.com/bogdan-petru/mcxa-pac (pinned rev a9dd3301) - Origin: Generated by svdtools + svd2rust from NXP SVD 25.06.00 - URL: N/A (generated from NXP SVD) - -examples Name: Example applications - Version: N/A - Outgoing License: MIT - License File: ./License.txt - Format: Rust source code (examples) - Description: Demonstrations for HAL peripherals and features: - - hello, blink - - lpuart_polling, lpuart_buffered, uart_interrupt - - rtc_alarm - - adc_polling, adc_interrupt - - ostimer_alarm, ostimer_async, ostimer_counter, ostimer_race_test - Location: ./examples - Origin: OEMWCSE (MIT) - -Non‑vendored Rust dependencies (resolved from crates.io at build time) -The following crates are not vendored in this repository. They are fetched during builds via Cargo. See Cargo.lock for exact versions. License summaries below are for convenience; consult each crate for authoritative terms. - -cortex-m License: MIT OR Apache-2.0 Purpose: Cortex-M core support -cortex-m-rt License: MIT OR Apache-2.0 Purpose: Cortex-M runtime (vectors, reset) -embassy-executor License: MIT OR Apache-2.0 Purpose: Async executor for Cortex-M -embassy-time (+ driver) License: MIT OR Apache-2.0 Purpose: Time abstraction and drivers -embassy-sync License: MIT OR Apache-2.0 Purpose: No-std synchronization primitives -embassy-embedded-hal License: MIT OR Apache-2.0 Purpose: Traits/adapters for embedded-hal -embassy-hal-internal License: MIT OR Apache-2.0 Purpose: HAL building blocks (peripherals!) -embedded-hal (1.0) License: MIT OR Apache-2.0 Purpose: Core embedded hardware traits -embedded-hal (0.2.6) License: MIT OR Apache-2.0 Purpose: Legacy HAL traits ("unproven") -embedded-hal-async License: MIT OR Apache-2.0 Purpose: Async HAL traits -embedded-hal-nb License: MIT OR Apache-2.0 Purpose: Non-blocking HAL traits -embedded-io / embedded-io-async License: MIT OR Apache-2.0 Purpose: IO traits -critical-section License: MIT OR Apache-2.0 Purpose: Critical-section abstraction -heapless License: MIT OR Apache-2.0 Purpose: Fixed-capacity data structures -paste License: MIT OR Apache-2.0 Purpose: Macro hygiene utility -nb License: MIT OR Apache-2.0 Purpose: Non-blocking result type -vcell License: MIT OR Apache-2.0 Purpose: Volatile cell (PAC dependency) - -defmt, defmt-rtt, rtt-target, panic-probe License: MIT OR Apache-2.0 Purpose: Optional (feature-gated) logging/panic crates - -Attribution notes -- MCXA276 PAC is sourced as an external dependency (see Cargo.toml). SVD 25.06.00 already includes OS_EVENT and WAKETIMER0 interrupts (no local fixes). -- Examples run from RAM using custom linker setup; see README.txt for platform-specific instructions. - -Compliance -- This repository’s distributed source is covered by MIT (see License.txt). Where crates are pulled from crates.io, those crates retain their own licenses; the dual-license compatibility (MIT OR Apache-2.0) is standard in the Rust embedded ecosystem and is compatible with this project’s selected MIT distribution. - - - diff --git a/defmt.x b/defmt.x deleted file mode 100644 index dbd6d0850..000000000 --- a/defmt.x +++ /dev/null @@ -1,6 +0,0 @@ -/* - Dummy defmt.x to satisfy -Tdefmt.x when building without the `defmt` feature. - When `defmt` is enabled, the real defmt.x provided by the defmt crate is used via the linker search path. - This file intentionally contains no SECTIONS so it won't override ram.ld. -*/ - diff --git a/deny.toml b/deny.toml deleted file mode 100644 index 7097f2f55..000000000 --- a/deny.toml +++ /dev/null @@ -1,241 +0,0 @@ -# This template contains all of the possible sections and their default values - -# Note that all fields that take a lint level have these possible values: -# * deny - An error will be produced and the check will fail -# * warn - A warning will be produced, but the check will not fail -# * allow - No warning or error will be produced, though in some cases a note -# will be - -# The values provided in this template are the default values that will be used -# when any section or field is not specified in your own configuration - -# Root options - -# The graph table configures how the dependency graph is constructed and thus -# which crates the checks are performed against -[graph] -# If 1 or more target triples (and optionally, target_features) are specified, -# only the specified targets will be checked when running `cargo deny check`. -# This means, if a particular package is only ever used as a target specific -# dependency, such as, for example, the `nix` crate only being used via the -# `target_family = "unix"` configuration, that only having windows targets in -# this list would mean the nix crate, as well as any of its exclusive -# dependencies not shared by any other crates, would be ignored, as the target -# list here is effectively saying which targets you are building for. -targets = [ - # The triple can be any string, but only the target triples built in to - # rustc (as of 1.40) can be checked against actual config expressions - #"x86_64-unknown-linux-musl", - # You can also specify which target_features you promise are enabled for a - # particular target. target_features are currently not validated against - # the actual valid features supported by the target architecture. - #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, -] -# When creating the dependency graph used as the source of truth when checks are -# executed, this field can be used to prune crates from the graph, removing them -# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate -# is pruned from the graph, all of its dependencies will also be pruned unless -# they are connected to another crate in the graph that hasn't been pruned, -# so it should be used with care. The identifiers are [Package ID Specifications] -# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) -#exclude = [] -# If true, metadata will be collected with `--all-features`. Note that this can't -# be toggled off if true, if you want to conditionally enable `--all-features` it -# is recommended to pass `--all-features` on the cmd line instead -all-features = false -# If true, metadata will be collected with `--no-default-features`. The same -# caveat with `all-features` applies -no-default-features = false -# If set, these feature will be enabled when collecting metadata. If `--features` -# is specified on the cmd line they will take precedence over this option. -#features = [] - -# The output table provides options for how/if diagnostics are outputted -[output] -# When outputting inclusion graphs in diagnostics that include features, this -# option can be used to specify the depth at which feature edges will be added. -# This option is included since the graphs can be quite large and the addition -# of features from the crate(s) to all of the graph roots can be far too verbose. -# This option can be overridden via `--feature-depth` on the cmd line -feature-depth = 1 - -# This section is considered when running `cargo deny check advisories` -# More documentation for the advisories section can be found here: -# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html -[advisories] -# The path where the advisory databases are cloned/fetched into -#db-path = "$CARGO_HOME/advisory-dbs" -# The url(s) of the advisory databases to use -#db-urls = ["https://github.com/rustsec/advisory-db"] -# A list of advisory IDs to ignore. Note that ignored advisories will still -# output a note when they are encountered. -ignore = [ - #"RUSTSEC-0000-0000", - #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, - #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish - #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, - # { id = "RUSTSEC-2024-0370", reason = "proc-macro-error is unmaintained, no safe upgrade available, need upstream dependencies to migrate away from it." }, - { id = "RUSTSEC-2024-0436", reason = "there are no suitable replacements for paste right now; paste has been archived as read-only. It only affects compile time concatenation in macros. We will allow it for now" }, - # { id = "RUSTSEC-2023-0089", reason = "this is a deprecation warning for a dependency of a dependency. https://github.com/jamesmunns/postcard/issues/223 tracks fixing the dependency; until that's resolved, we can accept the deprecated code as it has no known vulnerabilities."} -] -# If this is true, then cargo deny will use the git executable to fetch advisory database. -# If this is false, then it uses a built-in git library. -# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. -# See Git Authentication for more information about setting up git authentication. -#git-fetch-with-cli = true - -# This section is considered when running `cargo deny check licenses` -# More documentation for the licenses section can be found here: -# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html -[licenses] -# List of explicitly allowed licenses -# See https://spdx.org/licenses/ for list of possible licenses -# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. -allow = [ - "MIT", - "Apache-2.0", - - # unicode-ident 1.0.14 switched from Unicode-DFS-2016 to Unicode-3.0 license. - "Unicode-3.0", - #"Apache-2.0 WITH LLVM-exception", -] -# The confidence threshold for detecting a license from license text. -# The higher the value, the more closely the license text must be to the -# canonical license text of a valid SPDX license file. -# [possible values: any between 0.0 and 1.0]. -confidence-threshold = 0.8 -# Allow 1 or more licenses on a per-crate basis, so that particular licenses -# aren't accepted for every possible crate as with the normal allow list -exceptions = [ - # Each entry is the crate and version constraint, and its specific allow - # list - #{ allow = ["Zlib"], crate = "adler32" }, -] - -# Some crates don't have (easily) machine readable licensing information, -# adding a clarification entry for it allows you to manually specify the -# licensing information -#[[licenses.clarify]] -# The package spec the clarification applies to -#crate = "ring" -# The SPDX expression for the license requirements of the crate -#expression = "MIT AND ISC AND OpenSSL" -# One or more files in the crate's source used as the "source of truth" for -# the license expression. If the contents match, the clarification will be used -# when running the license check, otherwise the clarification will be ignored -# and the crate will be checked normally, which may produce warnings or errors -# depending on the rest of your configuration -#license-files = [ -# Each entry is a crate relative path, and the (opaque) hash of its contents -#{ path = "LICENSE", hash = 0xbd0eed23 } -#] - -[licenses.private] -# If true, ignores workspace crates that aren't published, or are only -# published to private registries. -# To see how to mark a crate as unpublished (to the official registry), -# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. -ignore = false -# One or more private registries that you might publish crates to, if a crate -# is only published to private registries, and ignore is true, the crate will -# not have its license(s) checked -registries = [ - #"https://sekretz.com/registry -] - -# This section is considered when running `cargo deny check bans`. -# More documentation about the 'bans' section can be found here: -# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html -[bans] -# Lint level for when multiple versions of the same crate are detected -multiple-versions = "warn" -# Lint level for when a crate version requirement is `*` -wildcards = "allow" -# The graph highlighting used when creating dotgraphs for crates -# with multiple versions -# * lowest-version - The path to the lowest versioned duplicate is highlighted -# * simplest-path - The path to the version with the fewest edges is highlighted -# * all - Both lowest-version and simplest-path are used -highlight = "all" -# The default lint level for `default` features for crates that are members of -# the workspace that is being checked. This can be overridden by allowing/denying -# `default` on a crate-by-crate basis if desired. -workspace-default-features = "allow" -# The default lint level for `default` features for external crates that are not -# members of the workspace. This can be overridden by allowing/denying `default` -# on a crate-by-crate basis if desired. -external-default-features = "allow" -# List of crates that are allowed. Use with care! -allow = [ - #"ansi_term@0.11.0", - #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, -] -# List of crates to deny -deny = [ - #"ansi_term@0.11.0", - #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, - # Wrapper crates can optionally be specified to allow the crate when it - # is a direct dependency of the otherwise banned crate - #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, -] - -# List of features to allow/deny -# Each entry the name of a crate and a version range. If version is -# not specified, all versions will be matched. -#[[bans.features]] -#crate = "reqwest" -# Features to not allow -#deny = ["json"] -# Features to allow -#allow = [ -# "rustls", -# "__rustls", -# "__tls", -# "hyper-rustls", -# "rustls", -# "rustls-pemfile", -# "rustls-tls-webpki-roots", -# "tokio-rustls", -# "webpki-roots", -#] -# If true, the allowed features must exactly match the enabled feature set. If -# this is set there is no point setting `deny` -#exact = true - -# Certain crates/versions that will be skipped when doing duplicate detection. -skip = [ - #"ansi_term@0.11.0", - #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, -] -# Similarly to `skip` allows you to skip certain crates during duplicate -# detection. Unlike skip, it also includes the entire tree of transitive -# dependencies starting at the specified crate, up to a certain depth, which is -# by default infinite. -skip-tree = [ - #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies - #{ crate = "ansi_term@0.11.0", depth = 20 }, -] - -# This section is considered when running `cargo deny check sources`. -# More documentation about the 'sources' section can be found here: -# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html -[sources] -# Lint level for what to happen when a crate from a crate registry that is not -# in the allow list is encountered -unknown-registry = "warn" -# Lint level for what to happen when a crate from a git repository that is not -# in the allow list is encountered -unknown-git = "warn" -# List of URLs for allowed crate registries. Defaults to the crates.io index -# if not specified. If it is specified but empty, no registries are allowed. -allow-registry = ["https://github.com/rust-lang/crates.io-index"] -# List of URLs for allowed Git repositories -allow-git = [] - -[sources.allow-org] -# github.com organizations to allow git sources for -github = ["OpenDevicePartnership"] -# gitlab.com organizations to allow git sources for -gitlab = [] -# bitbucket.org organizations to allow git sources for -bitbucket = [] diff --git a/embassy-mcxa/CODE_OF_CONDUCT.md b/embassy-mcxa/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..54a673e04 --- /dev/null +++ b/embassy-mcxa/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +odpadmin@microsoft.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/embassy-mcxa/CONTRIBUTING.md b/embassy-mcxa/CONTRIBUTING.md new file mode 100644 index 000000000..7c8289a58 --- /dev/null +++ b/embassy-mcxa/CONTRIBUTING.md @@ -0,0 +1,48 @@ +# Contributing to Open Device Partnership + +The Open Device Partnership project welcomes your suggestions and contributions! Before opening your first issue or pull request, please review our +[Code of Conduct](CODE_OF_CONDUCT.md) to understand how our community interacts in an inclusive and respectful manner. + +## Contribution Licensing + +Most of our code is distributed under the terms of the [MIT license](LICENSE), and when you contribute code that you wrote to our repositories, +you agree that you are contributing under those same terms. In addition, by submitting your contributions you are indicating that +you have the right to submit those contributions under those terms. + +## Other Contribution Information + +If you wish to contribute code or documentation authored by others, or using the terms of any other license, please indicate that clearly in your +pull request so that the project team can discuss the situation with you. + +# Contribution Guideline + +* For any new HAL driver added, please add corresponding test in the examples +* Format the code with `cargo fmt`. Or better yet, enable format on save in your IDE for rust source files. +* Use meaningful commit messages. See [this blogpost](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) + +# PR Etiquette + +* Create a draft PR first +* Make sure that your branch has `.github` folder and all the code linting/sanity check workflows are passing in your draft PR before sending it out to code reviewers. + +# Careful Use of `Unsafe` + +Working with embedded, using of `unsafe` is a necessity. However, please wrap unsafe code with safe interfaces to prevent `unsafe` keyword being sprinkled everywhere. + +# RFC Draft PR + +If you want feedback on your design or HAL driver early, please create a draft PR with title prefix `RFC:`. + +# Branch Naming Scheme + +For now, we're not using forks. Eventually a personal fork will be required for any PRs to limit the amount of people with merge access to the main branch. Until that happens, please use meaningful branch names like this `user_alias/feature` and avoid sending PRs from branches containing prefixes such as "wip", "test", etc. Prior to sending a PR, please rename the branch. + +# Clean Commit History + +We disabled squashing of commit and would like to maintain a clean commit history. So please reorganize your commits with the following items: + * Each commit builds successfully without warning from `rustc` or `clippy` + * Miscellaneous commits to fix typos + formatting are squashed + +# Regressions + +When reporting a regression, please ensure that you use `git bisect` to find the first offending commit, as that will help us finding the culprit a lot faster. diff --git a/embassy-mcxa/Cargo.lock b/embassy-mcxa/Cargo.lock new file mode 100644 index 000000000..747a69c08 --- /dev/null +++ b/embassy-mcxa/Cargo.lock @@ -0,0 +1,918 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cordyceps" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" +dependencies = [ + "loom", + "tracing", +] + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield", + "critical-section", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "embassy-embedded-hal" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "554e3e840696f54b4c9afcf28a0f24da431c927f4151040020416e7393d6d0d8" +dependencies = [ + "embassy-futures", + "embassy-hal-internal", + "embassy-sync", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-storage", + "embedded-storage-async", + "nb 1.1.0", +] + +[[package]] +name = "embassy-futures" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" + +[[package]] +name = "embassy-hal-internal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" +dependencies = [ + "cortex-m", + "critical-section", + "num-traits", +] + +[[package]] +name = "embassy-mcxa" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "defmt", + "embassy-embedded-hal", + "embassy-hal-internal", + "embassy-sync", + "embassy-time", + "embassy-time-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "embedded-io-async", + "heapless", + "maitake-sync", + "mcxa-pac", + "nb 1.1.0", + "paste", +] + +[[package]] +name = "embassy-sync" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io-async", + "futures-core", + "futures-sink", + "heapless", +] + +[[package]] +name = "embassy-time" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" +dependencies = [ + "cfg-if", + "critical-section", + "document-features", + "embassy-time-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-core", +] + +[[package]] +name = "embassy-time-driver" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" +dependencies = [ + "document-features", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "embedded-io", +] + +[[package]] +name = "embedded-storage" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" + +[[package]] +name = "embedded-storage-async" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1763775e2323b7d5f0aa6090657f5e21cfa02ede71f5dc40eead06d64dcd15cc" +dependencies = [ + "embedded-storage", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "generator" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "maitake-sync" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "748f86d9befd480b602c3bebc9ef30dbf2f3dfc8acc4a73d07b90f0117e6de3f" +dependencies = [ + "cordyceps", + "critical-section", + "loom", + "mutex-traits", + "mycelium-bitfield", + "pin-project", + "portable-atomic", + "tracing", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "mcxa-pac" +version = "0.1.0" +source = "git+https://github.com/OpenDevicePartnership/mcxa-pac#e18dfb52500ca77b8d8326662b966a80251182ca" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "defmt", + "vcell", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mutex-traits" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3929f2b5633d29cf7b6624992e5f3c1e9334f1193423e12d17be4faf678cde3f" + +[[package]] +name = "mycelium-bitfield" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc" + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +dependencies = [ + "critical-section", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "2.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] diff --git a/embassy-mcxa/Cargo.toml b/embassy-mcxa/Cargo.toml new file mode 100644 index 000000000..f86b92c32 --- /dev/null +++ b/embassy-mcxa/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "embassy-mcxa" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Embassy Hardware Abstraction Layer (HAL) for NXP MCXA series of MCUs" +keywords = ["embedded", "hal", "nxp", "mcxa", "embassy"] +categories = ["embedded", "hardware-support", "no-std"] + +[dependencies] +cortex-m = { version = "0.7", features = ["critical-section-single-core"] } +# If you would like "device" to be an optional feature, please open an issue. +cortex-m-rt = { version = "0.7", features = ["device"] } +critical-section = "1.2.0" +defmt = { version = "1.0", optional = true } +embassy-embedded-hal = "0.5.0" +embassy-hal-internal = { version = "0.3.0", features = ["cortex-m", "prio-bits-3"] } +embassy-sync = "0.7.2" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } +embedded-hal-async = { version = "1.0" } +embedded-hal-nb = { version = "1.0" } +embedded-io = "0.6" +embedded-io-async = { version = "0.6.1" } +heapless = "0.8" +mcxa-pac = { git = "https://github.com/OpenDevicePartnership/mcxa-pac", features = ["rt", "critical-section"], version = "0.1.0" } +nb = "1.1.0" +paste = "1.0.15" +maitake-sync = { version = "0.2.2", default-features = false, features = ["critical-section", "no-cache-pad"] } + +# `time` dependencies +embassy-time = { version = "0.5.0", optional = true } +embassy-time-driver = { version = "0.2.1", optional = true } + +[features] +default = [] + +# Base defmt feature enables core + panic handler +# Use with one logger feature: defmt-rtt (preferred) or defmt-uart (fallback) +defmt = ["dep:defmt", "mcxa-pac/defmt"] + +unstable-pac = [] + +# Embassy time +time = [ + "dep:embassy-time", + "dep:embassy-time-driver", +] + +[lints.rust] +# The `rt` cfg is checked by embassy_hal_internal::interrupt_mod! macro +# even though it's defined in the macro's crate, not ours. This suppresses +# the "unexpected cfg condition value" warning. +unexpected_cfgs = { level = "warn", check-cfg = ["cfg(feature, values(\"rt\"))"] } diff --git a/embassy-mcxa/Embed.toml b/embassy-mcxa/Embed.toml new file mode 100644 index 000000000..40218c8bf --- /dev/null +++ b/embassy-mcxa/Embed.toml @@ -0,0 +1,28 @@ +[default.probe] +# Use the first available probe +protocol = "Swd" +speed = 1000 + +[default.flashing] +# Attach-only: don't flash (we already loaded to RAM) +enabled = false + +[default.reset] +# Don't reset; keep running app started by run_jlink_noblock.sh +enabled = false + +[default.general] +# The chip name of the target +chip = "MCXA276" + +[default.gdb] +# Whether or not a GDB server should be opened after loading +enabled = false + +[default.rtt] +# Enable RTT for debugging output +enabled = true + +# Increase timeout for RTT CB discovery +up_channels = [ { channel = 0, mode = "BlockIfFull" } ] +timeout = 5000 diff --git a/embassy-mcxa/LICENSE b/embassy-mcxa/LICENSE new file mode 100644 index 000000000..479657c89 --- /dev/null +++ b/embassy-mcxa/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2025 OEMWCSE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/embassy-mcxa/README.md b/embassy-mcxa/README.md new file mode 100644 index 000000000..6e7d61c0e --- /dev/null +++ b/embassy-mcxa/README.md @@ -0,0 +1,13 @@ +# Embassy MCXA256 HAL + +[![check](https://github.com/OpenDevicePartnership/embassy-mcxa/actions/workflows/check.yml/badge.svg)](https://github.com/OpenDevicePartnership/embassy-mcxa/actions/workflows/check.yml) +[![no-std](https://github.com/OpenDevicePartnership/embassy-mcxa/actions/workflows/nostd.yml/badge.svg)](https://github.com/OpenDevicePartnership/embassy-mcxa/actions/workflows/nostd.yml) +[![rolling](https://github.com/OpenDevicePartnership/embassy-mcxa/actions/workflows/rolling.yml/badge.svg)](https://github.com/OpenDevicePartnership/embassy-mcxa/actions/workflows/rolling.yml) + +A Hardware Abstraction Layer (HAL) for the NXP MCXA256 microcontroller +using the Embassy async framework. This HAL provides safe, idiomatic +Rust interfaces for GPIO, UART, and OSTIMER peripherals. + +## License + +This project is licensed under MIT OR Apache-2.0. diff --git a/embassy-mcxa/SECURITY.md b/embassy-mcxa/SECURITY.md new file mode 100644 index 000000000..5357b8824 --- /dev/null +++ b/embassy-mcxa/SECURITY.md @@ -0,0 +1,66 @@ +# Vulnerability Disclosure and Embargo Policy + +The Open Device Partnership project welcomes the responsible disclosure of vulnerabilities. + +## Initial Contact + +All security bugs in Open Device Partnership should be reported to the security team. +To do so, please reach out in the form of a +[Github Security Advisory](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities). + +You will be invited to join this private area to discuss specifics. Doing so +allows us to start with a high level of confidentiality and relax it if the +issue is less critical, moving to work on the fix in the open. + +Your initial contact will be acknowledged within 48 hours, and you’ll receive +a more detailed response within 96 hours indicating the next steps in handling +your report. + +After the initial reply to your report, the security team will endeavor to +keep you informed of the progress being made towards a fix and full +announcement. As recommended by +[RFPolicy](https://dl.packetstormsecurity.net/papers/general/rfpolicy-2.0.txt), +these updates will be sent at least every five working days. + +## Disclosure Policy + +The Open Device Partnership project has a 5 step disclosure process. + +1. Contact is established, a private channel created, and the security report + is received and is assigned a primary handler. This person will coordinate + the fix and release process. +2. The problem is confirmed and a list of all affected versions is determined. + If an embargo is needed (see below), details of the embargo are decided. +3. Code is audited to find any potential similar problems. +4. Fixes are prepared for all releases which are still under maintenance. In + case of embargo, these fixes are not committed to the public repository but + rather held in a private fork pending the announcement. +5. The changes are pushed to the public repository and new builds are deployed. + +This process can take some time, especially when coordination is required +with maintainers of other projects. Every effort will be made to handle the bug +in as timely a manner as possible, however it is important that we follow the +release process above to ensure that the disclosure is handled in a consistent +manner. + +## Embargoes + +While the Open Device Partnership project aims to follow the highest standards of +transparency and openness, handling some security issues may pose such an +immediate threat to various stakeholders and require coordination between +various actors that it cannot be made immediately public. + +In this case, security issues will fall under an embargo. + +An embargo can be called for in various cases: + +- when disclosing the issue without simultaneously providing a mitigation + would seriously endanger users, +- when producing a fix requires coordinating between multiple actors (such as + upstream or downstream/dependency projects), or simply +- when proper analysis of the issue and its ramifications demands time. + +If we determine that an issue you report requires an embargo, we will discuss +this with you and try to find a reasonable expiry date (aka “embargo +completion date”), as well as who should be included in the list of +need-to-know people. diff --git a/embassy-mcxa/SW-Content-Register.txt b/embassy-mcxa/SW-Content-Register.txt new file mode 100644 index 000000000..09f879c2f --- /dev/null +++ b/embassy-mcxa/SW-Content-Register.txt @@ -0,0 +1,78 @@ +Release Name: Embassy MCXA276 HAL +Release Version: 0.1.0 +Package License: MIT (see ./License.txt) +Note: The crate is dual-licensed “MIT OR Apache-2.0” per Cargo.toml; choosing MIT satisfies the dual-license terms. + +Scope of this Register +This file documents software content that is part of this repository and, for transparency, lists major non-vendored Rust dependencies that are resolved from crates.io during builds. Components that are not present in the repository (e.g., external crates) are listed under “Non‑vendored Rust dependencies”. + +Repository Components (included in this release) + +embassy_mcxa276_hal Name: Embassy MCXA276 HAL (library + examples) + Version: 0.1.0 + Outgoing License: MIT + License File: ./License.txt + Format: Rust source code, examples, scripts, linker scripts + Description: Hardware Abstraction Layer for NXP MCXA276 using the Embassy async framework. Implements drivers and helpers for: + - LPUART2 (UART), OSTIMER0, RTC0, ADC1, GPIO + - Embassy integration (executors, time) + Location: ./src, ./examples, ./build.rs, ./memory.x, ./ram.ld, ./defmt.x, ./.cargo/config.toml, ./run.sh, ./tools/ + Origin: OEMWCSE (MIT) + URL: https://bitbucket.sw.nxp.com/scm/oemwcse/mcxa_rust.git + +mcxa276_pac Name: MCXA276 Peripheral Access Crate (PAC) + Version: 0.1.0 + Outgoing License: MIT OR Apache-2.0 + License File: (see crate metadata) + Format: Rust source code (auto-generated PAC) + Description: Auto-generated register mappings for MCXA276 peripherals (svd2rust). Includes device.x and interrupt vectors. + Location: External (git): https://github.com/bogdan-petru/mcxa-pac (pinned rev a9dd3301) + Origin: Generated by svdtools + svd2rust from NXP SVD 25.06.00 + URL: N/A (generated from NXP SVD) + +examples Name: Example applications + Version: N/A + Outgoing License: MIT + License File: ./License.txt + Format: Rust source code (examples) + Description: Demonstrations for HAL peripherals and features: + - hello, blink + - lpuart_polling, lpuart_buffered, uart_interrupt + - rtc_alarm + - adc_polling, adc_interrupt + - ostimer_alarm, ostimer_async, ostimer_counter, ostimer_race_test + Location: ./examples + Origin: OEMWCSE (MIT) + +Non‑vendored Rust dependencies (resolved from crates.io at build time) +The following crates are not vendored in this repository. They are fetched during builds via Cargo. See Cargo.lock for exact versions. License summaries below are for convenience; consult each crate for authoritative terms. + +cortex-m License: MIT OR Apache-2.0 Purpose: Cortex-M core support +cortex-m-rt License: MIT OR Apache-2.0 Purpose: Cortex-M runtime (vectors, reset) +embassy-executor License: MIT OR Apache-2.0 Purpose: Async executor for Cortex-M +embassy-time (+ driver) License: MIT OR Apache-2.0 Purpose: Time abstraction and drivers +embassy-sync License: MIT OR Apache-2.0 Purpose: No-std synchronization primitives +embassy-embedded-hal License: MIT OR Apache-2.0 Purpose: Traits/adapters for embedded-hal +embassy-hal-internal License: MIT OR Apache-2.0 Purpose: HAL building blocks (peripherals!) +embedded-hal (1.0) License: MIT OR Apache-2.0 Purpose: Core embedded hardware traits +embedded-hal (0.2.6) License: MIT OR Apache-2.0 Purpose: Legacy HAL traits ("unproven") +embedded-hal-async License: MIT OR Apache-2.0 Purpose: Async HAL traits +embedded-hal-nb License: MIT OR Apache-2.0 Purpose: Non-blocking HAL traits +embedded-io / embedded-io-async License: MIT OR Apache-2.0 Purpose: IO traits +critical-section License: MIT OR Apache-2.0 Purpose: Critical-section abstraction +heapless License: MIT OR Apache-2.0 Purpose: Fixed-capacity data structures +paste License: MIT OR Apache-2.0 Purpose: Macro hygiene utility +nb License: MIT OR Apache-2.0 Purpose: Non-blocking result type +vcell License: MIT OR Apache-2.0 Purpose: Volatile cell (PAC dependency) + +defmt, defmt-rtt, rtt-target, panic-probe License: MIT OR Apache-2.0 Purpose: Optional (feature-gated) logging/panic crates + +Attribution notes +- MCXA276 PAC is sourced as an external dependency (see Cargo.toml). SVD 25.06.00 already includes OS_EVENT and WAKETIMER0 interrupts (no local fixes). +- Examples run from RAM using custom linker setup; see README.txt for platform-specific instructions. + +Compliance +- This repository’s distributed source is covered by MIT (see License.txt). Where crates are pulled from crates.io, those crates retain their own licenses; the dual-license compatibility (MIT OR Apache-2.0) is standard in the Rust embedded ecosystem and is compatible with this project’s selected MIT distribution. + + + diff --git a/embassy-mcxa/defmt.x b/embassy-mcxa/defmt.x new file mode 100644 index 000000000..dbd6d0850 --- /dev/null +++ b/embassy-mcxa/defmt.x @@ -0,0 +1,6 @@ +/* + Dummy defmt.x to satisfy -Tdefmt.x when building without the `defmt` feature. + When `defmt` is enabled, the real defmt.x provided by the defmt crate is used via the linker search path. + This file intentionally contains no SECTIONS so it won't override ram.ld. +*/ + diff --git a/embassy-mcxa/deny.toml b/embassy-mcxa/deny.toml new file mode 100644 index 000000000..7097f2f55 --- /dev/null +++ b/embassy-mcxa/deny.toml @@ -0,0 +1,241 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# Root options + +# The graph table configures how the dependency graph is constructed and thus +# which crates the checks are performed against +[graph] +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #"x86_64-unknown-linux-musl", + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = false +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] + +# The output table provides options for how/if diagnostics are outputted +[output] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory databases are cloned/fetched into +#db-path = "$CARGO_HOME/advisory-dbs" +# The url(s) of the advisory databases to use +#db-urls = ["https://github.com/rustsec/advisory-db"] +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", + #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, + #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish + #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, + # { id = "RUSTSEC-2024-0370", reason = "proc-macro-error is unmaintained, no safe upgrade available, need upstream dependencies to migrate away from it." }, + { id = "RUSTSEC-2024-0436", reason = "there are no suitable replacements for paste right now; paste has been archived as read-only. It only affects compile time concatenation in macros. We will allow it for now" }, + # { id = "RUSTSEC-2023-0089", reason = "this is a deprecation warning for a dependency of a dependency. https://github.com/jamesmunns/postcard/issues/223 tracks fixing the dependency; until that's resolved, we can accept the deprecated code as it has no known vulnerabilities."} +] +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + + # unicode-ident 1.0.14 switched from Unicode-DFS-2016 to Unicode-3.0 license. + "Unicode-3.0", + #"Apache-2.0 WITH LLVM-exception", +] +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], crate = "adler32" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The package spec the clarification applies to +#crate = "ring" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ +# Each entry is a crate relative path, and the (opaque) hash of its contents +#{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, +] +# List of crates to deny +deny = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#crate = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies + #{ crate = "ansi_term@0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# github.com organizations to allow git sources for +github = ["OpenDevicePartnership"] +# gitlab.com organizations to allow git sources for +gitlab = [] +# bitbucket.org organizations to allow git sources for +bitbucket = [] diff --git a/embassy-mcxa/ram.ld b/embassy-mcxa/ram.ld new file mode 100644 index 000000000..816ab6819 --- /dev/null +++ b/embassy-mcxa/ram.ld @@ -0,0 +1,109 @@ +/* Simple RAM execution linker script for MCXA276 */ +MEMORY +{ + RAM : ORIGIN = 0x20000000, LENGTH = 128K +} + +/* Pull in device default interrupt symbol aliases (e.g., CMC = DefaultHandler) */ +INCLUDE device.x + + +/* Provide core exception weak aliases if not supplied by cortex-m-rt's link.x */ +PROVIDE(NonMaskableInt = DefaultHandler); +PROVIDE(HardFault = DefaultHandler); +PROVIDE(MemoryManagement = DefaultHandler); +PROVIDE(BusFault = DefaultHandler); +PROVIDE(UsageFault = DefaultHandler); +PROVIDE(SecureFault = DefaultHandler); +PROVIDE(SVCall = DefaultHandler); +PROVIDE(DebugMonitor = DefaultHandler); +PROVIDE(PendSV = DefaultHandler); +PROVIDE(SysTick = DefaultHandler); + +/* In RAM-run we have no FLASH sidata; copy from sdata */ +__sidata = __sdata; + +/* Ensure the PAC interrupt table is kept */ +EXTERN(__INTERRUPTS); + + +/* Pull in defmt's linker script to generate the defmt table that host decoders expect */ +INCLUDE defmt.x + +ENTRY(Reset) +EXTERN(VECTOR_TABLE) +EXTERN(Reset) +EXTERN(main) + +/* Define _stack_start at end of RAM BEFORE it's used in vector table */ +_stack_start = ORIGIN(RAM) + LENGTH(RAM); + +SECTIONS +{ + .vector_table ORIGIN(RAM) : + { + /* Slot 0: Initial stack pointer - use our explicitly set _stack_start */ + LONG(_stack_start); + /* Slot 1: Reset vector - address of Reset function with Thumb bit set */ + LONG(Reset | 1); + /* Cortex-M33 core exceptions (slots 2-14) */ + KEEP(*(.vector_table.exceptions)); + /* Peripheral interrupt vectors provided by PAC (slots 16+) */ + KEEP(*(.vector_table.interrupts)); + } > RAM + + .text : + { + KEEP(*(.text.Reset)); + KEEP(*(.text.main)); + *(.text .text.*); + } > RAM + + /* Keep defmt table and fragments so host decoders can find metadata */ + .defmt : + { + KEEP(*(.defmt)); + KEEP(*(.defmt.*)); + } > RAM + + .rodata : + { + *(.rodata .rodata.*); + } > RAM + + .data : + { + . = ALIGN(4); + __sdata = .; + *(.data .data.*); + . = ALIGN(4); + __edata = .; + } > RAM + + /* Ensure RTT control block with "SEGGER RTT" signature is loaded to RAM */ + .rtt : + { + KEEP(*(.rtt)); + } > RAM + + /* Place uninitialized buffers (like defmt-rtt) in RAM; load is fine for RAM-run */ + .uninit : + { + *(.uninit .uninit.*); + } > RAM + + .bss : + { + . = ALIGN(4); + __sbss = .; + *(.bss .bss.*); + . = ALIGN(4); + __ebss = .; + } > RAM + + /* Discard exception unwinding info */ + /DISCARD/ : + { + *(.ARM.exidx .ARM.exidx.*); + } +} diff --git a/embassy-mcxa/run.sh b/embassy-mcxa/run.sh new file mode 100644 index 000000000..418dc8a24 --- /dev/null +++ b/embassy-mcxa/run.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +set -euo pipefail + +ELF="${1:-}" +if [[ -z "${ELF}" ]]; then + echo "Usage: $0 " + exit 1 +fi +if [[ ! -f "${ELF}" ]]; then + echo "ELF not found: ${ELF}" + exit 1 +fi + +# Configurable via env +CHIP="${CHIP:-MCXA276}" +SPEED="${PROBE_SPEED:-1000}" # kHz +# Default to J-Link if PROBE not provided +PROBE_OPT=(--probe "${PROBE:-1366:0101:000600110607}") +PORT="${PROBE_RS_GDB_PORT:-1337}" + +cleanup() { + if [[ -n "${GDB_SERVER_PID:-}" ]]; then kill "${GDB_SERVER_PID}" 2>/dev/null || true; fi + [[ -n "${GDB_SCRIPT:-}" ]] && rm -f "${GDB_SCRIPT}" || true + [[ -n "${SERVER_LOG:-}" ]] && rm -f "${SERVER_LOG}" || true +} +trap cleanup EXIT + +if ! command -v probe-rs >/dev/null 2>&1; then + echo "probe-rs not found (cargo install probe-rs --features cli)" + exit 1 +fi +if ! command -v gdb-multiarch >/dev/null 2>&1; then + echo "gdb-multiarch not found; install it (e.g., sudo apt install gdb-multiarch)." + exit 1 +fi + +# Start probe-rs GDB server and capture its output to a log (do not hide errors) +SERVER_LOG=$(mktemp) +set +e +probe-rs gdb --chip "${CHIP}" --protocol swd --speed "${SPEED}" --non-interactive "${ELF}" "${PROBE_OPT[@]}" \ + >"${SERVER_LOG}" 2>&1 & +GDB_SERVER_PID=$! +set -e + +# Wait for server readiness without touching the TCP port to avoid corrupting the GDB protocol +ready="" +for _ in {1..50}; do + if grep -q "Firing up GDB stub" "${SERVER_LOG}"; then ready=1; break; fi + if grep -q "Connecting to the chip was unsuccessful" "${SERVER_LOG}"; then + echo "probe-rs gdb server failed to connect to target. Log:" >&2 + echo "----- probe-rs gdb log -----" >&2 + sed -e 's/^/ /' "${SERVER_LOG}" >&2 || true + exit 1 + fi + sleep 0.1 +done +if [[ -z "${ready}" ]]; then + echo "probe-rs gdb server did not report readiness. Log:" >&2 + echo "----- probe-rs gdb log -----" >&2 + sed -e 's/^/ /' "${SERVER_LOG}" >&2 || true + exit 1 +fi + +# GDB script: load to RAM and run, no reset +GDB_SCRIPT=$(mktemp) +cat >"${GDB_SCRIPT}" <&2 + echo "----- probe-rs gdb log -----" >&2 + sed -e 's/^/ /' "${SERVER_LOG}" >&2 || true + exit 1 +fi + +echo "Program loaded and started (no reset)" diff --git a/embassy-mcxa/rustfmt.toml b/embassy-mcxa/rustfmt.toml new file mode 100644 index 000000000..9eb3c3b4f --- /dev/null +++ b/embassy-mcxa/rustfmt.toml @@ -0,0 +1,3 @@ +group_imports = "StdExternalCrate" +imports_granularity = "Module" +max_width = 120 diff --git a/embassy-mcxa/src/adc.rs b/embassy-mcxa/src/adc.rs new file mode 100644 index 000000000..b5ec5983f --- /dev/null +++ b/embassy-mcxa/src/adc.rs @@ -0,0 +1,409 @@ +//! ADC driver +use core::sync::atomic::{AtomicBool, Ordering}; + +use embassy_hal_internal::{Peri, PeripheralType}; + +use crate::clocks::periph_helpers::{AdcClockSel, AdcConfig, Div4}; +use crate::clocks::{enable_and_reset, Gate, PoweredClock}; +use crate::pac; +use crate::pac::adc1::cfg::{HptExdi, Pwrsel, Refsel, Tcmdres, Tprictrl, Tres}; +use crate::pac::adc1::cmdh1::{Avgs, Cmpen, Next, Sts}; +use crate::pac::adc1::cmdl1::{Adch, Ctype, Mode}; +use crate::pac::adc1::ctrl::CalAvgs; +use crate::pac::adc1::tctrl::{Tcmd, Tpri}; + +type Regs = pac::adc1::RegisterBlock; + +static INTERRUPT_TRIGGERED: AtomicBool = AtomicBool::new(false); +// Token-based instance pattern like embassy-imxrt +pub trait Instance: Gate + PeripheralType { + fn ptr() -> *const Regs; +} + +/// Token for ADC1 +pub type Adc1 = crate::peripherals::ADC1; +impl Instance for crate::peripherals::ADC1 { + #[inline(always)] + fn ptr() -> *const Regs { + pac::Adc1::ptr() + } +} + +// Also implement Instance for the Peri wrapper type +// impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::ADC1> { +// #[inline(always)] +// fn ptr() -> *const Regs { +// pac::Adc1::ptr() +// } +// } + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum TriggerPriorityPolicy { + ConvPreemptImmediatelyNotAutoResumed = 0, + ConvPreemptSoftlyNotAutoResumed = 1, + ConvPreemptImmediatelyAutoRestarted = 4, + ConvPreemptSoftlyAutoRestarted = 5, + ConvPreemptImmediatelyAutoResumed = 12, + ConvPreemptSoftlyAutoResumed = 13, + ConvPreemptSubsequentlyNotAutoResumed = 2, + ConvPreemptSubsequentlyAutoRestarted = 6, + ConvPreemptSubsequentlyAutoResumed = 14, + TriggerPriorityExceptionDisabled = 16, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct LpadcConfig { + pub enable_in_doze_mode: bool, + pub conversion_average_mode: CalAvgs, + pub enable_analog_preliminary: bool, + pub power_up_delay: u8, + pub reference_voltage_source: Refsel, + pub power_level_mode: Pwrsel, + pub trigger_priority_policy: TriggerPriorityPolicy, + pub enable_conv_pause: bool, + pub conv_pause_delay: u16, + pub fifo_watermark: u8, + pub power: PoweredClock, + pub source: AdcClockSel, + pub div: Div4, +} + +impl Default for LpadcConfig { + fn default() -> Self { + LpadcConfig { + enable_in_doze_mode: true, + conversion_average_mode: CalAvgs::NoAverage, + enable_analog_preliminary: false, + power_up_delay: 0x80, + reference_voltage_source: Refsel::Option1, + power_level_mode: Pwrsel::Lowest, + trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, + enable_conv_pause: false, + conv_pause_delay: 0, + fifo_watermark: 0, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: AdcClockSel::FroLfDiv, + div: Div4::no_div(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ConvCommandConfig { + pub sample_channel_mode: Ctype, + pub channel_number: Adch, + pub chained_next_command_number: Next, + pub enable_auto_channel_increment: bool, + pub loop_count: u8, + pub hardware_average_mode: Avgs, + pub sample_time_mode: Sts, + pub hardware_compare_mode: Cmpen, + pub hardware_compare_value_high: u32, + pub hardware_compare_value_low: u32, + pub conversion_resolution_mode: Mode, + pub enable_wait_trigger: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ConvTriggerConfig { + pub target_command_id: Tcmd, + pub delay_power: u8, + pub priority: Tpri, + pub enable_hardware_trigger: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ConvResult { + pub command_id_source: u32, + pub loop_count_index: u32, + pub trigger_id_source: u32, + pub conv_value: u16, +} + +pub struct Adc<'a, I: Instance> { + _inst: core::marker::PhantomData<&'a mut I>, +} + +impl<'a, I: Instance> Adc<'a, I> { + /// initialize ADC + pub fn new(_inst: Peri<'a, I>, config: LpadcConfig) -> Self { + let adc = unsafe { &*I::ptr() }; + + let _clock_freq = unsafe { + enable_and_reset::(&AdcConfig { + power: config.power, + source: config.source, + div: config.div, + }) + .expect("Adc Init should not fail") + }; + + /* Reset the module. */ + adc.ctrl().modify(|_, w| w.rst().held_in_reset()); + adc.ctrl().modify(|_, w| w.rst().released_from_reset()); + + adc.ctrl().modify(|_, w| w.rstfifo0().trigger_reset()); + + /* Disable the module before setting configuration. */ + adc.ctrl().modify(|_, w| w.adcen().disabled()); + + /* Configure the module generally. */ + if config.enable_in_doze_mode { + adc.ctrl().modify(|_, w| w.dozen().enabled()); + } else { + adc.ctrl().modify(|_, w| w.dozen().disabled()); + } + + /* Set calibration average mode. */ + adc.ctrl() + .modify(|_, w| w.cal_avgs().variant(config.conversion_average_mode)); + + adc.cfg().write(|w| unsafe { + let w = if config.enable_analog_preliminary { + w.pwren().pre_enabled() + } else { + w + }; + + w.pudly() + .bits(config.power_up_delay) + .refsel() + .variant(config.reference_voltage_source) + .pwrsel() + .variant(config.power_level_mode) + .tprictrl() + .variant(match config.trigger_priority_policy { + TriggerPriorityPolicy::ConvPreemptSoftlyNotAutoResumed + | TriggerPriorityPolicy::ConvPreemptSoftlyAutoRestarted + | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed => Tprictrl::FinishCurrentOnPriority, + TriggerPriorityPolicy::ConvPreemptSubsequentlyNotAutoResumed + | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoRestarted + | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed => Tprictrl::FinishSequenceOnPriority, + _ => Tprictrl::AbortCurrentOnPriority, + }) + .tres() + .variant(match config.trigger_priority_policy { + TriggerPriorityPolicy::ConvPreemptImmediatelyAutoRestarted + | TriggerPriorityPolicy::ConvPreemptSoftlyAutoRestarted + | TriggerPriorityPolicy::ConvPreemptImmediatelyAutoResumed + | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed + | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoRestarted + | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed => Tres::Enabled, + _ => Tres::Disabled, + }) + .tcmdres() + .variant(match config.trigger_priority_policy { + TriggerPriorityPolicy::ConvPreemptImmediatelyAutoResumed + | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed + | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed + | TriggerPriorityPolicy::TriggerPriorityExceptionDisabled => Tcmdres::Enabled, + _ => Tcmdres::Disabled, + }) + .hpt_exdi() + .variant(match config.trigger_priority_policy { + TriggerPriorityPolicy::TriggerPriorityExceptionDisabled => HptExdi::Disabled, + _ => HptExdi::Enabled, + }) + }); + + if config.enable_conv_pause { + adc.pause() + .modify(|_, w| unsafe { w.pauseen().enabled().pausedly().bits(config.conv_pause_delay) }); + } else { + adc.pause().write(|w| unsafe { w.bits(0) }); + } + + adc.fctrl0() + .write(|w| unsafe { w.fwmark().bits(config.fifo_watermark) }); + + // Enable ADC + adc.ctrl().modify(|_, w| w.adcen().enabled()); + + Self { + _inst: core::marker::PhantomData, + } + } + + pub fn deinit(&self) { + let adc = unsafe { &*I::ptr() }; + adc.ctrl().modify(|_, w| w.adcen().disabled()); + } + + pub fn do_offset_calibration(&self) { + let adc = unsafe { &*I::ptr() }; + // Enable calibration mode + adc.ctrl() + .modify(|_, w| w.calofs().offset_calibration_request_pending()); + + // Wait for calibration to complete (polling status register) + while adc.stat().read().cal_rdy().is_not_set() {} + } + + pub fn get_gain_conv_result(&self, mut gain_adjustment: f32) -> u32 { + let mut gcra_array = [0u32; 17]; + let mut gcalr: u32 = 0; + + for i in (1..=17).rev() { + let shift = 16 - (i - 1); + let step = 1.0 / (1u32 << shift) as f32; + let tmp = (gain_adjustment / step) as u32; + gcra_array[i - 1] = tmp; + gain_adjustment -= tmp as f32 * step; + } + + for i in (1..=17).rev() { + gcalr += gcra_array[i - 1] << (i - 1); + } + gcalr + } + + pub fn do_auto_calibration(&self) { + let adc = unsafe { &*I::ptr() }; + adc.ctrl().modify(|_, w| w.cal_req().calibration_request_pending()); + + while adc.gcc0().read().rdy().is_gain_cal_not_valid() {} + + let mut gcca = adc.gcc0().read().gain_cal().bits() as u32; + if gcca & ((0xFFFF + 1) >> 1) != 0 { + gcca |= !0xFFFF; + } + + let gcra = 131072.0 / (131072.0 - gcca as f32); + + // Write to GCR0 + adc.gcr0().write(|w| unsafe { w.bits(self.get_gain_conv_result(gcra)) }); + + adc.gcr0().modify(|_, w| w.rdy().set_bit()); + + // Wait for calibration to complete (polling status register) + while adc.stat().read().cal_rdy().is_not_set() {} + } + + pub fn do_software_trigger(&self, trigger_id_mask: u32) { + let adc = unsafe { &*I::ptr() }; + adc.swtrig().write(|w| unsafe { w.bits(trigger_id_mask) }); + } + + pub fn get_default_conv_command_config(&self) -> ConvCommandConfig { + ConvCommandConfig { + sample_channel_mode: Ctype::SingleEndedASideChannel, + channel_number: Adch::SelectCh0, + chained_next_command_number: Next::NoNextCmdTerminateOnFinish, + enable_auto_channel_increment: false, + loop_count: 0, + hardware_average_mode: Avgs::NoAverage, + sample_time_mode: Sts::Sample3p5, + hardware_compare_mode: Cmpen::DisabledAlwaysStoreResult, + hardware_compare_value_high: 0, + hardware_compare_value_low: 0, + conversion_resolution_mode: Mode::Data12Bits, + enable_wait_trigger: false, + } + } + + //TBD Need to add cmdlx and cmdhx with x {2..7} + pub fn set_conv_command_config(&self, index: u32, config: &ConvCommandConfig) { + let adc = unsafe { &*I::ptr() }; + + match index { + 1 => { + adc.cmdl1().write(|w| { + w.adch() + .variant(config.channel_number) + .mode() + .variant(config.conversion_resolution_mode) + }); + adc.cmdh1().write(|w| unsafe { + w.next() + .variant(config.chained_next_command_number) + .loop_() + .bits(config.loop_count) + .avgs() + .variant(config.hardware_average_mode) + .sts() + .variant(config.sample_time_mode) + .cmpen() + .variant(config.hardware_compare_mode); + if config.enable_wait_trigger { + w.wait_trig().enabled(); + } + if config.enable_auto_channel_increment { + w.lwi().enabled(); + } + w + }); + } + _ => panic!("Invalid command index: must be between 1 and 7"), + } + } + + pub fn get_default_conv_trigger_config(&self) -> ConvTriggerConfig { + ConvTriggerConfig { + target_command_id: Tcmd::NotValid, + delay_power: 0, + priority: Tpri::HighestPriority, + enable_hardware_trigger: false, + } + } + + pub fn set_conv_trigger_config(&self, trigger_id: usize, config: &ConvTriggerConfig) { + let adc = unsafe { &*I::ptr() }; + let tctrl = &adc.tctrl(trigger_id); + + tctrl.write(|w| unsafe { + let w = w.tcmd().variant(config.target_command_id); + let w = w.tdly().bits(config.delay_power); + w.tpri().variant(config.priority); + if config.enable_hardware_trigger { + w.hten().enabled() + } else { + w + } + }); + } + + pub fn do_reset_fifo(&self) { + let adc = unsafe { &*I::ptr() }; + adc.ctrl().modify(|_, w| w.rstfifo0().trigger_reset()); + } + + pub fn enable_interrupt(&self, mask: u32) { + let adc = unsafe { &*I::ptr() }; + adc.ie().modify(|r, w| unsafe { w.bits(r.bits() | mask) }); + INTERRUPT_TRIGGERED.store(false, Ordering::SeqCst); + } + + pub fn is_interrupt_triggered(&self) -> bool { + INTERRUPT_TRIGGERED.load(Ordering::Relaxed) + } +} + +pub fn get_conv_result() -> Option { + let adc = unsafe { &*pac::Adc1::ptr() }; + let fifo = adc.resfifo0().read().bits(); + const VALID_MASK: u32 = 1 << 31; + if fifo & VALID_MASK == 0 { + return None; + } + + Some(ConvResult { + command_id_source: (fifo >> 24) & 0x0F, + loop_count_index: (fifo >> 20) & 0x0F, + trigger_id_source: (fifo >> 16) & 0x0F, + conv_value: (fifo & 0xFFFF) as u16, + }) +} + +pub fn on_interrupt() { + if get_conv_result().is_some() { + INTERRUPT_TRIGGERED.store(true, Ordering::SeqCst); + } +} + +pub struct AdcHandler; +impl crate::interrupt::typelevel::Handler for AdcHandler { + unsafe fn on_interrupt() { + on_interrupt(); + } +} diff --git a/embassy-mcxa/src/clkout.rs b/embassy-mcxa/src/clkout.rs new file mode 100644 index 000000000..88c731df1 --- /dev/null +++ b/embassy-mcxa/src/clkout.rs @@ -0,0 +1,169 @@ +//! CLKOUT pseudo-peripheral +//! +//! CLKOUT is a part of the clock generation subsystem, and can be used +//! either to generate arbitrary waveforms, or to debug the state of +//! internal oscillators. + +use core::marker::PhantomData; + +use embassy_hal_internal::Peri; + +pub use crate::clocks::periph_helpers::Div4; +use crate::clocks::{with_clocks, ClockError, PoweredClock}; +use crate::pac::mrcc0::mrcc_clkout_clksel::Mux; +use crate::peripherals::CLKOUT; + +/// A peripheral representing the CLKOUT pseudo-peripheral +pub struct ClockOut<'a> { + _p: PhantomData<&'a mut CLKOUT>, + freq: u32, +} + +/// Selected clock source to output +pub enum ClockOutSel { + /// 12MHz Internal Oscillator + Fro12M, + /// FRO180M Internal Oscillator, via divisor + FroHfDiv, + /// External Oscillator + ClkIn, + /// 16KHz oscillator + Clk16K, + /// Output of PLL1 + Pll1Clk, + /// Main System CPU clock, divided by 6 + SlowClk, +} + +/// Configuration for the ClockOut +pub struct Config { + /// Selected Source Clock + pub sel: ClockOutSel, + /// Selected division level + pub div: Div4, + /// Selected power level + pub level: PoweredClock, +} + +impl<'a> ClockOut<'a> { + /// Create a new ClockOut pin. On success, the clock signal will begin immediately + /// on the given pin. + pub fn new( + _peri: Peri<'a, CLKOUT>, + pin: Peri<'a, impl sealed::ClockOutPin>, + cfg: Config, + ) -> Result { + // There's no MRCC enable bit, so we check the validity of the clocks here + // + // TODO: Should we check that the frequency is suitably low? + let (freq, mux) = check_sel(cfg.sel, cfg.level)?; + + // All good! Apply requested config, starting with the pin. + pin.mux(); + + setup_clkout(mux, cfg.div); + + Ok(Self { + _p: PhantomData, + freq: freq / cfg.div.into_divisor(), + }) + } + + /// Frequency of the clkout pin + #[inline] + pub fn frequency(&self) -> u32 { + self.freq + } +} + +impl Drop for ClockOut<'_> { + fn drop(&mut self) { + disable_clkout(); + } +} + +/// Check whether the given clock selection is valid +fn check_sel(sel: ClockOutSel, level: PoweredClock) -> Result<(u32, Mux), ClockError> { + let res = with_clocks(|c| { + Ok(match sel { + ClockOutSel::Fro12M => (c.ensure_fro_hf_active(&level)?, Mux::Clkroot12m), + ClockOutSel::FroHfDiv => (c.ensure_fro_hf_div_active(&level)?, Mux::ClkrootFircDiv), + ClockOutSel::ClkIn => (c.ensure_clk_in_active(&level)?, Mux::ClkrootSosc), + ClockOutSel::Clk16K => (c.ensure_clk_16k_vdd_core_active(&level)?, Mux::Clkroot16k), + ClockOutSel::Pll1Clk => (c.ensure_pll1_clk_active(&level)?, Mux::ClkrootSpll), + ClockOutSel::SlowClk => (c.ensure_slow_clk_active(&level)?, Mux::ClkrootSlow), + }) + }); + let Some(res) = res else { + return Err(ClockError::NeverInitialized); + }; + res +} + +/// Set up the clkout pin using the given mux and div settings +fn setup_clkout(mux: Mux, div: Div4) { + let mrcc = unsafe { crate::pac::Mrcc0::steal() }; + + mrcc.mrcc_clkout_clksel().write(|w| w.mux().variant(mux)); + + // Set up clkdiv + mrcc.mrcc_clkout_clkdiv().write(|w| { + w.halt().set_bit(); + w.reset().set_bit(); + unsafe { w.div().bits(div.into_bits()) }; + w + }); + mrcc.mrcc_clkout_clkdiv().write(|w| { + w.halt().clear_bit(); + w.reset().clear_bit(); + unsafe { w.div().bits(div.into_bits()) }; + w + }); + + while mrcc.mrcc_clkout_clkdiv().read().unstab().bit_is_set() {} +} + +/// Stop the clkout +fn disable_clkout() { + // Stop the output by selecting the "none" clock + // + // TODO: restore the pin to hi-z or something? + let mrcc = unsafe { crate::pac::Mrcc0::steal() }; + mrcc.mrcc_clkout_clkdiv().write(|w| { + w.reset().set_bit(); + w.halt().set_bit(); + unsafe { + w.div().bits(0); + } + w + }); + mrcc.mrcc_clkout_clksel().write(|w| unsafe { w.bits(0b111) }); +} + +mod sealed { + use embassy_hal_internal::PeripheralType; + + use crate::gpio::{Pull, SealedPin}; + + /// Sealed marker trait for clockout pins + pub trait ClockOutPin: PeripheralType { + /// Set the given pin to the correct muxing state + fn mux(&self); + } + + macro_rules! impl_pin { + ($pin:ident, $func:ident) => { + impl ClockOutPin for crate::peripherals::$pin { + fn mux(&self) { + self.set_function(crate::pac::port0::pcr0::Mux::$func); + self.set_pull(Pull::Disabled); + } + } + }; + } + + impl_pin!(P0_6, Mux12); + impl_pin!(P3_6, Mux1); + impl_pin!(P3_8, Mux12); + impl_pin!(P4_2, Mux1); +} diff --git a/embassy-mcxa/src/clocks/config.rs b/embassy-mcxa/src/clocks/config.rs new file mode 100644 index 000000000..0563b8917 --- /dev/null +++ b/embassy-mcxa/src/clocks/config.rs @@ -0,0 +1,204 @@ +//! Clock Configuration +//! +//! This module holds configuration types used for the system clocks. For +//! configuration of individual peripherals, see [`super::periph_helpers`]. + +use super::PoweredClock; + +/// This type represents a divider in the range 1..=256. +/// +/// At a hardware level, this is an 8-bit register from 0..=255, +/// which adds one. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Div8(pub(super) u8); + +impl Div8 { + /// Store a "raw" divisor value that will divide the source by + /// `(n + 1)`, e.g. `Div8::from_raw(0)` will divide the source + /// by 1, and `Div8::from_raw(255)` will divide the source by + /// 256. + pub const fn from_raw(n: u8) -> Self { + Self(n) + } + + /// Divide by one, or no division + pub const fn no_div() -> Self { + Self(0) + } + + /// Store a specific divisor value that will divide the source + /// by `n`. e.g. `Div8::from_divisor(1)` will divide the source + /// by 1, and `Div8::from_divisor(256)` will divide the source + /// by 256. + /// + /// Will return `None` if `n` is not in the range `1..=256`. + /// Consider [`Self::from_raw`] for an infallible version. + pub const fn from_divisor(n: u16) -> Option { + let Some(n) = n.checked_sub(1) else { + return None; + }; + if n > (u8::MAX as u16) { + return None; + } + Some(Self(n as u8)) + } + + /// Convert into "raw" bits form + #[inline(always)] + pub const fn into_bits(self) -> u8 { + self.0 + } + + /// Convert into "divisor" form, as a u32 for convenient frequency math + #[inline(always)] + pub const fn into_divisor(self) -> u32 { + self.0 as u32 + 1 + } +} + +/// ```text +/// ┌─────────────────────────────────────────────────────────┐ +/// │ │ +/// │ ┌───────────┐ clk_out ┌─────────┐ │ +/// XTAL ──────┼──▷│ System │───────────▷│ │ clk_in │ +/// │ │ OSC │ clkout_byp │ MUX │──────────────────┼──────▷ +/// EXTAL ──────┼──▷│ │───────────▷│ │ │ +/// │ └───────────┘ └─────────┘ │ +/// │ │ +/// │ ┌───────────┐ fro_hf_root ┌────┐ fro_hf │ +/// │ │ FRO180 ├───────┬─────▷│ CG │─────────────────────┼──────▷ +/// │ │ │ │ ├────┤ clk_45m │ +/// │ │ │ └─────▷│ CG │─────────────────────┼──────▷ +/// │ └───────────┘ └────┘ │ +/// │ ┌───────────┐ fro_12m_root ┌────┐ fro_12m │ +/// │ │ FRO12M │────────┬─────▷│ CG │────────────────────┼──────▷ +/// │ │ │ │ ├────┤ clk_1m │ +/// │ │ │ └─────▷│1/12│────────────────────┼──────▷ +/// │ └───────────┘ └────┘ │ +/// │ │ +/// │ ┌──────────┐ │ +/// │ │000 │ │ +/// │ clk_in │ │ │ +/// │ ───────────────▷│001 │ │ +/// │ fro_12m │ │ │ +/// │ ───────────────▷│010 │ │ +/// │ fro_hf_root │ │ │ +/// │ ───────────────▷│011 │ main_clk │ +/// │ │ │───────────────────────────┼──────▷ +/// clk_16k ──────┼─────────────────▷│100 │ │ +/// │ none │ │ │ +/// │ ───────────────▷│101 │ │ +/// │ pll1_clk │ │ │ +/// │ ───────────────▷│110 │ │ +/// │ none │ │ │ +/// │ ───────────────▷│111 │ │ +/// │ └──────────┘ │ +/// │ ▲ │ +/// │ │ │ +/// │ SCG SCS │ +/// │ SCG-Lite │ +/// └─────────────────────────────────────────────────────────┘ +/// +/// +/// clk_in ┌─────┐ +/// ───────────────▷│00 │ +/// clk_45m │ │ +/// ───────────────▷│01 │ ┌───────────┐ pll1_clk +/// none │ │─────▷│ SPLL │───────────────▷ +/// ───────────────▷│10 │ └───────────┘ +/// fro_12m │ │ +/// ───────────────▷│11 │ +/// └─────┘ +/// ``` +#[non_exhaustive] +pub struct ClocksConfig { + /// FIRC, FRO180, 45/60/90/180M clock source + pub firc: Option, + /// SIRC, FRO12M, clk_12m clock source + // NOTE: I don't think we *can* disable the SIRC? + pub sirc: SircConfig, + /// FRO16K clock source + pub fro16k: Option, +} + +// FIRC/FRO180M + +/// ```text +/// ┌───────────┐ fro_hf_root ┌────┐ fro_hf +/// │ FRO180M ├───────┬─────▷│GATE│──────────▷ +/// │ │ │ ├────┤ clk_45m +/// │ │ └─────▷│GATE│──────────▷ +/// └───────────┘ └────┘ +/// ``` +#[non_exhaustive] +pub struct FircConfig { + /// Selected clock frequency + pub frequency: FircFreqSel, + /// Selected power state of the clock + pub power: PoweredClock, + /// Is the "fro_hf" gated clock enabled? + pub fro_hf_enabled: bool, + /// Is the "clk_45m" gated clock enabled? + pub clk_45m_enabled: bool, + /// Is the "fro_hf_div" clock enabled? Requires `fro_hf`! + pub fro_hf_div: Option, +} + +/// Selected FIRC frequency +pub enum FircFreqSel { + /// 45MHz Output + Mhz45, + /// 60MHz Output + Mhz60, + /// 90MHz Output + Mhz90, + /// 180MHz Output + Mhz180, +} + +// SIRC/FRO12M + +/// ```text +/// ┌───────────┐ fro_12m_root ┌────┐ fro_12m +/// │ FRO12M │────────┬─────▷│ CG │──────────▷ +/// │ │ │ ├────┤ clk_1m +/// │ │ └─────▷│1/12│──────────▷ +/// └───────────┘ └────┘ +/// ``` +#[non_exhaustive] +pub struct SircConfig { + pub power: PoweredClock, + // peripheral output, aka sirc_12mhz + pub fro_12m_enabled: bool, + /// Is the "fro_lf_div" clock enabled? Requires `fro_12m`! + pub fro_lf_div: Option, +} + +#[non_exhaustive] +pub struct Fro16KConfig { + pub vsys_domain_active: bool, + pub vdd_core_domain_active: bool, +} + +impl Default for ClocksConfig { + fn default() -> Self { + Self { + firc: Some(FircConfig { + frequency: FircFreqSel::Mhz45, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + fro_hf_enabled: true, + clk_45m_enabled: true, + fro_hf_div: None, + }), + sirc: SircConfig { + power: PoweredClock::AlwaysEnabled, + fro_12m_enabled: true, + fro_lf_div: None, + }, + fro16k: Some(Fro16KConfig { + vsys_domain_active: true, + vdd_core_domain_active: true, + }), + } + } +} diff --git a/embassy-mcxa/src/clocks/mod.rs b/embassy-mcxa/src/clocks/mod.rs new file mode 100644 index 000000000..ac30115f6 --- /dev/null +++ b/embassy-mcxa/src/clocks/mod.rs @@ -0,0 +1,950 @@ +//! # Clock Module +//! +//! For the MCX-A, we separate clock and peripheral control into two main stages: +//! +//! 1. At startup, e.g. when `embassy_mcxa::init()` is called, we configure the +//! core system clocks, including external and internal oscillators. This +//! configuration is then largely static for the duration of the program. +//! 2. When HAL drivers are created, e.g. `Lpuart::new()` is called, the driver +//! is responsible for two main things: +//! * Ensuring that any required "upstream" core system clocks necessary for +//! clocking the peripheral is active and configured to a reasonable value +//! * Enabling the clock gates for that peripheral, and resetting the peripheral +//! +//! From a user perspective, only step 1 is visible. Step 2 is automatically handled +//! by HAL drivers, using interfaces defined in this module. +//! +//! It is also possible to *view* the state of the clock configuration after [`init()`] +//! has been called, using the [`with_clocks()`] function, which provides a view of the +//! [`Clocks`] structure. +//! +//! ## For HAL driver implementors +//! +//! The majority of peripherals in the MCXA chip are fed from either a "hard-coded" or +//! configurable clock source, e.g. selecting the FROM12M or `clk_1m` as a source. This +//! selection, as well as often any pre-scaler division from that source clock, is made +//! through MRCC registers. +//! +//! Any peripheral that is controlled through the MRCC register can automatically implement +//! the necessary APIs using the `impl_cc_gate!` macro in this module. You will also need +//! to define the configuration surface and steps necessary to fully configure that peripheral +//! from a clocks perspective by: +//! +//! 1. Defining a configuration type in the [`periph_helpers`] module that contains any selects +//! or divisions available to the HAL driver +//! 2. Implementing the [`periph_helpers::SPConfHelper`] trait, which should check that the +//! necessary input clocks are reasonable + +use core::cell::RefCell; + +use config::{ClocksConfig, FircConfig, FircFreqSel, Fro16KConfig, SircConfig}; +use mcxa_pac::scg0::firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten}; +use mcxa_pac::scg0::sirccsr::{SircClkPeriphEn, Sircsten}; +use periph_helpers::SPConfHelper; + +use crate::pac; +pub mod config; +pub mod periph_helpers; + +// +// Statics/Consts +// + +/// The state of system core clocks. +/// +/// Initialized by [`init()`], and then unchanged for the remainder of the program. +static CLOCKS: critical_section::Mutex>> = critical_section::Mutex::new(RefCell::new(None)); + +// +// Free functions +// + +/// Initialize the core system clocks with the given [`ClocksConfig`]. +/// +/// This function should be called EXACTLY once at start-up, usually via a +/// call to [`embassy_mcxa::init()`](crate::init()). Subsequent calls will +/// return an error. +pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { + critical_section::with(|cs| { + if CLOCKS.borrow_ref(cs).is_some() { + Err(ClockError::AlreadyInitialized) + } else { + Ok(()) + } + })?; + + let mut clocks = Clocks::default(); + let mut operator = ClockOperator { + clocks: &mut clocks, + config: &settings, + + _mrcc0: unsafe { pac::Mrcc0::steal() }, + scg0: unsafe { pac::Scg0::steal() }, + syscon: unsafe { pac::Syscon::steal() }, + vbat0: unsafe { pac::Vbat0::steal() }, + }; + + operator.configure_firc_clocks()?; + operator.configure_sirc_clocks()?; + operator.configure_fro16k_clocks()?; + + // For now, just use FIRC as the main/cpu clock, which should already be + // the case on reset + assert!(operator.scg0.rccr().read().scs().is_firc()); + let input = operator.clocks.fro_hf_root.clone().unwrap(); + operator.clocks.main_clk = Some(input.clone()); + // We can also assume cpu/system clk == fro_hf because div is /1. + assert_eq!(operator.syscon.ahbclkdiv().read().div().bits(), 0); + operator.clocks.cpu_system_clk = Some(input); + + critical_section::with(|cs| { + let mut clks = CLOCKS.borrow_ref_mut(cs); + assert!(clks.is_none(), "Clock setup race!"); + *clks = Some(clocks); + }); + + Ok(()) +} + +/// Obtain the full clocks structure, calling the given closure in a critical section. +/// +/// The given closure will be called with read-only access to the state of the system +/// clocks. This can be used to query and return the state of a given clock. +/// +/// As the caller's closure will be called in a critical section, care must be taken +/// not to block or cause any other undue delays while accessing. +/// +/// Calls to this function will not succeed until after a successful call to `init()`, +/// and will always return None. +pub fn with_clocks R>(f: F) -> Option { + critical_section::with(|cs| { + let c = CLOCKS.borrow_ref(cs); + let c = c.as_ref()?; + Some(f(c)) + }) +} + +// +// Structs/Enums +// + +/// The `Clocks` structure contains the initialized state of the core system clocks +/// +/// These values are configured by providing [`config::ClocksConfig`] to the [`init()`] function +/// at boot time. +#[derive(Default, Debug, Clone)] +#[non_exhaustive] +pub struct Clocks { + /// The `clk_in` is a clock provided by an external oscillator + pub clk_in: Option, + + // FRO180M stuff + // + /// `fro_hf_root` is the direct output of the `FRO180M` internal oscillator + /// + /// It is used to feed downstream clocks, such as `fro_hf`, `clk_45m`, + /// and `fro_hf_div`. + pub fro_hf_root: Option, + + /// `fro_hf` is the same frequency as `fro_hf_root`, but behind a gate. + pub fro_hf: Option, + + /// `clk_45` is a 45MHz clock, sourced from `fro_hf`. + pub clk_45m: Option, + + /// `fro_hf_div` is a configurable frequency clock, sourced from `fro_hf`. + pub fro_hf_div: Option, + + // + // End FRO180M + + // FRO12M stuff + // + /// `fro_12m_root` is the direct output of the `FRO12M` internal oscillator + /// + /// It is used to feed downstream clocks, such as `fro_12m`, `clk_1m`, + /// `and `fro_lf_div`. + pub fro_12m_root: Option, + + /// `fro_12m` is the same frequency as `fro_12m_root`, but behind a gate. + pub fro_12m: Option, + + /// `clk_1m` is a 1MHz clock, sourced from `fro_12m` + pub clk_1m: Option, + + /// `fro_lf_div` is a configurable frequency clock, sourced from `fro_12m` + pub fro_lf_div: Option, + // + // End FRO12M stuff + /// `clk_16k_vsys` is one of two outputs of the `FRO16K` internal oscillator. + /// + /// Also referred to as `clk_16k[0]` in the datasheet, it feeds peripherals in + /// the system domain, such as the CMP and RTC. + pub clk_16k_vsys: Option, + + /// `clk_16k_vdd_core` is one of two outputs of the `FRO16K` internal oscillator. + /// + /// Also referred to as `clk_16k[1]` in the datasheet, it feeds peripherals in + /// the VDD Core domain, such as the OSTimer or LPUarts. + pub clk_16k_vdd_core: Option, + + /// `main_clk` is the main clock used by the CPU, AHB, APB, IPS bus, and some + /// peripherals. + pub main_clk: Option, + + /// `CPU_CLK` or `SYSTEM_CLK` is the output of `main_clk`, run through the `AHBCLKDIV` + pub cpu_system_clk: Option, + + /// `pll1_clk` is the output of the main system PLL, `pll1`. + pub pll1_clk: Option, +} + +/// `ClockError` is the main error returned when configuring or checking clock state +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ClockError { + /// The system clocks were never initialized by calling [`init()`] + NeverInitialized, + /// The [`init()`] function was called more than once + AlreadyInitialized, + /// The requested configuration was not possible to fulfill, as the system clocks + /// were not configured in a compatible way + BadConfig { clock: &'static str, reason: &'static str }, + /// The requested configuration was not possible to fulfill, as the required system + /// clocks have not yet been implemented. + NotImplemented { clock: &'static str }, + /// The requested peripheral could not be configured, as the steps necessary to + /// enable it have not yet been implemented. + UnimplementedConfig, +} + +/// Information regarding a system clock +#[derive(Debug, Clone)] +pub struct Clock { + /// The frequency, in Hz, of the given clock + pub frequency: u32, + /// The power state of the clock, e.g. whether it is active in deep sleep mode + /// or not. + pub power: PoweredClock, +} + +/// The power state of a given clock. +/// +/// On the MCX-A, when Deep-Sleep is entered, any clock not configured for Deep Sleep +/// mode will be stopped. This means that any downstream usage, e.g. by peripherals, +/// will also stop. +/// +/// In the future, we will provide an API for entering Deep Sleep, and if there are +/// any peripherals that are NOT using an `AlwaysEnabled` clock active, entry into +/// Deep Sleep will be prevented, in order to avoid misbehaving peripherals. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PoweredClock { + /// The given clock will NOT continue running in Deep Sleep mode + NormalEnabledDeepSleepDisabled, + /// The given clock WILL continue running in Deep Sleep mode + AlwaysEnabled, +} + +/// The ClockOperator is a private helper type that contains the methods used +/// during system clock initialization. +/// +/// # SAFETY +/// +/// Concurrent access to clock-relevant peripheral registers, such as `MRCC`, `SCG`, +/// `SYSCON`, and `VBAT` should not be allowed for the duration of the [`init()`] function. +struct ClockOperator<'a> { + /// A mutable reference to the current state of system clocks + clocks: &'a mut Clocks, + /// A reference to the requested configuration provided by the caller of [`init()`] + config: &'a ClocksConfig, + + // We hold on to stolen peripherals + _mrcc0: pac::Mrcc0, + scg0: pac::Scg0, + syscon: pac::Syscon, + vbat0: pac::Vbat0, +} + +/// Trait describing an AHB clock gate that can be toggled through MRCC. +pub trait Gate { + type MrccPeriphConfig: SPConfHelper; + + /// Enable the clock gate. + /// + /// # SAFETY + /// + /// The current peripheral must be disabled prior to calling this method + unsafe fn enable_clock(); + + /// Disable the clock gate. + /// + /// # SAFETY + /// + /// There must be no active user of this peripheral when calling this method + unsafe fn disable_clock(); + + /// Drive the peripheral into reset. + /// + /// # SAFETY + /// + /// There must be no active user of this peripheral when calling this method + unsafe fn assert_reset(); + + /// Drive the peripheral out of reset. + /// + /// # SAFETY + /// + /// There must be no active user of this peripheral when calling this method + unsafe fn release_reset(); + + /// Return whether the clock gate for this peripheral is currently enabled. + fn is_clock_enabled() -> bool; + + /// Return whether the peripheral is currently held in reset. + fn is_reset_released() -> bool; +} + +/// This is the primary helper method HAL drivers are expected to call when creating +/// an instance of the peripheral. +/// +/// This method: +/// +/// 1. Enables the MRCC clock gate for this peripheral +/// 2. Calls the `G::MrccPeriphConfig::post_enable_config()` method, returning an error +/// and re-disabling the peripheral if this fails. +/// 3. Pulses the MRCC reset line, to reset the peripheral to the default state +/// 4. Returns the frequency, in Hz that is fed into the peripheral, taking into account +/// the selected upstream clock, as well as any division specified by `cfg`. +/// +/// NOTE: if a clock is disabled, sourced from an "ambient" clock source, this method +/// may return `Ok(0)`. In the future, this might be updated to return the correct +/// "ambient" clock, e.g. the AHB/APB frequency. +/// +/// # SAFETY +/// +/// This peripheral must not yet be in use prior to calling `enable_and_reset`. +#[inline] +pub unsafe fn enable_and_reset(cfg: &G::MrccPeriphConfig) -> Result { + let freq = enable::(cfg).inspect_err(|_| disable::())?; + pulse_reset::(); + Ok(freq) +} + +/// Enable the clock gate for the given peripheral. +/// +/// Prefer [`enable_and_reset`] unless you are specifically avoiding a pulse of the reset, or need +/// to control the duration of the pulse more directly. +/// +/// # SAFETY +/// +/// This peripheral must not yet be in use prior to calling `enable`. +#[inline] +pub unsafe fn enable(cfg: &G::MrccPeriphConfig) -> Result { + G::enable_clock(); + while !G::is_clock_enabled() {} + core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); + + let freq = critical_section::with(|cs| { + let clocks = CLOCKS.borrow_ref(cs); + let clocks = clocks.as_ref().ok_or(ClockError::NeverInitialized)?; + cfg.post_enable_config(clocks) + }); + + freq.inspect_err(|_e| { + G::disable_clock(); + }) +} + +/// Disable the clock gate for the given peripheral. +/// +/// # SAFETY +/// +/// This peripheral must no longer be in use prior to calling `enable`. +#[allow(dead_code)] +#[inline] +pub unsafe fn disable() { + G::disable_clock(); +} + +/// Check whether a gate is currently enabled. +#[allow(dead_code)] +#[inline] +pub fn is_clock_enabled() -> bool { + G::is_clock_enabled() +} + +/// Release a reset line for the given peripheral set. +/// +/// Prefer [`enable_and_reset`]. +/// +/// # SAFETY +/// +/// This peripheral must not yet be in use prior to calling `release_reset`. +#[inline] +pub unsafe fn release_reset() { + G::release_reset(); +} + +/// Assert a reset line for the given peripheral set. +/// +/// Prefer [`enable_and_reset`]. +/// +/// # SAFETY +/// +/// This peripheral must not yet be in use prior to calling `assert_reset`. +#[inline] +pub unsafe fn assert_reset() { + G::assert_reset(); +} + +/// Check whether the peripheral is held in reset. +/// +/// # Safety +/// +/// Must be called with a valid peripheral gate type. +#[inline] +pub unsafe fn is_reset_released() -> bool { + G::is_reset_released() +} + +/// Pulse a reset line (assert then release) with a short delay. +/// +/// Prefer [`enable_and_reset`]. +/// +/// # SAFETY +/// +/// This peripheral must not yet be in use prior to calling `release_reset`. +#[inline] +pub unsafe fn pulse_reset() { + G::assert_reset(); + cortex_m::asm::nop(); + cortex_m::asm::nop(); + G::release_reset(); +} + +// +// `impl`s for structs/enums +// + +/// The [`Clocks`] type's methods generally take the form of "ensure X clock is active". +/// +/// These methods are intended to be used by HAL peripheral implementors to ensure that their +/// selected clocks are active at a suitable level at time of construction. These methods +/// return the frequency of the requested clock, in Hertz, or a [`ClockError`]. +impl Clocks { + /// Ensure the `fro_lf_div` clock is active and valid at the given power state. + pub fn ensure_fro_lf_div_active(&self, at_level: &PoweredClock) -> Result { + let Some(clk) = self.fro_lf_div.as_ref() else { + return Err(ClockError::BadConfig { + clock: "fro_lf_div", + reason: "required but not active", + }); + }; + if !clk.power.meets_requirement_of(at_level) { + return Err(ClockError::BadConfig { + clock: "fro_lf_div", + reason: "not low power active", + }); + } + Ok(clk.frequency) + } + + /// Ensure the `fro_hf` clock is active and valid at the given power state. + pub fn ensure_fro_hf_active(&self, at_level: &PoweredClock) -> Result { + let Some(clk) = self.fro_hf.as_ref() else { + return Err(ClockError::BadConfig { + clock: "fro_hf", + reason: "required but not active", + }); + }; + if !clk.power.meets_requirement_of(at_level) { + return Err(ClockError::BadConfig { + clock: "fro_hf", + reason: "not low power active", + }); + } + Ok(clk.frequency) + } + + /// Ensure the `fro_hf_div` clock is active and valid at the given power state. + pub fn ensure_fro_hf_div_active(&self, at_level: &PoweredClock) -> Result { + let Some(clk) = self.fro_hf_div.as_ref() else { + return Err(ClockError::BadConfig { + clock: "fro_hf_div", + reason: "required but not active", + }); + }; + if !clk.power.meets_requirement_of(at_level) { + return Err(ClockError::BadConfig { + clock: "fro_hf_div", + reason: "not low power active", + }); + } + Ok(clk.frequency) + } + + /// Ensure the `clk_in` clock is active and valid at the given power state. + pub fn ensure_clk_in_active(&self, _at_level: &PoweredClock) -> Result { + Err(ClockError::NotImplemented { clock: "clk_in" }) + } + + /// Ensure the `clk_16k_vsys` clock is active and valid at the given power state. + pub fn ensure_clk_16k_vsys_active(&self, _at_level: &PoweredClock) -> Result { + // NOTE: clk_16k is always active in low power mode + Ok(self + .clk_16k_vsys + .as_ref() + .ok_or(ClockError::BadConfig { + clock: "clk_16k_vsys", + reason: "required but not active", + })? + .frequency) + } + + /// Ensure the `clk_16k_vdd_core` clock is active and valid at the given power state. + pub fn ensure_clk_16k_vdd_core_active(&self, _at_level: &PoweredClock) -> Result { + // NOTE: clk_16k is always active in low power mode + Ok(self + .clk_16k_vdd_core + .as_ref() + .ok_or(ClockError::BadConfig { + clock: "clk_16k_vdd_core", + reason: "required but not active", + })? + .frequency) + } + + /// Ensure the `clk_1m` clock is active and valid at the given power state. + pub fn ensure_clk_1m_active(&self, at_level: &PoweredClock) -> Result { + let Some(clk) = self.clk_1m.as_ref() else { + return Err(ClockError::BadConfig { + clock: "clk_1m", + reason: "required but not active", + }); + }; + if !clk.power.meets_requirement_of(at_level) { + return Err(ClockError::BadConfig { + clock: "clk_1m", + reason: "not low power active", + }); + } + Ok(clk.frequency) + } + + /// Ensure the `pll1_clk` clock is active and valid at the given power state. + pub fn ensure_pll1_clk_active(&self, _at_level: &PoweredClock) -> Result { + Err(ClockError::NotImplemented { clock: "pll1_clk" }) + } + + /// Ensure the `pll1_clk_div` clock is active and valid at the given power state. + pub fn ensure_pll1_clk_div_active(&self, _at_level: &PoweredClock) -> Result { + Err(ClockError::NotImplemented { clock: "pll1_clk_div" }) + } + + /// Ensure the `CPU_CLK` or `SYSTEM_CLK` is active + pub fn ensure_cpu_system_clk_active(&self, at_level: &PoweredClock) -> Result { + let Some(clk) = self.cpu_system_clk.as_ref() else { + return Err(ClockError::BadConfig { + clock: "cpu_system_clk", + reason: "required but not active", + }); + }; + // Can the main_clk ever be active in deep sleep? I think it is gated? + match at_level { + PoweredClock::NormalEnabledDeepSleepDisabled => {} + PoweredClock::AlwaysEnabled => { + return Err(ClockError::BadConfig { + clock: "main_clk", + reason: "not low power active", + }); + } + } + + Ok(clk.frequency) + } + + pub fn ensure_slow_clk_active(&self, at_level: &PoweredClock) -> Result { + let freq = self.ensure_cpu_system_clk_active(at_level)?; + + Ok(freq / 6) + } +} + +impl PoweredClock { + /// Does THIS clock meet the power requirements of the OTHER clock? + pub fn meets_requirement_of(&self, other: &Self) -> bool { + match (self, other) { + (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::AlwaysEnabled) => false, + (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true, + (PoweredClock::AlwaysEnabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true, + (PoweredClock::AlwaysEnabled, PoweredClock::AlwaysEnabled) => true, + } + } +} + +impl ClockOperator<'_> { + /// Configure the FIRC/FRO180M clock family + /// + /// NOTE: Currently we require this to be a fairly hardcoded value, as this clock is used + /// as the main clock used for the CPU, AHB, APB, etc. + fn configure_firc_clocks(&mut self) -> Result<(), ClockError> { + const HARDCODED_ERR: Result<(), ClockError> = Err(ClockError::BadConfig { + clock: "firc", + reason: "For now, FIRC must be enabled and in default state!", + }); + + // Did the user give us a FIRC config? + let Some(firc) = self.config.firc.as_ref() else { + return HARDCODED_ERR; + }; + // Is the FIRC set to 45MHz (should be reset default) + if !matches!(firc.frequency, FircFreqSel::Mhz45) { + return HARDCODED_ERR; + } + let base_freq = 45_000_000; + + // Now, check if the FIRC as expected for our hardcoded value + let mut firc_ok = true; + + // Is the hardware currently set to the default 45MHz? + // + // NOTE: the SVD currently has the wrong(?) values for these: + // 45 -> 48 + // 60 -> 64 + // 90 -> 96 + // 180 -> 192 + // Probably correct-ish, but for a different trim value? + firc_ok &= self.scg0.firccfg().read().freq_sel().is_firc_48mhz_192s(); + + // Check some values in the CSR + let csr = self.scg0.firccsr().read(); + // Is it enabled? + firc_ok &= csr.fircen().is_enabled(); + // Is it accurate? + firc_ok &= csr.fircacc().is_enabled_and_valid(); + // Is there no error? + firc_ok &= csr.fircerr().is_error_not_detected(); + // Is the FIRC the system clock? + firc_ok &= csr.fircsel().is_firc(); + // Is it valid? + firc_ok &= csr.fircvld().is_enabled_and_valid(); + + // Are we happy with the current (hardcoded) state? + if !firc_ok { + return HARDCODED_ERR; + } + + // Note that the fro_hf_root is active + self.clocks.fro_hf_root = Some(Clock { + frequency: base_freq, + power: firc.power, + }); + + // Okay! Now we're past that, let's enable all the downstream clocks. + let FircConfig { + frequency: _, + power, + fro_hf_enabled, + clk_45m_enabled, + fro_hf_div, + } = firc; + + // When is the FRO enabled? + let pow_set = match power { + PoweredClock::NormalEnabledDeepSleepDisabled => Fircsten::DisabledInStopModes, + PoweredClock::AlwaysEnabled => Fircsten::EnabledInStopModes, + }; + + // Do we enable the `fro_hf` output? + let fro_hf_set = if *fro_hf_enabled { + self.clocks.fro_hf = Some(Clock { + frequency: base_freq, + power: *power, + }); + FircFclkPeriphEn::Enabled + } else { + FircFclkPeriphEn::Disabled + }; + + // Do we enable the `clk_45m` output? + let clk_45m_set = if *clk_45m_enabled { + self.clocks.clk_45m = Some(Clock { + frequency: 45_000_000, + power: *power, + }); + FircSclkPeriphEn::Enabled + } else { + FircSclkPeriphEn::Disabled + }; + + self.scg0.firccsr().modify(|_r, w| { + w.fircsten().variant(pow_set); + w.firc_fclk_periph_en().variant(fro_hf_set); + w.firc_sclk_periph_en().variant(clk_45m_set); + w + }); + + // Do we enable the `fro_hf_div` output? + if let Some(d) = fro_hf_div.as_ref() { + // We need `fro_hf` to be enabled + if !*fro_hf_enabled { + return Err(ClockError::BadConfig { + clock: "fro_hf_div", + reason: "fro_hf not enabled", + }); + } + + // Halt and reset the div; then set our desired div. + self.syscon.frohfdiv().write(|w| { + w.halt().halt(); + w.reset().asserted(); + unsafe { w.div().bits(d.into_bits()) }; + w + }); + // Then unhalt it, and reset it + self.syscon.frohfdiv().write(|w| { + w.halt().run(); + w.reset().released(); + w + }); + + // Wait for clock to stabilize + while self.syscon.frohfdiv().read().unstab().is_ongoing() {} + + // Store off the clock info + self.clocks.fro_hf_div = Some(Clock { + frequency: base_freq / d.into_divisor(), + power: *power, + }); + } + + Ok(()) + } + + /// Configure the SIRC/FRO12M clock family + fn configure_sirc_clocks(&mut self) -> Result<(), ClockError> { + let SircConfig { + power, + fro_12m_enabled, + fro_lf_div, + } = &self.config.sirc; + let base_freq = 12_000_000; + + // Allow writes + self.scg0.sirccsr().modify(|_r, w| w.lk().write_enabled()); + self.clocks.fro_12m_root = Some(Clock { + frequency: base_freq, + power: *power, + }); + + let deep = match power { + PoweredClock::NormalEnabledDeepSleepDisabled => Sircsten::Disabled, + PoweredClock::AlwaysEnabled => Sircsten::Enabled, + }; + let pclk = if *fro_12m_enabled { + self.clocks.fro_12m = Some(Clock { + frequency: base_freq, + power: *power, + }); + self.clocks.clk_1m = Some(Clock { + frequency: base_freq / 12, + power: *power, + }); + SircClkPeriphEn::Enabled + } else { + SircClkPeriphEn::Disabled + }; + + // Set sleep/peripheral usage + self.scg0.sirccsr().modify(|_r, w| { + w.sircsten().variant(deep); + w.sirc_clk_periph_en().variant(pclk); + w + }); + + while self.scg0.sirccsr().read().sircvld().is_disabled_or_not_valid() {} + if self.scg0.sirccsr().read().sircerr().is_error_detected() { + return Err(ClockError::BadConfig { + clock: "sirc", + reason: "error set", + }); + } + + // reset lock + self.scg0.sirccsr().modify(|_r, w| w.lk().write_disabled()); + + // Do we enable the `fro_lf_div` output? + if let Some(d) = fro_lf_div.as_ref() { + // We need `fro_lf` to be enabled + if !*fro_12m_enabled { + return Err(ClockError::BadConfig { + clock: "fro_lf_div", + reason: "fro_12m not enabled", + }); + } + + // Halt and reset the div; then set our desired div. + self.syscon.frolfdiv().write(|w| { + w.halt().halt(); + w.reset().asserted(); + unsafe { w.div().bits(d.into_bits()) }; + w + }); + // Then unhalt it, and reset it + self.syscon.frolfdiv().modify(|_r, w| { + w.halt().run(); + w.reset().released(); + w + }); + + // Wait for clock to stabilize + while self.syscon.frolfdiv().read().unstab().is_ongoing() {} + + // Store off the clock info + self.clocks.fro_lf_div = Some(Clock { + frequency: base_freq / d.into_divisor(), + power: *power, + }); + } + + Ok(()) + } + + /// Configure the FRO16K/clk_16k clock family + fn configure_fro16k_clocks(&mut self) -> Result<(), ClockError> { + let Some(fro16k) = self.config.fro16k.as_ref() else { + return Ok(()); + }; + // Enable FRO16K oscillator + self.vbat0.froctla().modify(|_, w| w.fro_en().set_bit()); + + // Lock the control register + self.vbat0.frolcka().modify(|_, w| w.lock().set_bit()); + + let Fro16KConfig { + vsys_domain_active, + vdd_core_domain_active, + } = fro16k; + + // Enable clock outputs to both VSYS and VDD_CORE domains + // Bit 0: clk_16k0 to VSYS domain + // Bit 1: clk_16k1 to VDD_CORE domain + // + // TODO: Define sub-fields for this register with a PAC patch? + let mut bits = 0; + if *vsys_domain_active { + bits |= 0b01; + self.clocks.clk_16k_vsys = Some(Clock { + frequency: 16_384, + power: PoweredClock::AlwaysEnabled, + }); + } + if *vdd_core_domain_active { + bits |= 0b10; + self.clocks.clk_16k_vdd_core = Some(Clock { + frequency: 16_384, + power: PoweredClock::AlwaysEnabled, + }); + } + self.vbat0.froclke().modify(|_r, w| unsafe { w.clke().bits(bits) }); + + Ok(()) + } +} + +// +// Macros/macro impls +// + +/// This macro is used to implement the [`Gate`] trait for a given peripheral +/// that is controlled by the MRCC peripheral. +macro_rules! impl_cc_gate { + ($name:ident, $clk_reg:ident, $rst_reg:ident, $field:ident, $config:ty) => { + impl Gate for crate::peripherals::$name { + type MrccPeriphConfig = $config; + + #[inline] + unsafe fn enable_clock() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$clk_reg().modify(|_, w| w.$field().enabled()); + } + + #[inline] + unsafe fn disable_clock() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$clk_reg().modify(|_r, w| w.$field().disabled()); + } + + #[inline] + fn is_clock_enabled() -> bool { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$clk_reg().read().$field().is_enabled() + } + + #[inline] + unsafe fn release_reset() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$rst_reg().modify(|_, w| w.$field().enabled()); + } + + #[inline] + unsafe fn assert_reset() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$rst_reg().modify(|_, w| w.$field().disabled()); + } + + #[inline] + fn is_reset_released() -> bool { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$rst_reg().read().$field().is_enabled() + } + } + }; +} + +/// This module contains implementations of MRCC APIs, specifically of the [`Gate`] trait, +/// for various low level peripherals. +pub(crate) mod gate { + #[cfg(not(feature = "time"))] + use super::periph_helpers::OsTimerConfig; + use super::periph_helpers::{AdcConfig, Lpi2cConfig, LpuartConfig, NoConfig}; + use super::*; + + // These peripherals have no additional upstream clocks or configuration required + // other than enabling through the MRCC gate. Currently, these peripherals will + // ALWAYS return `Ok(0)` when calling [`enable_and_reset()`] and/or + // [`SPConfHelper::post_enable_config()`]. + impl_cc_gate!(PORT0, mrcc_glb_cc1, mrcc_glb_rst1, port0, NoConfig); + impl_cc_gate!(PORT1, mrcc_glb_cc1, mrcc_glb_rst1, port1, NoConfig); + impl_cc_gate!(PORT2, mrcc_glb_cc1, mrcc_glb_rst1, port2, NoConfig); + impl_cc_gate!(PORT3, mrcc_glb_cc1, mrcc_glb_rst1, port3, NoConfig); + impl_cc_gate!(PORT4, mrcc_glb_cc1, mrcc_glb_rst1, port4, NoConfig); + + impl_cc_gate!(GPIO0, mrcc_glb_cc2, mrcc_glb_rst2, gpio0, NoConfig); + impl_cc_gate!(GPIO1, mrcc_glb_cc2, mrcc_glb_rst2, gpio1, NoConfig); + impl_cc_gate!(GPIO2, mrcc_glb_cc2, mrcc_glb_rst2, gpio2, NoConfig); + impl_cc_gate!(GPIO3, mrcc_glb_cc2, mrcc_glb_rst2, gpio3, NoConfig); + impl_cc_gate!(GPIO4, mrcc_glb_cc2, mrcc_glb_rst2, gpio4, NoConfig); + + // These peripherals DO have meaningful configuration, and could fail if the system + // clocks do not match their needs. + #[cfg(not(feature = "time"))] + impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig); + + impl_cc_gate!(LPI2C0, mrcc_glb_cc0, mrcc_glb_rst0, lpi2c0, Lpi2cConfig); + impl_cc_gate!(LPI2C1, mrcc_glb_cc0, mrcc_glb_rst0, lpi2c1, Lpi2cConfig); + impl_cc_gate!(LPI2C2, mrcc_glb_cc1, mrcc_glb_rst1, lpi2c2, Lpi2cConfig); + impl_cc_gate!(LPI2C3, mrcc_glb_cc1, mrcc_glb_rst1, lpi2c3, Lpi2cConfig); + + impl_cc_gate!(LPUART0, mrcc_glb_cc0, mrcc_glb_rst0, lpuart0, LpuartConfig); + impl_cc_gate!(LPUART1, mrcc_glb_cc0, mrcc_glb_rst0, lpuart1, LpuartConfig); + impl_cc_gate!(LPUART2, mrcc_glb_cc0, mrcc_glb_rst0, lpuart2, LpuartConfig); + impl_cc_gate!(LPUART3, mrcc_glb_cc0, mrcc_glb_rst0, lpuart3, LpuartConfig); + impl_cc_gate!(LPUART4, mrcc_glb_cc0, mrcc_glb_rst0, lpuart4, LpuartConfig); + impl_cc_gate!(LPUART5, mrcc_glb_cc1, mrcc_glb_rst1, lpuart5, LpuartConfig); + impl_cc_gate!(ADC1, mrcc_glb_cc1, mrcc_glb_rst1, adc1, AdcConfig); + + // DMA0 peripheral - uses NoConfig since it has no selectable clock source + impl_cc_gate!(DMA0, mrcc_glb_cc0, mrcc_glb_rst0, dma0, NoConfig); +} diff --git a/embassy-mcxa/src/clocks/periph_helpers.rs b/embassy-mcxa/src/clocks/periph_helpers.rs new file mode 100644 index 000000000..24d074e8a --- /dev/null +++ b/embassy-mcxa/src/clocks/periph_helpers.rs @@ -0,0 +1,506 @@ +//! Peripheral Helpers +//! +//! The purpose of this module is to define the per-peripheral special handling +//! required from a clocking perspective. Different peripherals have different +//! selectable source clocks, and some peripherals have additional pre-dividers +//! that can be used. +//! +//! See the docs of [`SPConfHelper`] for more details. + +use super::{ClockError, Clocks, PoweredClock}; +use crate::pac; + +/// Sealed Peripheral Configuration Helper +/// +/// NOTE: the name "sealed" doesn't *totally* make sense because its not sealed yet in the +/// embassy-mcxa project, but it derives from embassy-imxrt where it is. We should +/// fix the name, or actually do the sealing of peripherals. +/// +/// This trait serves to act as a per-peripheral customization for clocking behavior. +/// +/// This trait should be implemented on a configuration type for a given peripheral, and +/// provide the methods that will be called by the higher level operations like +/// `embassy_mcxa::clocks::enable_and_reset()`. +pub trait SPConfHelper { + /// This method is called AFTER a given MRCC peripheral has been enabled (e.g. un-gated), + /// but BEFORE the peripheral reset line is reset. + /// + /// This function should check that any relevant upstream clocks are enabled, are in a + /// reasonable power state, and that the requested configuration can be made. If any of + /// these checks fail, an `Err(ClockError)` should be returned, likely `ClockError::BadConfig`. + /// + /// This function SHOULD NOT make any changes to the system clock configuration, even + /// unsafely, as this should remain static for the duration of the program. + /// + /// This function WILL be called in a critical section, care should be taken not to delay + /// for an unreasonable amount of time. + /// + /// On success, this function MUST return an `Ok(freq)`, where `freq` is the frequency + /// fed into the peripheral, taking into account the selected source clock, as well as + /// any pre-divisors. + fn post_enable_config(&self, clocks: &Clocks) -> Result; +} + +// config types + +/// This type represents a divider in the range 1..=16. +/// +/// At a hardware level, this is an 8-bit register from 0..=15, +/// which adds one. +/// +/// While the *clock* domain seems to use 8-bit dividers, the *peripheral* domain +/// seems to use 4 bit dividers! +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Div4(pub(super) u8); + +impl Div4 { + /// Divide by one, or no division + pub const fn no_div() -> Self { + Self(0) + } + + /// Store a "raw" divisor value that will divide the source by + /// `(n + 1)`, e.g. `Div4::from_raw(0)` will divide the source + /// by 1, and `Div4::from_raw(15)` will divide the source by + /// 16. + pub const fn from_raw(n: u8) -> Option { + if n > 0b1111 { + None + } else { + Some(Self(n)) + } + } + + /// Store a specific divisor value that will divide the source + /// by `n`. e.g. `Div4::from_divisor(1)` will divide the source + /// by 1, and `Div4::from_divisor(16)` will divide the source + /// by 16. + /// + /// Will return `None` if `n` is not in the range `1..=16`. + /// Consider [`Self::from_raw`] for an infallible version. + pub const fn from_divisor(n: u8) -> Option { + let Some(n) = n.checked_sub(1) else { + return None; + }; + if n > 0b1111 { + return None; + } + Some(Self(n)) + } + + /// Convert into "raw" bits form + #[inline(always)] + pub const fn into_bits(self) -> u8 { + self.0 + } + + /// Convert into "divisor" form, as a u32 for convenient frequency math + #[inline(always)] + pub const fn into_divisor(self) -> u32 { + self.0 as u32 + 1 + } +} + +/// A basic type that always returns an error when `post_enable_config` is called. +/// +/// Should only be used as a placeholder. +pub struct UnimplementedConfig; + +impl SPConfHelper for UnimplementedConfig { + fn post_enable_config(&self, _clocks: &Clocks) -> Result { + Err(ClockError::UnimplementedConfig) + } +} + +/// A basic type that always returns `Ok(0)` when `post_enable_config` is called. +/// +/// This should only be used for peripherals that are "ambiently" clocked, like `PORTn` +/// peripherals, which have no selectable/configurable source clock. +pub struct NoConfig; +impl SPConfHelper for NoConfig { + fn post_enable_config(&self, _clocks: &Clocks) -> Result { + Ok(0) + } +} + +// +// LPI2c +// + +/// Selectable clocks for `Lpi2c` peripherals +#[derive(Debug, Clone, Copy)] +pub enum Lpi2cClockSel { + /// FRO12M/FRO_LF/SIRC clock source, passed through divider + /// "fro_lf_div" + FroLfDiv, + /// FRO180M/FRO_HF/FIRC clock source, passed through divider + /// "fro_hf_div" + FroHfDiv, + /// SOSC/XTAL/EXTAL clock source + ClkIn, + /// clk_1m/FRO_LF divided by 12 + Clk1M, + /// Output of PLL1, passed through clock divider, + /// "pll1_clk_div", maybe "pll1_lf_div"? + Pll1ClkDiv, + /// Disabled + None, +} + +/// Which instance of the `Lpi2c` is this? +/// +/// Should not be directly selectable by end-users. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Lpi2cInstance { + /// Instance 0 + Lpi2c0, + /// Instance 1 + Lpi2c1, + /// Instance 2 + Lpi2c2, + /// Instance 3 + Lpi2c3, +} + +/// Top level configuration for `Lpi2c` instances. +pub struct Lpi2cConfig { + /// Power state required for this peripheral + pub power: PoweredClock, + /// Clock source + pub source: Lpi2cClockSel, + /// Clock divisor + pub div: Div4, + /// Which instance is this? + // NOTE: should not be user settable + pub(crate) instance: Lpi2cInstance, +} + +impl SPConfHelper for Lpi2cConfig { + fn post_enable_config(&self, clocks: &Clocks) -> Result { + // check that source is suitable + let mrcc0 = unsafe { pac::Mrcc0::steal() }; + use mcxa_pac::mrcc0::mrcc_lpi2c0_clksel::Mux; + + let (clkdiv, clksel) = match self.instance { + Lpi2cInstance::Lpi2c0 => (mrcc0.mrcc_lpi2c0_clkdiv(), mrcc0.mrcc_lpi2c0_clksel()), + Lpi2cInstance::Lpi2c1 => (mrcc0.mrcc_lpi2c1_clkdiv(), mrcc0.mrcc_lpi2c1_clksel()), + Lpi2cInstance::Lpi2c2 => (mrcc0.mrcc_lpi2c2_clkdiv(), mrcc0.mrcc_lpi2c2_clksel()), + Lpi2cInstance::Lpi2c3 => (mrcc0.mrcc_lpi2c3_clkdiv(), mrcc0.mrcc_lpi2c3_clksel()), + }; + + let (freq, variant) = match self.source { + Lpi2cClockSel::FroLfDiv => { + let freq = clocks.ensure_fro_lf_div_active(&self.power)?; + (freq, Mux::ClkrootFunc0) + } + Lpi2cClockSel::FroHfDiv => { + let freq = clocks.ensure_fro_hf_div_active(&self.power)?; + (freq, Mux::ClkrootFunc2) + } + Lpi2cClockSel::ClkIn => { + let freq = clocks.ensure_clk_in_active(&self.power)?; + (freq, Mux::ClkrootFunc3) + } + Lpi2cClockSel::Clk1M => { + let freq = clocks.ensure_clk_1m_active(&self.power)?; + (freq, Mux::ClkrootFunc5) + } + Lpi2cClockSel::Pll1ClkDiv => { + let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; + (freq, Mux::ClkrootFunc6) + } + Lpi2cClockSel::None => unsafe { + // no ClkrootFunc7, just write manually for now + clksel.write(|w| w.bits(0b111)); + clkdiv.modify(|_r, w| w.reset().asserted().halt().asserted()); + return Ok(0); + }, + }; + + // set clksel + clksel.modify(|_r, w| w.mux().variant(variant)); + + // Set up clkdiv + clkdiv.modify(|_r, w| { + unsafe { w.div().bits(self.div.into_bits()) } + .halt() + .asserted() + .reset() + .asserted() + }); + clkdiv.modify(|_r, w| w.halt().deasserted().reset().deasserted()); + + while clkdiv.read().unstab().is_unstable() {} + + Ok(freq / self.div.into_divisor()) + } +} + +// +// LPUart +// + +/// Selectable clocks for Lpuart peripherals +#[derive(Debug, Clone, Copy)] +pub enum LpuartClockSel { + /// FRO12M/FRO_LF/SIRC clock source, passed through divider + /// "fro_lf_div" + FroLfDiv, + /// FRO180M/FRO_HF/FIRC clock source, passed through divider + /// "fro_hf_div" + FroHfDiv, + /// SOSC/XTAL/EXTAL clock source + ClkIn, + /// FRO16K/clk_16k source + Clk16K, + /// clk_1m/FRO_LF divided by 12 + Clk1M, + /// Output of PLL1, passed through clock divider, + /// "pll1_clk_div", maybe "pll1_lf_div"? + Pll1ClkDiv, + /// Disabled + None, +} + +/// Which instance of the Lpuart is this? +/// +/// Should not be directly selectable by end-users. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum LpuartInstance { + /// Instance 0 + Lpuart0, + /// Instance 1 + Lpuart1, + /// Instance 2 + Lpuart2, + /// Instance 3 + Lpuart3, + /// Instance 4 + Lpuart4, + /// Instance 5 + Lpuart5, +} + +/// Top level configuration for `Lpuart` instances. +pub struct LpuartConfig { + /// Power state required for this peripheral + pub power: PoweredClock, + /// Clock source + pub source: LpuartClockSel, + /// Clock divisor + pub div: Div4, + /// Which instance is this? + // NOTE: should not be user settable + pub(crate) instance: LpuartInstance, +} + +impl SPConfHelper for LpuartConfig { + fn post_enable_config(&self, clocks: &Clocks) -> Result { + // check that source is suitable + let mrcc0 = unsafe { pac::Mrcc0::steal() }; + use mcxa_pac::mrcc0::mrcc_lpuart0_clksel::Mux; + + let (clkdiv, clksel) = match self.instance { + LpuartInstance::Lpuart0 => (mrcc0.mrcc_lpuart0_clkdiv(), mrcc0.mrcc_lpuart0_clksel()), + LpuartInstance::Lpuart1 => (mrcc0.mrcc_lpuart1_clkdiv(), mrcc0.mrcc_lpuart1_clksel()), + LpuartInstance::Lpuart2 => (mrcc0.mrcc_lpuart2_clkdiv(), mrcc0.mrcc_lpuart2_clksel()), + LpuartInstance::Lpuart3 => (mrcc0.mrcc_lpuart3_clkdiv(), mrcc0.mrcc_lpuart3_clksel()), + LpuartInstance::Lpuart4 => (mrcc0.mrcc_lpuart4_clkdiv(), mrcc0.mrcc_lpuart4_clksel()), + LpuartInstance::Lpuart5 => (mrcc0.mrcc_lpuart5_clkdiv(), mrcc0.mrcc_lpuart5_clksel()), + }; + + let (freq, variant) = match self.source { + LpuartClockSel::FroLfDiv => { + let freq = clocks.ensure_fro_lf_div_active(&self.power)?; + (freq, Mux::ClkrootFunc0) + } + LpuartClockSel::FroHfDiv => { + let freq = clocks.ensure_fro_hf_div_active(&self.power)?; + (freq, Mux::ClkrootFunc2) + } + LpuartClockSel::ClkIn => { + let freq = clocks.ensure_clk_in_active(&self.power)?; + (freq, Mux::ClkrootFunc3) + } + LpuartClockSel::Clk16K => { + let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?; + (freq, Mux::ClkrootFunc4) + } + LpuartClockSel::Clk1M => { + let freq = clocks.ensure_clk_1m_active(&self.power)?; + (freq, Mux::ClkrootFunc5) + } + LpuartClockSel::Pll1ClkDiv => { + let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; + (freq, Mux::ClkrootFunc6) + } + LpuartClockSel::None => unsafe { + // no ClkrootFunc7, just write manually for now + clksel.write(|w| w.bits(0b111)); + clkdiv.modify(|_r, w| { + w.reset().asserted(); + w.halt().asserted(); + w + }); + return Ok(0); + }, + }; + + // set clksel + clksel.modify(|_r, w| w.mux().variant(variant)); + + // Set up clkdiv + clkdiv.modify(|_r, w| { + w.halt().asserted(); + w.reset().asserted(); + unsafe { w.div().bits(self.div.into_bits()) }; + w + }); + clkdiv.modify(|_r, w| { + w.halt().deasserted(); + w.reset().deasserted(); + w + }); + + while clkdiv.read().unstab().is_unstable() {} + + Ok(freq / self.div.into_divisor()) + } +} + +// +// OSTimer +// + +/// Selectable clocks for the OSTimer peripheral +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum OstimerClockSel { + /// 16k clock, sourced from FRO16K (Vdd Core) + Clk16kVddCore, + /// 1 MHz Clock sourced from FRO12M + Clk1M, + /// Disabled + None, +} + +/// Top level configuration for the `OSTimer` peripheral +pub struct OsTimerConfig { + /// Power state required for this peripheral + pub power: PoweredClock, + /// Selected clock source for this peripheral + pub source: OstimerClockSel, +} + +impl SPConfHelper for OsTimerConfig { + fn post_enable_config(&self, clocks: &Clocks) -> Result { + let mrcc0 = unsafe { pac::Mrcc0::steal() }; + Ok(match self.source { + OstimerClockSel::Clk16kVddCore => { + let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?; + mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_16k()); + freq + } + OstimerClockSel::Clk1M => { + let freq = clocks.ensure_clk_1m_active(&self.power)?; + mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); + freq + } + OstimerClockSel::None => { + mrcc0.mrcc_ostimer0_clksel().write(|w| unsafe { w.mux().bits(0b11) }); + 0 + } + }) + } +} + +// +// Adc +// + +/// Selectable clocks for the ADC peripheral +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum AdcClockSel { + /// Divided `fro_lf`/`clk_12m`/FRO12M source + FroLfDiv, + /// Gated `fro_hf`/`FRO180M` source + FroHf, + /// External Clock Source + ClkIn, + /// 1MHz clock sourced by a divided `fro_lf`/`clk_12m` + Clk1M, + /// Internal PLL output, with configurable divisor + Pll1ClkDiv, + /// No clock/disabled + None, +} + +/// Top level configuration for the ADC peripheral +pub struct AdcConfig { + /// Power state required for this peripheral + pub power: PoweredClock, + /// Selected clock-source for this peripheral + pub source: AdcClockSel, + /// Pre-divisor, applied to the upstream clock output + pub div: Div4, +} + +impl SPConfHelper for AdcConfig { + fn post_enable_config(&self, clocks: &Clocks) -> Result { + use mcxa_pac::mrcc0::mrcc_adc_clksel::Mux; + let mrcc0 = unsafe { pac::Mrcc0::steal() }; + let (freq, variant) = match self.source { + AdcClockSel::FroLfDiv => { + let freq = clocks.ensure_fro_lf_div_active(&self.power)?; + (freq, Mux::ClkrootFunc0) + } + AdcClockSel::FroHf => { + let freq = clocks.ensure_fro_hf_active(&self.power)?; + (freq, Mux::ClkrootFunc1) + } + AdcClockSel::ClkIn => { + let freq = clocks.ensure_clk_in_active(&self.power)?; + (freq, Mux::ClkrootFunc3) + } + AdcClockSel::Clk1M => { + let freq = clocks.ensure_clk_1m_active(&self.power)?; + (freq, Mux::ClkrootFunc5) + } + AdcClockSel::Pll1ClkDiv => { + let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; + (freq, Mux::ClkrootFunc6) + } + AdcClockSel::None => { + mrcc0.mrcc_adc_clksel().write(|w| unsafe { + // no ClkrootFunc7, just write manually for now + w.mux().bits(0b111) + }); + mrcc0.mrcc_adc_clkdiv().modify(|_r, w| { + w.reset().asserted(); + w.halt().asserted(); + w + }); + return Ok(0); + } + }; + + // set clksel + mrcc0.mrcc_adc_clksel().modify(|_r, w| w.mux().variant(variant)); + + // Set up clkdiv + mrcc0.mrcc_adc_clkdiv().modify(|_r, w| { + w.halt().asserted(); + w.reset().asserted(); + unsafe { w.div().bits(self.div.into_bits()) }; + w + }); + mrcc0.mrcc_adc_clkdiv().modify(|_r, w| { + w.halt().deasserted(); + w.reset().deasserted(); + w + }); + + while mrcc0.mrcc_adc_clkdiv().read().unstab().is_unstable() {} + + Ok(freq / self.div.into_divisor()) + } +} diff --git a/embassy-mcxa/src/config.rs b/embassy-mcxa/src/config.rs new file mode 100644 index 000000000..8d52a1d44 --- /dev/null +++ b/embassy-mcxa/src/config.rs @@ -0,0 +1,27 @@ +// HAL configuration (minimal), mirroring embassy-imxrt style + +use crate::clocks::config::ClocksConfig; +use crate::interrupt::Priority; + +#[non_exhaustive] +pub struct Config { + #[cfg(feature = "time")] + pub time_interrupt_priority: Priority, + pub rtc_interrupt_priority: Priority, + pub adc_interrupt_priority: Priority, + pub gpio_interrupt_priority: Priority, + pub clock_cfg: ClocksConfig, +} + +impl Default for Config { + fn default() -> Self { + Self { + #[cfg(feature = "time")] + time_interrupt_priority: Priority::from(0), + rtc_interrupt_priority: Priority::from(0), + adc_interrupt_priority: Priority::from(0), + gpio_interrupt_priority: Priority::from(0), + clock_cfg: ClocksConfig::default(), + } + } +} diff --git a/embassy-mcxa/src/dma.rs b/embassy-mcxa/src/dma.rs new file mode 100644 index 000000000..7d1588516 --- /dev/null +++ b/embassy-mcxa/src/dma.rs @@ -0,0 +1,2594 @@ +//! DMA driver for MCXA276. +//! +//! This module provides a typed channel abstraction over the EDMA_0_TCD0 array +//! and helpers for configuring the channel MUX. The driver supports both +//! low-level TCD configuration and higher-level async transfer APIs. +//! +//! # Architecture +//! +//! The MCXA276 has 8 DMA channels (0-7), each with its own interrupt vector. +//! Each channel has a Transfer Control Descriptor (TCD) that defines the +//! transfer parameters. +//! +//! # Choosing the Right API +//! +//! This module provides several API levels to match different use cases: +//! +//! ## High-Level Async API (Recommended for Most Users) +//! +//! Use the async methods when you want simple, safe DMA transfers: +//! +//! | Method | Description | +//! |--------|-------------| +//! | [`DmaChannel::mem_to_mem()`] | Memory-to-memory copy | +//! | [`DmaChannel::memset()`] | Fill memory with a pattern | +//! | [`DmaChannel::write()`] | Memory-to-peripheral (TX) | +//! | [`DmaChannel::read()`] | Peripheral-to-memory (RX) | +//! +//! These return a [`Transfer`] future that can be `.await`ed: +//! +//! ```no_run +//! # use embassy_mcxa::dma::{DmaChannel, TransferOptions}; +//! # let dma_ch = DmaChannel::new(p.DMA_CH0); +//! # let src = [0u32; 4]; +//! # let mut dst = [0u32; 4]; +//! // Simple memory-to-memory transfer +//! unsafe { +//! dma_ch.mem_to_mem(&src, &mut dst, TransferOptions::default()).await; +//! } +//! ``` +//! +//! ## Setup Methods (For Peripheral Drivers) +//! +//! Use setup methods when you need manual lifecycle control: +//! +//! | Method | Description | +//! |--------|-------------| +//! | [`DmaChannel::setup_write()`] | Configure TX without starting | +//! | [`DmaChannel::setup_read()`] | Configure RX without starting | +//! +//! These configure the TCD but don't start the transfer. You control: +//! 1. When to call [`DmaChannel::enable_request()`] +//! 2. How to detect completion (polling or interrupts) +//! 3. When to clean up with [`DmaChannel::clear_done()`] +//! +//! ## Circular/Ring Buffer API (For Continuous Reception) +//! +//! Use [`DmaChannel::setup_circular_read()`] for continuous data reception: +//! +//! ```no_run +//! # use embassy_mcxa::dma::DmaChannel; +//! # let dma_ch = DmaChannel::new(p.DMA_CH0); +//! # let uart_rx_addr = 0x4000_0000 as *const u8; +//! static mut RX_BUF: [u8; 64] = [0; 64]; +//! +//! let ring_buf = unsafe { +//! dma_ch.setup_circular_read(uart_rx_addr, &mut RX_BUF) +//! }; +//! +//! // Read data as it arrives +//! let mut buf = [0u8; 16]; +//! let n = ring_buf.read(&mut buf).await.unwrap(); +//! ``` +//! +//! ## Scatter-Gather Builder (For Chained Transfers) +//! +//! Use [`ScatterGatherBuilder`] for complex multi-segment transfers: +//! +//! ```no_run +//! # use embassy_mcxa::dma::{DmaChannel, ScatterGatherBuilder}; +//! # let dma_ch = DmaChannel::new(p.DMA_CH0); +//! let mut builder = ScatterGatherBuilder::::new(); +//! builder.add_transfer(&src1, &mut dst1); +//! builder.add_transfer(&src2, &mut dst2); +//! +//! let transfer = unsafe { builder.build(&dma_ch).unwrap() }; +//! transfer.await; +//! ``` +//! +//! ## Direct TCD Access (For Advanced Use Cases) +//! +//! For full control, use the channel's `tcd()` method to access TCD registers directly. +//! See the `dma_*` examples for patterns. +//! +//! # Example +//! +//! ```no_run +//! use embassy_mcxa::dma::{DmaChannel, TransferOptions, Direction}; +//! +//! let dma_ch = DmaChannel::new(p.DMA_CH0); +//! // Configure and trigger a transfer... +//! ``` + +use core::future::Future; +use core::marker::PhantomData; +use core::pin::Pin; +use core::ptr::NonNull; +use core::sync::atomic::{fence, AtomicBool, AtomicUsize, Ordering}; +use core::task::{Context, Poll}; + +use embassy_hal_internal::PeripheralType; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::clocks::Gate; +use crate::pac; +use crate::pac::Interrupt; +use crate::peripherals::DMA0; + +/// Static flag to track whether DMA has been initialized. +static DMA_INITIALIZED: AtomicBool = AtomicBool::new(false); + +/// Initialize DMA controller (clock enabled, reset released, controller configured). +/// +/// This function is intended to be called during HAL initialization (`hal::init()`). +/// It is idempotent - it will only initialize DMA once, even if called multiple times. +/// +/// The function enables the DMA0 clock, releases reset, and configures the controller +/// for normal operation with round-robin arbitration. +pub fn init() { + // Fast path: already initialized + if DMA_INITIALIZED.load(Ordering::Acquire) { + return; + } + + // Slow path: initialize DMA + // Use compare_exchange to ensure only one caller initializes + if DMA_INITIALIZED + .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) + .is_ok() + { + // We won the race - initialize DMA + unsafe { + // Enable DMA0 clock and release reset + DMA0::enable_clock(); + DMA0::release_reset(); + + // Configure DMA controller + let dma = &(*pac::Dma0::ptr()); + dma.mp_csr().modify(|_, w| { + w.edbg() + .enable() + .erca() + .enable() + .halt() + .normal_operation() + .gclc() + .available() + .gmrc() + .available() + }); + } + } +} + +// ============================================================================ +// Phase 1: Foundation Types (Embassy-aligned) +// ============================================================================ + +/// DMA transfer direction. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Direction { + /// Transfer from memory to memory. + MemoryToMemory, + /// Transfer from memory to a peripheral register. + MemoryToPeripheral, + /// Transfer from a peripheral register to memory. + PeripheralToMemory, +} + +/// DMA transfer priority. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Priority { + /// Low priority (channel priority 7). + Low, + /// Medium priority (channel priority 4). + Medium, + /// High priority (channel priority 1). + #[default] + High, + /// Highest priority (channel priority 0). + Highest, +} + +impl Priority { + /// Convert to hardware priority value (0 = highest, 7 = lowest). + pub fn to_hw_priority(self) -> u8 { + match self { + Priority::Low => 7, + Priority::Medium => 4, + Priority::High => 1, + Priority::Highest => 0, + } + } +} + +/// DMA transfer data width. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum WordSize { + /// 8-bit (1 byte) transfers. + OneByte, + /// 16-bit (2 byte) transfers. + TwoBytes, + /// 32-bit (4 byte) transfers. + #[default] + FourBytes, +} + +impl WordSize { + /// Size in bytes. + pub const fn bytes(self) -> usize { + match self { + WordSize::OneByte => 1, + WordSize::TwoBytes => 2, + WordSize::FourBytes => 4, + } + } + + /// Convert to hardware SSIZE/DSIZE field value. + pub const fn to_hw_size(self) -> u8 { + match self { + WordSize::OneByte => 0, + WordSize::TwoBytes => 1, + WordSize::FourBytes => 2, + } + } + + /// Create from byte width (1, 2, or 4). + pub const fn from_bytes(bytes: u8) -> Option { + match bytes { + 1 => Some(WordSize::OneByte), + 2 => Some(WordSize::TwoBytes), + 4 => Some(WordSize::FourBytes), + _ => None, + } + } +} + +/// Trait for types that can be transferred via DMA. +/// +/// This provides compile-time type safety for DMA transfers. +pub trait Word: Copy + 'static { + /// The word size for this type. + fn size() -> WordSize; +} + +impl Word for u8 { + fn size() -> WordSize { + WordSize::OneByte + } +} + +impl Word for u16 { + fn size() -> WordSize { + WordSize::TwoBytes + } +} + +impl Word for u32 { + fn size() -> WordSize { + WordSize::FourBytes + } +} + +/// DMA transfer options. +/// +/// This struct configures various aspects of a DMA transfer. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct TransferOptions { + /// Transfer priority. + pub priority: Priority, + /// Enable circular (continuous) mode. + /// + /// When enabled, the transfer repeats automatically after completing. + pub circular: bool, + /// Enable interrupt on half transfer complete. + pub half_transfer_interrupt: bool, + /// Enable interrupt on transfer complete. + pub complete_transfer_interrupt: bool, +} + +impl Default for TransferOptions { + fn default() -> Self { + Self { + priority: Priority::High, + circular: false, + half_transfer_interrupt: false, + complete_transfer_interrupt: true, + } + } +} + +/// DMA error types. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The DMA controller reported a bus error. + BusError, + /// The transfer was aborted. + Aborted, + /// Configuration error (e.g., invalid parameters). + Configuration, + /// Buffer overrun (for ring buffers). + Overrun, +} + +/// Whether to enable the major loop completion interrupt. +/// +/// This enum provides better readability than a boolean parameter +/// for functions that configure DMA interrupt behavior. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EnableInterrupt { + /// Enable the interrupt on major loop completion. + Yes, + /// Do not enable the interrupt. + No, +} + +// ============================================================================ +// DMA Constants +// ============================================================================ + +/// Maximum bytes per DMA transfer (eDMA4 CITER/BITER are 15-bit fields). +/// +/// This is a hardware limitation of the eDMA4 controller. Transfers larger +/// than this must be split into multiple DMA operations. +pub const DMA_MAX_TRANSFER_SIZE: usize = 0x7FFF; + +// ============================================================================ +// DMA Request Source Types (Type-Safe API) +// ============================================================================ + +/// Trait for type-safe DMA request sources. +/// +/// Each peripheral that can trigger DMA requests implements this trait +/// with marker types that encode the correct request source number at +/// compile time. This prevents using the wrong request source for a +/// peripheral. +/// +/// # Example +/// +/// ```ignore +/// // The LPUART2 RX request source is automatically derived from the type: +/// channel.set_request_source::(); +/// ``` +/// +/// This trait is sealed and cannot be implemented outside this crate. +#[allow(private_bounds)] +pub trait DmaRequest: sealed::SealedDmaRequest { + /// The hardware request source number for the DMA mux. + const REQUEST_NUMBER: u8; +} + +/// Macro to define a DMA request type. +/// +/// Creates a zero-sized marker type that implements `DmaRequest` with +/// the specified request number. +macro_rules! define_dma_request { + ($(#[$meta:meta])* $name:ident = $num:expr) => { + $(#[$meta])* + #[derive(Debug, Copy, Clone)] + pub struct $name; + + impl sealed::SealedDmaRequest for $name {} + + impl DmaRequest for $name { + const REQUEST_NUMBER: u8 = $num; + } + }; +} + +// LPUART DMA request sources (from MCXA276 reference manual Table 4-8) +define_dma_request!( + /// DMA request source for LPUART0 RX. + Lpuart0RxRequest = 21 +); +define_dma_request!( + /// DMA request source for LPUART0 TX. + Lpuart0TxRequest = 22 +); +define_dma_request!( + /// DMA request source for LPUART1 RX. + Lpuart1RxRequest = 23 +); +define_dma_request!( + /// DMA request source for LPUART1 TX. + Lpuart1TxRequest = 24 +); +define_dma_request!( + /// DMA request source for LPUART2 RX. + Lpuart2RxRequest = 25 +); +define_dma_request!( + /// DMA request source for LPUART2 TX. + Lpuart2TxRequest = 26 +); +define_dma_request!( + /// DMA request source for LPUART3 RX. + Lpuart3RxRequest = 27 +); +define_dma_request!( + /// DMA request source for LPUART3 TX. + Lpuart3TxRequest = 28 +); +define_dma_request!( + /// DMA request source for LPUART4 RX. + Lpuart4RxRequest = 29 +); +define_dma_request!( + /// DMA request source for LPUART4 TX. + Lpuart4TxRequest = 30 +); +define_dma_request!( + /// DMA request source for LPUART5 RX. + Lpuart5RxRequest = 31 +); +define_dma_request!( + /// DMA request source for LPUART5 TX. + Lpuart5TxRequest = 32 +); + +// ============================================================================ +// Channel Trait (Sealed Pattern) +// ============================================================================ + +mod sealed { + use crate::pac::Interrupt; + + /// Sealed trait for DMA channels. + pub trait SealedChannel { + /// Zero-based channel index into the TCD array. + fn index(&self) -> usize; + /// Interrupt vector for this channel. + fn interrupt(&self) -> Interrupt; + } + + /// Sealed trait for DMA request sources. + pub trait SealedDmaRequest {} +} + +/// Marker trait implemented by HAL peripheral tokens that map to a DMA0 +/// channel backed by one EDMA_0_TCD0 TCD slot. +/// +/// This trait is sealed and cannot be implemented outside this crate. +#[allow(private_bounds)] +pub trait Channel: sealed::SealedChannel + PeripheralType + Into + 'static { + /// Zero-based channel index into the TCD array. + const INDEX: usize; + /// Interrupt vector for this channel. + const INTERRUPT: Interrupt; +} + +/// Type-erased DMA channel. +/// +/// This allows storing DMA channels in a uniform way regardless of their +/// concrete type, useful for async transfer futures and runtime channel selection. +#[derive(Debug, Clone, Copy)] +pub struct AnyChannel { + index: usize, + interrupt: Interrupt, +} + +impl AnyChannel { + /// Get the channel index. + #[inline] + pub const fn index(&self) -> usize { + self.index + } + + /// Get the channel interrupt. + #[inline] + pub const fn interrupt(&self) -> Interrupt { + self.interrupt + } + + /// Get a reference to the TCD register block for this channel. + /// + /// This steals the eDMA pointer internally since MCXA276 has only one eDMA instance. + #[inline] + fn tcd(&self) -> &'static pac::edma_0_tcd0::Tcd { + // Safety: MCXA276 has a single eDMA instance, and we're only accessing + // the TCD for this specific channel + let edma = unsafe { &*pac::Edma0Tcd0::ptr() }; + edma.tcd(self.index) + } + + /// Check if the channel's DONE flag is set. + pub fn is_done(&self) -> bool { + self.tcd().ch_csr().read().done().bit_is_set() + } + + /// Get the waker for this channel. + pub fn waker(&self) -> &'static AtomicWaker { + &STATES[self.index].waker + } +} + +impl sealed::SealedChannel for AnyChannel { + fn index(&self) -> usize { + self.index + } + + fn interrupt(&self) -> Interrupt { + self.interrupt + } +} + +/// Macro to implement Channel trait for a peripheral. +macro_rules! impl_channel { + ($peri:ident, $index:expr, $irq:ident) => { + impl sealed::SealedChannel for crate::peripherals::$peri { + fn index(&self) -> usize { + $index + } + + fn interrupt(&self) -> Interrupt { + Interrupt::$irq + } + } + + impl Channel for crate::peripherals::$peri { + const INDEX: usize = $index; + const INTERRUPT: Interrupt = Interrupt::$irq; + } + + impl From for AnyChannel { + fn from(_: crate::peripherals::$peri) -> Self { + AnyChannel { + index: $index, + interrupt: Interrupt::$irq, + } + } + } + }; +} + +impl_channel!(DMA_CH0, 0, DMA_CH0); +impl_channel!(DMA_CH1, 1, DMA_CH1); +impl_channel!(DMA_CH2, 2, DMA_CH2); +impl_channel!(DMA_CH3, 3, DMA_CH3); +impl_channel!(DMA_CH4, 4, DMA_CH4); +impl_channel!(DMA_CH5, 5, DMA_CH5); +impl_channel!(DMA_CH6, 6, DMA_CH6); +impl_channel!(DMA_CH7, 7, DMA_CH7); + +/// Strongly-typed handle to a DMA0 channel. +/// +/// The lifetime of this value is tied to the unique peripheral token +/// supplied by `embassy_hal_internal::peripherals!`, so safe code cannot +/// create two `DmaChannel` instances for the same hardware channel. +pub struct DmaChannel { + _ch: core::marker::PhantomData, +} + +// ============================================================================ +// DMA Transfer Methods - API Overview +// ============================================================================ +// +// The DMA API provides two categories of methods for configuring transfers: +// +// ## 1. Async Methods (Return `Transfer` Future) +// +// These methods return a [`Transfer`] Future that must be `.await`ed: +// +// - [`write()`](DmaChannel::write) - Memory-to-peripheral using default eDMA TCD block +// - [`read()`](DmaChannel::read) - Peripheral-to-memory using default eDMA TCD block +// - [`write_to_peripheral()`](DmaChannel::write_to_peripheral) - Memory-to-peripheral with custom eDMA TCD block +// - [`read_from_peripheral()`](DmaChannel::read_from_peripheral) - Peripheral-to-memory with custom eDMA TCD block +// - [`mem_to_mem()`](DmaChannel::mem_to_mem) - Memory-to-memory using default eDMA TCD block +// - [`transfer_mem_to_mem()`](DmaChannel::transfer_mem_to_mem) - Memory-to-memory with custom eDMA TCD block +// +// The `Transfer` manages the DMA lifecycle automatically: +// - Enables channel request +// - Waits for completion via async/await +// - Cleans up on completion +// +// **Important:** `Transfer::Drop` aborts the transfer if dropped before completion. +// This means you MUST `.await` the Transfer or it will be aborted when it goes out of scope. +// +// **Use case:** When you want to use async/await and let the Transfer handle lifecycle management. +// +// ## 2. Setup Methods (Configure TCD Only) +// +// These methods configure the TCD but do NOT return a `Transfer`: +// +// - [`setup_write()`](DmaChannel::setup_write) - Memory-to-peripheral using default eDMA TCD block +// - [`setup_read()`](DmaChannel::setup_read) - Peripheral-to-memory using default eDMA TCD block +// - [`setup_write_to_peripheral()`](DmaChannel::setup_write_to_peripheral) - Memory-to-peripheral with custom eDMA TCD block +// - [`setup_read_from_peripheral()`](DmaChannel::setup_read_from_peripheral) - Peripheral-to-memory with custom eDMA TCD block +// +// The caller is responsible for the complete DMA lifecycle: +// 1. Call [`enable_request()`](DmaChannel::enable_request) to start the transfer +// 2. Poll [`is_done()`](DmaChannel::is_done) or use interrupts to detect completion +// 3. Call [`disable_request()`](DmaChannel::disable_request), [`clear_done()`](DmaChannel::clear_done), +// [`clear_interrupt()`](DmaChannel::clear_interrupt) for cleanup +// +// **Use case:** Peripheral drivers (like LPUART) that need fine-grained control over +// DMA setup before starting a `Transfer`. +// +// ============================================================================ + +impl DmaChannel { + /// Wrap a DMA channel token (takes ownership of the Peri wrapper). + /// + /// Note: DMA is initialized during `hal::init()` via `dma::init()`. + #[inline] + pub fn new(_ch: embassy_hal_internal::Peri<'_, C>) -> Self { + Self { + _ch: core::marker::PhantomData, + } + } + + /// Wrap a DMA channel token directly (for internal use). + /// + /// Note: DMA is initialized during `hal::init()` via `dma::init()`. + #[inline] + pub fn from_token(_ch: C) -> Self { + Self { + _ch: core::marker::PhantomData, + } + } + + /// Channel index in the EDMA_0_TCD0 array. + #[inline] + pub const fn index(&self) -> usize { + C::INDEX + } + + /// Convert this typed channel into a type-erased `AnyChannel`. + #[inline] + pub fn into_any(self) -> AnyChannel { + AnyChannel { + index: C::INDEX, + interrupt: C::INTERRUPT, + } + } + + /// Get a reference to the type-erased channel info. + #[inline] + pub fn as_any(&self) -> AnyChannel { + AnyChannel { + index: C::INDEX, + interrupt: C::INTERRUPT, + } + } + + /// Return a reference to the underlying TCD register block. + /// + /// This steals the eDMA pointer internally since MCXA276 has only one eDMA instance. + /// + /// # Note + /// + /// This is exposed for advanced use cases that need direct TCD access. + /// For most use cases, prefer the higher-level transfer methods. + #[inline] + pub fn tcd(&self) -> &'static pac::edma_0_tcd0::Tcd { + // Safety: MCXA276 has a single eDMA instance + let edma = unsafe { &*pac::Edma0Tcd0::ptr() }; + edma.tcd(C::INDEX) + } + + /// Start an async transfer. + /// + /// The channel must already be configured. This enables the channel + /// request and returns a `Transfer` future that resolves when the + /// DMA transfer completes. + /// + /// # Safety + /// + /// The caller must ensure the DMA channel has been properly configured + /// and that source/destination buffers remain valid for the duration + /// of the transfer. + pub unsafe fn start_transfer(&self) -> Transfer<'_> { + // Clear any previous DONE/INT flags + let t = self.tcd(); + t.ch_csr().modify(|_, w| w.done().clear_bit_by_one()); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Enable the channel request + t.ch_csr().modify(|_, w| w.erq().enable()); + + Transfer::new(self.as_any()) + } + + // ======================================================================== + // Type-Safe Transfer Methods (Embassy-style API) + // ======================================================================== + + /// Perform a memory-to-memory DMA transfer (simplified API). + /// + /// This is a type-safe wrapper that uses the `Word` trait to determine + /// the correct transfer width automatically. Uses the global eDMA TCD + /// register accessor internally. + /// + /// # Arguments + /// + /// * `src` - Source buffer + /// * `dst` - Destination buffer (must be at least as large as src) + /// * `options` - Transfer configuration options + /// + /// # Safety + /// + /// The source and destination buffers must remain valid for the + /// duration of the transfer. + pub unsafe fn mem_to_mem(&self, src: &[W], dst: &mut [W], options: TransferOptions) -> Transfer<'_> { + self.transfer_mem_to_mem(src, dst, options) + } + + /// Perform a memory-to-memory DMA transfer. + /// + /// This is a type-safe wrapper that uses the `Word` trait to determine + /// the correct transfer width automatically. + /// + /// # Arguments + /// + /// * `edma` - Reference to the eDMA TCD register block + /// * `src` - Source buffer + /// * `dst` - Destination buffer (must be at least as large as src) + /// * `options` - Transfer configuration options + /// + /// # Safety + /// + /// The source and destination buffers must remain valid for the + /// duration of the transfer. + pub unsafe fn transfer_mem_to_mem( + &self, + src: &[W], + dst: &mut [W], + options: TransferOptions, + ) -> Transfer<'_> { + assert!(!src.is_empty()); + assert!(dst.len() >= src.len()); + assert!(src.len() <= 0x7fff); + + let size = W::size(); + let byte_count = (src.len() * size.bytes()) as u32; + + let t = self.tcd(); + + // Reset channel state - clear DONE, disable requests, clear errors + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.err().clear_bit_by_one()); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Memory barrier to ensure channel state is fully reset before touching TCD + cortex_m::asm::dsb(); + + // Full TCD reset following NXP SDK pattern (EDMA_TcdResetExt). + // Reset ALL TCD registers to 0 to clear any stale configuration from + // previous transfers. This is critical when reusing a channel. + t.tcd_saddr().write(|w| w.saddr().bits(0)); + t.tcd_soff().write(|w| w.soff().bits(0)); + t.tcd_attr().write(|w| w.bits(0)); + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(0)); + t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); + t.tcd_daddr().write(|w| w.daddr().bits(0)); + t.tcd_doff().write(|w| w.doff().bits(0)); + t.tcd_citer_elinkno().write(|w| w.bits(0)); + t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(0)); + t.tcd_csr().write(|w| w.bits(0)); // Clear CSR completely + t.tcd_biter_elinkno().write(|w| w.bits(0)); + + // Memory barrier after TCD reset + cortex_m::asm::dsb(); + + // Note: Priority is managed by round-robin arbitration (set in init()) + // Per-channel priority can be configured via ch_pri() if needed + + // Now configure the new transfer + + // Source address and increment + t.tcd_saddr().write(|w| w.saddr().bits(src.as_ptr() as u32)); + t.tcd_soff().write(|w| w.soff().bits(size.bytes() as u16)); + + // Destination address and increment + t.tcd_daddr().write(|w| w.daddr().bits(dst.as_mut_ptr() as u32)); + t.tcd_doff().write(|w| w.doff().bits(size.bytes() as u16)); + + // Transfer attributes (size) + let hw_size = size.to_hw_size(); + t.tcd_attr().write(|w| w.ssize().bits(hw_size).dsize().bits(hw_size)); + + // Minor loop: transfer all bytes in one minor loop + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(byte_count)); + + // No source/dest adjustment after major loop + t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); + t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(0)); + + // Major loop count = 1 (single major loop) + // Write BITER first, then CITER (CITER must match BITER at start) + t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); + t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); + + // Memory barrier before setting START + cortex_m::asm::dsb(); + + // Control/status: interrupt on major complete, start + // Write this last after all other TCD registers are configured + let int_major = options.complete_transfer_interrupt; + t.tcd_csr().write(|w| { + w.intmajor() + .bit(int_major) + .inthalf() + .bit(options.half_transfer_interrupt) + .dreq() + .set_bit() // Auto-disable request after major loop + .start() + .set_bit() // Start the channel + }); + + Transfer::new(self.as_any()) + } + + /// Fill a memory buffer with a pattern value (memset). + /// + /// This performs a DMA transfer where the source address remains fixed + /// (pattern value) while the destination address increments through the buffer. + /// It's useful for quickly filling large memory regions with a constant value. + /// + /// # Arguments + /// + /// * `pattern` - Reference to the pattern value (will be read repeatedly) + /// * `dst` - Destination buffer to fill + /// * `options` - Transfer configuration options + /// + /// # Example + /// + /// ```no_run + /// use embassy_mcxa::dma::{DmaChannel, TransferOptions}; + /// + /// let dma_ch = DmaChannel::new(p.DMA_CH0); + /// let pattern: u32 = 0xDEADBEEF; + /// let mut buffer = [0u32; 256]; + /// + /// unsafe { + /// dma_ch.memset(&pattern, &mut buffer, TransferOptions::default()).await; + /// } + /// // buffer is now filled with 0xDEADBEEF + /// ``` + /// + /// # Safety + /// + /// - The pattern and destination buffer must remain valid for the duration of the transfer. + pub unsafe fn memset(&self, pattern: &W, dst: &mut [W], options: TransferOptions) -> Transfer<'_> { + assert!(!dst.is_empty()); + assert!(dst.len() <= 0x7fff); + + let size = W::size(); + let byte_size = size.bytes(); + // Total bytes to transfer - all in one minor loop for software-triggered transfers + let total_bytes = (dst.len() * byte_size) as u32; + + let t = self.tcd(); + + // Reset channel state - clear DONE, disable requests, clear errors + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.err().clear_bit_by_one()); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Memory barrier to ensure channel state is fully reset before touching TCD + cortex_m::asm::dsb(); + + // Full TCD reset following NXP SDK pattern (EDMA_TcdResetExt). + // Reset ALL TCD registers to 0 to clear any stale configuration from + // previous transfers. This is critical when reusing a channel. + t.tcd_saddr().write(|w| w.saddr().bits(0)); + t.tcd_soff().write(|w| w.soff().bits(0)); + t.tcd_attr().write(|w| w.bits(0)); + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(0)); + t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); + t.tcd_daddr().write(|w| w.daddr().bits(0)); + t.tcd_doff().write(|w| w.doff().bits(0)); + t.tcd_citer_elinkno().write(|w| w.bits(0)); + t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(0)); + t.tcd_csr().write(|w| w.bits(0)); // Clear CSR completely + t.tcd_biter_elinkno().write(|w| w.bits(0)); + + // Memory barrier after TCD reset + cortex_m::asm::dsb(); + + // Now configure the new transfer + // + // For software-triggered memset, we use a SINGLE minor loop that transfers + // all bytes at once. The source address stays fixed (SOFF=0) while the + // destination increments (DOFF=byte_size). The eDMA will read from the + // same source address for each destination word. + // + // This is necessary because the START bit only triggers ONE minor loop + // iteration. Using CITER>1 with software trigger would require multiple + // START triggers. + + // Source: pattern address, fixed (soff=0) + t.tcd_saddr().write(|w| w.saddr().bits(pattern as *const W as u32)); + t.tcd_soff().write(|w| w.soff().bits(0)); // Fixed source - reads pattern repeatedly + + // Destination: memory buffer, incrementing by word size + t.tcd_daddr().write(|w| w.daddr().bits(dst.as_mut_ptr() as u32)); + t.tcd_doff().write(|w| w.doff().bits(byte_size as u16)); + + // Transfer attributes - source and dest are same word size + let hw_size = size.to_hw_size(); + t.tcd_attr().write(|w| w.ssize().bits(hw_size).dsize().bits(hw_size)); + + // Minor loop: transfer ALL bytes in one minor loop (like mem_to_mem) + // This allows the entire transfer to complete with a single START trigger + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(total_bytes)); + + // No address adjustment after major loop + t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); + t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(0)); + + // Major loop count = 1 (single major loop, all data in minor loop) + // Write BITER first, then CITER (CITER must match BITER at start) + t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); + t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); + + // Memory barrier before setting START + cortex_m::asm::dsb(); + + // Control/status: interrupt on major complete, start immediately + // Write this last after all other TCD registers are configured + let int_major = options.complete_transfer_interrupt; + t.tcd_csr().write(|w| { + w.intmajor() + .bit(int_major) + .inthalf() + .bit(options.half_transfer_interrupt) + .dreq() + .set_bit() // Auto-disable request after major loop + .start() + .set_bit() // Start the channel + }); + + Transfer::new(self.as_any()) + } + + /// Write data from memory to a peripheral register. + /// + /// The destination address remains fixed (peripheral register) while + /// the source address increments through the buffer. + /// + /// # Arguments + /// + /// * `buf` - Source buffer to write from + /// * `peri_addr` - Peripheral register address + /// * `options` - Transfer configuration options + /// + /// # Safety + /// + /// - The buffer must remain valid for the duration of the transfer. + /// - The peripheral address must be valid for writes. + pub unsafe fn write(&self, buf: &[W], peri_addr: *mut W, options: TransferOptions) -> Transfer<'_> { + self.write_to_peripheral(buf, peri_addr, options) + } + + /// Configure a memory-to-peripheral DMA transfer without starting it. + /// + /// This is a convenience wrapper around [`setup_write_to_peripheral()`](Self::setup_write_to_peripheral) + /// that uses the default eDMA TCD register block. + /// + /// This method configures the TCD but does NOT return a `Transfer`. The caller + /// is responsible for the complete DMA lifecycle: + /// 1. Call [`enable_request()`](Self::enable_request) to start the transfer + /// 2. Poll [`is_done()`](Self::is_done) or use interrupts to detect completion + /// 3. Call [`disable_request()`](Self::disable_request), [`clear_done()`](Self::clear_done), + /// [`clear_interrupt()`](Self::clear_interrupt) for cleanup + /// + /// # Example + /// + /// ```no_run + /// # use embassy_mcxa::dma::DmaChannel; + /// # let dma_ch = DmaChannel::new(p.DMA_CH0); + /// # let uart_tx_addr = 0x4000_0000 as *mut u8; + /// let data = [0x48, 0x65, 0x6c, 0x6c, 0x6f]; // "Hello" + /// + /// unsafe { + /// // Configure the transfer + /// dma_ch.setup_write(&data, uart_tx_addr, EnableInterrupt::Yes); + /// + /// // Start when peripheral is ready + /// dma_ch.enable_request(); + /// + /// // Wait for completion (or use interrupt) + /// while !dma_ch.is_done() {} + /// + /// // Clean up + /// dma_ch.clear_done(); + /// dma_ch.clear_interrupt(); + /// } + /// ``` + /// + /// # Arguments + /// + /// * `buf` - Source buffer to write from + /// * `peri_addr` - Peripheral register address + /// * `enable_interrupt` - Whether to enable interrupt on completion + /// + /// # Safety + /// + /// - The buffer must remain valid for the duration of the transfer. + /// - The peripheral address must be valid for writes. + pub unsafe fn setup_write(&self, buf: &[W], peri_addr: *mut W, enable_interrupt: EnableInterrupt) { + self.setup_write_to_peripheral(buf, peri_addr, enable_interrupt) + } + + /// Write data from memory to a peripheral register. + /// + /// The destination address remains fixed (peripheral register) while + /// the source address increments through the buffer. + /// + /// # Arguments + /// + /// * `buf` - Source buffer to write from + /// * `peri_addr` - Peripheral register address + /// * `options` - Transfer configuration options + /// + /// # Safety + /// + /// - The buffer must remain valid for the duration of the transfer. + /// - The peripheral address must be valid for writes. + pub unsafe fn write_to_peripheral( + &self, + buf: &[W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Transfer<'_> { + assert!(!buf.is_empty()); + assert!(buf.len() <= 0x7fff); + + let size = W::size(); + let byte_size = size.bytes(); + + let t = self.tcd(); + + // Reset channel state + t.ch_csr().write(|w| w.erq().disable().done().clear_bit_by_one()); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Addresses + t.tcd_saddr().write(|w| w.saddr().bits(buf.as_ptr() as u32)); + t.tcd_daddr().write(|w| w.daddr().bits(peri_addr as u32)); + + // Offsets: Source increments, Dest fixed + t.tcd_soff().write(|w| w.soff().bits(byte_size as u16)); + t.tcd_doff().write(|w| w.doff().bits(0)); + + // Attributes: set size and explicitly disable modulo + let hw_size = size.to_hw_size(); + t.tcd_attr().write(|w| { + w.ssize() + .bits(hw_size) + .dsize() + .bits(hw_size) + .smod() + .disable() + .dmod() + .bits(0) + }); + + // Minor loop: transfer one word per request (match old: only set nbytes) + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(byte_size as u32)); + + // No final adjustments + t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); + t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(0)); + + // Major loop count = number of words + let count = buf.len() as u16; + t.tcd_citer_elinkno().write(|w| w.citer().bits(count).elink().disable()); + t.tcd_biter_elinkno().write(|w| w.biter().bits(count).elink().disable()); + + // CSR: interrupt on major loop complete and auto-clear ERQ + t.tcd_csr().write(|w| { + let w = if options.complete_transfer_interrupt { + w.intmajor().enable() + } else { + w.intmajor().disable() + }; + w.inthalf() + .disable() + .dreq() + .erq_field_clear() // Disable request when done + .esg() + .normal_format() + .majorelink() + .disable() + .eeop() + .disable() + .esda() + .disable() + .bwc() + .no_stall() + }); + + // Ensure all TCD writes have completed before DMA engine reads them + cortex_m::asm::dsb(); + + Transfer::new(self.as_any()) + } + + /// Read data from a peripheral register to memory. + /// + /// The source address remains fixed (peripheral register) while + /// the destination address increments through the buffer. + /// + /// # Arguments + /// + /// * `peri_addr` - Peripheral register address + /// * `buf` - Destination buffer to read into + /// * `options` - Transfer configuration options + /// + /// # Safety + /// + /// - The buffer must remain valid for the duration of the transfer. + /// - The peripheral address must be valid for reads. + pub unsafe fn read(&self, peri_addr: *const W, buf: &mut [W], options: TransferOptions) -> Transfer<'_> { + self.read_from_peripheral(peri_addr, buf, options) + } + + /// Configure a peripheral-to-memory DMA transfer without starting it. + /// + /// This is a convenience wrapper around [`setup_read_from_peripheral()`](Self::setup_read_from_peripheral) + /// that uses the default eDMA TCD register block. + /// + /// This method configures the TCD but does NOT return a `Transfer`. The caller + /// is responsible for the complete DMA lifecycle: + /// 1. Call [`enable_request()`](Self::enable_request) to start the transfer + /// 2. Poll [`is_done()`](Self::is_done) or use interrupts to detect completion + /// 3. Call [`disable_request()`](Self::disable_request), [`clear_done()`](Self::clear_done), + /// [`clear_interrupt()`](Self::clear_interrupt) for cleanup + /// + /// # Example + /// + /// ```no_run + /// # use embassy_mcxa::dma::DmaChannel; + /// # let dma_ch = DmaChannel::new(p.DMA_CH0); + /// # let uart_rx_addr = 0x4000_0000 as *const u8; + /// let mut buf = [0u8; 32]; + /// + /// unsafe { + /// // Configure the transfer + /// dma_ch.setup_read(uart_rx_addr, &mut buf, EnableInterrupt::Yes); + /// + /// // Start when peripheral is ready + /// dma_ch.enable_request(); + /// + /// // Wait for completion (or use interrupt) + /// while !dma_ch.is_done() {} + /// + /// // Clean up + /// dma_ch.clear_done(); + /// dma_ch.clear_interrupt(); + /// } + /// // buf now contains received data + /// ``` + /// + /// # Arguments + /// + /// * `peri_addr` - Peripheral register address + /// * `buf` - Destination buffer to read into + /// * `enable_interrupt` - Whether to enable interrupt on completion + /// + /// # Safety + /// + /// - The buffer must remain valid for the duration of the transfer. + /// - The peripheral address must be valid for reads. + pub unsafe fn setup_read(&self, peri_addr: *const W, buf: &mut [W], enable_interrupt: EnableInterrupt) { + self.setup_read_from_peripheral(peri_addr, buf, enable_interrupt) + } + + /// Read data from a peripheral register to memory. + /// + /// The source address remains fixed (peripheral register) while + /// the destination address increments through the buffer. + /// + /// # Arguments + /// + /// * `peri_addr` - Peripheral register address + /// * `buf` - Destination buffer to read into + /// * `options` - Transfer configuration options + /// + /// # Safety + /// + /// - The buffer must remain valid for the duration of the transfer. + /// - The peripheral address must be valid for reads. + pub unsafe fn read_from_peripheral( + &self, + peri_addr: *const W, + buf: &mut [W], + options: TransferOptions, + ) -> Transfer<'_> { + assert!(!buf.is_empty()); + assert!(buf.len() <= 0x7fff); + + let size = W::size(); + let byte_size = size.bytes(); + + let t = self.tcd(); + + // Reset channel control/error/interrupt state + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .ebw() + .disable() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Source: peripheral register, fixed + t.tcd_saddr().write(|w| w.saddr().bits(peri_addr as u32)); + t.tcd_soff().write(|w| w.soff().bits(0)); // No increment + + // Destination: memory buffer, incrementing + t.tcd_daddr().write(|w| w.daddr().bits(buf.as_mut_ptr() as u32)); + t.tcd_doff().write(|w| w.doff().bits(byte_size as u16)); + + // Transfer attributes: set size and explicitly disable modulo + let hw_size = size.to_hw_size(); + t.tcd_attr().write(|w| { + w.ssize() + .bits(hw_size) + .dsize() + .bits(hw_size) + .smod() + .disable() + .dmod() + .bits(0) + }); + + // Minor loop: transfer one word per request, no offsets + t.tcd_nbytes_mloffno().write(|w| { + w.nbytes() + .bits(byte_size as u32) + .dmloe() + .offset_not_applied() + .smloe() + .offset_not_applied() + }); + + // Major loop count = number of words + let count = buf.len() as u16; + t.tcd_citer_elinkno().write(|w| w.citer().bits(count).elink().disable()); + t.tcd_biter_elinkno().write(|w| w.biter().bits(count).elink().disable()); + + // No address adjustment after major loop + t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); + t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(0)); + + // Control/status: interrupt on major complete, auto-clear ERQ when done + t.tcd_csr().write(|w| { + let w = if options.complete_transfer_interrupt { + w.intmajor().enable() + } else { + w.intmajor().disable() + }; + let w = if options.half_transfer_interrupt { + w.inthalf().enable() + } else { + w.inthalf().disable() + }; + w.dreq() + .erq_field_clear() // Disable request when done (important for peripheral DMA) + .esg() + .normal_format() + .majorelink() + .disable() + .eeop() + .disable() + .esda() + .disable() + .bwc() + .no_stall() + }); + + // Ensure all TCD writes have completed before DMA engine reads them + cortex_m::asm::dsb(); + + Transfer::new(self.as_any()) + } + + /// Configure a memory-to-peripheral DMA transfer without starting it. + /// + /// This configures the TCD for a memory-to-peripheral transfer but does NOT + /// return a Transfer object. The caller is responsible for: + /// 1. Enabling the peripheral's DMA request + /// 2. Calling `enable_request()` to start the transfer + /// 3. Polling `is_done()` or using interrupts to detect completion + /// 4. Calling `disable_request()`, `clear_done()`, `clear_interrupt()` for cleanup + /// + /// Use this when you need manual control over the DMA lifecycle (e.g., in + /// peripheral drivers that have their own completion polling). + /// + /// # Arguments + /// + /// * `buf` - Source buffer to write from + /// * `peri_addr` - Peripheral register address + /// * `enable_interrupt` - Whether to enable interrupt on completion + /// + /// # Safety + /// + /// - The buffer must remain valid for the duration of the transfer. + /// - The peripheral address must be valid for writes. + pub unsafe fn setup_write_to_peripheral( + &self, + buf: &[W], + peri_addr: *mut W, + enable_interrupt: EnableInterrupt, + ) { + assert!(!buf.is_empty()); + assert!(buf.len() <= 0x7fff); + + let size = W::size(); + let byte_size = size.bytes(); + + let t = self.tcd(); + + // Reset channel state + t.ch_csr().write(|w| w.erq().disable().done().clear_bit_by_one()); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Addresses + t.tcd_saddr().write(|w| w.saddr().bits(buf.as_ptr() as u32)); + t.tcd_daddr().write(|w| w.daddr().bits(peri_addr as u32)); + + // Offsets: Source increments, Dest fixed + t.tcd_soff().write(|w| w.soff().bits(byte_size as u16)); + t.tcd_doff().write(|w| w.doff().bits(0)); + + // Attributes: set size and explicitly disable modulo + let hw_size = size.to_hw_size(); + t.tcd_attr().write(|w| { + w.ssize() + .bits(hw_size) + .dsize() + .bits(hw_size) + .smod() + .disable() + .dmod() + .bits(0) + }); + + // Minor loop: transfer one word per request + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(byte_size as u32)); + + // No final adjustments + t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); + t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(0)); + + // Major loop count = number of words + let count = buf.len() as u16; + t.tcd_citer_elinkno().write(|w| w.citer().bits(count).elink().disable()); + t.tcd_biter_elinkno().write(|w| w.biter().bits(count).elink().disable()); + + // CSR: optional interrupt on major loop complete and auto-clear ERQ + t.tcd_csr().write(|w| { + let w = match enable_interrupt { + EnableInterrupt::Yes => w.intmajor().enable(), + EnableInterrupt::No => w.intmajor().disable(), + }; + w.inthalf() + .disable() + .dreq() + .erq_field_clear() + .esg() + .normal_format() + .majorelink() + .disable() + .eeop() + .disable() + .esda() + .disable() + .bwc() + .no_stall() + }); + + // Ensure all TCD writes have completed before DMA engine reads them + cortex_m::asm::dsb(); + } + + /// Configure a peripheral-to-memory DMA transfer without starting it. + /// + /// This configures the TCD for a peripheral-to-memory transfer but does NOT + /// return a Transfer object. The caller is responsible for: + /// 1. Enabling the peripheral's DMA request + /// 2. Calling `enable_request()` to start the transfer + /// 3. Polling `is_done()` or using interrupts to detect completion + /// 4. Calling `disable_request()`, `clear_done()`, `clear_interrupt()` for cleanup + /// + /// Use this when you need manual control over the DMA lifecycle (e.g., in + /// peripheral drivers that have their own completion polling). + /// + /// # Arguments + /// + /// * `peri_addr` - Peripheral register address + /// * `buf` - Destination buffer to read into + /// * `enable_interrupt` - Whether to enable interrupt on completion + /// + /// # Safety + /// + /// - The buffer must remain valid for the duration of the transfer. + /// - The peripheral address must be valid for reads. + pub unsafe fn setup_read_from_peripheral( + &self, + peri_addr: *const W, + buf: &mut [W], + enable_interrupt: EnableInterrupt, + ) { + assert!(!buf.is_empty()); + assert!(buf.len() <= 0x7fff); + + let size = W::size(); + let byte_size = size.bytes(); + + let t = self.tcd(); + + // Reset channel control/error/interrupt state + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .ebw() + .disable() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Source: peripheral register, fixed + t.tcd_saddr().write(|w| w.saddr().bits(peri_addr as u32)); + t.tcd_soff().write(|w| w.soff().bits(0)); + + // Destination: memory buffer, incrementing + t.tcd_daddr().write(|w| w.daddr().bits(buf.as_mut_ptr() as u32)); + t.tcd_doff().write(|w| w.doff().bits(byte_size as u16)); + + // Attributes: set size and explicitly disable modulo + let hw_size = size.to_hw_size(); + t.tcd_attr().write(|w| { + w.ssize() + .bits(hw_size) + .dsize() + .bits(hw_size) + .smod() + .disable() + .dmod() + .bits(0) + }); + + // Minor loop: transfer one word per request + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(byte_size as u32)); + + // No final adjustments + t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); + t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(0)); + + // Major loop count = number of words + let count = buf.len() as u16; + t.tcd_citer_elinkno().write(|w| w.citer().bits(count).elink().disable()); + t.tcd_biter_elinkno().write(|w| w.biter().bits(count).elink().disable()); + + // CSR: optional interrupt on major loop complete and auto-clear ERQ + t.tcd_csr().write(|w| { + let w = match enable_interrupt { + EnableInterrupt::Yes => w.intmajor().enable(), + EnableInterrupt::No => w.intmajor().disable(), + }; + w.inthalf() + .disable() + .dreq() + .erq_field_clear() + .esg() + .normal_format() + .majorelink() + .disable() + .eeop() + .disable() + .esda() + .disable() + .bwc() + .no_stall() + }); + + // Ensure all TCD writes have completed before DMA engine reads them + cortex_m::asm::dsb(); + } + + /// Configure the integrated channel MUX to use the given typed + /// DMA request source (e.g., [`Lpuart2TxRequest`] or [`Lpuart2RxRequest`]). + /// + /// This is the type-safe version that uses marker types to ensure + /// compile-time verification of request source validity. + /// + /// # Safety + /// + /// The channel must be properly configured before enabling requests. + /// The caller must ensure the DMA request source matches the peripheral + /// that will drive this channel. + /// + /// # Note + /// + /// The NXP SDK requires a two-step write sequence: first clear + /// the mux to 0, then set the actual source. This is a hardware + /// requirement on eDMA4 for the mux to properly latch. + /// + /// # Example + /// + /// ```ignore + /// use embassy_mcxa::dma::{DmaChannel, Lpuart2RxRequest}; + /// + /// // Type-safe: compiler verifies this is a valid DMA request type + /// unsafe { + /// channel.set_request_source::(); + /// } + /// ``` + #[inline] + pub unsafe fn set_request_source(&self) { + // Two-step write per NXP SDK: clear to 0, then set actual source. + self.tcd().ch_mux().write(|w| w.src().bits(0)); + cortex_m::asm::dsb(); // Ensure the clear completes before setting new source + self.tcd().ch_mux().write(|w| w.src().bits(R::REQUEST_NUMBER)); + } + + /// Enable hardware requests for this channel (ERQ=1). + /// + /// # Safety + /// + /// The channel must be properly configured before enabling requests. + pub unsafe fn enable_request(&self) { + let t = self.tcd(); + t.ch_csr().modify(|_, w| w.erq().enable()); + } + + /// Disable hardware requests for this channel (ERQ=0). + /// + /// # Safety + /// + /// Disabling requests on an active transfer may leave the transfer incomplete. + pub unsafe fn disable_request(&self) { + let t = self.tcd(); + t.ch_csr().modify(|_, w| w.erq().disable()); + } + + /// Return true if the channel's DONE flag is set. + pub fn is_done(&self) -> bool { + let t = self.tcd(); + t.ch_csr().read().done().bit_is_set() + } + + /// Clear the DONE flag for this channel. + /// + /// Uses modify to preserve other bits (especially ERQ) unlike write + /// which would clear ERQ and halt an active transfer. + /// + /// # Safety + /// + /// Clearing DONE while a transfer is in progress may cause undefined behavior. + pub unsafe fn clear_done(&self) { + let t = self.tcd(); + t.ch_csr().modify(|_, w| w.done().clear_bit_by_one()); + } + + /// Clear the channel interrupt flag (CH_INT.INT). + /// + /// # Safety + /// + /// Must be called from the correct interrupt context or with interrupts disabled. + pub unsafe fn clear_interrupt(&self) { + let t = self.tcd(); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + } + + /// Trigger a software start for this channel. + /// + /// # Safety + /// + /// The channel must be properly configured with a valid TCD before triggering. + pub unsafe fn trigger_start(&self) { + let t = self.tcd(); + t.tcd_csr().modify(|_, w| w.start().channel_started()); + } + + /// Get the waker for this channel + pub fn waker(&self) -> &'static AtomicWaker { + &STATES[C::INDEX].waker + } + + /// Enable the interrupt for this channel in the NVIC. + pub fn enable_interrupt(&self) { + unsafe { + cortex_m::peripheral::NVIC::unmask(C::INTERRUPT); + } + } + + /// Enable Major Loop Linking. + /// + /// When the major loop completes, the hardware will trigger a service request + /// on `link_ch`. + /// + /// # Arguments + /// + /// * `link_ch` - Target channel index (0-7) to link to + /// + /// # Safety + /// + /// The channel must be properly configured before setting up linking. + pub unsafe fn set_major_link(&self, link_ch: usize) { + let t = self.tcd(); + t.tcd_csr() + .modify(|_, w| w.majorelink().enable().majorlinkch().bits(link_ch as u8)); + } + + /// Disable Major Loop Linking. + /// + /// Removes any major loop channel linking previously configured. + /// + /// # Safety + /// + /// The caller must ensure this doesn't disrupt an active transfer that + /// depends on the linking. + pub unsafe fn clear_major_link(&self) { + let t = self.tcd(); + t.tcd_csr().modify(|_, w| w.majorelink().disable()); + } + + /// Enable Minor Loop Linking. + /// + /// After each minor loop, the hardware will trigger a service request + /// on `link_ch`. + /// + /// # Arguments + /// + /// * `link_ch` - Target channel index (0-7) to link to + /// + /// # Note + /// + /// This rewrites CITER and BITER registers to the ELINKYES format. + /// It preserves the current loop count. + /// + /// # Safety + /// + /// The channel must be properly configured before setting up linking. + pub unsafe fn set_minor_link(&self, link_ch: usize) { + let t = self.tcd(); + + // Read current CITER (assuming ELINKNO format initially) + let current_citer = t.tcd_citer_elinkno().read().citer().bits(); + let current_biter = t.tcd_biter_elinkno().read().biter().bits(); + + // Write back using ELINKYES format + t.tcd_citer_elinkyes().write(|w| { + w.citer() + .bits(current_citer) + .elink() + .enable() + .linkch() + .bits(link_ch as u8) + }); + + t.tcd_biter_elinkyes().write(|w| { + w.biter() + .bits(current_biter) + .elink() + .enable() + .linkch() + .bits(link_ch as u8) + }); + } + + /// Disable Minor Loop Linking. + /// + /// Removes any minor loop channel linking previously configured. + /// This rewrites CITER and BITER registers to the ELINKNO format, + /// preserving the current loop count. + /// + /// # Safety + /// + /// The caller must ensure this doesn't disrupt an active transfer that + /// depends on the linking. + pub unsafe fn clear_minor_link(&self) { + let t = self.tcd(); + + // Read current CITER (could be in either format, but we only need the count) + // Note: In ELINKYES format, citer is 9 bits; in ELINKNO, it's 15 bits. + // We read from ELINKNO which will give us the combined value. + let current_citer = t.tcd_citer_elinkno().read().citer().bits(); + let current_biter = t.tcd_biter_elinkno().read().biter().bits(); + + // Write back using ELINKNO format (disabling link) + t.tcd_citer_elinkno() + .write(|w| w.citer().bits(current_citer).elink().disable()); + + t.tcd_biter_elinkno() + .write(|w| w.biter().bits(current_biter).elink().disable()); + } + + /// Load a TCD from memory into the hardware channel registers. + /// + /// This is useful for scatter/gather and ping-pong transfers where + /// TCDs are prepared in RAM and then loaded into the hardware. + /// + /// # Safety + /// + /// - The TCD must be properly initialized. + /// - The caller must ensure no concurrent access to the same channel. + pub unsafe fn load_tcd(&self, tcd: &Tcd) { + let t = self.tcd(); + t.tcd_saddr().write(|w| w.saddr().bits(tcd.saddr)); + t.tcd_soff().write(|w| w.soff().bits(tcd.soff as u16)); + t.tcd_attr().write(|w| w.bits(tcd.attr)); + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(tcd.nbytes)); + t.tcd_slast_sda().write(|w| w.slast_sda().bits(tcd.slast as u32)); + t.tcd_daddr().write(|w| w.daddr().bits(tcd.daddr)); + t.tcd_doff().write(|w| w.doff().bits(tcd.doff as u16)); + t.tcd_citer_elinkno().write(|w| w.citer().bits(tcd.citer)); + t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(tcd.dlast_sga as u32)); + t.tcd_csr().write(|w| w.bits(tcd.csr)); + t.tcd_biter_elinkno().write(|w| w.biter().bits(tcd.biter)); + } +} + +/// In-memory representation of a Transfer Control Descriptor (TCD). +/// +/// This matches the hardware layout (32 bytes). +#[repr(C, align(32))] +#[derive(Clone, Copy, Debug, Default)] +pub struct Tcd { + pub saddr: u32, + pub soff: i16, + pub attr: u16, + pub nbytes: u32, + pub slast: i32, + pub daddr: u32, + pub doff: i16, + pub citer: u16, + pub dlast_sga: i32, + pub csr: u16, + pub biter: u16, +} + +struct State { + /// Waker for transfer complete interrupt + waker: AtomicWaker, + /// Waker for half-transfer interrupt + half_waker: AtomicWaker, +} + +impl State { + const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + half_waker: AtomicWaker::new(), + } + } +} + +static STATES: [State; 8] = [ + State::new(), + State::new(), + State::new(), + State::new(), + State::new(), + State::new(), + State::new(), + State::new(), +]; + +pub(crate) fn waker(idx: usize) -> &'static AtomicWaker { + &STATES[idx].waker +} + +pub(crate) fn half_waker(idx: usize) -> &'static AtomicWaker { + &STATES[idx].half_waker +} + +// ============================================================================ +// Async Transfer Future +// ============================================================================ + +/// An in-progress DMA transfer. +/// +/// This type implements `Future` and can be `.await`ed to wait for the +/// transfer to complete. Dropping the transfer will abort it. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Transfer<'a> { + channel: AnyChannel, + _phantom: core::marker::PhantomData<&'a ()>, +} + +impl<'a> Transfer<'a> { + /// Create a new transfer for the given channel. + /// + /// The caller must have already configured and started the DMA channel. + pub(crate) fn new(channel: AnyChannel) -> Self { + Self { + channel, + _phantom: core::marker::PhantomData, + } + } + + /// Check if the transfer is still running. + pub fn is_running(&self) -> bool { + !self.channel.is_done() + } + + /// Get the remaining transfer count. + pub fn remaining(&self) -> u16 { + let t = self.channel.tcd(); + t.tcd_citer_elinkno().read().citer().bits() + } + + /// Block until the transfer completes. + pub fn blocking_wait(self) { + while self.is_running() { + core::hint::spin_loop(); + } + + // Ensure all DMA writes are visible + fence(Ordering::SeqCst); + + // Don't run drop (which would abort) + core::mem::forget(self); + } + + /// Wait for the half-transfer interrupt asynchronously. + /// + /// This is useful for double-buffering scenarios where you want to process + /// the first half of the buffer while the second half is being filled. + /// + /// Returns `true` if the half-transfer occurred, `false` if the transfer + /// completed before the half-transfer interrupt. + /// + /// # Note + /// + /// The transfer must be configured with `TransferOptions::half_transfer_interrupt = true` + /// for this method to work correctly. + pub async fn wait_half(&mut self) -> bool { + use core::future::poll_fn; + + poll_fn(|cx| { + let state = &STATES[self.channel.index]; + + // Register the half-transfer waker + state.half_waker.register(cx.waker()); + + // Check if we're past the half-way point + let t = self.channel.tcd(); + let biter = t.tcd_biter_elinkno().read().biter().bits(); + let citer = t.tcd_citer_elinkno().read().citer().bits(); + let half_point = biter / 2; + + if self.channel.is_done() { + // Transfer completed before half-transfer + Poll::Ready(false) + } else if citer <= half_point { + // We're past the half-way point + fence(Ordering::SeqCst); + Poll::Ready(true) + } else { + Poll::Pending + } + }) + .await + } + + /// Abort the transfer. + fn abort(&mut self) { + let t = self.channel.tcd(); + + // Disable channel requests + t.ch_csr().modify(|_, w| w.erq().disable()); + + // Clear any pending interrupt + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Clear DONE flag + t.ch_csr().modify(|_, w| w.done().clear_bit_by_one()); + + fence(Ordering::SeqCst); + } +} + +impl<'a> Unpin for Transfer<'a> {} + +impl<'a> Future for Transfer<'a> { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let state = &STATES[self.channel.index]; + + // Register waker first + state.waker.register(cx.waker()); + + let done = self.channel.is_done(); + + if done { + // Ensure all DMA writes are visible before returning + fence(Ordering::SeqCst); + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +impl<'a> Drop for Transfer<'a> { + fn drop(&mut self) { + // Only abort if the transfer is still running + // If already complete, no need to abort + if self.is_running() { + self.abort(); + + // Wait for abort to complete + while self.is_running() { + core::hint::spin_loop(); + } + } + + fence(Ordering::SeqCst); + } +} + +// ============================================================================ +// Ring Buffer for Circular DMA +// ============================================================================ + +/// A ring buffer for continuous DMA reception. +/// +/// This structure manages a circular DMA transfer, allowing continuous +/// reception of data without losing bytes between reads. It uses both +/// half-transfer and complete-transfer interrupts to track available data. +/// +/// # Example +/// +/// ```no_run +/// use embassy_mcxa::dma::{DmaChannel, RingBuffer, TransferOptions}; +/// +/// static mut RX_BUF: [u8; 64] = [0; 64]; +/// +/// let dma_ch = DmaChannel::new(p.DMA_CH0); +/// let ring_buf = unsafe { +/// dma_ch.setup_circular_read( +/// uart_rx_addr, +/// &mut RX_BUF, +/// ) +/// }; +/// +/// // Read data as it arrives +/// let mut buf = [0u8; 16]; +/// let n = ring_buf.read(&mut buf).await?; +/// ``` +pub struct RingBuffer<'a, W: Word> { + channel: AnyChannel, + /// Buffer pointer. We use NonNull instead of &mut because DMA acts like + /// a separate thread writing to this buffer, and &mut claims exclusive + /// access which the compiler could optimize incorrectly. + buf: NonNull<[W]>, + /// Buffer length cached for convenience + buf_len: usize, + /// Read position in the buffer (consumer side) + read_pos: AtomicUsize, + /// Phantom data to tie the lifetime to the original buffer + _lt: PhantomData<&'a mut [W]>, +} + +impl<'a, W: Word> RingBuffer<'a, W> { + /// Create a new ring buffer for the given channel and buffer. + /// + /// # Safety + /// + /// The caller must ensure: + /// - The DMA channel has been configured for circular transfer + /// - The buffer remains valid for the lifetime of the ring buffer + /// - Only one RingBuffer exists per DMA channel at a time + pub(crate) unsafe fn new(channel: AnyChannel, buf: &'a mut [W]) -> Self { + let buf_len = buf.len(); + Self { + channel, + buf: NonNull::from(buf), + buf_len, + read_pos: AtomicUsize::new(0), + _lt: PhantomData, + } + } + + /// Get a slice reference to the buffer. + /// + /// # Safety + /// + /// The caller must ensure that DMA is not actively writing to the + /// portion of the buffer being accessed, or that the access is + /// appropriately synchronized. + #[inline] + unsafe fn buf_slice(&self) -> &[W] { + self.buf.as_ref() + } + + /// Get the current DMA write position in the buffer. + /// + /// This reads the current destination address from the DMA controller + /// and calculates the buffer offset. + fn dma_write_pos(&self) -> usize { + let t = self.channel.tcd(); + let daddr = t.tcd_daddr().read().daddr().bits() as usize; + let buf_start = self.buf.as_ptr() as *const W as usize; + + // Calculate offset from buffer start + let offset = daddr.wrapping_sub(buf_start) / core::mem::size_of::(); + + // Ensure we're within bounds (DMA wraps around) + offset % self.buf_len + } + + /// Returns the number of bytes available to read. + pub fn available(&self) -> usize { + let write_pos = self.dma_write_pos(); + let read_pos = self.read_pos.load(Ordering::Acquire); + + if write_pos >= read_pos { + write_pos - read_pos + } else { + self.buf_len - read_pos + write_pos + } + } + + /// Check if the buffer has overrun (data was lost). + /// + /// This happens when DMA writes faster than the application reads. + pub fn is_overrun(&self) -> bool { + // In a true overrun, the DMA would have wrapped around and caught up + // to our read position. We can detect this by checking if available() + // equals the full buffer size (minus 1 to distinguish from empty). + self.available() >= self.buf_len - 1 + } + + /// Read data from the ring buffer into the provided slice. + /// + /// Returns the number of elements read, which may be less than + /// `dst.len()` if not enough data is available. + /// + /// This method does not block; use `read_async()` for async waiting. + pub fn read_immediate(&self, dst: &mut [W]) -> usize { + let write_pos = self.dma_write_pos(); + let read_pos = self.read_pos.load(Ordering::Acquire); + + // Calculate available bytes + let available = if write_pos >= read_pos { + write_pos - read_pos + } else { + self.buf_len - read_pos + write_pos + }; + + let to_read = dst.len().min(available); + if to_read == 0 { + return 0; + } + + // Safety: We only read from portions of the buffer that DMA has + // already written to (between read_pos and write_pos). + let buf = unsafe { self.buf_slice() }; + + // Read data, handling wrap-around + let first_chunk = (self.buf_len - read_pos).min(to_read); + dst[..first_chunk].copy_from_slice(&buf[read_pos..read_pos + first_chunk]); + + if to_read > first_chunk { + let second_chunk = to_read - first_chunk; + dst[first_chunk..to_read].copy_from_slice(&buf[..second_chunk]); + } + + // Update read position + let new_read_pos = (read_pos + to_read) % self.buf_len; + self.read_pos.store(new_read_pos, Ordering::Release); + + to_read + } + + /// Read data from the ring buffer asynchronously. + /// + /// This waits until at least one byte is available, then reads as much + /// as possible into the destination buffer. + /// + /// Returns the number of elements read. + pub async fn read(&self, dst: &mut [W]) -> Result { + use core::future::poll_fn; + + if dst.is_empty() { + return Ok(0); + } + + poll_fn(|cx| { + // Check for overrun + if self.is_overrun() { + return Poll::Ready(Err(Error::Overrun)); + } + + // Try to read immediately + let n = self.read_immediate(dst); + if n > 0 { + return Poll::Ready(Ok(n)); + } + + // Register wakers for both half and complete interrupts + let state = &STATES[self.channel.index()]; + state.waker.register(cx.waker()); + state.half_waker.register(cx.waker()); + + // Check again after registering waker (avoid race) + let n = self.read_immediate(dst); + if n > 0 { + return Poll::Ready(Ok(n)); + } + + Poll::Pending + }) + .await + } + + /// Clear the ring buffer, discarding all unread data. + pub fn clear(&self) { + let write_pos = self.dma_write_pos(); + self.read_pos.store(write_pos, Ordering::Release); + } + + /// Stop the DMA transfer and consume the ring buffer. + /// + /// Returns any remaining unread data count. + pub fn stop(self) -> usize { + let available = self.available(); + + // Disable the channel + let t = self.channel.tcd(); + t.ch_csr().modify(|_, w| w.erq().disable()); + + // Clear flags + t.ch_int().write(|w| w.int().clear_bit_by_one()); + t.ch_csr().modify(|_, w| w.done().clear_bit_by_one()); + + fence(Ordering::SeqCst); + + available + } +} + +impl DmaChannel { + /// Set up a circular DMA transfer for continuous peripheral-to-memory reception. + /// + /// This configures the DMA channel for circular operation with both half-transfer + /// and complete-transfer interrupts enabled. The transfer runs continuously until + /// stopped via [`RingBuffer::stop()`]. + /// + /// # Arguments + /// + /// * `peri_addr` - Peripheral register address to read from + /// * `buf` - Destination buffer (should be power-of-2 size for best efficiency) + /// + /// # Returns + /// + /// A [`RingBuffer`] that can be used to read received data. + /// + /// # Safety + /// + /// - The buffer must remain valid for the lifetime of the returned RingBuffer. + /// - The peripheral address must be valid for reads. + /// - The peripheral's DMA request must be configured to trigger this channel. + pub unsafe fn setup_circular_read<'a, W: Word>(&self, peri_addr: *const W, buf: &'a mut [W]) -> RingBuffer<'a, W> { + assert!(!buf.is_empty()); + assert!(buf.len() <= 0x7fff); + // For circular mode, buffer size should ideally be power of 2 + // but we don't enforce it + + let size = W::size(); + let byte_size = size.bytes(); + + let t = self.tcd(); + + // Reset channel state + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .ebw() + .disable() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Source: peripheral register, fixed + t.tcd_saddr().write(|w| w.saddr().bits(peri_addr as u32)); + t.tcd_soff().write(|w| w.soff().bits(0)); // No increment + + // Destination: memory buffer, incrementing + t.tcd_daddr().write(|w| w.daddr().bits(buf.as_mut_ptr() as u32)); + t.tcd_doff().write(|w| w.doff().bits(byte_size as u16)); + + // Transfer attributes + let hw_size = size.to_hw_size(); + t.tcd_attr().write(|w| { + w.ssize() + .bits(hw_size) + .dsize() + .bits(hw_size) + .smod() + .disable() + .dmod() + .bits(0) + }); + + // Minor loop: transfer one word per request + t.tcd_nbytes_mloffno().write(|w| { + w.nbytes() + .bits(byte_size as u32) + .dmloe() + .offset_not_applied() + .smloe() + .offset_not_applied() + }); + + // Major loop count = buffer size + let count = buf.len() as u16; + t.tcd_citer_elinkno().write(|w| w.citer().bits(count).elink().disable()); + t.tcd_biter_elinkno().write(|w| w.biter().bits(count).elink().disable()); + + // After major loop: reset destination to buffer start (circular) + let buf_bytes = (buf.len() * byte_size) as i32; + t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); // Source doesn't change + t.tcd_dlast_sga().write(|w| w.dlast_sga().bits((-buf_bytes) as u32)); + + // Control/status: enable both half and complete interrupts, NO DREQ (continuous) + t.tcd_csr().write(|w| { + w.intmajor() + .enable() + .inthalf() + .enable() + .dreq() + .channel_not_affected() // Don't clear ERQ on complete (circular) + .esg() + .normal_format() + .majorelink() + .disable() + .eeop() + .disable() + .esda() + .disable() + .bwc() + .no_stall() + }); + + cortex_m::asm::dsb(); + + // Enable the channel request + t.ch_csr().modify(|_, w| w.erq().enable()); + + // Enable NVIC interrupt for this channel so async wakeups work + self.enable_interrupt(); + + RingBuffer::new(self.as_any(), buf) + } +} + +// ============================================================================ +// Scatter-Gather Builder +// ============================================================================ + +/// Maximum number of TCDs in a scatter-gather chain. +pub const MAX_SCATTER_GATHER_TCDS: usize = 16; + +/// A builder for constructing scatter-gather DMA transfer chains. +/// +/// This provides a type-safe way to build TCD chains for scatter-gather +/// transfers without manual TCD manipulation. +/// +/// # Example +/// +/// ```no_run +/// use embassy_mcxa::dma::{DmaChannel, ScatterGatherBuilder}; +/// +/// let mut builder = ScatterGatherBuilder::::new(); +/// +/// // Add transfer segments +/// builder.add_transfer(&src1, &mut dst1); +/// builder.add_transfer(&src2, &mut dst2); +/// builder.add_transfer(&src3, &mut dst3); +/// +/// // Build and execute +/// let transfer = unsafe { builder.build(&dma_ch).unwrap() }; +/// transfer.await; +/// ``` +pub struct ScatterGatherBuilder { + /// TCD pool (must be 32-byte aligned) + tcds: [Tcd; MAX_SCATTER_GATHER_TCDS], + /// Number of TCDs configured + count: usize, + /// Phantom marker for word type + _phantom: core::marker::PhantomData, +} + +impl ScatterGatherBuilder { + /// Create a new scatter-gather builder. + pub fn new() -> Self { + Self { + tcds: [Tcd::default(); MAX_SCATTER_GATHER_TCDS], + count: 0, + _phantom: core::marker::PhantomData, + } + } + + /// Add a memory-to-memory transfer segment to the chain. + /// + /// # Arguments + /// + /// * `src` - Source buffer for this segment + /// * `dst` - Destination buffer for this segment + /// + /// # Panics + /// + /// Panics if the maximum number of segments (16) is exceeded. + pub fn add_transfer(&mut self, src: &[W], dst: &mut [W]) -> &mut Self { + assert!(self.count < MAX_SCATTER_GATHER_TCDS, "Too many scatter-gather segments"); + assert!(!src.is_empty()); + assert!(dst.len() >= src.len()); + + let size = W::size(); + let byte_size = size.bytes(); + let hw_size = size.to_hw_size(); + let nbytes = (src.len() * byte_size) as u32; + + // Build the TCD for this segment + self.tcds[self.count] = Tcd { + saddr: src.as_ptr() as u32, + soff: byte_size as i16, + attr: ((hw_size as u16) << 8) | (hw_size as u16), // SSIZE | DSIZE + nbytes, + slast: 0, + daddr: dst.as_mut_ptr() as u32, + doff: byte_size as i16, + citer: 1, + dlast_sga: 0, // Will be filled in by build() + csr: 0x0002, // INTMAJOR only (ESG will be set for non-last TCDs) + biter: 1, + }; + + self.count += 1; + self + } + + /// Get the number of transfer segments added. + pub fn segment_count(&self) -> usize { + self.count + } + + /// Build the scatter-gather chain and start the transfer. + /// + /// # Arguments + /// + /// * `channel` - The DMA channel to use for the transfer + /// + /// # Returns + /// + /// A `Transfer` future that completes when the entire chain has executed. + /// + /// # Safety + /// + /// All source and destination buffers passed to `add_transfer()` must + /// remain valid for the duration of the transfer. + pub unsafe fn build(&mut self, channel: &DmaChannel) -> Result, Error> { + if self.count == 0 { + return Err(Error::Configuration); + } + + // Link TCDs together + // + // CSR bit definitions: + // - START = bit 0 = 0x0001 (triggers transfer when set) + // - INTMAJOR = bit 1 = 0x0002 (interrupt on major loop complete) + // - ESG = bit 4 = 0x0010 (enable scatter-gather, loads next TCD on complete) + // + // When hardware loads a TCD via scatter-gather (ESG), it copies the TCD's + // CSR directly into the hardware register. If START is not set in that CSR, + // the hardware will NOT auto-execute the loaded TCD. + // + // Strategy: + // - First TCD: ESG | INTMAJOR (no START - we add it manually after loading) + // - Middle TCDs: ESG | INTMAJOR | START (auto-execute when loaded via S/G) + // - Last TCD: INTMAJOR | START (auto-execute, no further linking) + for i in 0..self.count { + let is_first = i == 0; + let is_last = i == self.count - 1; + + if is_first { + if is_last { + // Only one TCD - no ESG, no START (we add START manually) + self.tcds[i].dlast_sga = 0; + self.tcds[i].csr = 0x0002; // INTMAJOR only + } else { + // First of multiple - ESG to link, no START (we add START manually) + self.tcds[i].dlast_sga = &self.tcds[i + 1] as *const Tcd as i32; + self.tcds[i].csr = 0x0012; // ESG | INTMAJOR + } + } else if is_last { + // Last TCD (not first) - no ESG, but START so it auto-executes + self.tcds[i].dlast_sga = 0; + self.tcds[i].csr = 0x0003; // INTMAJOR | START + } else { + // Middle TCD - ESG to link, and START so it auto-executes + self.tcds[i].dlast_sga = &self.tcds[i + 1] as *const Tcd as i32; + self.tcds[i].csr = 0x0013; // ESG | INTMAJOR | START + } + } + + let t = channel.tcd(); + + // Reset channel state - clear DONE, disable requests, clear errors + // This ensures the channel is in a clean state before loading the TCD + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.err().clear_bit_by_one()); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Memory barrier to ensure channel state is reset before loading TCD + cortex_m::asm::dsb(); + + // Load first TCD into hardware + channel.load_tcd(&self.tcds[0]); + + // Memory barrier before setting START + cortex_m::asm::dsb(); + + // Start the transfer + t.tcd_csr().modify(|_, w| w.start().channel_started()); + + Ok(Transfer::new(channel.as_any())) + } + + /// Reset the builder for reuse. + pub fn clear(&mut self) { + self.count = 0; + } +} + +impl Default for ScatterGatherBuilder { + fn default() -> Self { + Self::new() + } +} + +/// A completed scatter-gather transfer result. +/// +/// This type is returned after a scatter-gather transfer completes, +/// providing access to any error information. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ScatterGatherResult { + /// Number of segments successfully transferred + pub segments_completed: usize, + /// Error if any occurred + pub error: Option, +} + +// ============================================================================ +// Interrupt Handler +// ============================================================================ + +/// Interrupt handler helper. +/// +/// Call this from your interrupt handler to clear the interrupt flag and wake the waker. +/// This handles both half-transfer and complete-transfer interrupts. +/// +/// # Safety +/// Must be called from the correct DMA channel interrupt context. +pub unsafe fn on_interrupt(ch_index: usize) { + let p = pac::Peripherals::steal(); + let edma = &p.edma_0_tcd0; + let t = edma.tcd(ch_index); + + // Read TCD CSR to determine interrupt source + let csr = t.tcd_csr().read(); + + // Check if this is a half-transfer interrupt + // INTHALF is set and we're at or past the half-way point + if csr.inthalf().bit_is_set() { + let biter = t.tcd_biter_elinkno().read().biter().bits(); + let citer = t.tcd_citer_elinkno().read().citer().bits(); + let half_point = biter / 2; + + if citer <= half_point && citer > 0 { + // Half-transfer interrupt - wake half_waker + half_waker(ch_index).wake(); + } + } + + // Clear INT flag + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // If DONE is set, this is a complete-transfer interrupt + // Only wake the full-transfer waker when the transfer is actually complete + if t.ch_csr().read().done().bit_is_set() { + waker(ch_index).wake(); + } +} + +// ============================================================================ +// Type-level Interrupt Handlers for bind_interrupts! macro +// ============================================================================ + +/// Macro to generate DMA channel interrupt handlers. +/// +/// This generates handler structs that implement the `Handler` trait for use +/// with the `bind_interrupts!` macro. +macro_rules! impl_dma_interrupt_handler { + ($name:ident, $irq:ident, $ch:expr) => { + /// Interrupt handler for DMA channel. + /// + /// Use this with the `bind_interrupts!` macro: + /// ```ignore + /// bind_interrupts!(struct Irqs { + #[doc = concat!(" ", stringify!($irq), " => dma::", stringify!($name), ";")] + /// }); + /// ``` + pub struct $name; + + impl crate::interrupt::typelevel::Handler for $name { + unsafe fn on_interrupt() { + on_interrupt($ch); + } + } + }; +} + +impl_dma_interrupt_handler!(DmaCh0InterruptHandler, DMA_CH0, 0); +impl_dma_interrupt_handler!(DmaCh1InterruptHandler, DMA_CH1, 1); +impl_dma_interrupt_handler!(DmaCh2InterruptHandler, DMA_CH2, 2); +impl_dma_interrupt_handler!(DmaCh3InterruptHandler, DMA_CH3, 3); +impl_dma_interrupt_handler!(DmaCh4InterruptHandler, DMA_CH4, 4); +impl_dma_interrupt_handler!(DmaCh5InterruptHandler, DMA_CH5, 5); +impl_dma_interrupt_handler!(DmaCh6InterruptHandler, DMA_CH6, 6); +impl_dma_interrupt_handler!(DmaCh7InterruptHandler, DMA_CH7, 7); diff --git a/embassy-mcxa/src/gpio.rs b/embassy-mcxa/src/gpio.rs new file mode 100644 index 000000000..65f8df985 --- /dev/null +++ b/embassy-mcxa/src/gpio.rs @@ -0,0 +1,1062 @@ +//! GPIO driver built around a type-erased `Flex` pin, similar to other Embassy HALs. +//! The exported `Output`/`Input` drivers own a `Flex` so they no longer depend on the +//! concrete pin type. + +use core::convert::Infallible; +use core::future::Future; +use core::marker::PhantomData; +use core::pin::pin; + +use embassy_hal_internal::{Peri, PeripheralType}; +use maitake_sync::WaitMap; +use paste::paste; + +use crate::pac::interrupt; +use crate::pac::port0::pcr0::{Dse, Inv, Mux, Pe, Ps, Sre}; + +struct BitIter(u32); + +impl Iterator for BitIter { + type Item = usize; + + fn next(&mut self) -> Option { + match self.0.trailing_zeros() { + 32 => None, + b => { + self.0 &= !(1 << b); + Some(b as usize) + } + } + } +} + +const PORT_COUNT: usize = 5; + +static PORT_WAIT_MAPS: [WaitMap; PORT_COUNT] = [ + WaitMap::new(), + WaitMap::new(), + WaitMap::new(), + WaitMap::new(), + WaitMap::new(), +]; + +fn irq_handler(port_index: usize, gpio_base: *const crate::pac::gpio0::RegisterBlock) { + let gpio = unsafe { &*gpio_base }; + let isfr = gpio.isfr0().read().bits(); + + for pin in BitIter(isfr) { + // Clear all pending interrupts + gpio.isfr0().write(|w| unsafe { w.bits(1 << pin) }); + gpio.icr(pin).modify(|_, w| w.irqc().irqc0()); // Disable interrupt + + // Wake the corresponding port waker + if let Some(w) = PORT_WAIT_MAPS.get(port_index) { + w.wake(&pin, ()); + } + } +} + +#[interrupt] +fn GPIO0() { + irq_handler(0, crate::pac::Gpio0::ptr()); +} + +#[interrupt] +fn GPIO1() { + irq_handler(1, crate::pac::Gpio1::ptr()); +} + +#[interrupt] +fn GPIO2() { + irq_handler(2, crate::pac::Gpio2::ptr()); +} + +#[interrupt] +fn GPIO3() { + irq_handler(3, crate::pac::Gpio3::ptr()); +} + +#[interrupt] +fn GPIO4() { + irq_handler(4, crate::pac::Gpio4::ptr()); +} + +pub(crate) unsafe fn init() { + use embassy_hal_internal::interrupt::InterruptExt; + + crate::pac::interrupt::GPIO0.enable(); + crate::pac::interrupt::GPIO1.enable(); + crate::pac::interrupt::GPIO2.enable(); + crate::pac::interrupt::GPIO3.enable(); + crate::pac::interrupt::GPIO4.enable(); + + cortex_m::interrupt::enable(); +} + +/// Logical level for GPIO pins. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Level { + Low, + High, +} + +impl From for Level { + fn from(val: bool) -> Self { + match val { + true => Self::High, + false => Self::Low, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum Pull { + Disabled, + Up, + Down, +} + +impl From for (Pe, Ps) { + fn from(pull: Pull) -> Self { + match pull { + Pull::Disabled => (Pe::Pe0, Ps::Ps0), + Pull::Up => (Pe::Pe1, Ps::Ps1), + Pull::Down => (Pe::Pe1, Ps::Ps0), + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum SlewRate { + Fast, + Slow, +} + +impl From for Sre { + fn from(slew_rate: SlewRate) -> Self { + match slew_rate { + SlewRate::Fast => Sre::Sre0, + SlewRate::Slow => Sre::Sre1, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum DriveStrength { + Normal, + Double, +} + +impl From for Dse { + fn from(strength: DriveStrength) -> Self { + match strength { + DriveStrength::Normal => Dse::Dse0, + DriveStrength::Double => Dse::Dse1, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum Inverter { + Disabled, + Enabled, +} + +impl From for Inv { + fn from(strength: Inverter) -> Self { + match strength { + Inverter::Disabled => Inv::Inv0, + Inverter::Enabled => Inv::Inv1, + } + } +} + +pub type Gpio = crate::peripherals::GPIO0; + +/// Type-erased representation of a GPIO pin. +pub struct AnyPin { + port: usize, + pin: usize, + gpio: &'static crate::pac::gpio0::RegisterBlock, + port_reg: &'static crate::pac::port0::RegisterBlock, + pcr_reg: &'static crate::pac::port0::Pcr0, +} + +impl AnyPin { + /// Create an `AnyPin` from raw components. + fn new( + port: usize, + pin: usize, + gpio: &'static crate::pac::gpio0::RegisterBlock, + port_reg: &'static crate::pac::port0::RegisterBlock, + pcr_reg: &'static crate::pac::port0::Pcr0, + ) -> Self { + Self { + port, + pin, + gpio, + port_reg, + pcr_reg, + } + } + + #[inline(always)] + fn mask(&self) -> u32 { + 1 << self.pin + } + + #[inline(always)] + fn gpio(&self) -> &'static crate::pac::gpio0::RegisterBlock { + self.gpio + } + + #[inline(always)] + pub fn port_index(&self) -> usize { + self.port + } + + #[inline(always)] + pub fn pin_index(&self) -> usize { + self.pin + } + + #[inline(always)] + fn port_reg(&self) -> &'static crate::pac::port0::RegisterBlock { + self.port_reg + } + + #[inline(always)] + fn pcr_reg(&self) -> &'static crate::pac::port0::Pcr0 { + self.pcr_reg + } +} + +embassy_hal_internal::impl_peripheral!(AnyPin); + +pub(crate) trait SealedPin { + fn pin_port(&self) -> usize; + + fn port(&self) -> usize { + self.pin_port() / 32 + } + + fn pin(&self) -> usize { + self.pin_port() % 32 + } + + fn gpio(&self) -> &'static crate::pac::gpio0::RegisterBlock; + + fn port_reg(&self) -> &'static crate::pac::port0::RegisterBlock; + + fn pcr_reg(&self) -> &'static crate::pac::port0::Pcr0; + + fn set_function(&self, function: Mux); + + fn set_pull(&self, pull: Pull); + + fn set_drive_strength(&self, strength: Dse); + + fn set_slew_rate(&self, slew_rate: Sre); + + fn set_enable_input_buffer(&self); +} + +/// GPIO pin trait. +#[allow(private_bounds)] +pub trait GpioPin: SealedPin + Sized + PeripheralType + Into + 'static { + /// Type-erase the pin. + fn degrade(self) -> AnyPin { + // SAFETY: This is only called within the GpioPin trait, which is only + // implemented within this module on valid pin peripherals and thus + // has been verified to be correct. + AnyPin::new(self.port(), self.pin(), self.gpio(), self.port_reg(), self.pcr_reg()) + } +} + +impl SealedPin for AnyPin { + fn pin_port(&self) -> usize { + self.port * 32 + self.pin + } + + fn gpio(&self) -> &'static crate::pac::gpio0::RegisterBlock { + self.gpio() + } + + fn port_reg(&self) -> &'static crate::pac::port0::RegisterBlock { + self.port_reg() + } + + fn pcr_reg(&self) -> &'static crate::pac::port0::Pcr0 { + self.pcr_reg() + } + + fn set_function(&self, function: Mux) { + self.pcr_reg().modify(|_, w| w.mux().variant(function)); + } + + fn set_pull(&self, pull: Pull) { + let (pull_enable, pull_select) = pull.into(); + self.pcr_reg().modify(|_, w| { + w.pe().variant(pull_enable); + w.ps().variant(pull_select) + }); + } + + fn set_drive_strength(&self, strength: Dse) { + self.pcr_reg().modify(|_, w| w.dse().variant(strength)); + } + + fn set_slew_rate(&self, slew_rate: Sre) { + self.pcr_reg().modify(|_, w| w.sre().variant(slew_rate)); + } + + fn set_enable_input_buffer(&self) { + self.pcr_reg().modify(|_, w| w.ibe().ibe1()); + } +} + +impl GpioPin for AnyPin {} + +macro_rules! impl_pin { + ($peri:ident, $port:expr, $pin:expr, $block:ident) => { + paste! { + impl SealedPin for crate::peripherals::$peri { + fn pin_port(&self) -> usize { + $port * 32 + $pin + } + + fn gpio(&self) -> &'static crate::pac::gpio0::RegisterBlock { + unsafe { &*crate::pac::$block::ptr() } + } + + fn port_reg(&self) -> &'static crate::pac::port0::RegisterBlock { + unsafe { &*crate::pac::[]::ptr() } + } + + fn pcr_reg(&self) -> &'static crate::pac::port0::Pcr0 { + self.port_reg().[]() + } + + fn set_function(&self, function: Mux) { + unsafe { + let port_reg = &*crate::pac::[]::ptr(); + port_reg.[]().modify(|_, w| { + w.mux().variant(function) + }); + } + } + + fn set_pull(&self, pull: Pull) { + let port_reg = unsafe {&*crate::pac::[]::ptr()}; + let (pull_enable, pull_select) = pull.into(); + port_reg.[]().modify(|_, w| { + w.pe().variant(pull_enable); + w.ps().variant(pull_select) + }); + } + + fn set_drive_strength(&self, strength: Dse) { + let port_reg = unsafe {&*crate::pac::[]::ptr()}; + port_reg.[]().modify(|_, w| w.dse().variant(strength)); + } + + fn set_slew_rate(&self, slew_rate: Sre) { + let port_reg = unsafe {&*crate::pac::[]::ptr()}; + port_reg.[]().modify(|_, w| w.sre().variant(slew_rate)); + } + + fn set_enable_input_buffer(&self) { + let port_reg = unsafe {&*crate::pac::[]::ptr()}; + port_reg.[]().modify(|_, w| w.ibe().ibe1()); + } + } + + impl GpioPin for crate::peripherals::$peri {} + + impl From for AnyPin { + fn from(value: crate::peripherals::$peri) -> Self { + value.degrade() + } + } + + impl crate::peripherals::$peri { + /// Convenience helper to obtain a type-erased handle to this pin. + pub fn degrade(&self) -> AnyPin { + AnyPin::new(self.port(), self.pin(), self.gpio(), self.port_reg(), self.pcr_reg()) + } + } + } + }; +} + +impl_pin!(P0_0, 0, 0, Gpio0); +impl_pin!(P0_1, 0, 1, Gpio0); +impl_pin!(P0_2, 0, 2, Gpio0); +impl_pin!(P0_3, 0, 3, Gpio0); +impl_pin!(P0_4, 0, 4, Gpio0); +impl_pin!(P0_5, 0, 5, Gpio0); +impl_pin!(P0_6, 0, 6, Gpio0); +impl_pin!(P0_7, 0, 7, Gpio0); +impl_pin!(P0_8, 0, 8, Gpio0); +impl_pin!(P0_9, 0, 9, Gpio0); +impl_pin!(P0_10, 0, 10, Gpio0); +impl_pin!(P0_11, 0, 11, Gpio0); +impl_pin!(P0_12, 0, 12, Gpio0); +impl_pin!(P0_13, 0, 13, Gpio0); +impl_pin!(P0_14, 0, 14, Gpio0); +impl_pin!(P0_15, 0, 15, Gpio0); +impl_pin!(P0_16, 0, 16, Gpio0); +impl_pin!(P0_17, 0, 17, Gpio0); +impl_pin!(P0_18, 0, 18, Gpio0); +impl_pin!(P0_19, 0, 19, Gpio0); +impl_pin!(P0_20, 0, 20, Gpio0); +impl_pin!(P0_21, 0, 21, Gpio0); +impl_pin!(P0_22, 0, 22, Gpio0); +impl_pin!(P0_23, 0, 23, Gpio0); +impl_pin!(P0_24, 0, 24, Gpio0); +impl_pin!(P0_25, 0, 25, Gpio0); +impl_pin!(P0_26, 0, 26, Gpio0); +impl_pin!(P0_27, 0, 27, Gpio0); +impl_pin!(P0_28, 0, 28, Gpio0); +impl_pin!(P0_29, 0, 29, Gpio0); +impl_pin!(P0_30, 0, 30, Gpio0); +impl_pin!(P0_31, 0, 31, Gpio0); + +impl_pin!(P1_0, 1, 0, Gpio1); +impl_pin!(P1_1, 1, 1, Gpio1); +impl_pin!(P1_2, 1, 2, Gpio1); +impl_pin!(P1_3, 1, 3, Gpio1); +impl_pin!(P1_4, 1, 4, Gpio1); +impl_pin!(P1_5, 1, 5, Gpio1); +impl_pin!(P1_6, 1, 6, Gpio1); +impl_pin!(P1_7, 1, 7, Gpio1); +impl_pin!(P1_8, 1, 8, Gpio1); +impl_pin!(P1_9, 1, 9, Gpio1); +impl_pin!(P1_10, 1, 10, Gpio1); +impl_pin!(P1_11, 1, 11, Gpio1); +impl_pin!(P1_12, 1, 12, Gpio1); +impl_pin!(P1_13, 1, 13, Gpio1); +impl_pin!(P1_14, 1, 14, Gpio1); +impl_pin!(P1_15, 1, 15, Gpio1); +impl_pin!(P1_16, 1, 16, Gpio1); +impl_pin!(P1_17, 1, 17, Gpio1); +impl_pin!(P1_18, 1, 18, Gpio1); +impl_pin!(P1_19, 1, 19, Gpio1); +impl_pin!(P1_20, 1, 20, Gpio1); +impl_pin!(P1_21, 1, 21, Gpio1); +impl_pin!(P1_22, 1, 22, Gpio1); +impl_pin!(P1_23, 1, 23, Gpio1); +impl_pin!(P1_24, 1, 24, Gpio1); +impl_pin!(P1_25, 1, 25, Gpio1); +impl_pin!(P1_26, 1, 26, Gpio1); +impl_pin!(P1_27, 1, 27, Gpio1); +impl_pin!(P1_28, 1, 28, Gpio1); +impl_pin!(P1_29, 1, 29, Gpio1); +impl_pin!(P1_30, 1, 30, Gpio1); +impl_pin!(P1_31, 1, 31, Gpio1); + +impl_pin!(P2_0, 2, 0, Gpio2); +impl_pin!(P2_1, 2, 1, Gpio2); +impl_pin!(P2_2, 2, 2, Gpio2); +impl_pin!(P2_3, 2, 3, Gpio2); +impl_pin!(P2_4, 2, 4, Gpio2); +impl_pin!(P2_5, 2, 5, Gpio2); +impl_pin!(P2_6, 2, 6, Gpio2); +impl_pin!(P2_7, 2, 7, Gpio2); +impl_pin!(P2_8, 2, 8, Gpio2); +impl_pin!(P2_9, 2, 9, Gpio2); +impl_pin!(P2_10, 2, 10, Gpio2); +impl_pin!(P2_11, 2, 11, Gpio2); +impl_pin!(P2_12, 2, 12, Gpio2); +impl_pin!(P2_13, 2, 13, Gpio2); +impl_pin!(P2_14, 2, 14, Gpio2); +impl_pin!(P2_15, 2, 15, Gpio2); +impl_pin!(P2_16, 2, 16, Gpio2); +impl_pin!(P2_17, 2, 17, Gpio2); +impl_pin!(P2_18, 2, 18, Gpio2); +impl_pin!(P2_19, 2, 19, Gpio2); +impl_pin!(P2_20, 2, 20, Gpio2); +impl_pin!(P2_21, 2, 21, Gpio2); +impl_pin!(P2_22, 2, 22, Gpio2); +impl_pin!(P2_23, 2, 23, Gpio2); +impl_pin!(P2_24, 2, 24, Gpio2); +impl_pin!(P2_25, 2, 25, Gpio2); +impl_pin!(P2_26, 2, 26, Gpio2); +impl_pin!(P2_27, 2, 27, Gpio2); +impl_pin!(P2_28, 2, 28, Gpio2); +impl_pin!(P2_29, 2, 29, Gpio2); +impl_pin!(P2_30, 2, 30, Gpio2); +impl_pin!(P2_31, 2, 31, Gpio2); + +impl_pin!(P3_0, 3, 0, Gpio3); +impl_pin!(P3_1, 3, 1, Gpio3); +impl_pin!(P3_2, 3, 2, Gpio3); +impl_pin!(P3_3, 3, 3, Gpio3); +impl_pin!(P3_4, 3, 4, Gpio3); +impl_pin!(P3_5, 3, 5, Gpio3); +impl_pin!(P3_6, 3, 6, Gpio3); +impl_pin!(P3_7, 3, 7, Gpio3); +impl_pin!(P3_8, 3, 8, Gpio3); +impl_pin!(P3_9, 3, 9, Gpio3); +impl_pin!(P3_10, 3, 10, Gpio3); +impl_pin!(P3_11, 3, 11, Gpio3); +impl_pin!(P3_12, 3, 12, Gpio3); +impl_pin!(P3_13, 3, 13, Gpio3); +impl_pin!(P3_14, 3, 14, Gpio3); +impl_pin!(P3_15, 3, 15, Gpio3); +impl_pin!(P3_16, 3, 16, Gpio3); +impl_pin!(P3_17, 3, 17, Gpio3); +impl_pin!(P3_18, 3, 18, Gpio3); +impl_pin!(P3_19, 3, 19, Gpio3); +impl_pin!(P3_20, 3, 20, Gpio3); +impl_pin!(P3_21, 3, 21, Gpio3); +impl_pin!(P3_22, 3, 22, Gpio3); +impl_pin!(P3_23, 3, 23, Gpio3); +impl_pin!(P3_24, 3, 24, Gpio3); +impl_pin!(P3_25, 3, 25, Gpio3); +impl_pin!(P3_26, 3, 26, Gpio3); +impl_pin!(P3_27, 3, 27, Gpio3); +impl_pin!(P3_28, 3, 28, Gpio3); +impl_pin!(P3_29, 3, 29, Gpio3); +impl_pin!(P3_30, 3, 30, Gpio3); +impl_pin!(P3_31, 3, 31, Gpio3); + +impl_pin!(P4_0, 4, 0, Gpio4); +impl_pin!(P4_1, 4, 1, Gpio4); +impl_pin!(P4_2, 4, 2, Gpio4); +impl_pin!(P4_3, 4, 3, Gpio4); +impl_pin!(P4_4, 4, 4, Gpio4); +impl_pin!(P4_5, 4, 5, Gpio4); +impl_pin!(P4_6, 4, 6, Gpio4); +impl_pin!(P4_7, 4, 7, Gpio4); +impl_pin!(P4_8, 4, 8, Gpio4); +impl_pin!(P4_9, 4, 9, Gpio4); +impl_pin!(P4_10, 4, 10, Gpio4); +impl_pin!(P4_11, 4, 11, Gpio4); +impl_pin!(P4_12, 4, 12, Gpio4); +impl_pin!(P4_13, 4, 13, Gpio4); +impl_pin!(P4_14, 4, 14, Gpio4); +impl_pin!(P4_15, 4, 15, Gpio4); +impl_pin!(P4_16, 4, 16, Gpio4); +impl_pin!(P4_17, 4, 17, Gpio4); +impl_pin!(P4_18, 4, 18, Gpio4); +impl_pin!(P4_19, 4, 19, Gpio4); +impl_pin!(P4_20, 4, 20, Gpio4); +impl_pin!(P4_21, 4, 21, Gpio4); +impl_pin!(P4_22, 4, 22, Gpio4); +impl_pin!(P4_23, 4, 23, Gpio4); +impl_pin!(P4_24, 4, 24, Gpio4); +impl_pin!(P4_25, 4, 25, Gpio4); +impl_pin!(P4_26, 4, 26, Gpio4); +impl_pin!(P4_27, 4, 27, Gpio4); +impl_pin!(P4_28, 4, 28, Gpio4); +impl_pin!(P4_29, 4, 29, Gpio4); +impl_pin!(P4_30, 4, 30, Gpio4); +impl_pin!(P4_31, 4, 31, Gpio4); + +/// A flexible pin that can be configured as input or output. +pub struct Flex<'d> { + pin: Peri<'d, AnyPin>, + _marker: PhantomData<&'d mut ()>, +} + +impl<'d> Flex<'d> { + /// Wrap the pin in a `Flex`. + /// + /// The pin remains unmodified. The initial output level is unspecified, but + /// can be changed before the pin is put into output mode. + pub fn new(pin: Peri<'d, impl GpioPin>) -> Self { + pin.set_function(Mux::Mux0); + Self { + pin: pin.into(), + _marker: PhantomData, + } + } + + #[inline] + fn gpio(&self) -> &'static crate::pac::gpio0::RegisterBlock { + self.pin.gpio() + } + + #[inline] + fn mask(&self) -> u32 { + self.pin.mask() + } + + /// Put the pin into input mode. + pub fn set_as_input(&mut self) { + let mask = self.mask(); + let gpio = self.gpio(); + + self.set_enable_input_buffer(); + + gpio.pddr().modify(|r, w| unsafe { w.bits(r.bits() & !mask) }); + } + + /// Put the pin into output mode. + pub fn set_as_output(&mut self) { + let mask = self.mask(); + let gpio = self.gpio(); + + self.set_pull(Pull::Disabled); + + gpio.pddr().modify(|r, w| unsafe { w.bits(r.bits() | mask) }); + } + + /// Set output level to High. + #[inline] + pub fn set_high(&mut self) { + self.gpio().psor().write(|w| unsafe { w.bits(self.mask()) }); + } + + /// Set output level to Low. + #[inline] + pub fn set_low(&mut self) { + self.gpio().pcor().write(|w| unsafe { w.bits(self.mask()) }); + } + + /// Set output level to the given `Level`. + #[inline] + pub fn set_level(&mut self, level: Level) { + match level { + Level::High => self.set_high(), + Level::Low => self.set_low(), + } + } + + /// Toggle output level. + #[inline] + pub fn toggle(&mut self) { + self.gpio().ptor().write(|w| unsafe { w.bits(self.mask()) }); + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + (self.gpio().pdir().read().bits() & self.mask()) != 0 + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + !self.is_high() + } + + /// Is the output pin set as high? + #[inline] + pub fn is_set_high(&self) -> bool { + self.is_high() + } + + /// Is the output pin set as low? + #[inline] + pub fn is_set_low(&self) -> bool { + !self.is_set_high() + } + + /// Configure the pin pull up/down level. + pub fn set_pull(&mut self, pull_select: Pull) { + self.pin.set_pull(pull_select); + } + + /// Configure the pin drive strength. + pub fn set_drive_strength(&mut self, strength: DriveStrength) { + self.pin.set_drive_strength(strength.into()); + } + + /// Configure the pin slew rate. + pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { + self.pin.set_slew_rate(slew_rate.into()); + } + + /// Enable input buffer for the pin. + pub fn set_enable_input_buffer(&mut self) { + self.pin.set_enable_input_buffer(); + } + + /// Get pin level. + pub fn get_level(&self) -> Level { + self.is_high().into() + } +} + +/// Async methods +impl<'d> Flex<'d> { + /// Helper function that waits for a given interrupt trigger + async fn wait_for_inner(&mut self, level: crate::pac::gpio0::icr::Irqc) { + // First, ensure that we have a waker that is ready for this port+pin + let w = PORT_WAIT_MAPS[self.pin.port].wait(self.pin.pin); + let mut w = pin!(w); + // Wait for the subscription to occur, which requires polling at least once + // + // This function returns a result, but can only be an Err if: + // + // * We call `.close()` on a WaitMap, which we never do + // * We have a duplicate key, which can't happen because `wait_for_*` methods + // take an &mut ref of their unique port+pin combo + // + // So we wait for it to complete, but ignore the result. + _ = w.as_mut().subscribe().await; + + // Now that our waker is in the map, we can enable the appropriate interrupt + // + // Clear any existing pending interrupt on this pin + self.pin + .gpio() + .isfr0() + .write(|w| unsafe { w.bits(1 << self.pin.pin()) }); + self.pin.gpio().icr(self.pin.pin()).write(|w| w.isf().isf1()); + + // Pin interrupt configuration + self.pin + .gpio() + .icr(self.pin.pin()) + .modify(|_, w| w.irqc().variant(level)); + + // Finally, we can await the matching call to `.wake()` from the interrupt. + // + // Again, technically, this could return a result, but for the same reasons + // as above, this can't be an error in our case, so just wait for it to complete + _ = w.await; + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub fn wait_for_high(&mut self) -> impl Future + use<'_, 'd> { + self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc12) + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub fn wait_for_low(&mut self) -> impl Future + use<'_, 'd> { + self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc8) + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub fn wait_for_rising_edge(&mut self) -> impl Future + use<'_, 'd> { + self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc9) + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub fn wait_for_falling_edge(&mut self) -> impl Future + use<'_, 'd> { + self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc10) + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub fn wait_for_any_edge(&mut self) -> impl Future + use<'_, 'd> { + self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc11) + } +} + +/// GPIO output driver that owns a `Flex` pin. +pub struct Output<'d> { + flex: Flex<'d>, +} + +impl<'d> Output<'d> { + /// Create a GPIO output driver for a [GpioPin] with the provided [Level]. + pub fn new(pin: Peri<'d, impl GpioPin>, initial: Level, strength: DriveStrength, slew_rate: SlewRate) -> Self { + let mut flex = Flex::new(pin); + flex.set_level(initial); + flex.set_as_output(); + flex.set_drive_strength(strength); + flex.set_slew_rate(slew_rate); + Self { flex } + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.flex.set_high(); + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.flex.set_low(); + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + self.flex.set_level(level); + } + + /// Toggle the output level. + #[inline] + pub fn toggle(&mut self) { + self.flex.toggle(); + } + + /// Is the output pin set as high? + #[inline] + pub fn is_set_high(&self) -> bool { + self.flex.is_high() + } + + /// Is the output pin set as low? + #[inline] + pub fn is_set_low(&self) -> bool { + !self.is_set_high() + } + + /// Expose the inner `Flex` if callers need to reconfigure the pin. + #[inline] + pub fn into_flex(self) -> Flex<'d> { + self.flex + } +} + +/// GPIO input driver that owns a `Flex` pin. +pub struct Input<'d> { + flex: Flex<'d>, +} + +impl<'d> Input<'d> { + /// Create a GPIO input driver for a [GpioPin]. + /// + pub fn new(pin: Peri<'d, impl GpioPin>, pull_select: Pull) -> Self { + let mut flex = Flex::new(pin); + flex.set_as_input(); + flex.set_pull(pull_select); + Self { flex } + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + self.flex.is_high() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + self.flex.is_low() + } + + /// Expose the inner `Flex` if callers need to reconfigure the pin. + /// + /// Since Drive Strength and Slew Rate are not set when creating the Input + /// pin, they need to be set when converting + #[inline] + pub fn into_flex(mut self, strength: DriveStrength, slew_rate: SlewRate) -> Flex<'d> { + self.flex.set_drive_strength(strength); + self.flex.set_slew_rate(slew_rate); + self.flex + } + + /// Get the pin level. + pub fn get_level(&self) -> Level { + self.flex.get_level() + } +} + +/// Async methods +impl<'d> Input<'d> { + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub fn wait_for_high(&mut self) -> impl Future + use<'_, 'd> { + self.flex.wait_for_high() + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub fn wait_for_low(&mut self) -> impl Future + use<'_, 'd> { + self.flex.wait_for_low() + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub fn wait_for_rising_edge(&mut self) -> impl Future + use<'_, 'd> { + self.flex.wait_for_rising_edge() + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub fn wait_for_falling_edge(&mut self) -> impl Future + use<'_, 'd> { + self.flex.wait_for_falling_edge() + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub fn wait_for_any_edge(&mut self) -> impl Future + use<'_, 'd> { + self.flex.wait_for_any_edge() + } +} + +impl embedded_hal_async::digital::Wait for Input<'_> { + 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(()) + } +} + +impl embedded_hal_async::digital::Wait for Flex<'_> { + 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(()) + } +} + +// Both embedded_hal 0.2 and 1.0 must be supported by embassy HALs. +impl embedded_hal_02::digital::v2::InputPin for Flex<'_> { + // GPIO operations on this block cannot fail, therefor we set the error type + // to Infallible to guarantee that we can only produce Ok variants. + type Error = Infallible; + + #[inline] + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + #[inline] + fn is_low(&self) -> Result { + Ok(self.is_low()) + } +} + +impl embedded_hal_02::digital::v2::InputPin for Input<'_> { + type Error = Infallible; + + #[inline] + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + #[inline] + fn is_low(&self) -> Result { + Ok(self.is_low()) + } +} + +impl embedded_hal_02::digital::v2::OutputPin for Flex<'_> { + type Error = Infallible; + + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } +} + +impl embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'_> { + #[inline] + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + #[inline] + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } +} + +impl embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'_> { + type Error = Infallible; + + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + self.toggle(); + Ok(()) + } +} + +impl embedded_hal_1::digital::ErrorType for Flex<'_> { + type Error = Infallible; +} + +impl embedded_hal_1::digital::ErrorType for Input<'_> { + type Error = Infallible; +} + +impl embedded_hal_1::digital::ErrorType for Output<'_> { + type Error = Infallible; +} + +impl embedded_hal_1::digital::InputPin for Input<'_> { + #[inline] + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + #[inline] + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl embedded_hal_1::digital::OutputPin for Flex<'_> { + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } +} + +impl embedded_hal_1::digital::StatefulOutputPin for Flex<'_> { + #[inline] + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + #[inline] + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} diff --git a/embassy-mcxa/src/i2c/controller.rs b/embassy-mcxa/src/i2c/controller.rs new file mode 100644 index 000000000..41bbc821d --- /dev/null +++ b/embassy-mcxa/src/i2c/controller.rs @@ -0,0 +1,455 @@ +//! LPI2C controller driver + +use core::marker::PhantomData; + +use embassy_hal_internal::Peri; +use mcxa_pac::lpi2c0::mtdr::Cmd; + +use super::{Blocking, Error, Instance, Mode, Result, SclPin, SdaPin}; +use crate::clocks::periph_helpers::{Div4, Lpi2cClockSel, Lpi2cConfig}; +use crate::clocks::{enable_and_reset, PoweredClock}; +use crate::AnyPin; + +/// Bus speed (nominal SCL, no clock stretching) +#[derive(Clone, Copy, Default, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Speed { + #[default] + /// 100 kbit/sec + Standard, + /// 400 kbit/sec + Fast, + /// 1 Mbit/sec + FastPlus, + /// 3.4 Mbit/sec + UltraFast, +} + +impl From for (u8, u8, u8, u8) { + fn from(value: Speed) -> (u8, u8, u8, u8) { + match value { + Speed::Standard => (0x3d, 0x37, 0x3b, 0x1d), + Speed::Fast => (0x0e, 0x0c, 0x0d, 0x06), + Speed::FastPlus => (0x04, 0x03, 0x03, 0x02), + + // UltraFast is "special". Leaving it unimplemented until + // the driver and the clock API is further stabilized. + Speed::UltraFast => todo!(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum SendStop { + No, + Yes, +} + +/// I2C controller configuration +#[derive(Clone, Copy, Default)] +#[non_exhaustive] +pub struct Config { + /// Bus speed + pub speed: Speed, +} + +/// I2C Controller Driver. +pub struct I2c<'d, T: Instance, M: Mode> { + _peri: Peri<'d, T>, + _scl: Peri<'d, AnyPin>, + _sda: Peri<'d, AnyPin>, + _phantom: PhantomData, + is_hs: bool, +} + +impl<'d, T: Instance> I2c<'d, T, Blocking> { + /// Create a new blocking instance of the I2C Controller bus driver. + pub fn new_blocking( + peri: Peri<'d, T>, + scl: Peri<'d, impl SclPin>, + sda: Peri<'d, impl SdaPin>, + config: Config, + ) -> Result { + Self::new_inner(peri, scl, sda, config) + } +} + +impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { + fn new_inner( + _peri: Peri<'d, T>, + scl: Peri<'d, impl SclPin>, + sda: Peri<'d, impl SdaPin>, + config: Config, + ) -> Result { + let (power, source, div) = Self::clock_config(config.speed); + + // Enable clocks + let conf = Lpi2cConfig { + power, + source, + div, + instance: T::CLOCK_INSTANCE, + }; + + _ = unsafe { enable_and_reset::(&conf).map_err(Error::ClockSetup)? }; + + scl.mux(); + sda.mux(); + + let _scl = scl.into(); + let _sda = sda.into(); + + Self::set_config(&config)?; + + Ok(Self { + _peri, + _scl, + _sda, + _phantom: PhantomData, + is_hs: config.speed == Speed::UltraFast, + }) + } + + fn set_config(config: &Config) -> Result<()> { + // Disable the controller. + critical_section::with(|_| T::regs().mcr().modify(|_, w| w.men().disabled())); + + // Soft-reset the controller, read and write FIFOs. + critical_section::with(|_| { + T::regs() + .mcr() + .modify(|_, w| w.rst().reset().rtf().reset().rrf().reset()); + // According to Reference Manual section 40.7.1.4, "There + // is no minimum delay required before clearing the + // software reset", therefore we clear it immediately. + T::regs().mcr().modify(|_, w| w.rst().not_reset()); + + T::regs().mcr().modify(|_, w| w.dozen().clear_bit().dbgen().clear_bit()); + }); + + let (clklo, clkhi, sethold, datavd) = config.speed.into(); + + critical_section::with(|_| { + T::regs().mccr0().modify(|_, w| unsafe { + w.clklo() + .bits(clklo) + .clkhi() + .bits(clkhi) + .sethold() + .bits(sethold) + .datavd() + .bits(datavd) + }) + }); + + // Enable the controller. + critical_section::with(|_| T::regs().mcr().modify(|_, w| w.men().enabled())); + + // Clear all flags + T::regs().msr().write(|w| { + w.epf() + .clear_bit_by_one() + .sdf() + .clear_bit_by_one() + .ndf() + .clear_bit_by_one() + .alf() + .clear_bit_by_one() + .fef() + .clear_bit_by_one() + .pltf() + .clear_bit_by_one() + .dmf() + .clear_bit_by_one() + .stf() + .clear_bit_by_one() + }); + + Ok(()) + } + + // REVISIT: turn this into a function of the speed parameter + fn clock_config(speed: Speed) -> (PoweredClock, Lpi2cClockSel, Div4) { + match speed { + Speed::Standard | Speed::Fast | Speed::FastPlus => ( + PoweredClock::NormalEnabledDeepSleepDisabled, + Lpi2cClockSel::FroLfDiv, + const { Div4::no_div() }, + ), + Speed::UltraFast => ( + PoweredClock::NormalEnabledDeepSleepDisabled, + Lpi2cClockSel::FroHfDiv, + const { Div4::no_div() }, + ), + } + } + + fn is_tx_fifo_full(&mut self) -> bool { + let txfifo_size = 1 << T::regs().param().read().mtxfifo().bits(); + T::regs().mfsr().read().txcount().bits() == txfifo_size + } + + fn is_tx_fifo_empty(&mut self) -> bool { + T::regs().mfsr().read().txcount() == 0 + } + + fn is_rx_fifo_empty(&mut self) -> bool { + T::regs().mfsr().read().rxcount() == 0 + } + + fn status(&mut self) -> Result<()> { + // Wait for TxFIFO to be drained + while !self.is_tx_fifo_empty() {} + + let msr = T::regs().msr().read(); + T::regs().msr().write(|w| { + w.epf() + .clear_bit_by_one() + .sdf() + .clear_bit_by_one() + .ndf() + .clear_bit_by_one() + .alf() + .clear_bit_by_one() + .fef() + .clear_bit_by_one() + .fef() + .clear_bit_by_one() + .pltf() + .clear_bit_by_one() + .dmf() + .clear_bit_by_one() + .stf() + .clear_bit_by_one() + }); + + if msr.ndf().bit_is_set() { + Err(Error::AddressNack) + } else if msr.alf().bit_is_set() { + Err(Error::ArbitrationLoss) + } else { + Ok(()) + } + } + + fn send_cmd(&mut self, cmd: Cmd, data: u8) { + #[cfg(feature = "defmt")] + defmt::trace!( + "Sending cmd '{}' ({}) with data '{:08x}' MSR: {:08x}", + cmd, + cmd as u8, + data, + T::regs().msr().read().bits() + ); + + T::regs() + .mtdr() + .write(|w| unsafe { w.data().bits(data) }.cmd().variant(cmd)); + } + + fn start(&mut self, address: u8, read: bool) -> Result<()> { + if address >= 0x80 { + return Err(Error::AddressOutOfRange(address)); + } + + // Wait until we have space in the TxFIFO + while self.is_tx_fifo_full() {} + + let addr_rw = address << 1 | if read { 1 } else { 0 }; + self.send_cmd(if self.is_hs { Cmd::StartHs } else { Cmd::Start }, addr_rw); + + // Check controller status + self.status() + } + + fn stop(&mut self) -> Result<()> { + // Wait until we have space in the TxFIFO + while self.is_tx_fifo_full() {} + + self.send_cmd(Cmd::Stop, 0); + self.status() + } + + fn blocking_read_internal(&mut self, address: u8, read: &mut [u8], send_stop: SendStop) -> Result<()> { + self.start(address, true)?; + + if read.is_empty() { + return Err(Error::InvalidReadBufferLength); + } + + for chunk in read.chunks_mut(256) { + // Wait until we have space in the TxFIFO + while self.is_tx_fifo_full() {} + + self.send_cmd(Cmd::Receive, (chunk.len() - 1) as u8); + + for byte in chunk.iter_mut() { + // Wait until there's data in the RxFIFO + while self.is_rx_fifo_empty() {} + + *byte = T::regs().mrdr().read().data().bits(); + } + + if send_stop == SendStop::Yes { + self.stop()?; + } + } + + Ok(()) + } + + fn blocking_write_internal(&mut self, address: u8, write: &[u8], send_stop: SendStop) -> Result<()> { + self.start(address, false)?; + + // Usually, embassy HALs error out with an empty write, + // however empty writes are useful for writing I2C scanning + // logic through write probing. That is, we send a start with + // R/w bit cleared, but instead of writing any data, just send + // the stop onto the bus. This has the effect of checking if + // the resulting address got an ACK but causing no + // side-effects to the device on the other end. + // + // Because of this, we are not going to error out in case of + // empty writes. + #[cfg(feature = "defmt")] + if write.is_empty() { + defmt::trace!("Empty write, write probing?"); + } + + for byte in write { + // Wait until we have space in the TxFIFO + while self.is_tx_fifo_full() {} + + self.send_cmd(Cmd::Transmit, *byte); + } + + if send_stop == SendStop::Yes { + self.stop()?; + } + + Ok(()) + } + + // Public API: Blocking + + /// Read from address into buffer blocking caller until done. + pub fn blocking_read(&mut self, address: u8, read: &mut [u8]) -> Result<()> { + self.blocking_read_internal(address, read, SendStop::Yes) + // Automatic Stop + } + + /// Write to address from buffer blocking caller until done. + pub fn blocking_write(&mut self, address: u8, write: &[u8]) -> Result<()> { + self.blocking_write_internal(address, write, SendStop::Yes) + } + + /// Write to address from bytes and read from address into buffer blocking caller until done. + pub fn blocking_write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<()> { + self.blocking_write_internal(address, write, SendStop::No)?; + self.blocking_read_internal(address, read, SendStop::Yes) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Read for I2c<'d, T, M> { + type Error = Error; + + fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<()> { + self.blocking_read(address, buffer) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Write for I2c<'d, T, M> { + type Error = Error; + + fn write(&mut self, address: u8, bytes: &[u8]) -> Result<()> { + self.blocking_write(address, bytes) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T, M> { + type Error = Error; + + fn write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<()> { + self.blocking_write_read(address, bytes, buffer) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Transactional for I2c<'d, T, M> { + type Error = Error; + + fn exec(&mut self, address: u8, operations: &mut [embedded_hal_02::blocking::i2c::Operation<'_>]) -> Result<()> { + if let Some((last, rest)) = operations.split_last_mut() { + for op in rest { + match op { + embedded_hal_02::blocking::i2c::Operation::Read(buf) => { + self.blocking_read_internal(address, buf, SendStop::No)? + } + embedded_hal_02::blocking::i2c::Operation::Write(buf) => { + self.blocking_write_internal(address, buf, SendStop::No)? + } + } + } + + match last { + embedded_hal_02::blocking::i2c::Operation::Read(buf) => { + self.blocking_read_internal(address, buf, SendStop::Yes) + } + embedded_hal_02::blocking::i2c::Operation::Write(buf) => { + self.blocking_write_internal(address, buf, SendStop::Yes) + } + } + } else { + Ok(()) + } + } +} + +impl embedded_hal_1::i2c::Error for Error { + fn kind(&self) -> embedded_hal_1::i2c::ErrorKind { + match *self { + Self::ArbitrationLoss => embedded_hal_1::i2c::ErrorKind::ArbitrationLoss, + Self::AddressNack => { + embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address) + } + _ => embedded_hal_1::i2c::ErrorKind::Other, + } + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::ErrorType for I2c<'d, T, M> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::I2c for I2c<'d, T, M> { + fn transaction(&mut self, address: u8, operations: &mut [embedded_hal_1::i2c::Operation<'_>]) -> Result<()> { + if let Some((last, rest)) = operations.split_last_mut() { + for op in rest { + match op { + embedded_hal_1::i2c::Operation::Read(buf) => { + self.blocking_read_internal(address, buf, SendStop::No)? + } + embedded_hal_1::i2c::Operation::Write(buf) => { + self.blocking_write_internal(address, buf, SendStop::No)? + } + } + } + + match last { + embedded_hal_1::i2c::Operation::Read(buf) => self.blocking_read_internal(address, buf, SendStop::Yes), + embedded_hal_1::i2c::Operation::Write(buf) => self.blocking_write_internal(address, buf, SendStop::Yes), + } + } else { + Ok(()) + } + } +} + +impl<'d, T: Instance, M: Mode> embassy_embedded_hal::SetConfig for I2c<'d, T, M> { + type Config = Config; + type ConfigError = Error; + + fn set_config(&mut self, config: &Self::Config) -> Result<()> { + Self::set_config(config) + } +} diff --git a/embassy-mcxa/src/i2c/mod.rs b/embassy-mcxa/src/i2c/mod.rs new file mode 100644 index 000000000..a1f842029 --- /dev/null +++ b/embassy-mcxa/src/i2c/mod.rs @@ -0,0 +1,171 @@ +//! I2C Support + +use core::marker::PhantomData; + +use embassy_hal_internal::PeripheralType; +use embassy_sync::waitqueue::AtomicWaker; +use paste::paste; + +use crate::clocks::periph_helpers::Lpi2cConfig; +use crate::clocks::{ClockError, Gate}; +use crate::gpio::{GpioPin, SealedPin}; +use crate::{interrupt, pac}; + +/// Shorthand for `Result`. +pub type Result = core::result::Result; + +pub mod controller; + +/// Error information type +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Clock configuration error. + ClockSetup(ClockError), + /// Reading for I2C failed. + ReadFail, + /// Writing to I2C failed. + WriteFail, + /// I2C address NAK condition. + AddressNack, + /// Bus level arbitration loss. + ArbitrationLoss, + /// Address out of range. + AddressOutOfRange(u8), + /// Invalid write buffer length. + InvalidWriteBufferLength, + /// Invalid read buffer length. + InvalidReadBufferLength, + /// Other internal errors or unexpected state. + Other, +} + +/// I2C interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let waker = T::waker(); + + waker.wake(); + + todo!() + } +} + +mod sealed { + /// Seal a trait + pub trait Sealed {} +} + +impl sealed::Sealed for T {} + +trait SealedInstance { + fn regs() -> &'static pac::lpi2c0::RegisterBlock; + fn waker() -> &'static AtomicWaker; +} + +/// I2C Instance +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType + 'static + Send + Gate { + /// Interrupt for this I2C instance. + type Interrupt: interrupt::typelevel::Interrupt; + /// Clock instance + const CLOCK_INSTANCE: crate::clocks::periph_helpers::Lpi2cInstance; +} + +macro_rules! impl_instance { + ($($n:expr),*) => { + $( + paste!{ + impl SealedInstance for crate::peripherals::[] { + fn regs() -> &'static pac::lpi2c0::RegisterBlock { + unsafe { &*pac::[]::ptr() } + } + + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } + } + + impl Instance for crate::peripherals::[] { + type Interrupt = crate::interrupt::typelevel::[]; + const CLOCK_INSTANCE: crate::clocks::periph_helpers::Lpi2cInstance + = crate::clocks::periph_helpers::Lpi2cInstance::[]; + } + } + )* + }; +} + +impl_instance!(0, 1, 2, 3); + +/// SCL pin trait. +pub trait SclPin: GpioPin + sealed::Sealed + PeripheralType { + fn mux(&self); +} + +/// SDA pin trait. +pub trait SdaPin: GpioPin + sealed::Sealed + PeripheralType { + fn mux(&self); +} + +/// Driver mode. +#[allow(private_bounds)] +pub trait Mode: sealed::Sealed {} + +/// Blocking mode. +pub struct Blocking; +impl sealed::Sealed for Blocking {} +impl Mode for Blocking {} + +/// Async mode. +pub struct Async; +impl sealed::Sealed for Async {} +impl Mode for Async {} + +macro_rules! impl_pin { + ($pin:ident, $peri:ident, $fn:ident, $trait:ident) => { + impl $trait for crate::peripherals::$pin { + fn mux(&self) { + self.set_pull(crate::gpio::Pull::Disabled); + self.set_slew_rate(crate::gpio::SlewRate::Fast.into()); + self.set_drive_strength(crate::gpio::DriveStrength::Double.into()); + self.set_function(crate::pac::port0::pcr0::Mux::$fn); + self.set_enable_input_buffer(); + } + } + }; +} + +impl_pin!(P0_16, LPI2C0, Mux2, SdaPin); +impl_pin!(P0_17, LPI2C0, Mux2, SclPin); +impl_pin!(P0_18, LPI2C0, Mux2, SclPin); +impl_pin!(P0_19, LPI2C0, Mux2, SdaPin); +impl_pin!(P1_0, LPI2C1, Mux3, SdaPin); +impl_pin!(P1_1, LPI2C1, Mux3, SclPin); +impl_pin!(P1_2, LPI2C1, Mux3, SdaPin); +impl_pin!(P1_3, LPI2C1, Mux3, SclPin); +impl_pin!(P1_8, LPI2C2, Mux3, SdaPin); +impl_pin!(P1_9, LPI2C2, Mux3, SclPin); +impl_pin!(P1_10, LPI2C2, Mux3, SdaPin); +impl_pin!(P1_11, LPI2C2, Mux3, SclPin); +impl_pin!(P1_12, LPI2C1, Mux2, SdaPin); +impl_pin!(P1_13, LPI2C1, Mux2, SclPin); +impl_pin!(P1_14, LPI2C1, Mux2, SclPin); +impl_pin!(P1_15, LPI2C1, Mux2, SdaPin); +impl_pin!(P1_30, LPI2C0, Mux3, SdaPin); +impl_pin!(P1_31, LPI2C0, Mux3, SclPin); +impl_pin!(P3_27, LPI2C3, Mux2, SclPin); +impl_pin!(P3_28, LPI2C3, Mux2, SdaPin); +// impl_pin!(P3_29, LPI2C3, Mux2, HreqPin); What is this HREQ pin? +impl_pin!(P3_30, LPI2C3, Mux2, SclPin); +impl_pin!(P3_31, LPI2C3, Mux2, SdaPin); +impl_pin!(P4_2, LPI2C2, Mux2, SdaPin); +impl_pin!(P4_3, LPI2C0, Mux2, SclPin); +impl_pin!(P4_4, LPI2C2, Mux2, SdaPin); +impl_pin!(P4_5, LPI2C0, Mux2, SclPin); +// impl_pin!(P4_6, LPI2C0, Mux2, HreqPin); What is this HREQ pin? diff --git a/embassy-mcxa/src/interrupt.rs b/embassy-mcxa/src/interrupt.rs new file mode 100644 index 000000000..000b2f9cd --- /dev/null +++ b/embassy-mcxa/src/interrupt.rs @@ -0,0 +1,546 @@ +//! Minimal interrupt helpers mirroring embassy-imxrt style for OS_EVENT and LPUART2. +//! Type-level interrupt traits and bindings are provided by the +//! `embassy_hal_internal::interrupt_mod!` macro via the generated module below. + +// TODO(AJM): As of 2025-11-13, we need to do a pass to ensure safety docs +// are complete prior to release. +#![allow(clippy::missing_safety_doc)] + +mod generated { + embassy_hal_internal::interrupt_mod!( + OS_EVENT, RTC, ADC1, GPIO0, GPIO1, GPIO2, GPIO3, GPIO4, LPI2C0, LPI2C1, LPI2C2, LPI2C3, LPUART0, LPUART1, + LPUART2, LPUART3, LPUART4, LPUART5, DMA_CH0, DMA_CH1, DMA_CH2, DMA_CH3, DMA_CH4, DMA_CH5, DMA_CH6, DMA_CH7, + ); +} + +use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; + +pub use generated::interrupt::{typelevel, Priority}; + +use crate::pac::Interrupt; + +/// Trait for configuring and controlling interrupts. +/// +/// This trait provides a consistent interface for interrupt management across +/// different interrupt sources, similar to embassy-imxrt's InterruptExt. +pub trait InterruptExt { + /// Clear any pending interrupt in NVIC. + fn unpend(&self); + + /// Set NVIC priority for this interrupt. + fn set_priority(&self, priority: Priority); + + /// Enable this interrupt in NVIC. + /// + /// # Safety + /// This function is unsafe because it can enable interrupts that may not be + /// properly configured, potentially leading to undefined behavior. + unsafe fn enable(&self); + + /// Disable this interrupt in NVIC. + /// + /// # Safety + /// This function is unsafe because disabling interrupts may leave the system + /// in an inconsistent state if the interrupt was expected to fire. + unsafe fn disable(&self); + + /// Check if the interrupt is pending in NVIC. + fn is_pending(&self) -> bool; +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct DefaultHandlerSnapshot { + pub vector: u16, + pub count: u32, + pub cfsr: u32, + pub hfsr: u32, + pub stacked_pc: u32, + pub stacked_lr: u32, + pub stacked_sp: u32, +} + +static LAST_DEFAULT_VECTOR: AtomicU16 = AtomicU16::new(0); +static LAST_DEFAULT_COUNT: AtomicU32 = AtomicU32::new(0); +static LAST_DEFAULT_CFSR: AtomicU32 = AtomicU32::new(0); +static LAST_DEFAULT_HFSR: AtomicU32 = AtomicU32::new(0); +static LAST_DEFAULT_PC: AtomicU32 = AtomicU32::new(0); +static LAST_DEFAULT_LR: AtomicU32 = AtomicU32::new(0); +static LAST_DEFAULT_SP: AtomicU32 = AtomicU32::new(0); + +#[inline] +pub fn default_handler_snapshot() -> DefaultHandlerSnapshot { + DefaultHandlerSnapshot { + vector: LAST_DEFAULT_VECTOR.load(Ordering::Relaxed), + count: LAST_DEFAULT_COUNT.load(Ordering::Relaxed), + cfsr: LAST_DEFAULT_CFSR.load(Ordering::Relaxed), + hfsr: LAST_DEFAULT_HFSR.load(Ordering::Relaxed), + stacked_pc: LAST_DEFAULT_PC.load(Ordering::Relaxed), + stacked_lr: LAST_DEFAULT_LR.load(Ordering::Relaxed), + stacked_sp: LAST_DEFAULT_SP.load(Ordering::Relaxed), + } +} + +#[inline] +pub fn clear_default_handler_snapshot() { + LAST_DEFAULT_VECTOR.store(0, Ordering::Relaxed); + LAST_DEFAULT_COUNT.store(0, Ordering::Relaxed); + LAST_DEFAULT_CFSR.store(0, Ordering::Relaxed); + LAST_DEFAULT_HFSR.store(0, Ordering::Relaxed); + LAST_DEFAULT_PC.store(0, Ordering::Relaxed); + LAST_DEFAULT_LR.store(0, Ordering::Relaxed); + LAST_DEFAULT_SP.store(0, Ordering::Relaxed); +} + +/// OS_EVENT interrupt helper with methods similar to embassy-imxrt's InterruptExt. +pub struct OsEvent; +pub const OS_EVENT: OsEvent = OsEvent; + +impl InterruptExt for OsEvent { + /// Clear any pending OS_EVENT in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::OS_EVENT); + } + + /// Set NVIC priority for OS_EVENT. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::OS_EVENT, u8::from(priority)); + } + } + + /// Enable OS_EVENT in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::OS_EVENT); + } + + /// Disable OS_EVENT in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::OS_EVENT); + } + + /// Check if OS_EVENT is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::OS_EVENT) + } +} + +impl OsEvent { + /// Configure OS_EVENT interrupt for timer operation. + /// This sets up the NVIC priority, enables the interrupt, and ensures global interrupts are enabled. + /// Also performs a software event to wake any pending WFE. + pub fn configure_for_timer(&self, priority: Priority) { + // Configure NVIC + self.unpend(); + self.set_priority(priority); + unsafe { + self.enable(); + } + + // Ensure global interrupts are enabled in no-reset scenarios (e.g., cargo run) + // Debuggers typically perform a reset which leaves PRIMASK=0; cargo run may not. + unsafe { + cortex_m::interrupt::enable(); + } + + // Wake any executor WFE that might be sleeping when we armed the first deadline + cortex_m::asm::sev(); + } +} + +/// LPUART2 interrupt helper with methods similar to embassy-imxrt's InterruptExt. +pub struct Lpuart2; +pub const LPUART2: Lpuart2 = Lpuart2; + +impl InterruptExt for Lpuart2 { + /// Clear any pending LPUART2 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::LPUART2); + } + + /// Set NVIC priority for LPUART2. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::LPUART2, u8::from(priority)); + } + } + + /// Enable LPUART2 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::LPUART2); + } + + /// Disable LPUART2 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::LPUART2); + } + + /// Check if LPUART2 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::LPUART2) + } +} + +impl Lpuart2 { + /// Configure LPUART2 interrupt for UART operation. + /// This sets up the NVIC priority, enables the interrupt, and ensures global interrupts are enabled. + pub fn configure_for_uart(&self, priority: Priority) { + // Configure NVIC + self.unpend(); + self.set_priority(priority); + unsafe { + self.enable(); + } + + // Ensure global interrupts are enabled in no-reset scenarios (e.g., cargo run) + // Debuggers typically perform a reset which leaves PRIMASK=0; cargo run may not. + unsafe { + cortex_m::interrupt::enable(); + } + } + + /// Install LPUART2 handler into the RAM vector table. + /// Safety: See `install_irq_handler`. + pub unsafe fn install_handler(&self, handler: unsafe extern "C" fn()) { + install_irq_handler(Interrupt::LPUART2, handler); + } +} + +pub struct Rtc; +pub const RTC: Rtc = Rtc; + +impl InterruptExt for Rtc { + /// Clear any pending RTC in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::RTC); + } + + /// Set NVIC priority for RTC. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::RTC, u8::from(priority)); + } + } + + /// Enable RTC in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::RTC); + } + + /// Disable RTC in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::RTC); + } + + /// Check if RTC is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::RTC) + } +} + +pub struct Adc; +pub const ADC1: Adc = Adc; + +impl InterruptExt for Adc { + /// Clear any pending ADC1 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::ADC1); + } + + /// Set NVIC priority for ADC1. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::ADC1, u8::from(priority)); + } + } + + /// Enable ADC1 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::ADC1); + } + + /// Disable ADC1 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::ADC1); + } + + /// Check if ADC1 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::ADC1) + } +} + +pub struct Gpio0; +pub const GPIO0: Gpio0 = Gpio0; + +impl InterruptExt for Gpio0 { + /// Clear any pending GPIO0 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO0); + } + + /// Set NVIC priority for GPIO0. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::GPIO0, u8::from(priority)); + } + } + + /// Enable GPIO0 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO0); + } + + /// Disable GPIO0 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::GPIO0); + } + + /// Check if GPIO0 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO0) + } +} + +pub struct Gpio1; +pub const GPIO1: Gpio1 = Gpio1; + +impl InterruptExt for Gpio1 { + /// Clear any pending GPIO1 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO1); + } + + /// Set NVIC priority for GPIO1. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::GPIO1, u8::from(priority)); + } + } + + /// Enable GPIO1 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO1); + } + + /// Disable GPIO1 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::GPIO1); + } + + /// Check if GPIO1 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO1) + } +} + +pub struct Gpio2; +pub const GPIO2: Gpio2 = Gpio2; + +impl InterruptExt for Gpio2 { + /// Clear any pending GPIO2 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO2); + } + + /// Set NVIC priority for GPIO2. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::GPIO2, u8::from(priority)); + } + } + + /// Enable GPIO2 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO2); + } + + /// Disable GPIO2 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::GPIO2); + } + + /// Check if GPIO2 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO2) + } +} + +pub struct Gpio3; +pub const GPIO3: Gpio3 = Gpio3; + +impl InterruptExt for Gpio3 { + /// Clear any pending GPIO3 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO3); + } + + /// Set NVIC priority for GPIO3. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::GPIO3, u8::from(priority)); + } + } + + /// Enable GPIO3 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO3); + } + + /// Disable GPIO3 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::GPIO3); + } + + /// Check if GPIO3 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO3) + } +} + +pub struct Gpio4; +pub const GPIO4: Gpio4 = Gpio4; + +impl InterruptExt for Gpio4 { + /// Clear any pending GPIO4 in NVIC. + #[inline] + fn unpend(&self) { + cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO4); + } + + /// Set NVIC priority for GPIO4. + #[inline] + fn set_priority(&self, priority: Priority) { + unsafe { + let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; + nvic.set_priority(Interrupt::GPIO4, u8::from(priority)); + } + } + + /// Enable GPIO4 in NVIC. + #[inline] + unsafe fn enable(&self) { + cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO4); + } + + /// Disable GPIO4 in NVIC. + #[inline] + unsafe fn disable(&self) { + cortex_m::peripheral::NVIC::mask(Interrupt::GPIO4); + } + + /// Check if GPIO4 is pending in NVIC. + #[inline] + fn is_pending(&self) -> bool { + cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO4) + } +} + +/// Set VTOR (Vector Table Offset) to a RAM-based vector table. +/// Pass a pointer to the first word in the RAM table (stack pointer slot 0). +/// Safety: Caller must ensure the RAM table is valid and aligned as required by the core. +pub unsafe fn vtor_set_ram_vector_base(base: *const u32) { + core::ptr::write_volatile(0xE000_ED08 as *mut u32, base as u32); +} + +/// Install an interrupt handler into the current VTOR-based vector table. +/// This writes the function pointer at index 16 + irq number. +/// Safety: Caller must ensure VTOR points at a writable RAM table and that `handler` +/// has the correct ABI and lifetime. +pub unsafe fn install_irq_handler(irq: Interrupt, handler: unsafe extern "C" fn()) { + let vtor_base = core::ptr::read_volatile(0xE000_ED08 as *const u32) as *mut u32; + let idx = 16 + (irq as isize as usize); + core::ptr::write_volatile(vtor_base.add(idx), handler as usize as u32); +} + +impl OsEvent { + /// Convenience to install the OS_EVENT handler into the RAM vector table. + /// Safety: See `install_irq_handler`. + pub unsafe fn install_handler(&self, handler: extern "C" fn()) { + install_irq_handler(Interrupt::OS_EVENT, handler); + } +} + +/// Install OS_EVENT handler by raw address. Useful to avoid fn pointer type mismatches. +/// Safety: Caller must ensure the address is a valid `extern "C" fn()` handler. +pub unsafe fn os_event_install_handler_raw(handler_addr: usize) { + let vtor_base = core::ptr::read_volatile(0xE000_ED08 as *const u32) as *mut u32; + let idx = 16 + (Interrupt::OS_EVENT as isize as usize); + core::ptr::write_volatile(vtor_base.add(idx), handler_addr as u32); +} + +/// Provide a conservative default IRQ handler that avoids wedging the system. +/// It clears all NVIC pending bits and returns, so spurious or reserved IRQs +/// don’t trap the core in an infinite WFI loop during bring-up. +#[no_mangle] +pub unsafe extern "C" fn DefaultHandler() { + let active = core::ptr::read_volatile(0xE000_ED04 as *const u32) & 0x1FF; + let cfsr = core::ptr::read_volatile(0xE000_ED28 as *const u32); + let hfsr = core::ptr::read_volatile(0xE000_ED2C as *const u32); + + let sp = cortex_m::register::msp::read(); + let stacked = sp as *const u32; + // Stacked registers follow ARMv8-M procedure call standard order + let stacked_pc = unsafe { stacked.add(6).read() }; + let stacked_lr = unsafe { stacked.add(5).read() }; + + LAST_DEFAULT_VECTOR.store(active as u16, Ordering::Relaxed); + LAST_DEFAULT_CFSR.store(cfsr, Ordering::Relaxed); + LAST_DEFAULT_HFSR.store(hfsr, Ordering::Relaxed); + LAST_DEFAULT_COUNT.fetch_add(1, Ordering::Relaxed); + LAST_DEFAULT_PC.store(stacked_pc, Ordering::Relaxed); + LAST_DEFAULT_LR.store(stacked_lr, Ordering::Relaxed); + LAST_DEFAULT_SP.store(sp, Ordering::Relaxed); + + // Do nothing here: on some MCUs/TrustZone setups, writing NVIC from a spurious + // handler can fault if targeting the Secure bank. Just return. + cortex_m::asm::dsb(); + cortex_m::asm::isb(); +} diff --git a/embassy-mcxa/src/lib.rs b/embassy-mcxa/src/lib.rs new file mode 100644 index 000000000..d721f53e6 --- /dev/null +++ b/embassy-mcxa/src/lib.rs @@ -0,0 +1,484 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![doc = include_str!("../README.md")] + +// //! ## Feature flags +// #![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +pub mod clocks; // still provide clock helpers +pub mod dma; +pub mod gpio; +pub mod pins; // pin mux helpers + +pub mod adc; +pub mod clkout; +pub mod config; +pub mod i2c; +pub mod interrupt; +pub mod lpuart; +pub mod ostimer; +pub mod rtc; + +pub use crate::pac::NVIC_PRIO_BITS; + +#[rustfmt::skip] +embassy_hal_internal::peripherals!( + ADC0, + ADC1, + + AOI0, + AOI1, + + CAN0, + CAN1, + + CDOG0, + CDOG1, + + // CLKOUT is not specifically a peripheral (it's part of SYSCON), + // but we still want it to be a singleton. + CLKOUT, + + CMC, + CMP0, + CMP1, + CRC0, + + CTIMER0, + CTIMER1, + CTIMER2, + CTIMER3, + CTIMER4, + + DBGMAILBOX, + DMA0, + DMA_CH0, + DMA_CH1, + DMA_CH2, + DMA_CH3, + DMA_CH4, + DMA_CH5, + DMA_CH6, + DMA_CH7, + EDMA0_TCD0, + EIM0, + EQDC0, + EQDC1, + ERM0, + FLEXIO0, + FLEXPWM0, + FLEXPWM1, + FMC0, + FMU0, + FREQME0, + GLIKEY0, + + GPIO0, + GPIO1, + GPIO2, + GPIO3, + GPIO4, + + I3C0, + INPUTMUX0, + + LPI2C0, + LPI2C1, + LPI2C2, + LPI2C3, + + LPSPI0, + LPSPI1, + + LPTMR0, + + LPUART0, + LPUART1, + LPUART2, + LPUART3, + LPUART4, + LPUART5, + + MAU0, + MBC0, + MRCC0, + OPAMP0, + + #[cfg(not(feature = "time"))] + OSTIMER0, + + P0_0, + P0_1, + P0_2, + P0_3, + P0_4, + P0_5, + P0_6, + P0_7, + P0_8, + P0_9, + P0_10, + P0_11, + P0_12, + P0_13, + P0_14, + P0_15, + P0_16, + P0_17, + P0_18, + P0_19, + P0_20, + P0_21, + P0_22, + P0_23, + P0_24, + P0_25, + P0_26, + P0_27, + P0_28, + P0_29, + P0_30, + P0_31, + + P1_0, + P1_1, + P1_2, + P1_3, + P1_4, + P1_5, + P1_6, + P1_7, + P1_8, + P1_9, + P1_10, + P1_11, + P1_12, + P1_13, + P1_14, + P1_15, + P1_16, + P1_17, + P1_18, + P1_19, + P1_20, + P1_21, + P1_22, + P1_23, + P1_24, + P1_25, + P1_26, + P1_27, + P1_28, + P1_29, + P1_30, + P1_31, + + P2_0, + P2_1, + P2_2, + P2_3, + P2_4, + P2_5, + P2_6, + P2_7, + P2_8, + P2_9, + P2_10, + P2_11, + P2_12, + P2_13, + P2_14, + P2_15, + P2_16, + P2_17, + P2_18, + P2_19, + P2_20, + P2_21, + P2_22, + P2_23, + P2_24, + P2_25, + P2_26, + P2_27, + P2_28, + P2_29, + P2_30, + P2_31, + + P3_0, + P3_1, + P3_2, + P3_3, + P3_4, + P3_5, + P3_6, + P3_7, + P3_8, + P3_9, + P3_10, + P3_11, + P3_12, + P3_13, + P3_14, + P3_15, + P3_16, + P3_17, + P3_18, + P3_19, + P3_20, + P3_21, + P3_22, + P3_23, + P3_24, + P3_25, + P3_26, + P3_27, + P3_28, + P3_29, + P3_30, + P3_31, + + P4_0, + P4_1, + P4_2, + P4_3, + P4_4, + P4_5, + P4_6, + P4_7, + P4_8, + P4_9, + P4_10, + P4_11, + P4_12, + P4_13, + P4_14, + P4_15, + P4_16, + P4_17, + P4_18, + P4_19, + P4_20, + P4_21, + P4_22, + P4_23, + P4_24, + P4_25, + P4_26, + P4_27, + P4_28, + P4_29, + P4_30, + P4_31, + + P5_0, + P5_1, + P5_2, + P5_3, + P5_4, + P5_5, + P5_6, + P5_7, + P5_8, + P5_9, + P5_10, + P5_11, + P5_12, + P5_13, + P5_14, + P5_15, + P5_16, + P5_17, + P5_18, + P5_19, + P5_20, + P5_21, + P5_22, + P5_23, + P5_24, + P5_25, + P5_26, + P5_27, + P5_28, + P5_29, + P5_30, + P5_31, + + PKC0, + + PORT0, + PORT1, + PORT2, + PORT3, + PORT4, + + RTC0, + SAU, + SCG0, + SCN_SCB, + SGI0, + SMARTDMA0, + SPC0, + SYSCON, + TDET0, + TRNG0, + UDF0, + USB0, + UTICK0, + VBAT0, + WAKETIMER0, + WUU0, + WWDT0, +); + +// Use cortex-m-rt's #[interrupt] attribute directly; PAC does not re-export it. + +// Re-export interrupt traits and types +pub use adc::Adc1 as Adc1Token; +pub use gpio::{AnyPin, Flex, Gpio as GpioToken, Input, Level, Output}; +pub use interrupt::InterruptExt; +#[cfg(feature = "unstable-pac")] +pub use mcxa_pac as pac; +#[cfg(not(feature = "unstable-pac"))] +pub(crate) use mcxa_pac as pac; +pub use rtc::Rtc0 as Rtc0Token; + +/// Initialize HAL with configuration (mirrors embassy-imxrt style). Minimal: just take peripherals. +/// Also applies configurable NVIC priority for the OSTIMER OS_EVENT interrupt (no enabling). +pub fn init(cfg: crate::config::Config) -> Peripherals { + let peripherals = Peripherals::take(); + // Apply user-configured priority early; enabling is left to examples/apps + #[cfg(feature = "time")] + crate::interrupt::OS_EVENT.set_priority(cfg.time_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::RTC.set_priority(cfg.rtc_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::ADC1.set_priority(cfg.adc_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::GPIO0.set_priority(cfg.gpio_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::GPIO1.set_priority(cfg.gpio_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::GPIO2.set_priority(cfg.gpio_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::GPIO3.set_priority(cfg.gpio_interrupt_priority); + // Apply user-configured priority early; enabling is left to examples/apps + crate::interrupt::GPIO4.set_priority(cfg.gpio_interrupt_priority); + + // Configure clocks + crate::clocks::init(cfg.clock_cfg).unwrap(); + + unsafe { + crate::gpio::init(); + } + + // Initialize DMA controller (clock, reset, configuration) + crate::dma::init(); + + // Initialize embassy-time global driver backed by OSTIMER0 + #[cfg(feature = "time")] + crate::ostimer::time_driver::init(crate::config::Config::default().time_interrupt_priority, 1_000_000); + + // Enable GPIO clocks + unsafe { + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); + } + + peripherals +} + +// /// Optional hook called by cortex-m-rt before RAM init. +// /// We proactively mask and clear all NVIC IRQs to avoid wedges from stale state +// /// left by soft resets/debug sessions. +// /// +// /// NOTE: Manual VTOR setup is required for RAM execution. The cortex-m-rt 'set-vtor' +// /// feature is incompatible with our setup because it expects __vector_table to be +// /// defined differently than how our RAM-based linker script arranges it. +// #[no_mangle] +// pub unsafe extern "C" fn __pre_init() { +// // Set the VTOR to point to the interrupt vector table in RAM +// // This is required since code runs from RAM on this MCU +// crate::interrupt::vtor_set_ram_vector_base(0x2000_0000 as *const u32); + +// // Mask and clear pending for all NVIC lines (0..127) to avoid stale state across runs. +// let nvic = &*cortex_m::peripheral::NVIC::PTR; +// for i in 0..4 { +// // 4 words x 32 = 128 IRQs +// nvic.icer[i].write(0xFFFF_FFFF); +// nvic.icpr[i].write(0xFFFF_FFFF); +// } +// // Do NOT touch peripheral registers here: clocks may be off and accesses can fault. +// crate::interrupt::clear_default_handler_snapshot(); +// } + +/// Internal helper to dispatch a type-level interrupt handler. +#[inline(always)] +#[doc(hidden)] +pub unsafe fn __handle_interrupt() +where + T: crate::interrupt::typelevel::Interrupt, + H: crate::interrupt::typelevel::Handler, +{ + H::on_interrupt(); +} + +/// Macro to bind interrupts to handlers, similar to embassy-imxrt. +/// +/// Example: +/// - Bind OS_EVENT to the OSTIMER time-driver handler +/// bind_interrupts!(struct Irqs { OS_EVENT => crate::ostimer::time_driver::OsEventHandler; }); +#[macro_export] +macro_rules! bind_interrupts { + ($(#[$attr:meta])* $vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { + #[derive(Copy, Clone)] + $(#[$attr])* + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + $(#[cfg($cond_irq)])? + unsafe extern "C" fn $irq() { + unsafe { + $( + $(#[cfg($cond_handler)])? + <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + )* + } + } + + $(#[cfg($cond_irq)])? + $crate::bind_interrupts!(@inner + $( + $(#[cfg($cond_handler)])? + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + ); + )* + }; + (@inner $($t:tt)*) => { + $($t)* + } +} diff --git a/embassy-mcxa/src/lpuart/buffered.rs b/embassy-mcxa/src/lpuart/buffered.rs new file mode 100644 index 000000000..8eb443ca7 --- /dev/null +++ b/embassy-mcxa/src/lpuart/buffered.rs @@ -0,0 +1,780 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::atomic_ring_buffer::RingBuffer; +use embassy_hal_internal::Peri; +use embassy_sync::waitqueue::AtomicWaker; + +use super::*; +use crate::interrupt; + +// ============================================================================ +// STATIC STATE MANAGEMENT +// ============================================================================ + +/// State for buffered LPUART operations +pub struct State { + tx_waker: AtomicWaker, + tx_buf: RingBuffer, + tx_done: AtomicBool, + rx_waker: AtomicWaker, + rx_buf: RingBuffer, + initialized: AtomicBool, +} + +impl Default for State { + fn default() -> Self { + Self::new() + } +} + +impl State { + /// Create a new state instance + pub const fn new() -> Self { + Self { + tx_waker: AtomicWaker::new(), + tx_buf: RingBuffer::new(), + tx_done: AtomicBool::new(true), + rx_waker: AtomicWaker::new(), + rx_buf: RingBuffer::new(), + initialized: AtomicBool::new(false), + } + } +} +// ============================================================================ +// BUFFERED DRIVER STRUCTURES +// ============================================================================ + +/// Buffered LPUART driver +pub struct BufferedLpuart<'a> { + tx: BufferedLpuartTx<'a>, + rx: BufferedLpuartRx<'a>, +} + +/// Buffered LPUART TX driver +pub struct BufferedLpuartTx<'a> { + info: Info, + state: &'static State, + _tx_pin: Peri<'a, AnyPin>, + _cts_pin: Option>, +} + +/// Buffered LPUART RX driver +pub struct BufferedLpuartRx<'a> { + info: Info, + state: &'static State, + _rx_pin: Peri<'a, AnyPin>, + _rts_pin: Option>, +} + +// ============================================================================ +// BUFFERED LPUART IMPLEMENTATION +// ============================================================================ + +impl<'a> BufferedLpuart<'a> { + /// Common initialization logic + fn init_common( + _inner: &Peri<'a, T>, + tx_buffer: Option<&'a mut [u8]>, + rx_buffer: Option<&'a mut [u8]>, + config: &Config, + enable_tx: bool, + enable_rx: bool, + enable_rts: bool, + enable_cts: bool, + ) -> Result<&'static State> { + let state = T::buffered_state(); + + if state.initialized.load(Ordering::Relaxed) { + return Err(Error::InvalidArgument); + } + + // Initialize buffers + if let Some(tx_buffer) = tx_buffer { + if tx_buffer.is_empty() { + return Err(Error::InvalidArgument); + } + unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), tx_buffer.len()) }; + } + + if let Some(rx_buffer) = rx_buffer { + if rx_buffer.is_empty() { + return Err(Error::InvalidArgument); + } + unsafe { state.rx_buf.init(rx_buffer.as_mut_ptr(), rx_buffer.len()) }; + } + + state.initialized.store(true, Ordering::Relaxed); + + // Enable clocks and initialize hardware + let conf = LpuartConfig { + power: config.power, + source: config.source, + div: config.div, + instance: T::CLOCK_INSTANCE, + }; + let clock_freq = unsafe { enable_and_reset::(&conf).map_err(Error::ClockSetup)? }; + + Self::init_hardware( + T::info().regs, + *config, + clock_freq, + enable_tx, + enable_rx, + enable_rts, + enable_cts, + )?; + + Ok(state) + } + + /// Helper for full-duplex initialization + fn new_inner( + inner: Peri<'a, T>, + tx_pin: Peri<'a, AnyPin>, + rx_pin: Peri<'a, AnyPin>, + rts_pin: Option>, + cts_pin: Option>, + tx_buffer: &'a mut [u8], + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result<(BufferedLpuartTx<'a>, BufferedLpuartRx<'a>)> { + let state = Self::init_common::( + &inner, + Some(tx_buffer), + Some(rx_buffer), + &config, + true, + true, + rts_pin.is_some(), + cts_pin.is_some(), + )?; + + let tx = BufferedLpuartTx { + info: T::info(), + state, + _tx_pin: tx_pin, + _cts_pin: cts_pin, + }; + + let rx = BufferedLpuartRx { + info: T::info(), + state, + _rx_pin: rx_pin, + _rts_pin: rts_pin, + }; + + Ok((tx, rx)) + } + + /// Common hardware initialization logic + fn init_hardware( + regs: &'static mcxa_pac::lpuart0::RegisterBlock, + config: Config, + clock_freq: u32, + enable_tx: bool, + enable_rx: bool, + enable_rts: bool, + enable_cts: bool, + ) -> Result<()> { + // Perform standard initialization + perform_software_reset(regs); + disable_transceiver(regs); + configure_baudrate(regs, config.baudrate_bps, clock_freq)?; + configure_frame_format(regs, &config); + configure_control_settings(regs, &config); + configure_fifo(regs, &config); + clear_all_status_flags(regs); + configure_flow_control(regs, enable_rts, enable_cts, &config); + configure_bit_order(regs, config.msb_first); + + // Enable interrupts for buffered operation + cortex_m::interrupt::free(|_| { + regs.ctrl().modify(|_, w| { + w.rie() + .enabled() // RX interrupt + .orie() + .enabled() // Overrun interrupt + .peie() + .enabled() // Parity error interrupt + .feie() + .enabled() // Framing error interrupt + .neie() + .enabled() // Noise error interrupt + }); + }); + + // Enable the transceiver + enable_transceiver(regs, enable_rx, enable_tx); + + Ok(()) + } + + /// Create a new full duplex buffered LPUART + pub fn new( + inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + rx_pin: Peri<'a, impl RxPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + tx_buffer: &'a mut [u8], + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + tx_pin.as_tx(); + rx_pin.as_rx(); + + let (tx, rx) = Self::new_inner::( + inner, + tx_pin.into(), + rx_pin.into(), + None, + None, + tx_buffer, + rx_buffer, + config, + )?; + + Ok(Self { tx, rx }) + } + + /// Create a new buffered LPUART instance with RTS/CTS flow control + pub fn new_with_rtscts( + inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + rx_pin: Peri<'a, impl RxPin>, + rts_pin: Peri<'a, impl RtsPin>, + cts_pin: Peri<'a, impl CtsPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + tx_buffer: &'a mut [u8], + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + tx_pin.as_tx(); + rx_pin.as_rx(); + rts_pin.as_rts(); + cts_pin.as_cts(); + + let (tx, rx) = Self::new_inner::( + inner, + tx_pin.into(), + rx_pin.into(), + Some(rts_pin.into()), + Some(cts_pin.into()), + tx_buffer, + rx_buffer, + config, + )?; + + Ok(Self { tx, rx }) + } + + /// Create a new buffered LPUART with only RTS flow control (RX flow control) + pub fn new_with_rts( + inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + rx_pin: Peri<'a, impl RxPin>, + rts_pin: Peri<'a, impl RtsPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + tx_buffer: &'a mut [u8], + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + tx_pin.as_tx(); + rx_pin.as_rx(); + rts_pin.as_rts(); + + let (tx, rx) = Self::new_inner::( + inner, + tx_pin.into(), + rx_pin.into(), + Some(rts_pin.into()), + None, + tx_buffer, + rx_buffer, + config, + )?; + + Ok(Self { tx, rx }) + } + + /// Create a new buffered LPUART with only CTS flow control (TX flow control) + pub fn new_with_cts( + inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + rx_pin: Peri<'a, impl RxPin>, + cts_pin: Peri<'a, impl CtsPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + tx_buffer: &'a mut [u8], + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + tx_pin.as_tx(); + rx_pin.as_rx(); + cts_pin.as_cts(); + + let (tx, rx) = Self::new_inner::( + inner, + tx_pin.into(), + rx_pin.into(), + None, + Some(cts_pin.into()), + tx_buffer, + rx_buffer, + config, + )?; + + Ok(Self { tx, rx }) + } + + /// Split the buffered LPUART into separate TX and RX parts + pub fn split(self) -> (BufferedLpuartTx<'a>, BufferedLpuartRx<'a>) { + (self.tx, self.rx) + } + + /// Get mutable references to TX and RX parts + pub fn split_ref(&mut self) -> (&mut BufferedLpuartTx<'a>, &mut BufferedLpuartRx<'a>) { + (&mut self.tx, &mut self.rx) + } +} + +// ============================================================================ +// BUFFERED TX IMPLEMENTATION +// ============================================================================ + +impl<'a> BufferedLpuartTx<'a> { + /// Helper for TX-only initialization + fn new_inner( + inner: Peri<'a, T>, + tx_pin: Peri<'a, AnyPin>, + cts_pin: Option>, + tx_buffer: &'a mut [u8], + config: Config, + ) -> Result> { + let state = BufferedLpuart::init_common::( + &inner, + Some(tx_buffer), + None, + &config, + true, + false, + false, + cts_pin.is_some(), + )?; + + Ok(BufferedLpuartTx { + info: T::info(), + state, + _tx_pin: tx_pin, + _cts_pin: cts_pin, + }) + } + + pub fn new( + inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + tx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + tx_pin.as_tx(); + + Self::new_inner::(inner, tx_pin.into(), None, tx_buffer, config) + } + + /// Create a new TX-only buffered LPUART with CTS flow control + pub fn new_with_cts( + inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + cts_pin: Peri<'a, impl CtsPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + tx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + tx_pin.as_tx(); + cts_pin.as_cts(); + + Self::new_inner::(inner, tx_pin.into(), Some(cts_pin.into()), tx_buffer, config) + } +} + +impl<'a> BufferedLpuartTx<'a> { + /// Write data asynchronously + pub async fn write(&mut self, buf: &[u8]) -> Result { + let mut written = 0; + + for &byte in buf { + // Wait for space in the buffer + poll_fn(|cx| { + self.state.tx_waker.register(cx.waker()); + + let mut writer = unsafe { self.state.tx_buf.writer() }; + if writer.push_one(byte) { + // Enable TX interrupt to start transmission + cortex_m::interrupt::free(|_| { + self.info.regs.ctrl().modify(|_, w| w.tie().enabled()); + }); + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }) + .await?; + + written += 1; + } + + Ok(written) + } + + /// Flush the TX buffer and wait for transmission to complete + pub async fn flush(&mut self) -> Result<()> { + // Wait for TX buffer to empty and transmission to complete + poll_fn(|cx| { + self.state.tx_waker.register(cx.waker()); + + let tx_empty = self.state.tx_buf.is_empty(); + let fifo_empty = self.info.regs.water().read().txcount().bits() == 0; + let tc_complete = self.info.regs.stat().read().tc().is_complete(); + + if tx_empty && fifo_empty && tc_complete { + Poll::Ready(Ok(())) + } else { + // Enable appropriate interrupt + cortex_m::interrupt::free(|_| { + if !tx_empty { + self.info.regs.ctrl().modify(|_, w| w.tie().enabled()); + } else { + self.info.regs.ctrl().modify(|_, w| w.tcie().enabled()); + } + }); + Poll::Pending + } + }) + .await + } + + /// Try to write without blocking + pub fn try_write(&mut self, buf: &[u8]) -> Result { + let mut writer = unsafe { self.state.tx_buf.writer() }; + let mut written = 0; + + for &byte in buf { + if writer.push_one(byte) { + written += 1; + } else { + break; + } + } + + if written > 0 { + // Enable TX interrupt to start transmission + cortex_m::interrupt::free(|_| { + self.info.regs.ctrl().modify(|_, w| w.tie().enabled()); + }); + } + + Ok(written) + } +} + +// ============================================================================ +// BUFFERED RX IMPLEMENTATION +// ============================================================================ + +impl<'a> BufferedLpuartRx<'a> { + /// Helper for RX-only initialization + fn new_inner( + inner: Peri<'a, T>, + rx_pin: Peri<'a, AnyPin>, + rts_pin: Option>, + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result> { + let state = BufferedLpuart::init_common::( + &inner, + None, + Some(rx_buffer), + &config, + false, + true, + rts_pin.is_some(), + false, + )?; + + Ok(BufferedLpuartRx { + info: T::info(), + state, + _rx_pin: rx_pin, + _rts_pin: rts_pin, + }) + } + + /// Create a new RX-only buffered LPUART + pub fn new( + inner: Peri<'a, T>, + rx_pin: Peri<'a, impl RxPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + rx_pin.as_rx(); + + Self::new_inner::(inner, rx_pin.into(), None, rx_buffer, config) + } + + /// Create a new RX-only buffered LPUART with RTS flow control + pub fn new_with_rts( + inner: Peri<'a, T>, + rx_pin: Peri<'a, impl RxPin>, + rts_pin: Peri<'a, impl RtsPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + rx_buffer: &'a mut [u8], + config: Config, + ) -> Result { + rx_pin.as_rx(); + rts_pin.as_rts(); + + Self::new_inner::(inner, rx_pin.into(), Some(rts_pin.into()), rx_buffer, config) + } +} + +impl<'a> BufferedLpuartRx<'a> { + /// Read data asynchronously + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + let mut read = 0; + + // Try to read available data + poll_fn(|cx| { + self.state.rx_waker.register(cx.waker()); + + // Disable RX interrupt while reading from buffer + cortex_m::interrupt::free(|_| { + self.info.regs.ctrl().modify(|_, w| w.rie().disabled()); + }); + + let mut reader = unsafe { self.state.rx_buf.reader() }; + let available = reader.pop(|data| { + let to_copy = core::cmp::min(data.len(), buf.len() - read); + if to_copy > 0 { + buf[read..read + to_copy].copy_from_slice(&data[..to_copy]); + read += to_copy; + } + to_copy + }); + + // Re-enable RX interrupt + cortex_m::interrupt::free(|_| { + self.info.regs.ctrl().modify(|_, w| w.rie().enabled()); + }); + + if read > 0 { + Poll::Ready(Ok(read)) + } else if available == 0 { + Poll::Pending + } else { + Poll::Ready(Ok(0)) + } + }) + .await + } + + /// Try to read without blocking + pub fn try_read(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + // Disable RX interrupt while reading from buffer + cortex_m::interrupt::free(|_| { + self.info.regs.ctrl().modify(|_, w| w.rie().disabled()); + }); + + let mut reader = unsafe { self.state.rx_buf.reader() }; + let read = reader.pop(|data| { + let to_copy = core::cmp::min(data.len(), buf.len()); + if to_copy > 0 { + buf[..to_copy].copy_from_slice(&data[..to_copy]); + } + to_copy + }); + + // Re-enable RX interrupt + cortex_m::interrupt::free(|_| { + self.info.regs.ctrl().modify(|_, w| w.rie().enabled()); + }); + + Ok(read) + } +} + +// ============================================================================ +// INTERRUPT HANDLER +// ============================================================================ + +/// Buffered UART interrupt handler +pub struct BufferedInterruptHandler { + _phantom: PhantomData, +} + +impl crate::interrupt::typelevel::Handler for BufferedInterruptHandler { + unsafe fn on_interrupt() { + let regs = T::info().regs; + let state = T::buffered_state(); + + // Check if this instance is initialized + if !state.initialized.load(Ordering::Relaxed) { + return; + } + + let ctrl = regs.ctrl().read(); + let stat = regs.stat().read(); + let has_fifo = regs.param().read().rxfifo().bits() > 0; + + // Handle overrun error + if stat.or().is_overrun() { + regs.stat().write(|w| w.or().clear_bit_by_one()); + state.rx_waker.wake(); + return; + } + + // Clear other error flags + if stat.pf().is_parity() { + regs.stat().write(|w| w.pf().clear_bit_by_one()); + } + if stat.fe().is_error() { + regs.stat().write(|w| w.fe().clear_bit_by_one()); + } + if stat.nf().is_noise() { + regs.stat().write(|w| w.nf().clear_bit_by_one()); + } + + // Handle RX data + if ctrl.rie().is_enabled() && (has_data(regs) || stat.idle().is_idle()) { + let mut pushed_any = false; + let mut writer = state.rx_buf.writer(); + + if has_fifo { + // Read from FIFO + while regs.water().read().rxcount().bits() > 0 { + let byte = (regs.data().read().bits() & 0xFF) as u8; + if writer.push_one(byte) { + pushed_any = true; + } else { + // Buffer full, stop reading + break; + } + } + } else { + // Read single byte + if regs.stat().read().rdrf().is_rxdata() { + let byte = (regs.data().read().bits() & 0xFF) as u8; + if writer.push_one(byte) { + pushed_any = true; + } + } + } + + if pushed_any { + state.rx_waker.wake(); + } + + // Clear idle flag if set + if stat.idle().is_idle() { + regs.stat().write(|w| w.idle().clear_bit_by_one()); + } + } + + // Handle TX data + if ctrl.tie().is_enabled() { + let mut sent_any = false; + let mut reader = state.tx_buf.reader(); + + // Send data while TX buffer is ready and we have data + while regs.stat().read().tdre().is_no_txdata() { + if let Some(byte) = reader.pop_one() { + regs.data().write(|w| w.bits(u32::from(byte))); + sent_any = true; + } else { + // No more data to send + break; + } + } + + if sent_any { + state.tx_waker.wake(); + } + + // If buffer is empty, switch to TC interrupt or disable + if state.tx_buf.is_empty() { + cortex_m::interrupt::free(|_| { + regs.ctrl().modify(|_, w| w.tie().disabled().tcie().enabled()); + }); + } + } + + // Handle transmission complete + if ctrl.tcie().is_enabled() && regs.stat().read().tc().is_complete() { + state.tx_done.store(true, Ordering::Release); + state.tx_waker.wake(); + + // Disable TC interrupt + cortex_m::interrupt::free(|_| { + regs.ctrl().modify(|_, w| w.tcie().disabled()); + }); + } + } +} + +// ============================================================================ +// EMBEDDED-IO ASYNC TRAIT IMPLEMENTATIONS +// ============================================================================ + +impl embedded_io_async::ErrorType for BufferedLpuartTx<'_> { + type Error = Error; +} + +impl embedded_io_async::ErrorType for BufferedLpuartRx<'_> { + type Error = Error; +} + +impl embedded_io_async::ErrorType for BufferedLpuart<'_> { + type Error = Error; +} + +impl embedded_io_async::Write for BufferedLpuartTx<'_> { + async fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.write(buf).await + } + + async fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.flush().await + } +} + +impl embedded_io_async::Read for BufferedLpuartRx<'_> { + async fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.read(buf).await + } +} + +impl embedded_io_async::Write for BufferedLpuart<'_> { + async fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.tx.write(buf).await + } + + async fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.tx.flush().await + } +} + +impl embedded_io_async::Read for BufferedLpuart<'_> { + async fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.rx.read(buf).await + } +} diff --git a/embassy-mcxa/src/lpuart/mod.rs b/embassy-mcxa/src/lpuart/mod.rs new file mode 100644 index 000000000..5722b759c --- /dev/null +++ b/embassy-mcxa/src/lpuart/mod.rs @@ -0,0 +1,1733 @@ +use core::marker::PhantomData; + +use embassy_hal_internal::{Peri, PeripheralType}; +use paste::paste; + +use crate::clocks::periph_helpers::{Div4, LpuartClockSel, LpuartConfig}; +use crate::clocks::{enable_and_reset, ClockError, Gate, PoweredClock}; +use crate::gpio::SealedPin; +use crate::pac::lpuart0::baud::Sbns as StopBits; +use crate::pac::lpuart0::ctrl::{Idlecfg as IdleConfig, Ilt as IdleType, Pt as Parity, M as DataBits}; +use crate::pac::lpuart0::modir::{Txctsc as TxCtsConfig, Txctssrc as TxCtsSource}; +use crate::pac::lpuart0::stat::Msbf as MsbFirst; +use crate::{interrupt, pac, AnyPin}; + +pub mod buffered; + +// ============================================================================ +// DMA INTEGRATION +// ============================================================================ + +use crate::dma::{ + Channel as DmaChannelTrait, DmaChannel, DmaRequest, EnableInterrupt, RingBuffer, DMA_MAX_TRANSFER_SIZE, +}; + +// ============================================================================ +// MISC +// ============================================================================ + +mod sealed { + /// Simply seal a trait to prevent external implementations + pub trait Sealed {} +} + +// ============================================================================ +// INSTANCE TRAIT +// ============================================================================ + +pub type Regs = &'static crate::pac::lpuart0::RegisterBlock; + +pub trait SealedInstance { + fn info() -> Info; + fn index() -> usize; + fn buffered_state() -> &'static buffered::State; +} + +pub struct Info { + pub regs: Regs, +} + +/// Trait for LPUART peripheral instances +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType + 'static + Send + Gate { + const CLOCK_INSTANCE: crate::clocks::periph_helpers::LpuartInstance; + type Interrupt: interrupt::typelevel::Interrupt; + /// Type-safe DMA request source for TX + type TxDmaRequest: DmaRequest; + /// Type-safe DMA request source for RX + type RxDmaRequest: DmaRequest; +} + +macro_rules! impl_instance { + ($($n:expr);* $(;)?) => { + $( + paste!{ + impl SealedInstance for crate::peripherals::[] { + fn info() -> Info { + Info { + regs: unsafe { &*pac::[]::ptr() }, + } + } + + #[inline] + fn index() -> usize { + $n + } + + fn buffered_state() -> &'static buffered::State { + static BUFFERED_STATE: buffered::State = buffered::State::new(); + &BUFFERED_STATE + } + } + + impl Instance for crate::peripherals::[] { + const CLOCK_INSTANCE: crate::clocks::periph_helpers::LpuartInstance + = crate::clocks::periph_helpers::LpuartInstance::[]; + type Interrupt = crate::interrupt::typelevel::[]; + type TxDmaRequest = crate::dma::[]; + type RxDmaRequest = crate::dma::[]; + } + } + )* + }; +} + +// DMA request sources are now type-safe via associated types. +// The request source numbers are defined in src/dma.rs: +// LPUART0: RX=21, TX=22 -> Lpuart0RxRequest, Lpuart0TxRequest +// LPUART1: RX=23, TX=24 -> Lpuart1RxRequest, Lpuart1TxRequest +// LPUART2: RX=25, TX=26 -> Lpuart2RxRequest, Lpuart2TxRequest +// LPUART3: RX=27, TX=28 -> Lpuart3RxRequest, Lpuart3TxRequest +// LPUART4: RX=29, TX=30 -> Lpuart4RxRequest, Lpuart4TxRequest +// LPUART5: RX=31, TX=32 -> Lpuart5RxRequest, Lpuart5TxRequest +impl_instance!(0; 1; 2; 3; 4; 5); + +// ============================================================================ +// INSTANCE HELPER FUNCTIONS +// ============================================================================ + +/// Perform software reset on the LPUART peripheral +pub fn perform_software_reset(regs: Regs) { + // Software reset - set and clear RST bit (Global register) + regs.global().write(|w| w.rst().reset()); + regs.global().write(|w| w.rst().no_effect()); +} + +/// Disable both transmitter and receiver +pub fn disable_transceiver(regs: Regs) { + regs.ctrl().modify(|_, w| w.te().disabled().re().disabled()); +} + +/// Calculate and configure baudrate settings +pub fn configure_baudrate(regs: Regs, baudrate_bps: u32, clock_freq: u32) -> Result<()> { + let (osr, sbr) = calculate_baudrate(baudrate_bps, clock_freq)?; + + // Configure BAUD register + regs.baud().modify(|_, w| unsafe { + // Clear and set OSR + w.osr().bits(osr - 1); + // Clear and set SBR + w.sbr().bits(sbr); + // Set BOTHEDGE if OSR is between 4 and 7 + if osr > 3 && osr < 8 { + w.bothedge().enabled() + } else { + w.bothedge().disabled() + } + }); + + Ok(()) +} + +/// Configure frame format (stop bits, data bits) +pub fn configure_frame_format(regs: Regs, config: &Config) { + // Configure stop bits + regs.baud().modify(|_, w| w.sbns().variant(config.stop_bits_count)); + + // Clear M10 for now (10-bit mode) + regs.baud().modify(|_, w| w.m10().disabled()); +} + +/// Configure control settings (parity, data bits, idle config, pin swap) +pub fn configure_control_settings(regs: Regs, config: &Config) { + regs.ctrl().modify(|_, w| { + // Parity configuration + let mut w = if let Some(parity) = config.parity_mode { + w.pe().enabled().pt().variant(parity) + } else { + w.pe().disabled() + }; + + // Data bits configuration + w = match config.data_bits_count { + DataBits::Data8 => { + if config.parity_mode.is_some() { + w.m().data9() // 8 data + 1 parity = 9 bits + } else { + w.m().data8() // 8 data bits only + } + } + DataBits::Data9 => w.m().data9(), + }; + + // Idle configuration + w = w.idlecfg().variant(config.rx_idle_config); + w = w.ilt().variant(config.rx_idle_type); + + // Swap TXD/RXD if configured + if config.swap_txd_rxd { + w.swap().swap() + } else { + w.swap().standard() + } + }); +} + +/// Configure FIFO settings and watermarks +pub fn configure_fifo(regs: Regs, config: &Config) { + // Configure WATER register for FIFO watermarks + regs.water().write(|w| unsafe { + w.rxwater() + .bits(config.rx_fifo_watermark) + .txwater() + .bits(config.tx_fifo_watermark) + }); + + // Enable TX/RX FIFOs + regs.fifo().modify(|_, w| w.txfe().enabled().rxfe().enabled()); + + // Flush FIFOs + regs.fifo() + .modify(|_, w| w.txflush().txfifo_rst().rxflush().rxfifo_rst()); +} + +/// Clear all status flags +pub fn clear_all_status_flags(regs: Regs) { + regs.stat().reset(); +} + +/// Configure hardware flow control if enabled +pub fn configure_flow_control(regs: Regs, enable_tx_cts: bool, enable_rx_rts: bool, config: &Config) { + if enable_rx_rts || enable_tx_cts { + regs.modir().modify(|_, w| { + let mut w = w; + + // Configure TX CTS + w = w.txctsc().variant(config.tx_cts_config); + w = w.txctssrc().variant(config.tx_cts_source); + + if enable_rx_rts { + w = w.rxrtse().enabled(); + } else { + w = w.rxrtse().disabled(); + } + + if enable_tx_cts { + w = w.txctse().enabled(); + } else { + w = w.txctse().disabled(); + } + + w + }); + } +} + +/// Configure bit order (MSB first or LSB first) +pub fn configure_bit_order(regs: Regs, msb_first: MsbFirst) { + regs.stat().modify(|_, w| w.msbf().variant(msb_first)); +} + +/// Enable transmitter and/or receiver based on configuration +pub fn enable_transceiver(regs: Regs, enable_tx: bool, enable_rx: bool) { + regs.ctrl().modify(|_, w| { + let mut w = w; + if enable_tx { + w = w.te().enabled(); + } + if enable_rx { + w = w.re().enabled(); + } + w + }); +} + +pub fn calculate_baudrate(baudrate: u32, src_clock_hz: u32) -> Result<(u8, u16)> { + let mut baud_diff = baudrate; + let mut osr = 0u8; + let mut sbr = 0u16; + + // Try OSR values from 4 to 32 + for osr_temp in 4u8..=32u8 { + // Calculate SBR: (srcClock_Hz * 2 / (baudRate * osr) + 1) / 2 + let sbr_calc = ((src_clock_hz * 2) / (baudrate * osr_temp as u32)).div_ceil(2); + + let sbr_temp = if sbr_calc == 0 { + 1 + } else if sbr_calc > 0x1FFF { + 0x1FFF + } else { + sbr_calc as u16 + }; + + // Calculate actual baud rate + let calculated_baud = src_clock_hz / (osr_temp as u32 * sbr_temp as u32); + + let temp_diff = calculated_baud.abs_diff(baudrate); + + if temp_diff <= baud_diff { + baud_diff = temp_diff; + osr = osr_temp; + sbr = sbr_temp; + } + } + + // Check if baud rate difference is within 3% + if baud_diff > (baudrate / 100) * 3 { + return Err(Error::UnsupportedBaudrate); + } + + Ok((osr, sbr)) +} + +/// Wait for all transmit operations to complete +pub fn wait_for_tx_complete(regs: Regs) { + // Wait for TX FIFO to empty + while regs.water().read().txcount().bits() != 0 { + // Wait for TX FIFO to drain + } + + // Wait for last character to shift out (TC = Transmission Complete) + while regs.stat().read().tc().is_active() { + // Wait for transmission to complete + } +} + +pub fn check_and_clear_rx_errors(regs: Regs) -> Result<()> { + let stat = regs.stat().read(); + let mut status = Ok(()); + + // Check for overrun first - other error flags are prevented when OR is set + if stat.or().is_overrun() { + regs.stat().write(|w| w.or().clear_bit_by_one()); + + return Err(Error::Overrun); + } + + if stat.pf().is_parity() { + regs.stat().write(|w| w.pf().clear_bit_by_one()); + status = Err(Error::Parity); + } + + if stat.fe().is_error() { + regs.stat().write(|w| w.fe().clear_bit_by_one()); + status = Err(Error::Framing); + } + + if stat.nf().is_noise() { + regs.stat().write(|w| w.nf().clear_bit_by_one()); + status = Err(Error::Noise); + } + + status +} + +pub fn has_data(regs: Regs) -> bool { + if regs.param().read().rxfifo().bits() > 0 { + // FIFO is available - check RXCOUNT in WATER register + regs.water().read().rxcount().bits() > 0 + } else { + // No FIFO - check RDRF flag in STAT register + regs.stat().read().rdrf().is_rxdata() + } +} + +// ============================================================================ +// PIN TRAITS FOR LPUART FUNCTIONALITY +// ============================================================================ + +impl sealed::Sealed for T {} + +/// io configuration trait for Lpuart Tx configuration +pub trait TxPin: Into + sealed::Sealed + PeripheralType { + /// convert the pin to appropriate function for Lpuart Tx usage + fn as_tx(&self); +} + +/// io configuration trait for Lpuart Rx configuration +pub trait RxPin: Into + sealed::Sealed + PeripheralType { + /// convert the pin to appropriate function for Lpuart Rx usage + fn as_rx(&self); +} + +/// io configuration trait for Lpuart Cts +pub trait CtsPin: Into + sealed::Sealed + PeripheralType { + /// convert the pin to appropriate function for Lpuart Cts usage + fn as_cts(&self); +} + +/// io configuration trait for Lpuart Rts +pub trait RtsPin: Into + sealed::Sealed + PeripheralType { + /// convert the pin to appropriate function for Lpuart Rts usage + fn as_rts(&self); +} + +macro_rules! impl_tx_pin { + ($inst:ident, $pin:ident, $alt:ident) => { + impl TxPin for crate::peripherals::$pin { + fn as_tx(&self) { + // TODO: Check these are right + self.set_pull(crate::gpio::Pull::Up); + self.set_slew_rate(crate::gpio::SlewRate::Fast.into()); + self.set_drive_strength(crate::gpio::DriveStrength::Double.into()); + self.set_function(crate::pac::port0::pcr0::Mux::$alt); + self.set_enable_input_buffer(); + } + } + }; +} + +macro_rules! impl_rx_pin { + ($inst:ident, $pin:ident, $alt:ident) => { + impl RxPin for crate::peripherals::$pin { + fn as_rx(&self) { + // TODO: Check these are right + self.set_pull(crate::gpio::Pull::Up); + self.set_slew_rate(crate::gpio::SlewRate::Fast.into()); + self.set_drive_strength(crate::gpio::DriveStrength::Double.into()); + self.set_function(crate::pac::port0::pcr0::Mux::$alt); + self.set_enable_input_buffer(); + } + } + }; +} + +// TODO: Macro and impls for CTS/RTS pins +macro_rules! impl_cts_pin { + ($inst:ident, $pin:ident, $alt:ident) => { + impl CtsPin for crate::peripherals::$pin { + fn as_cts(&self) { + todo!() + } + } + }; +} + +macro_rules! impl_rts_pin { + ($inst:ident, $pin:ident, $alt:ident) => { + impl RtsPin for crate::peripherals::$pin { + fn as_rts(&self) { + todo!() + } + } + }; +} + +// LPUART 0 +impl_tx_pin!(LPUART0, P0_3, Mux2); +impl_tx_pin!(LPUART0, P0_21, Mux3); +impl_tx_pin!(LPUART0, P2_1, Mux2); + +impl_rx_pin!(LPUART0, P0_2, Mux2); +impl_rx_pin!(LPUART0, P0_20, Mux3); +impl_rx_pin!(LPUART0, P2_0, Mux2); + +impl_cts_pin!(LPUART0, P0_1, Mux2); +impl_cts_pin!(LPUART0, P0_23, Mux3); +impl_cts_pin!(LPUART0, P2_3, Mux2); + +impl_rts_pin!(LPUART0, P0_0, Mux2); +impl_rts_pin!(LPUART0, P0_22, Mux3); +impl_rts_pin!(LPUART0, P2_2, Mux2); + +// LPUART 1 +impl_tx_pin!(LPUART1, P1_9, Mux2); +impl_tx_pin!(LPUART1, P2_13, Mux3); +impl_tx_pin!(LPUART1, P3_9, Mux3); +impl_tx_pin!(LPUART1, P3_21, Mux3); + +impl_rx_pin!(LPUART1, P1_8, Mux2); +impl_rx_pin!(LPUART1, P2_12, Mux3); +impl_rx_pin!(LPUART1, P3_8, Mux3); +impl_rx_pin!(LPUART1, P3_20, Mux3); + +impl_cts_pin!(LPUART1, P1_11, Mux2); +impl_cts_pin!(LPUART1, P2_17, Mux3); +impl_cts_pin!(LPUART1, P3_11, Mux3); +impl_cts_pin!(LPUART1, P3_23, Mux3); + +impl_rts_pin!(LPUART1, P1_10, Mux2); +impl_rts_pin!(LPUART1, P2_15, Mux3); +impl_rts_pin!(LPUART1, P2_16, Mux3); +impl_rts_pin!(LPUART1, P3_10, Mux3); + +// LPUART 2 +impl_tx_pin!(LPUART2, P1_5, Mux3); +impl_tx_pin!(LPUART2, P1_13, Mux3); +impl_tx_pin!(LPUART2, P2_2, Mux3); +impl_tx_pin!(LPUART2, P2_10, Mux3); +impl_tx_pin!(LPUART2, P3_15, Mux2); + +impl_rx_pin!(LPUART2, P1_4, Mux3); +impl_rx_pin!(LPUART2, P1_12, Mux3); +impl_rx_pin!(LPUART2, P2_3, Mux3); +impl_rx_pin!(LPUART2, P2_11, Mux3); +impl_rx_pin!(LPUART2, P3_14, Mux2); + +impl_cts_pin!(LPUART2, P1_7, Mux3); +impl_cts_pin!(LPUART2, P1_15, Mux3); +impl_cts_pin!(LPUART2, P2_4, Mux3); +impl_cts_pin!(LPUART2, P3_13, Mux2); + +impl_rts_pin!(LPUART2, P1_6, Mux3); +impl_rts_pin!(LPUART2, P1_14, Mux3); +impl_rts_pin!(LPUART2, P2_5, Mux3); +impl_rts_pin!(LPUART2, P3_12, Mux2); + +// LPUART 3 +impl_tx_pin!(LPUART3, P3_1, Mux3); +impl_tx_pin!(LPUART3, P3_12, Mux3); +impl_tx_pin!(LPUART3, P4_5, Mux3); + +impl_rx_pin!(LPUART3, P3_0, Mux3); +impl_rx_pin!(LPUART3, P3_13, Mux3); +impl_rx_pin!(LPUART3, P4_2, Mux3); + +impl_cts_pin!(LPUART3, P3_7, Mux3); +impl_cts_pin!(LPUART3, P3_14, Mux3); +impl_cts_pin!(LPUART3, P4_6, Mux3); + +impl_rts_pin!(LPUART3, P3_6, Mux3); +impl_rts_pin!(LPUART3, P3_15, Mux3); +impl_rts_pin!(LPUART3, P4_7, Mux3); + +// LPUART 4 +impl_tx_pin!(LPUART4, P2_7, Mux3); +impl_tx_pin!(LPUART4, P3_19, Mux2); +impl_tx_pin!(LPUART4, P3_27, Mux3); +impl_tx_pin!(LPUART4, P4_3, Mux3); + +impl_rx_pin!(LPUART4, P2_6, Mux3); +impl_rx_pin!(LPUART4, P3_18, Mux2); +impl_rx_pin!(LPUART4, P3_28, Mux3); +impl_rx_pin!(LPUART4, P4_4, Mux3); + +impl_cts_pin!(LPUART4, P2_0, Mux3); +impl_cts_pin!(LPUART4, P3_17, Mux2); +impl_cts_pin!(LPUART4, P3_31, Mux3); + +impl_rts_pin!(LPUART4, P2_1, Mux3); +impl_rts_pin!(LPUART4, P3_16, Mux2); +impl_rts_pin!(LPUART4, P3_30, Mux3); + +// LPUART 5 +impl_tx_pin!(LPUART5, P1_10, Mux8); +impl_tx_pin!(LPUART5, P1_17, Mux8); + +impl_rx_pin!(LPUART5, P1_11, Mux8); +impl_rx_pin!(LPUART5, P1_16, Mux8); + +impl_cts_pin!(LPUART5, P1_12, Mux8); +impl_cts_pin!(LPUART5, P1_19, Mux8); + +impl_rts_pin!(LPUART5, P1_13, Mux8); +impl_rts_pin!(LPUART5, P1_18, Mux8); + +// ============================================================================ +// ERROR TYPES AND RESULTS +// ============================================================================ + +/// LPUART error types +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Read error + Read, + /// Buffer overflow + Overrun, + /// Noise error + Noise, + /// Framing error + Framing, + /// Parity error + Parity, + /// Failure + Fail, + /// Invalid argument + InvalidArgument, + /// Lpuart baud rate cannot be supported with the given clock + UnsupportedBaudrate, + /// RX FIFO Empty + RxFifoEmpty, + /// TX FIFO Full + TxFifoFull, + /// TX Busy + TxBusy, + /// Clock Error + ClockSetup(ClockError), +} + +/// A specialized Result type for LPUART operations +pub type Result = core::result::Result; + +// ============================================================================ +// CONFIGURATION STRUCTURES +// ============================================================================ + +/// Lpuart config +#[derive(Debug, Clone, Copy)] +pub struct Config { + /// Power state required for this peripheral + pub power: PoweredClock, + /// Clock source + pub source: LpuartClockSel, + /// Clock divisor + pub div: Div4, + /// Baud rate in bits per second + pub baudrate_bps: u32, + /// Parity configuration + pub parity_mode: Option, + /// Number of data bits + pub data_bits_count: DataBits, + /// MSB First or LSB First configuration + pub msb_first: MsbFirst, + /// Number of stop bits + pub stop_bits_count: StopBits, + /// TX FIFO watermark + pub tx_fifo_watermark: u8, + /// RX FIFO watermark + pub rx_fifo_watermark: u8, + /// TX CTS source + pub tx_cts_source: TxCtsSource, + /// TX CTS configure + pub tx_cts_config: TxCtsConfig, + /// RX IDLE type + pub rx_idle_type: IdleType, + /// RX IDLE configuration + pub rx_idle_config: IdleConfig, + /// Swap TXD and RXD pins + pub swap_txd_rxd: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + baudrate_bps: 115_200u32, + parity_mode: None, + data_bits_count: DataBits::Data8, + msb_first: MsbFirst::LsbFirst, + stop_bits_count: StopBits::One, + tx_fifo_watermark: 0, + rx_fifo_watermark: 1, + tx_cts_source: TxCtsSource::Cts, + tx_cts_config: TxCtsConfig::Start, + rx_idle_type: IdleType::FromStart, + rx_idle_config: IdleConfig::Idle1, + swap_txd_rxd: false, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: LpuartClockSel::FroLfDiv, + div: Div4::no_div(), + } + } +} + +/// LPUART status flags +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Status { + /// Transmit data register empty + pub tx_empty: bool, + /// Transmission complete + pub tx_complete: bool, + /// Receive data register full + pub rx_full: bool, + /// Idle line detected + pub idle: bool, + /// Receiver overrun + pub overrun: bool, + /// Noise error + pub noise: bool, + /// Framing error + pub framing: bool, + /// Parity error + pub parity: bool, +} + +// ============================================================================ +// MODE TRAITS (BLOCKING/ASYNC) +// ============================================================================ + +/// Driver move trait. +#[allow(private_bounds)] +pub trait Mode: sealed::Sealed {} + +/// Blocking mode. +pub struct Blocking; +impl sealed::Sealed for Blocking {} +impl Mode for Blocking {} + +/// Async mode. +pub struct Async; +impl sealed::Sealed for Async {} +impl Mode for Async {} + +// ============================================================================ +// CORE DRIVER STRUCTURES +// ============================================================================ + +/// Lpuart driver. +pub struct Lpuart<'a, M: Mode> { + info: Info, + tx: LpuartTx<'a, M>, + rx: LpuartRx<'a, M>, +} + +/// Lpuart TX driver. +pub struct LpuartTx<'a, M: Mode> { + info: Info, + _tx_pin: Peri<'a, AnyPin>, + _cts_pin: Option>, + mode: PhantomData<(&'a (), M)>, +} + +/// Lpuart Rx driver. +pub struct LpuartRx<'a, M: Mode> { + info: Info, + _rx_pin: Peri<'a, AnyPin>, + _rts_pin: Option>, + mode: PhantomData<(&'a (), M)>, +} + +/// Lpuart TX driver with DMA support. +pub struct LpuartTxDma<'a, T: Instance, C: DmaChannelTrait> { + info: Info, + _tx_pin: Peri<'a, AnyPin>, + tx_dma: DmaChannel, + _instance: core::marker::PhantomData, +} + +/// Lpuart RX driver with DMA support. +pub struct LpuartRxDma<'a, T: Instance, C: DmaChannelTrait> { + info: Info, + _rx_pin: Peri<'a, AnyPin>, + rx_dma: DmaChannel, + _instance: core::marker::PhantomData, +} + +/// Lpuart driver with DMA support for both TX and RX. +pub struct LpuartDma<'a, T: Instance, TxC: DmaChannelTrait, RxC: DmaChannelTrait> { + tx: LpuartTxDma<'a, T, TxC>, + rx: LpuartRxDma<'a, T, RxC>, +} + +// ============================================================================ +// LPUART CORE IMPLEMENTATION +// ============================================================================ + +impl<'a, M: Mode> Lpuart<'a, M> { + fn init( + enable_tx: bool, + enable_rx: bool, + enable_tx_cts: bool, + enable_rx_rts: bool, + config: Config, + ) -> Result<()> { + let regs = T::info().regs; + + // Enable clocks + let conf = LpuartConfig { + power: config.power, + source: config.source, + div: config.div, + instance: T::CLOCK_INSTANCE, + }; + let clock_freq = unsafe { enable_and_reset::(&conf).map_err(Error::ClockSetup)? }; + + // Perform initialization sequence + perform_software_reset(regs); + disable_transceiver(regs); + configure_baudrate(regs, config.baudrate_bps, clock_freq)?; + configure_frame_format(regs, &config); + configure_control_settings(regs, &config); + configure_fifo(regs, &config); + clear_all_status_flags(regs); + configure_flow_control(regs, enable_tx_cts, enable_rx_rts, &config); + configure_bit_order(regs, config.msb_first); + enable_transceiver(regs, enable_rx, enable_tx); + + Ok(()) + } + + /// Deinitialize the LPUART peripheral + pub fn deinit(&self) -> Result<()> { + let regs = self.info.regs; + + // Wait for TX operations to complete + wait_for_tx_complete(regs); + + // Clear all status flags + clear_all_status_flags(regs); + + // Disable the module - clear all CTRL register bits + regs.ctrl().reset(); + + Ok(()) + } + + /// Split the Lpuart into a transmitter and receiver + pub fn split(self) -> (LpuartTx<'a, M>, LpuartRx<'a, M>) { + (self.tx, self.rx) + } + + /// Split the Lpuart into a transmitter and receiver by mutable reference + pub fn split_ref(&mut self) -> (&mut LpuartTx<'a, M>, &mut LpuartRx<'a, M>) { + (&mut self.tx, &mut self.rx) + } +} + +// ============================================================================ +// BLOCKING MODE IMPLEMENTATIONS +// ============================================================================ + +impl<'a> Lpuart<'a, Blocking> { + /// Create a new blocking LPUART instance with RX/TX pins. + pub fn new_blocking( + _inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + rx_pin: Peri<'a, impl RxPin>, + config: Config, + ) -> Result { + // Configure the pins for LPUART usage + tx_pin.as_tx(); + rx_pin.as_rx(); + + // Initialize the peripheral + Self::init::(true, true, false, false, config)?; + + Ok(Self { + info: T::info(), + tx: LpuartTx::new_inner(T::info(), tx_pin.into(), None), + rx: LpuartRx::new_inner(T::info(), rx_pin.into(), None), + }) + } + + /// Create a new blocking LPUART instance with RX, TX and RTS/CTS flow control pins + pub fn new_blocking_with_rtscts( + _inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + rx_pin: Peri<'a, impl RxPin>, + cts_pin: Peri<'a, impl CtsPin>, + rts_pin: Peri<'a, impl RtsPin>, + config: Config, + ) -> Result { + // Configure the pins for LPUART usage + rx_pin.as_rx(); + tx_pin.as_tx(); + rts_pin.as_rts(); + cts_pin.as_cts(); + + // Initialize the peripheral with flow control + Self::init::(true, true, true, true, config)?; + + Ok(Self { + info: T::info(), + rx: LpuartRx::new_inner(T::info(), rx_pin.into(), Some(rts_pin.into())), + tx: LpuartTx::new_inner(T::info(), tx_pin.into(), Some(cts_pin.into())), + }) + } +} + +// ---------------------------------------------------------------------------- +// Blocking TX Implementation +// ---------------------------------------------------------------------------- + +impl<'a, M: Mode> LpuartTx<'a, M> { + fn new_inner(info: Info, tx_pin: Peri<'a, AnyPin>, cts_pin: Option>) -> Self { + Self { + info, + _tx_pin: tx_pin, + _cts_pin: cts_pin, + mode: PhantomData, + } + } +} + +impl<'a> LpuartTx<'a, Blocking> { + /// Create a new blocking LPUART transmitter instance + pub fn new_blocking( + _inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + config: Config, + ) -> Result { + // Configure the pins for LPUART usage + tx_pin.as_tx(); + + // Initialize the peripheral + Lpuart::::init::(true, false, false, false, config)?; + + Ok(Self::new_inner(T::info(), tx_pin.into(), None)) + } + + /// Create a new blocking LPUART transmitter instance with CTS flow control + pub fn new_blocking_with_cts( + _inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + cts_pin: Peri<'a, impl CtsPin>, + config: Config, + ) -> Result { + tx_pin.as_tx(); + cts_pin.as_cts(); + + Lpuart::::init::(true, false, true, false, config)?; + + Ok(Self::new_inner(T::info(), tx_pin.into(), Some(cts_pin.into()))) + } + + fn write_byte_internal(&mut self, byte: u8) -> Result<()> { + self.info.regs.data().modify(|_, w| unsafe { w.bits(u32::from(byte)) }); + + Ok(()) + } + + fn blocking_write_byte(&mut self, byte: u8) -> Result<()> { + while self.info.regs.stat().read().tdre().is_txdata() {} + self.write_byte_internal(byte) + } + + fn write_byte(&mut self, byte: u8) -> Result<()> { + if self.info.regs.stat().read().tdre().is_txdata() { + Err(Error::TxFifoFull) + } else { + self.write_byte_internal(byte) + } + } + + /// Write data to LPUART TX blocking execution until all data is sent. + pub fn blocking_write(&mut self, buf: &[u8]) -> Result<()> { + for x in buf { + self.blocking_write_byte(*x)?; + } + + Ok(()) + } + + pub fn write_str_blocking(&mut self, buf: &str) { + let _ = self.blocking_write(buf.as_bytes()); + } + + /// Write data to LPUART TX without blocking. + pub fn write(&mut self, buf: &[u8]) -> Result<()> { + for x in buf { + self.write_byte(*x)?; + } + + Ok(()) + } + + /// Flush LPUART TX blocking execution until all data has been transmitted. + pub fn blocking_flush(&mut self) -> Result<()> { + while self.info.regs.water().read().txcount().bits() != 0 { + // Wait for TX FIFO to drain + } + + // Wait for last character to shift out + while self.info.regs.stat().read().tc().is_active() { + // Wait for transmission to complete + } + + Ok(()) + } + + /// Flush LPUART TX. + pub fn flush(&mut self) -> Result<()> { + // Check if TX FIFO is empty + if self.info.regs.water().read().txcount().bits() != 0 { + return Err(Error::TxBusy); + } + + // Check if transmission is complete + if self.info.regs.stat().read().tc().is_active() { + return Err(Error::TxBusy); + } + + Ok(()) + } +} + +// ---------------------------------------------------------------------------- +// Blocking RX Implementation +// ---------------------------------------------------------------------------- + +impl<'a, M: Mode> LpuartRx<'a, M> { + fn new_inner(info: Info, rx_pin: Peri<'a, AnyPin>, rts_pin: Option>) -> Self { + Self { + info, + _rx_pin: rx_pin, + _rts_pin: rts_pin, + mode: PhantomData, + } + } +} + +impl<'a> LpuartRx<'a, Blocking> { + /// Create a new blocking LPUART Receiver instance + pub fn new_blocking( + _inner: Peri<'a, T>, + rx_pin: Peri<'a, impl RxPin>, + config: Config, + ) -> Result { + rx_pin.as_rx(); + + Lpuart::::init::(false, true, false, false, config)?; + + Ok(Self::new_inner(T::info(), rx_pin.into(), None)) + } + + /// Create a new blocking LPUART Receiver instance with RTS flow control + pub fn new_blocking_with_rts( + _inner: Peri<'a, T>, + rx_pin: Peri<'a, impl RxPin>, + rts_pin: Peri<'a, impl RtsPin>, + config: Config, + ) -> Result { + rx_pin.as_rx(); + rts_pin.as_rts(); + + Lpuart::::init::(false, true, false, true, config)?; + + Ok(Self::new_inner(T::info(), rx_pin.into(), Some(rts_pin.into()))) + } + + fn read_byte_internal(&mut self) -> Result { + let data = self.info.regs.data().read(); + + Ok((data.bits() & 0xFF) as u8) + } + + fn read_byte(&mut self) -> Result { + check_and_clear_rx_errors(self.info.regs)?; + + if !has_data(self.info.regs) { + return Err(Error::RxFifoEmpty); + } + + self.read_byte_internal() + } + + fn blocking_read_byte(&mut self) -> Result { + loop { + if has_data(self.info.regs) { + return self.read_byte_internal(); + } + + check_and_clear_rx_errors(self.info.regs)?; + } + } + + /// Read data from LPUART RX without blocking. + pub fn read(&mut self, buf: &mut [u8]) -> Result<()> { + for byte in buf.iter_mut() { + *byte = self.read_byte()?; + } + Ok(()) + } + + /// Read data from LPUART RX blocking execution until the buffer is filled. + pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<()> { + for byte in buf.iter_mut() { + *byte = self.blocking_read_byte()?; + } + Ok(()) + } +} + +impl<'a> Lpuart<'a, Blocking> { + /// Read data from LPUART RX blocking execution until the buffer is filled + pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<()> { + self.rx.blocking_read(buf) + } + + /// Read data from LPUART RX without blocking + pub fn read(&mut self, buf: &mut [u8]) -> Result<()> { + self.rx.read(buf) + } + + /// Write data to LPUART TX blocking execution until all data is sent + pub fn blocking_write(&mut self, buf: &[u8]) -> Result<()> { + self.tx.blocking_write(buf) + } + + pub fn write_byte(&mut self, byte: u8) -> Result<()> { + self.tx.write_byte(byte) + } + + pub fn read_byte_blocking(&mut self) -> u8 { + loop { + if let Ok(b) = self.rx.read_byte() { + return b; + } + } + } + + pub fn write_str_blocking(&mut self, buf: &str) { + self.tx.write_str_blocking(buf); + } + + /// Write data to LPUART TX without blocking + pub fn write(&mut self, buf: &[u8]) -> Result<()> { + self.tx.write(buf) + } + + /// Flush LPUART TX blocking execution until all data has been transmitted + pub fn blocking_flush(&mut self) -> Result<()> { + self.tx.blocking_flush() + } + + /// Flush LPUART TX without blocking + pub fn flush(&mut self) -> Result<()> { + self.tx.flush() + } +} + +// ============================================================================ +// ASYNC MODE IMPLEMENTATIONS (DMA-based) +// ============================================================================ + +/// Guard struct that ensures DMA is stopped if the async future is cancelled. +/// +/// This implements the RAII pattern: if the future is dropped before completion +/// (e.g., due to a timeout), the DMA transfer is automatically aborted to prevent +/// use-after-free when the buffer goes out of scope. +struct TxDmaGuard<'a, C: DmaChannelTrait> { + dma: &'a DmaChannel, + regs: Regs, +} + +impl<'a, C: DmaChannelTrait> TxDmaGuard<'a, C> { + fn new(dma: &'a DmaChannel, regs: Regs) -> Self { + Self { dma, regs } + } + + /// Complete the transfer normally (don't abort on drop). + fn complete(self) { + // Cleanup + self.regs.baud().modify(|_, w| w.tdmae().disabled()); + unsafe { + self.dma.disable_request(); + self.dma.clear_done(); + } + // Don't run drop since we've cleaned up + core::mem::forget(self); + } +} + +impl Drop for TxDmaGuard<'_, C> { + fn drop(&mut self) { + // Abort the DMA transfer if still running + unsafe { + self.dma.disable_request(); + self.dma.clear_done(); + self.dma.clear_interrupt(); + } + // Disable UART TX DMA request + self.regs.baud().modify(|_, w| w.tdmae().disabled()); + } +} + +/// Guard struct for RX DMA transfers. +struct RxDmaGuard<'a, C: DmaChannelTrait> { + dma: &'a DmaChannel, + regs: Regs, +} + +impl<'a, C: DmaChannelTrait> RxDmaGuard<'a, C> { + fn new(dma: &'a DmaChannel, regs: Regs) -> Self { + Self { dma, regs } + } + + /// Complete the transfer normally (don't abort on drop). + fn complete(self) { + // Ensure DMA writes are visible to CPU + cortex_m::asm::dsb(); + // Cleanup + self.regs.baud().modify(|_, w| w.rdmae().disabled()); + unsafe { + self.dma.disable_request(); + self.dma.clear_done(); + } + // Don't run drop since we've cleaned up + core::mem::forget(self); + } +} + +impl Drop for RxDmaGuard<'_, C> { + fn drop(&mut self) { + // Abort the DMA transfer if still running + unsafe { + self.dma.disable_request(); + self.dma.clear_done(); + self.dma.clear_interrupt(); + } + // Disable UART RX DMA request + self.regs.baud().modify(|_, w| w.rdmae().disabled()); + } +} + +impl<'a, T: Instance, C: DmaChannelTrait> LpuartTxDma<'a, T, C> { + /// Create a new LPUART TX driver with DMA support. + pub fn new( + _inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + tx_dma_ch: Peri<'a, C>, + config: Config, + ) -> Result { + tx_pin.as_tx(); + let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); + + // Initialize LPUART with TX enabled, RX disabled, no flow control + Lpuart::::init::(true, false, false, false, config)?; + + Ok(Self { + info: T::info(), + _tx_pin: tx_pin, + tx_dma: DmaChannel::new(tx_dma_ch), + _instance: core::marker::PhantomData, + }) + } + + /// Write data using DMA. + /// + /// This configures the DMA channel for a memory-to-peripheral transfer + /// and waits for completion asynchronously. Large buffers are automatically + /// split into chunks that fit within the DMA transfer limit. + /// + /// The DMA request source is automatically derived from the LPUART instance type. + /// + /// # Safety + /// + /// If the returned future is dropped before completion (e.g., due to a timeout), + /// the DMA transfer is automatically aborted to prevent use-after-free. + /// + /// # Arguments + /// * `buf` - Data buffer to transmit + pub async fn write_dma(&mut self, buf: &[u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + let mut total = 0; + for chunk in buf.chunks(DMA_MAX_TRANSFER_SIZE) { + total += self.write_dma_inner(chunk).await?; + } + + Ok(total) + } + + /// Internal helper to write a single chunk (max 0x7FFF bytes) using DMA. + async fn write_dma_inner(&mut self, buf: &[u8]) -> Result { + let len = buf.len(); + let peri_addr = self.info.regs.data().as_ptr() as *mut u8; + + unsafe { + // Clean up channel state + self.tx_dma.disable_request(); + self.tx_dma.clear_done(); + self.tx_dma.clear_interrupt(); + + // Set DMA request source from instance type (type-safe) + self.tx_dma.set_request_source::(); + + // Configure TCD for memory-to-peripheral transfer + self.tx_dma + .setup_write_to_peripheral(buf, peri_addr, EnableInterrupt::Yes); + + // Enable UART TX DMA request + self.info.regs.baud().modify(|_, w| w.tdmae().enabled()); + + // Enable DMA channel request + self.tx_dma.enable_request(); + } + + // Create guard that will abort DMA if this future is dropped + let guard = TxDmaGuard::new(&self.tx_dma, self.info.regs); + + // Wait for completion asynchronously + core::future::poll_fn(|cx| { + self.tx_dma.waker().register(cx.waker()); + if self.tx_dma.is_done() { + core::task::Poll::Ready(()) + } else { + core::task::Poll::Pending + } + }) + .await; + + // Transfer completed successfully - clean up without aborting + guard.complete(); + + Ok(len) + } + + /// Blocking write (fallback when DMA is not needed) + pub fn blocking_write(&mut self, buf: &[u8]) -> Result<()> { + for &byte in buf { + while self.info.regs.stat().read().tdre().is_txdata() {} + self.info.regs.data().modify(|_, w| unsafe { w.bits(u32::from(byte)) }); + } + Ok(()) + } + + /// Flush TX blocking + pub fn blocking_flush(&mut self) -> Result<()> { + while self.info.regs.water().read().txcount().bits() != 0 {} + while self.info.regs.stat().read().tc().is_active() {} + Ok(()) + } +} + +impl<'a, T: Instance, C: DmaChannelTrait> LpuartRxDma<'a, T, C> { + /// Create a new LPUART RX driver with DMA support. + pub fn new( + _inner: Peri<'a, T>, + rx_pin: Peri<'a, impl RxPin>, + rx_dma_ch: Peri<'a, C>, + config: Config, + ) -> Result { + rx_pin.as_rx(); + let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); + + // Initialize LPUART with TX disabled, RX enabled, no flow control + Lpuart::::init::(false, true, false, false, config)?; + + Ok(Self { + info: T::info(), + _rx_pin: rx_pin, + rx_dma: DmaChannel::new(rx_dma_ch), + _instance: core::marker::PhantomData, + }) + } + + /// Read data using DMA. + /// + /// This configures the DMA channel for a peripheral-to-memory transfer + /// and waits for completion asynchronously. Large buffers are automatically + /// split into chunks that fit within the DMA transfer limit. + /// + /// The DMA request source is automatically derived from the LPUART instance type. + /// + /// # Safety + /// + /// If the returned future is dropped before completion (e.g., due to a timeout), + /// the DMA transfer is automatically aborted to prevent use-after-free. + /// + /// # Arguments + /// * `buf` - Buffer to receive data into + pub async fn read_dma(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + let mut total = 0; + for chunk in buf.chunks_mut(DMA_MAX_TRANSFER_SIZE) { + total += self.read_dma_inner(chunk).await?; + } + + Ok(total) + } + + /// Internal helper to read a single chunk (max 0x7FFF bytes) using DMA. + async fn read_dma_inner(&mut self, buf: &mut [u8]) -> Result { + let len = buf.len(); + let peri_addr = self.info.regs.data().as_ptr() as *const u8; + + unsafe { + // Clean up channel state + self.rx_dma.disable_request(); + self.rx_dma.clear_done(); + self.rx_dma.clear_interrupt(); + + // Set DMA request source from instance type (type-safe) + self.rx_dma.set_request_source::(); + + // Configure TCD for peripheral-to-memory transfer + self.rx_dma + .setup_read_from_peripheral(peri_addr, buf, EnableInterrupt::Yes); + + // Enable UART RX DMA request + self.info.regs.baud().modify(|_, w| w.rdmae().enabled()); + + // Enable DMA channel request + self.rx_dma.enable_request(); + } + + // Create guard that will abort DMA if this future is dropped + let guard = RxDmaGuard::new(&self.rx_dma, self.info.regs); + + // Wait for completion asynchronously + core::future::poll_fn(|cx| { + self.rx_dma.waker().register(cx.waker()); + if self.rx_dma.is_done() { + core::task::Poll::Ready(()) + } else { + core::task::Poll::Pending + } + }) + .await; + + // Transfer completed successfully - clean up without aborting + guard.complete(); + + Ok(len) + } + + /// Blocking read (fallback when DMA is not needed) + pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<()> { + for byte in buf.iter_mut() { + loop { + if has_data(self.info.regs) { + *byte = (self.info.regs.data().read().bits() & 0xFF) as u8; + break; + } + check_and_clear_rx_errors(self.info.regs)?; + } + } + Ok(()) + } + + /// Set up a ring buffer for continuous DMA reception. + /// + /// This configures the DMA channel for circular operation, enabling continuous + /// reception of data without gaps. The DMA will continuously write received + /// bytes into the buffer, wrapping around when it reaches the end. + /// + /// This method encapsulates all the low-level setup: + /// - Configures the DMA request source for this LPUART instance + /// - Enables the RX DMA request in the LPUART peripheral + /// - Sets up the circular DMA transfer + /// - Enables the NVIC interrupt for async wakeups + /// + /// # Arguments + /// + /// * `buf` - Destination buffer for received data (power-of-2 size is ideal for efficiency) + /// + /// # Returns + /// + /// A [`RingBuffer`] that can be used to asynchronously read received data. + /// + /// # Example + /// + /// ```no_run + /// static mut RX_BUF: [u8; 64] = [0; 64]; + /// + /// let rx = LpuartRxDma::new(p.LPUART2, p.P2_3, p.DMA_CH0, config).unwrap(); + /// let ring_buf = unsafe { rx.setup_ring_buffer(&mut RX_BUF) }; + /// + /// // Read data as it arrives + /// let mut buf = [0u8; 16]; + /// let n = ring_buf.read(&mut buf).await.unwrap(); + /// ``` + /// + /// # Safety + /// + /// - The buffer must remain valid for the lifetime of the returned RingBuffer. + /// - Only one RingBuffer should exist per LPUART RX channel at a time. + /// - The caller must ensure the static buffer is not accessed elsewhere while + /// the ring buffer is active. + pub unsafe fn setup_ring_buffer<'b>(&self, buf: &'b mut [u8]) -> RingBuffer<'b, u8> { + // Get the peripheral data register address + let peri_addr = self.info.regs.data().as_ptr() as *const u8; + + // Configure DMA request source for this LPUART instance (type-safe) + self.rx_dma.set_request_source::(); + + // Enable RX DMA request in the LPUART peripheral + self.info.regs.baud().modify(|_, w| w.rdmae().enabled()); + + // Set up circular DMA transfer (this also enables NVIC interrupt) + self.rx_dma.setup_circular_read(peri_addr, buf) + } + + /// Enable the DMA channel request. + /// + /// Call this after `setup_ring_buffer()` to start continuous reception. + /// This is separated from setup to allow for any additional configuration + /// before starting the transfer. + pub unsafe fn enable_dma_request(&self) { + self.rx_dma.enable_request(); + } +} + +impl<'a, T: Instance, TxC: DmaChannelTrait, RxC: DmaChannelTrait> LpuartDma<'a, T, TxC, RxC> { + /// Create a new LPUART driver with DMA support for both TX and RX. + pub fn new( + _inner: Peri<'a, T>, + tx_pin: Peri<'a, impl TxPin>, + rx_pin: Peri<'a, impl RxPin>, + tx_dma_ch: Peri<'a, TxC>, + rx_dma_ch: Peri<'a, RxC>, + config: Config, + ) -> Result { + tx_pin.as_tx(); + rx_pin.as_rx(); + + let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); + let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); + + // Initialize LPUART with both TX and RX enabled, no flow control + Lpuart::::init::(true, true, false, false, config)?; + + Ok(Self { + tx: LpuartTxDma { + info: T::info(), + _tx_pin: tx_pin, + tx_dma: DmaChannel::new(tx_dma_ch), + _instance: core::marker::PhantomData, + }, + rx: LpuartRxDma { + info: T::info(), + _rx_pin: rx_pin, + rx_dma: DmaChannel::new(rx_dma_ch), + _instance: core::marker::PhantomData, + }, + }) + } + + /// Split into separate TX and RX drivers + pub fn split(self) -> (LpuartTxDma<'a, T, TxC>, LpuartRxDma<'a, T, RxC>) { + (self.tx, self.rx) + } + + /// Write data using DMA + pub async fn write_dma(&mut self, buf: &[u8]) -> Result { + self.tx.write_dma(buf).await + } + + /// Read data using DMA + pub async fn read_dma(&mut self, buf: &mut [u8]) -> Result { + self.rx.read_dma(buf).await + } +} + +// ============================================================================ +// EMBEDDED-IO-ASYNC TRAIT IMPLEMENTATIONS +// ============================================================================ + +impl embedded_io::ErrorType for LpuartTxDma<'_, T, C> { + type Error = Error; +} + +impl embedded_io::ErrorType for LpuartRxDma<'_, T, C> { + type Error = Error; +} + +impl embedded_io::ErrorType for LpuartDma<'_, T, TxC, RxC> { + type Error = Error; +} + +// ============================================================================ +// EMBEDDED-HAL 0.2 TRAIT IMPLEMENTATIONS +// ============================================================================ + +impl embedded_hal_02::serial::Read for LpuartRx<'_, Blocking> { + type Error = Error; + + fn read(&mut self) -> core::result::Result> { + let mut buf = [0; 1]; + match self.read(&mut buf) { + Ok(_) => Ok(buf[0]), + Err(Error::RxFifoEmpty) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } +} + +impl embedded_hal_02::serial::Write for LpuartTx<'_, Blocking> { + type Error = Error; + + fn write(&mut self, word: u8) -> core::result::Result<(), nb::Error> { + match self.write(&[word]) { + Ok(_) => Ok(()), + Err(Error::TxFifoFull) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } + + fn flush(&mut self) -> core::result::Result<(), nb::Error> { + match self.flush() { + Ok(_) => Ok(()), + Err(Error::TxBusy) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } +} + +impl embedded_hal_02::blocking::serial::Write for LpuartTx<'_, Blocking> { + type Error = Error; + + fn bwrite_all(&mut self, buffer: &[u8]) -> core::result::Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn bflush(&mut self) -> core::result::Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl embedded_hal_02::serial::Read for Lpuart<'_, Blocking> { + type Error = Error; + + fn read(&mut self) -> core::result::Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl embedded_hal_02::serial::Write for Lpuart<'_, Blocking> { + type Error = Error; + + fn write(&mut self, word: u8) -> core::result::Result<(), nb::Error> { + embedded_hal_02::serial::Write::write(&mut self.tx, word) + } + + fn flush(&mut self) -> core::result::Result<(), nb::Error> { + embedded_hal_02::serial::Write::flush(&mut self.tx) + } +} + +impl embedded_hal_02::blocking::serial::Write for Lpuart<'_, Blocking> { + type Error = Error; + + fn bwrite_all(&mut self, buffer: &[u8]) -> core::result::Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn bflush(&mut self) -> core::result::Result<(), Self::Error> { + self.blocking_flush() + } +} + +// ============================================================================ +// EMBEDDED-HAL-NB TRAIT IMPLEMENTATIONS +// ============================================================================ + +impl embedded_hal_nb::serial::Error for Error { + fn kind(&self) -> embedded_hal_nb::serial::ErrorKind { + match *self { + Self::Framing => embedded_hal_nb::serial::ErrorKind::FrameFormat, + Self::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun, + Self::Parity => embedded_hal_nb::serial::ErrorKind::Parity, + Self::Noise => embedded_hal_nb::serial::ErrorKind::Noise, + _ => embedded_hal_nb::serial::ErrorKind::Other, + } + } +} + +impl embedded_hal_nb::serial::ErrorType for LpuartRx<'_, Blocking> { + type Error = Error; +} + +impl embedded_hal_nb::serial::ErrorType for LpuartTx<'_, Blocking> { + type Error = Error; +} + +impl embedded_hal_nb::serial::ErrorType for Lpuart<'_, Blocking> { + type Error = Error; +} + +impl embedded_hal_nb::serial::Read for LpuartRx<'_, Blocking> { + fn read(&mut self) -> nb::Result { + let mut buf = [0; 1]; + match self.read(&mut buf) { + Ok(_) => Ok(buf[0]), + Err(Error::RxFifoEmpty) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } +} + +impl embedded_hal_nb::serial::Write for LpuartTx<'_, Blocking> { + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + match self.write(&[word]) { + Ok(_) => Ok(()), + Err(Error::TxFifoFull) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + match self.flush() { + Ok(_) => Ok(()), + Err(Error::TxBusy) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } +} + +impl embedded_hal_nb::serial::Read for Lpuart<'_, Blocking> { + fn read(&mut self) -> nb::Result { + embedded_hal_nb::serial::Read::read(&mut self.rx) + } +} + +impl embedded_hal_nb::serial::Write for Lpuart<'_, Blocking> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + embedded_hal_nb::serial::Write::write(&mut self.tx, char) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + embedded_hal_nb::serial::Write::flush(&mut self.tx) + } +} + +// ============================================================================ +// EMBEDDED-IO TRAIT IMPLEMENTATIONS +// ============================================================================ + +impl embedded_io::Error for Error { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } +} + +impl embedded_io::ErrorType for LpuartRx<'_, Blocking> { + type Error = Error; +} + +impl embedded_io::ErrorType for LpuartTx<'_, Blocking> { + type Error = Error; +} + +impl embedded_io::ErrorType for Lpuart<'_, Blocking> { + type Error = Error; +} + +impl embedded_io::Read for LpuartRx<'_, Blocking> { + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + self.blocking_read(buf).map(|_| buf.len()) + } +} + +impl embedded_io::Write for LpuartTx<'_, Blocking> { + fn write(&mut self, buf: &[u8]) -> core::result::Result { + self.blocking_write(buf).map(|_| buf.len()) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl embedded_io::Read for Lpuart<'_, Blocking> { + fn read(&mut self, buf: &mut [u8]) -> core::result::Result { + embedded_io::Read::read(&mut self.rx, buf) + } +} + +impl embedded_io::Write for Lpuart<'_, Blocking> { + fn write(&mut self, buf: &[u8]) -> core::result::Result { + embedded_io::Write::write(&mut self.tx, buf) + } + + fn flush(&mut self) -> core::result::Result<(), Self::Error> { + embedded_io::Write::flush(&mut self.tx) + } +} diff --git a/embassy-mcxa/src/ostimer.rs b/embassy-mcxa/src/ostimer.rs new file mode 100644 index 000000000..c51812e3d --- /dev/null +++ b/embassy-mcxa/src/ostimer.rs @@ -0,0 +1,745 @@ +//! # OSTIMER Driver with Robustness Features +//! +//! This module provides an async timer driver for the NXP MCXA276 OSTIMER peripheral +//! with protection against race conditions and timer rollover issues. +//! +//! ## Features +//! +//! - Async timing with embassy-time integration +//! - Gray code counter handling (42-bit counter) +//! - Interrupt-driven wakeups +//! - Configurable interrupt priority +//! - **Race condition protection**: Critical sections and atomic operations +//! - **Timer rollover handling**: Bounds checking and rollover prevention +//! +//! ## Clock Frequency Configuration +//! +//! The OSTIMER frequency depends on your system's clock configuration. You must provide +//! the actual frequency when calling `time_driver::init()`. +//! +//! ## Race Condition Protection +//! - Critical sections in interrupt handlers prevent concurrent access +//! - Atomic register operations with memory barriers +//! - Proper interrupt flag clearing and validation +//! +//! ## Timer Rollover Handling +//! - Bounds checking prevents scheduling beyond timer capacity +//! - Immediate wake for timestamps that would cause rollover issues +#![allow(dead_code)] + +use core::sync::atomic::{AtomicBool, Ordering}; + +use embassy_hal_internal::{Peri, PeripheralType}; + +use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; +use crate::clocks::{assert_reset, enable_and_reset, is_reset_released, release_reset, Gate, PoweredClock}; +use crate::interrupt::InterruptExt; +use crate::pac; + +// PAC defines the shared RegisterBlock under `ostimer0`. +type Regs = pac::ostimer0::RegisterBlock; + +// OSTIMER EVTIMER register layout constants +/// Total width of the EVTIMER counter in bits (42 bits total) +const EVTIMER_TOTAL_BITS: u32 = 42; +/// Width of the low part of EVTIMER (bits 31:0) +const EVTIMER_LO_BITS: u32 = 32; +/// Width of the high part of EVTIMER (bits 41:32) +const EVTIMER_HI_BITS: u32 = 10; +/// Bit position where high part starts in the combined 64-bit value +const EVTIMER_HI_SHIFT: u32 = 32; + +/// Bit mask for the high part of EVTIMER +const EVTIMER_HI_MASK: u16 = (1 << EVTIMER_HI_BITS) - 1; + +/// Maximum value for MATCH_L register (32-bit) +const MATCH_L_MAX: u32 = u32::MAX; +/// Maximum value for MATCH_H register (10-bit) +const MATCH_H_MAX: u16 = EVTIMER_HI_MASK; + +/// Bit mask for extracting the low 32 bits from a 64-bit value +const LOW_32_BIT_MASK: u64 = u32::MAX as u64; + +/// Gray code conversion bit shifts (most significant to least) +const GRAY_CONVERSION_SHIFTS: [u32; 6] = [32, 16, 8, 4, 2, 1]; + +/// Maximum timer value before rollover (2^42 - 1 ticks) +/// Actual rollover time depends on the configured clock frequency +const TIMER_MAX_VALUE: u64 = (1u64 << EVTIMER_TOTAL_BITS) - 1; + +/// Threshold for detecting timer rollover in comparisons (1 second at 1MHz) +const TIMER_ROLLOVER_THRESHOLD: u64 = 1_000_000; + +/// Common default interrupt priority for OSTIMER +const DEFAULT_INTERRUPT_PRIORITY: u8 = 3; + +// Global alarm state for interrupt handling +static ALARM_ACTIVE: AtomicBool = AtomicBool::new(false); +static mut ALARM_CALLBACK: Option = None; +static mut ALARM_FLAG: Option<*const AtomicBool> = None; +static mut ALARM_TARGET_TIME: u64 = 0; + +/// Number of tight spin iterations between elapsed time checks while waiting for MATCH writes to return to the idle (0) state. +const MATCH_WRITE_READY_SPINS: usize = 512; +/// Maximum time (in OSTIMER ticks) to wait for MATCH registers to become writable (~5 ms at 1 MHz). +const MATCH_WRITE_READY_TIMEOUT_TICKS: u64 = 5_000; +/// Short stabilization delay executed after toggling the MRCC reset line to let the OSTIMER bus interface settle. +const RESET_STABILIZE_SPINS: usize = 512; + +pub(super) fn wait_for_match_write_ready(r: &Regs) -> bool { + let start = now_ticks_read(); + let mut spin_budget = 0usize; + + loop { + if r.osevent_ctrl().read().match_wr_rdy().bit_is_clear() { + return true; + } + + cortex_m::asm::nop(); + spin_budget += 1; + + if spin_budget >= MATCH_WRITE_READY_SPINS { + spin_budget = 0; + + let elapsed = now_ticks_read().wrapping_sub(start); + if elapsed >= MATCH_WRITE_READY_TIMEOUT_TICKS { + return false; + } + } + } +} + +pub(super) fn wait_for_match_write_complete(r: &Regs) -> bool { + let start = now_ticks_read(); + let mut spin_budget = 0usize; + + loop { + if r.osevent_ctrl().read().match_wr_rdy().bit_is_clear() { + return true; + } + + cortex_m::asm::nop(); + spin_budget += 1; + + if spin_budget >= MATCH_WRITE_READY_SPINS { + spin_budget = 0; + + let elapsed = now_ticks_read().wrapping_sub(start); + if elapsed >= MATCH_WRITE_READY_TIMEOUT_TICKS { + return false; + } + } + } +} + +fn prime_match_registers(r: &Regs) { + // Disable the interrupt, clear any pending flag, then wait until the MATCH registers are writable. + r.osevent_ctrl() + .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); + + if wait_for_match_write_ready(r) { + r.match_l().write(|w| unsafe { w.match_value().bits(MATCH_L_MAX) }); + r.match_h().write(|w| unsafe { w.match_value().bits(MATCH_H_MAX) }); + let _ = wait_for_match_write_complete(r); + } +} + +/// Single-shot alarm functionality for OSTIMER +pub struct Alarm<'d> { + /// Whether the alarm is currently active + active: AtomicBool, + /// Callback to execute when alarm expires (optional) + callback: Option, + /// Flag that gets set when alarm expires (optional) + flag: Option<&'d AtomicBool>, + _phantom: core::marker::PhantomData<&'d mut ()>, +} + +impl<'d> Default for Alarm<'d> { + fn default() -> Self { + Self::new() + } +} + +impl<'d> Alarm<'d> { + /// Create a new alarm instance + pub fn new() -> Self { + Self { + active: AtomicBool::new(false), + callback: None, + flag: None, + _phantom: core::marker::PhantomData, + } + } + + /// Set a callback that will be executed when the alarm expires + /// Note: Due to interrupt handler constraints, callbacks must be static function pointers + pub fn with_callback(mut self, callback: fn()) -> Self { + self.callback = Some(callback); + self + } + + /// Set a flag that will be set to true when the alarm expires + pub fn with_flag(mut self, flag: &'d AtomicBool) -> Self { + self.flag = Some(flag); + self + } + + /// Check if the alarm is currently active + pub fn is_active(&self) -> bool { + self.active.load(Ordering::Acquire) + } + + /// Cancel the alarm if it's active + pub fn cancel(&self) { + self.active.store(false, Ordering::Release); + } +} + +/// Configuration for Ostimer::new() +#[derive(Copy, Clone)] +pub struct Config { + /// Initialize MATCH registers to their max values and mask/clear the interrupt flag. + pub init_match_max: bool, + pub power: PoweredClock, + pub source: OstimerClockSel, +} + +impl Default for Config { + fn default() -> Self { + Self { + init_match_max: true, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: OstimerClockSel::Clk1M, + } + } +} + +/// OSTIMER peripheral instance +pub struct Ostimer<'d, I: Instance> { + _inst: core::marker::PhantomData, + clock_frequency_hz: u64, + _phantom: core::marker::PhantomData<&'d mut ()>, +} + +impl<'d, I: Instance> Ostimer<'d, I> { + /// Construct OSTIMER handle. + /// Requires clocks for the instance to be enabled by the board before calling. + /// Does not enable NVIC or INTENA; use time_driver::init() for async operation. + pub fn new(_inst: Peri<'d, I>, cfg: Config) -> Self { + let clock_freq = unsafe { + enable_and_reset::(&OsTimerConfig { + power: cfg.power, + source: cfg.source, + }) + .expect("Enabling OsTimer clock should not fail") + }; + + assert!(clock_freq > 0, "OSTIMER frequency must be greater than 0"); + + if cfg.init_match_max { + let r: &Regs = unsafe { &*I::ptr() }; + // Mask INTENA, clear pending flag, and set MATCH to max so no spurious IRQ fires. + prime_match_registers(r); + } + + Self { + _inst: core::marker::PhantomData, + clock_frequency_hz: clock_freq as u64, + _phantom: core::marker::PhantomData, + } + } + + /// Get the configured clock frequency in Hz + pub fn clock_frequency_hz(&self) -> u64 { + self.clock_frequency_hz + } + + /// Read the current timer counter value in timer ticks + /// + /// # Returns + /// Current timer counter value as a 64-bit unsigned integer + pub fn now(&self) -> u64 { + now_ticks_read() + } + + /// Reset the timer counter to zero + /// + /// This performs a hardware reset of the OSTIMER peripheral, which will reset + /// the counter to zero and clear any pending interrupts. Note that this will + /// affect all timer operations including embassy-time. + /// + /// # Safety + /// This operation will reset the entire OSTIMER peripheral. Any active alarms + /// or time_driver operations will be disrupted. Use with caution. + pub fn reset(&self, _peripherals: &crate::pac::Peripherals) { + critical_section::with(|_| { + let r: &Regs = unsafe { &*I::ptr() }; + + // Mask the peripheral interrupt flag before we toggle the reset line so that + // no new NVIC activity races with the reset sequence. + r.osevent_ctrl() + .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); + + unsafe { + assert_reset::(); + + for _ in 0..RESET_STABILIZE_SPINS { + cortex_m::asm::nop(); + } + + release_reset::(); + + while !is_reset_released::() { + cortex_m::asm::nop(); + } + } + + for _ in 0..RESET_STABILIZE_SPINS { + cortex_m::asm::nop(); + } + + // Clear alarm bookkeeping before re-arming MATCH registers. + ALARM_ACTIVE.store(false, Ordering::Release); + unsafe { + ALARM_TARGET_TIME = 0; + ALARM_CALLBACK = None; + ALARM_FLAG = None; + } + + prime_match_registers(r); + }); + + // Ensure no stale OS_EVENT request remains pending after the reset sequence. + crate::interrupt::OS_EVENT.unpend(); + } + + /// Schedule a single-shot alarm to expire after the specified delay in microseconds + /// + /// # Parameters + /// * `alarm` - The alarm instance to schedule + /// * `delay_us` - Delay in microseconds from now + /// + /// # Returns + /// `true` if the alarm was scheduled successfully, `false` if it would exceed timer capacity + pub fn schedule_alarm_delay(&self, alarm: &Alarm, delay_us: u64) -> bool { + let delay_ticks = (delay_us * self.clock_frequency_hz) / 1_000_000; + let target_time = now_ticks_read() + delay_ticks; + self.schedule_alarm_at(alarm, target_time) + } + + /// Schedule a single-shot alarm to expire at the specified absolute time in timer ticks + /// + /// # Parameters + /// * `alarm` - The alarm instance to schedule + /// * `target_ticks` - Absolute time in timer ticks when the alarm should expire + /// + /// # Returns + /// `true` if the alarm was scheduled successfully, `false` if it would exceed timer capacity + pub fn schedule_alarm_at(&self, alarm: &Alarm, target_ticks: u64) -> bool { + let now = now_ticks_read(); + + // Check if target time is in the past + if target_ticks <= now { + // Execute callback immediately if alarm was supposed to be active + if alarm.active.load(Ordering::Acquire) { + alarm.active.store(false, Ordering::Release); + if let Some(callback) = alarm.callback { + callback(); + } + if let Some(flag) = &alarm.flag { + flag.store(true, Ordering::Release); + } + } + return true; + } + + // Check for timer rollover + let max_future = now + TIMER_MAX_VALUE; + if target_ticks > max_future { + return false; // Would exceed timer capacity + } + + // Program the timer + let r: &Regs = unsafe { &*I::ptr() }; + + critical_section::with(|_| { + // Disable interrupt and clear flag + r.osevent_ctrl() + .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); + + if !wait_for_match_write_ready(r) { + prime_match_registers(r); + + if !wait_for_match_write_ready(r) { + alarm.active.store(false, Ordering::Release); + ALARM_ACTIVE.store(false, Ordering::Release); + unsafe { + ALARM_TARGET_TIME = 0; + ALARM_CALLBACK = None; + ALARM_FLAG = None; + } + return false; + } + } + + // Mark alarm as active now that we know the MATCH registers are writable + alarm.active.store(true, Ordering::Release); + + // Set global alarm state for interrupt handler + ALARM_ACTIVE.store(true, Ordering::Release); + unsafe { + ALARM_TARGET_TIME = target_ticks; + ALARM_CALLBACK = alarm.callback; + ALARM_FLAG = alarm.flag.map(|f| f as *const AtomicBool); + } + + // Program MATCH registers (Gray-coded) + let gray = bin_to_gray(target_ticks); + let l = (gray & LOW_32_BIT_MASK) as u32; + let h = (((gray >> EVTIMER_HI_SHIFT) as u16) & EVTIMER_HI_MASK) as u16; + + r.match_l().write(|w| unsafe { w.match_value().bits(l) }); + r.match_h().write(|w| unsafe { w.match_value().bits(h) }); + + if !wait_for_match_write_complete(r) { + alarm.active.store(false, Ordering::Release); + ALARM_ACTIVE.store(false, Ordering::Release); + unsafe { + ALARM_TARGET_TIME = 0; + ALARM_CALLBACK = None; + ALARM_FLAG = None; + } + return false; + } + + let now_after_program = now_ticks_read(); + let intrflag_set = r.osevent_ctrl().read().ostimer_intrflag().bit_is_set(); + if now_after_program >= target_ticks && !intrflag_set { + alarm.active.store(false, Ordering::Release); + ALARM_ACTIVE.store(false, Ordering::Release); + unsafe { + ALARM_TARGET_TIME = 0; + ALARM_CALLBACK = None; + ALARM_FLAG = None; + } + return false; + } + + // Enable interrupt + r.osevent_ctrl().write(|w| w.ostimer_intena().set_bit()); + + true + }) + } + + /// Cancel any active alarm + pub fn cancel_alarm(&self, alarm: &Alarm) { + critical_section::with(|_| { + alarm.cancel(); + + // Clear global alarm state + ALARM_ACTIVE.store(false, Ordering::Release); + unsafe { ALARM_TARGET_TIME = 0 }; + + // Reset MATCH registers to maximum values to prevent spurious interrupts + let r: &Regs = unsafe { &*I::ptr() }; + prime_match_registers(r); + }); + } + + /// Check if an alarm has expired (call this from your interrupt handler) + /// Returns true if the alarm was active and has now expired + pub fn check_alarm_expired(&self, alarm: &Alarm) -> bool { + if alarm.active.load(Ordering::Acquire) { + alarm.active.store(false, Ordering::Release); + + // Execute callback + if let Some(callback) = alarm.callback { + callback(); + } + + // Set flag + if let Some(flag) = &alarm.flag { + flag.store(true, Ordering::Release); + } + + true + } else { + false + } + } +} + +/// Read current EVTIMER (Gray-coded) and convert to binary ticks. +#[inline(always)] +fn now_ticks_read() -> u64 { + let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; + + // Read high then low to minimize incoherent snapshots + let hi = (r.evtimerh().read().evtimer_count_value().bits() as u64) & (EVTIMER_HI_MASK as u64); + let lo = r.evtimerl().read().evtimer_count_value().bits() as u64; + // Combine and convert from Gray code to binary + let gray = lo | (hi << EVTIMER_HI_SHIFT); + gray_to_bin(gray) +} + +// Instance trait like other drivers, providing a PAC pointer for this OSTIMER instance +pub trait Instance: Gate + PeripheralType { + fn ptr() -> *const Regs; +} + +#[cfg(not(feature = "time"))] +impl Instance for crate::peripherals::OSTIMER0 { + #[inline(always)] + fn ptr() -> *const Regs { + pac::Ostimer0::ptr() + } +} + +#[inline(always)] +fn bin_to_gray(x: u64) -> u64 { + x ^ (x >> 1) +} + +#[inline(always)] +fn gray_to_bin(gray: u64) -> u64 { + // More efficient iterative conversion using predefined shifts + let mut bin = gray; + for &shift in &GRAY_CONVERSION_SHIFTS { + bin ^= bin >> shift; + } + bin +} + +#[cfg(feature = "time")] +pub mod time_driver { + use core::sync::atomic::Ordering; + use core::task::Waker; + + use embassy_sync::waitqueue::AtomicWaker; + use embassy_time_driver as etd; + + use super::{ + bin_to_gray, now_ticks_read, Regs, ALARM_ACTIVE, ALARM_CALLBACK, ALARM_FLAG, ALARM_TARGET_TIME, + EVTIMER_HI_MASK, EVTIMER_HI_SHIFT, LOW_32_BIT_MASK, + }; + use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; + use crate::clocks::{enable_and_reset, PoweredClock}; + use crate::pac; + + #[allow(non_camel_case_types)] + pub(crate) struct _OSTIMER0_TIME_DRIVER { + _x: (), + } + + // #[cfg(feature = "time")] + // impl_cc_gate!(_OSTIMER0_TIME_DRIVER, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig); + + impl crate::clocks::Gate for _OSTIMER0_TIME_DRIVER { + type MrccPeriphConfig = crate::clocks::periph_helpers::OsTimerConfig; + + #[inline] + unsafe fn enable_clock() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_cc1().modify(|_, w| w.ostimer0().enabled()); + } + + #[inline] + unsafe fn disable_clock() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_cc1().modify(|_r, w| w.ostimer0().disabled()); + } + + #[inline] + fn is_clock_enabled() -> bool { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_cc1().read().ostimer0().is_enabled() + } + + #[inline] + unsafe fn release_reset() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_rst1().modify(|_, w| w.ostimer0().enabled()); + } + + #[inline] + unsafe fn assert_reset() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_rst1().modify(|_, w| w.ostimer0().disabled()); + } + + #[inline] + fn is_reset_released() -> bool { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_rst1().read().ostimer0().is_enabled() + } + } + + pub struct Driver; + static TIMER_WAKER: AtomicWaker = AtomicWaker::new(); + + impl etd::Driver for Driver { + fn now(&self) -> u64 { + // Use the hardware counter (frequency configured in init) + super::now_ticks_read() + } + + fn schedule_wake(&self, timestamp: u64, waker: &Waker) { + let now = self.now(); + + // If timestamp is in the past or very close to now, wake immediately + if timestamp <= now { + waker.wake_by_ref(); + return; + } + + // Prevent scheduling too far in the future (beyond timer rollover) + // This prevents wraparound issues + let max_future = now + super::TIMER_MAX_VALUE; + if timestamp > max_future { + // For very long timeouts, wake immediately to avoid rollover issues + waker.wake_by_ref(); + return; + } + + // Register the waker first so any immediate wake below is observed by the executor. + TIMER_WAKER.register(waker); + + let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; + + critical_section::with(|_| { + // Mask INTENA and clear flag + r.osevent_ctrl() + .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); + + // Read back to ensure W1C took effect on hardware + let _ = r.osevent_ctrl().read().ostimer_intrflag().bit(); + + if !super::wait_for_match_write_ready(r) { + super::prime_match_registers(r); + + if !super::wait_for_match_write_ready(r) { + // If we can't safely program MATCH, wake immediately and leave INTENA masked. + waker.wake_by_ref(); + return; + } + } + + // Program MATCH (Gray-coded). Write low then high, then fence. + let gray = bin_to_gray(timestamp); + let l = (gray & LOW_32_BIT_MASK) as u32; + + let h = (((gray >> EVTIMER_HI_SHIFT) as u16) & EVTIMER_HI_MASK) as u16; + + r.match_l().write(|w| unsafe { w.match_value().bits(l) }); + r.match_h().write(|w| unsafe { w.match_value().bits(h) }); + + if !super::wait_for_match_write_complete(r) { + waker.wake_by_ref(); + return; + } + + let now_after_program = super::now_ticks_read(); + let intrflag_set = r.osevent_ctrl().read().ostimer_intrflag().bit_is_set(); + if now_after_program >= timestamp && !intrflag_set { + waker.wake_by_ref(); + return; + } + + // Enable peripheral interrupt + r.osevent_ctrl().write(|w| w.ostimer_intena().set_bit()); + }); + } + } + + /// Install the global embassy-time driver and configure NVIC priority for OS_EVENT. + /// + /// # Parameters + /// * `priority` - Interrupt priority for the OSTIMER interrupt + /// * `frequency_hz` - Actual OSTIMER clock frequency in Hz (stored for future use) + /// + /// Note: The frequency parameter is currently accepted for API compatibility. + /// The embassy_time_driver macro handles driver registration automatically. + pub fn init(priority: crate::interrupt::Priority, frequency_hz: u64) { + let _clock_freq = unsafe { + enable_and_reset::<_OSTIMER0_TIME_DRIVER>(&OsTimerConfig { + power: PoweredClock::AlwaysEnabled, + source: OstimerClockSel::Clk1M, + }) + .expect("Enabling OsTimer clock should not fail") + }; + + // Mask/clear at peripheral and set default MATCH + let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; + super::prime_match_registers(r); + + // Configure NVIC for timer operation + crate::interrupt::OS_EVENT.configure_for_timer(priority); + + // Note: The embassy_time_driver macro automatically registers the driver + // The frequency parameter is accepted for future compatibility + let _ = frequency_hz; // Suppress unused parameter warning + } + + // Export the global time driver expected by embassy-time + embassy_time_driver::time_driver_impl!(static DRIVER: Driver = Driver); + + /// To be called from the OS_EVENT IRQ. + pub fn on_interrupt() { + let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; + + // Critical section to prevent races with schedule_wake + critical_section::with(|_| { + // Check if interrupt is actually pending and handle it atomically + if r.osevent_ctrl().read().ostimer_intrflag().bit_is_set() { + // Clear flag and disable interrupt atomically + r.osevent_ctrl().write(|w| { + w.ostimer_intrflag() + .clear_bit_by_one() // Write-1-to-clear using safe helper + .ostimer_intena() + .clear_bit() + }); + + // Wake the waiting task + TIMER_WAKER.wake(); + + // Handle alarm callback if active and this interrupt is for the alarm + if ALARM_ACTIVE.load(Ordering::SeqCst) { + let current_time = now_ticks_read(); + let target_time = unsafe { ALARM_TARGET_TIME }; + + // Check if current time is close to alarm target time (within 1000 ticks for timing variations) + if current_time >= target_time && current_time <= target_time + 1000 { + ALARM_ACTIVE.store(false, Ordering::SeqCst); + unsafe { ALARM_TARGET_TIME = 0 }; + + // Execute callback if set + unsafe { + if let Some(callback) = ALARM_CALLBACK { + callback(); + } + } + + // Set flag if provided + unsafe { + if let Some(flag) = ALARM_FLAG { + (*flag).store(true, Ordering::SeqCst); + } + } + } + } + } + }); + } +} + +#[cfg(feature = "time")] +use crate::pac::interrupt; + +#[cfg(feature = "time")] +#[allow(non_snake_case)] +#[interrupt] +fn OS_EVENT() { + time_driver::on_interrupt() +} diff --git a/embassy-mcxa/src/pins.rs b/embassy-mcxa/src/pins.rs new file mode 100644 index 000000000..9adbe64c8 --- /dev/null +++ b/embassy-mcxa/src/pins.rs @@ -0,0 +1,33 @@ +//! Pin configuration helpers (separate from peripheral drivers). +use crate::pac; + +/// Configure pins for ADC usage. +/// +/// # Safety +/// +/// Must be called after PORT clocks are enabled. +pub unsafe fn configure_adc_pins() { + // P1_10 = ADC1_A8 + let port1 = &*pac::Port1::ptr(); + port1.pcr10().write(|w| { + w.ps() + .ps0() + .pe() + .pe0() + .sre() + .sre0() + .ode() + .ode0() + .dse() + .dse0() + .mux() + .mux0() + .ibe() + .ibe0() + .inv() + .inv0() + .lk() + .lk0() + }); + core::arch::asm!("dsb sy; isb sy"); +} diff --git a/embassy-mcxa/src/rtc.rs b/embassy-mcxa/src/rtc.rs new file mode 100644 index 000000000..b750a97ea --- /dev/null +++ b/embassy-mcxa/src/rtc.rs @@ -0,0 +1,299 @@ +//! RTC DateTime driver. +use core::sync::atomic::{AtomicBool, Ordering}; + +use embassy_hal_internal::{Peri, PeripheralType}; + +use crate::clocks::with_clocks; +use crate::pac; +use crate::pac::rtc0::cr::Um; + +type Regs = pac::rtc0::RegisterBlock; + +static ALARM_TRIGGERED: AtomicBool = AtomicBool::new(false); + +// Token-based instance pattern like embassy-imxrt +pub trait Instance: PeripheralType { + fn ptr() -> *const Regs; +} + +/// Token for RTC0 +pub type Rtc0 = crate::peripherals::RTC0; +impl Instance for crate::peripherals::RTC0 { + #[inline(always)] + fn ptr() -> *const Regs { + pac::Rtc0::ptr() + } +} + +const DAYS_IN_A_YEAR: u32 = 365; +const SECONDS_IN_A_DAY: u32 = 86400; +const SECONDS_IN_A_HOUR: u32 = 3600; +const SECONDS_IN_A_MINUTE: u32 = 60; +const YEAR_RANGE_START: u16 = 1970; + +#[derive(Debug, Clone, Copy)] +pub struct RtcDateTime { + pub year: u16, + pub month: u8, + pub day: u8, + pub hour: u8, + pub minute: u8, + pub second: u8, +} +#[derive(Copy, Clone)] +pub struct RtcConfig { + #[allow(dead_code)] + wakeup_select: bool, + update_mode: Um, + #[allow(dead_code)] + supervisor_access: bool, + compensation_interval: u8, + compensation_time: u8, +} + +#[derive(Copy, Clone)] +pub struct RtcInterruptEnable; +impl RtcInterruptEnable { + pub const RTC_TIME_INVALID_INTERRUPT_ENABLE: u32 = 1 << 0; + pub const RTC_TIME_OVERFLOW_INTERRUPT_ENABLE: u32 = 1 << 1; + pub const RTC_ALARM_INTERRUPT_ENABLE: u32 = 1 << 2; + pub const RTC_SECONDS_INTERRUPT_ENABLE: u32 = 1 << 4; +} + +pub fn convert_datetime_to_seconds(datetime: &RtcDateTime) -> u32 { + let month_days: [u16; 13] = [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; + + let mut seconds = (datetime.year as u32 - 1970) * DAYS_IN_A_YEAR; + seconds += (datetime.year as u32 / 4) - (1970 / 4); + seconds += month_days[datetime.month as usize] as u32; + seconds += datetime.day as u32 - 1; + + if (datetime.year & 3 == 0) && (datetime.month <= 2) { + seconds -= 1; + } + + seconds = seconds * SECONDS_IN_A_DAY + + (datetime.hour as u32 * SECONDS_IN_A_HOUR) + + (datetime.minute as u32 * SECONDS_IN_A_MINUTE) + + datetime.second as u32; + + seconds +} + +pub fn convert_seconds_to_datetime(seconds: u32) -> RtcDateTime { + let mut seconds_remaining = seconds; + let mut days = seconds_remaining / SECONDS_IN_A_DAY + 1; + seconds_remaining %= SECONDS_IN_A_DAY; + + let hour = (seconds_remaining / SECONDS_IN_A_HOUR) as u8; + seconds_remaining %= SECONDS_IN_A_HOUR; + let minute = (seconds_remaining / SECONDS_IN_A_MINUTE) as u8; + let second = (seconds_remaining % SECONDS_IN_A_MINUTE) as u8; + + let mut year = YEAR_RANGE_START; + let mut days_in_year = DAYS_IN_A_YEAR; + + while days > days_in_year { + days -= days_in_year; + year += 1; + + days_in_year = if year.is_multiple_of(4) { + DAYS_IN_A_YEAR + 1 + } else { + DAYS_IN_A_YEAR + }; + } + + let days_per_month = [ + 31, + if year.is_multiple_of(4) { 29 } else { 28 }, + 31, + 30, + 31, + 30, + 31, + 31, + 30, + 31, + 30, + 31, + ]; + + let mut month = 1; + for (m, month_days) in days_per_month.iter().enumerate() { + let m = m + 1; + if days <= *month_days as u32 { + month = m; + break; + } else { + days -= *month_days as u32; + } + } + + let day = days as u8; + + RtcDateTime { + year, + month: month as u8, + day, + hour, + minute, + second, + } +} + +pub fn get_default_config() -> RtcConfig { + RtcConfig { + wakeup_select: false, + update_mode: Um::Um0, + supervisor_access: false, + compensation_interval: 0, + compensation_time: 0, + } +} +/// Minimal RTC handle for a specific instance I (store the zero-sized token like embassy) +pub struct Rtc<'a, I: Instance> { + _inst: core::marker::PhantomData<&'a mut I>, +} + +impl<'a, I: Instance> Rtc<'a, I> { + /// initialize RTC + pub fn new(_inst: Peri<'a, I>, config: RtcConfig) -> Self { + let rtc = unsafe { &*I::ptr() }; + + // The RTC is NOT gated by the MRCC, but we DO need to make sure the 16k clock + // on the vsys domain is active + let clocks = with_clocks(|c| c.clk_16k_vsys.clone()); + match clocks { + None => panic!("Clocks have not been initialized"), + Some(None) => panic!("Clocks initialized, but clk_16k_vsys not active"), + Some(Some(_)) => {} + } + + /* RTC reset */ + rtc.cr().modify(|_, w| w.swr().set_bit()); + rtc.cr().modify(|_, w| w.swr().clear_bit()); + rtc.tsr().write(|w| unsafe { w.bits(1) }); + + rtc.cr().modify(|_, w| w.um().variant(config.update_mode)); + + rtc.tcr().modify(|_, w| unsafe { + w.cir() + .bits(config.compensation_interval) + .tcr() + .bits(config.compensation_time) + }); + + Self { + _inst: core::marker::PhantomData, + } + } + + pub fn set_datetime(&self, datetime: RtcDateTime) { + let rtc = unsafe { &*I::ptr() }; + let seconds = convert_datetime_to_seconds(&datetime); + rtc.tsr().write(|w| unsafe { w.bits(seconds) }); + } + + pub fn get_datetime(&self) -> RtcDateTime { + let rtc = unsafe { &*I::ptr() }; + let seconds = rtc.tsr().read().bits(); + convert_seconds_to_datetime(seconds) + } + + pub fn set_alarm(&self, alarm: RtcDateTime) { + let rtc = unsafe { &*I::ptr() }; + let seconds = convert_datetime_to_seconds(&alarm); + + rtc.tar().write(|w| unsafe { w.bits(0) }); + let mut timeout = 10000; + while rtc.tar().read().bits() != 0 && timeout > 0 { + timeout -= 1; + } + + rtc.tar().write(|w| unsafe { w.bits(seconds) }); + + let mut timeout = 10000; + while rtc.tar().read().bits() != seconds && timeout > 0 { + timeout -= 1; + } + } + + pub fn get_alarm(&self) -> RtcDateTime { + let rtc = unsafe { &*I::ptr() }; + let alarm_seconds = rtc.tar().read().bits(); + convert_seconds_to_datetime(alarm_seconds) + } + + pub fn start(&self) { + let rtc = unsafe { &*I::ptr() }; + rtc.sr().modify(|_, w| w.tce().set_bit()); + } + + pub fn stop(&self) { + let rtc = unsafe { &*I::ptr() }; + rtc.sr().modify(|_, w| w.tce().clear_bit()); + } + + pub fn set_interrupt(&self, mask: u32) { + let rtc = unsafe { &*I::ptr() }; + + if (RtcInterruptEnable::RTC_TIME_INVALID_INTERRUPT_ENABLE & mask) != 0 { + rtc.ier().modify(|_, w| w.tiie().tiie_1()); + } + if (RtcInterruptEnable::RTC_TIME_OVERFLOW_INTERRUPT_ENABLE & mask) != 0 { + rtc.ier().modify(|_, w| w.toie().toie_1()); + } + if (RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE & mask) != 0 { + rtc.ier().modify(|_, w| w.taie().taie_1()); + } + if (RtcInterruptEnable::RTC_SECONDS_INTERRUPT_ENABLE & mask) != 0 { + rtc.ier().modify(|_, w| w.tsie().tsie_1()); + } + + ALARM_TRIGGERED.store(false, Ordering::SeqCst); + } + + pub fn disable_interrupt(&self, mask: u32) { + let rtc = unsafe { &*I::ptr() }; + + if (RtcInterruptEnable::RTC_TIME_INVALID_INTERRUPT_ENABLE & mask) != 0 { + rtc.ier().modify(|_, w| w.tiie().tiie_0()); + } + if (RtcInterruptEnable::RTC_TIME_OVERFLOW_INTERRUPT_ENABLE & mask) != 0 { + rtc.ier().modify(|_, w| w.toie().toie_0()); + } + if (RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE & mask) != 0 { + rtc.ier().modify(|_, w| w.taie().taie_0()); + } + if (RtcInterruptEnable::RTC_SECONDS_INTERRUPT_ENABLE & mask) != 0 { + rtc.ier().modify(|_, w| w.tsie().tsie_0()); + } + } + + pub fn clear_alarm_flag(&self) { + let rtc = unsafe { &*I::ptr() }; + rtc.ier().modify(|_, w| w.taie().clear_bit()); + } + + pub fn is_alarm_triggered(&self) -> bool { + ALARM_TRIGGERED.load(Ordering::Relaxed) + } +} + +pub fn on_interrupt() { + let rtc = unsafe { &*pac::Rtc0::ptr() }; + // Check if this is actually a time alarm interrupt + let sr = rtc.sr().read(); + if sr.taf().bit_is_set() { + rtc.ier().modify(|_, w| w.taie().clear_bit()); + ALARM_TRIGGERED.store(true, Ordering::SeqCst); + } +} + +pub struct RtcHandler; +impl crate::interrupt::typelevel::Handler for RtcHandler { + unsafe fn on_interrupt() { + on_interrupt(); + } +} diff --git a/embassy-mcxa/supply-chain/README.md b/embassy-mcxa/supply-chain/README.md new file mode 100644 index 000000000..12f8777b0 --- /dev/null +++ b/embassy-mcxa/supply-chain/README.md @@ -0,0 +1,149 @@ +# Working with cargo vet + +## Introduction + +`cargo vet` is a tool to help ensure that third-party Rust dependencies have been audited by a trusted entity. +It matches all dependencies against a set of audits conducted by the authors of the project or entities they trust. +To learn more, visit [mozilla/cargo-vet](https://github.com/mozilla/cargo-vet) + +--- + +## Adding a new dependency + +When updating or adding a new dependency, we need to ensure it's audited before being merged into main. +For our repositories, we have designated experts who are responsible for vetting any new dependencies being added to their repository. +_It is the shared responsibility of the developer creating the PR and the auditors to conduct a successful audit._ +Follow the process below to ensure compliance: + +### For Developers +1. **Respond to `cargo vet` failures**: + - If your PR fails the `cargo vet` step, the cargo-vet workflow will add a comment to the PR with a template questionnaire + - Copy the questionnaire, fill it out and paste it as a new comment on the PR. This greatly helps the auditors get some context of the changes requiring the new dependencies + +2. **Engage with auditors**: + - Respond to any questions that the auditors might have regarding the need of any new dependencies + +3. **Rebase and verify**: + - At their discretion, auditors will check in their audits into either [rust-crate-audits](https://github.com/OpenDevicePartnership/rust-crate-audits) or into the same repository + - Once the new audits have been merged, rebase your branch on main and verify it passes `cargo vet` + ```bash + git fetch upstream + git rebase upstream/main + cargo vet + ``` + +4. **Update PR**: + - If the audits were checked into rust-crate-audits, they will show up in _imports.lock_ on running `cargo vet`. In this case add the updated _imports.lock_ to your PR + - If the audits were checked into the same repository, they will be present in _audits.toml_ after rebase and you can simply force push to your PR after rebase + ```bash + git push -f + ``` + +5. **Check PR status**: + - The existing PR comment from the previous failure will be updated with a success message once the check passes + +### For Auditors + +1. **Review the questionnaire**: + - Check the filled questionnaire on the PR once the developer responds to the `cargo vet` failure + - Respond to the developer comment in case more information is needed + +2. **Audit new dependencies**: + - Inspect the `cargo vet` failures using your preferred method + - Use [gh pr checkout](https://cli.github.com/manual/gh_pr_checkout) to checkout the PR and run `cargo vet --locked` + - Use [Github Pull Requests for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github) to checkout the PR and run `cargo vet --locked` + - For more suggestions: [Checking out pull requests locally](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally) + +3. **Follow `cargo vet` recommendations**: + - Follow the recommendations of the `cargo vet` command output, either `cargo vet diff` for version update or `cargo vet inspect` for new dependencies + +4. **Record audits**: + - Use `cargo vet certify` to add new audits to _audits.toml_ + - Verify all dependencies pass using `cargo vet` + +5. **Decide audit location**: + - **Shared audits**: New audits should ideally be shared across ODP repositories to reduce the overhead of multiple audits for the same dependencies. To facilitate this, it's recommended to cut and paste the new audits and submit as a separate PR to the _audits.toml_ in [rust-crate-audits](https://github.com/OpenDevicePartnership/rust-crate-audits) + - If due to business reasons, the audits are not to be shared across repositories, copy the updated _audits.toml_ to a new branch off main in the same repository and submit the PR to update the audits + +6. **Communicate successful audit**: + - Communicate to the PR developer via a PR comment so they can update the PR and get `cargo vet` to pass + +--- + +## Audit criteria +`cargo vet` comes pre-equipped with two built-in criteria but supports adding new criteria to suit our needs. +As defined [here](https://mozilla.github.io/cargo-vet/built-in-criteria.html), the default criteria are: + +- **safe-to-run** + This crate can be compiled, run, and tested on a local workstation or in + controlled automation without surprising consequences, such as: + * Reading or writing data from sensitive or unrelated parts of the filesystem. + * Installing software or reconfiguring the device. + * Connecting to untrusted network endpoints. + * Misuse of system resources (e.g. cryptocurrency mining). + +- **safe-to-deploy** + This crate will not introduce a serious security vulnerability to production + software exposed to untrusted input. + + Auditors are not required to perform a full logic review of the entire crate. + Rather, they must review enough to fully reason about the behavior of all unsafe + blocks and usage of powerful imports. For any reasonable usage of the crate in + real-world software, an attacker must not be able to manipulate the runtime + behavior of these sections in an exploitable or surprising way. + + Ideally, all unsafe code is fully sound, and ambient capabilities (e.g. + filesystem access) are hardened against manipulation and consistent with the + advertised behavior of the crate. However, some discretion is permitted. In such + cases, the nature of the discretion should be recorded in the `notes` field of + the audit record. + + For crates which generate deployed code (e.g. build dependencies or procedural + macros), reasonable usage of the crate should output code which meets the above + criteria. + + **Note: `safe-to-deploy` implies `safe-to-run`** + +--- + +## Conducting an audit + +When performing an audit for a new or updated dependency, auditors may consider the following criteria to ensure the safety, reliability, and suitability of the crate for use in our projects: + +- **Security**: + - Review the crate for known vulnerabilities or security advisories. + - Check for unsafe code usage and ensure it is justified and well-documented. + - Evaluate the crate’s history of security issues and responsiveness to reported problems. + +- **Maintenance and Activity**: + - Assess the frequency of updates and the responsiveness of maintainers to issues and pull requests. + - Prefer crates that are actively maintained and have a healthy contributor base. + +- **License Compliance**: + - Verify that the crate’s license is compatible with our project’s licensing requirements. + +- **Community Trust and Adoption**: + - Consider the crate’s adoption in the wider Rust ecosystem. + - Prefer crates that are widely used and trusted by the community. + +- **Functionality and Suitability**: + - Confirm that the crate provides the required functionality without unnecessary features or bloat. + - Evaluate whether the crate’s API is stable and unlikely to introduce breaking changes unexpectedly. + +- **Audit Trail**: + - Record the audit decision, including any concerns, mitigations, or recommendations for future updates. + - If exemptions are granted, document the justification and any follow-up actions required. + +--- + +## Tips for using `cargo vet`: + +- **Update _imports.lock_**: + - Import trusted third party audits to reduce the number of new audits to be performed. Running `cargo vet` without `--locked` fetches new imports and updates _imports.lock_ with any audits that are helpful for our project. + +- **Add exemptions**: + - If an audit cannot be performed for some dependency due to time sensitivity or business justified reasons, use `cargo vet add-exemption ` to add the dependency to exemptions in _config.toml_ + - To add all remaining audits to exemptions at once, use `cargo vet regenerate exemptions` + +- **Prune unnecessary entries**: + - Remove unnecessary exemptions and imports using `cargo vet prune` \ No newline at end of file diff --git a/embassy-mcxa/supply-chain/audits.toml b/embassy-mcxa/supply-chain/audits.toml new file mode 100644 index 000000000..871109648 --- /dev/null +++ b/embassy-mcxa/supply-chain/audits.toml @@ -0,0 +1,349 @@ + +# cargo-vet audits file + +[[audits.autocfg]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.5.0" + +[[audits.cc]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.2.47" + +[[audits.cfg-if]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.0.4" + +[[audits.cordyceps]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "0.3.4" + +[[audits.darling]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "0.20.11" + +[[audits.darling_core]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "0.20.11" + +[[audits.darling_macro]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "0.20.11" + +[[audits.defmt-rtt]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.0.0" +notes = "defmt-rtt is used for all our logging purposes. Version 1.0.0 merely stabilizes what was already available previously." + +[[audits.defmt-rtt]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +delta = "1.0.0 -> 1.1.0" + +[[audits.document-features]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "0.2.12" + +[[audits.document-features]] +who = "Felipe Balbi " +criteria = "safe-to-run" +version = "0.2.12" + +[[audits.embassy-executor]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "0.9.1" + +[[audits.embassy-executor-macros]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "0.7.0" + +[[audits.embassy-executor-timer-queue]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "0.1.0" + +[[audits.embassy-executor-timer-queue]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "0.1.0" + +[[audits.embassy-time-queue-utils]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "0.3.0" + +[[audits.find-msvc-tools]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "0.1.5" + +[[audits.generator]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "0.8.7" + +[[audits.ident_case]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.0.1" + +[[audits.litrs]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.0.0" + +[[audits.maitake-sync]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "0.2.2" + +[[audits.mutex-traits]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.0.1" + +[[audits.mycelium-bitfield]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "0.1.5" + +[[audits.once_cell]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.20.1" + +[[audits.panic-probe]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.0.0" + +[[audits.pin-project]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.1.10" + +[[audits.pin-project-internal]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.1.10" + +[[audits.portable-atomic]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.11.1" + +[[audits.proc-macro2]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.0.103" + +[[audits.quote]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.0.42" + +[[audits.stable_deref_trait]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.2.1" + +[[audits.static_cell]] +who = "jerrysxie " +criteria = "safe-to-run" +delta = "2.1.0 -> 2.1.1" + +[[audits.syn]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "2.0.110" + +[[audits.syn]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +delta = "2.0.100 -> 2.0.109" + +[[audits.thiserror]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "2.0.17" + +[[audits.thiserror-impl]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "2.0.17" + +[[audits.unicode-ident]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "1.0.22" + +[[audits.valuable]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +version = "0.1.1" + +[[trusted.aho-corasick]] +criteria = "safe-to-deploy" +user-id = 189 # Andrew Gallant (BurntSushi) +start = "2019-03-28" +end = "2026-11-26" + +[[trusted.cc]] +criteria = "safe-to-deploy" +user-id = 55123 # rust-lang-owner +start = "2022-10-29" +end = "2026-11-26" + +[[trusted.find-msvc-tools]] +criteria = "safe-to-deploy" +user-id = 539 +start = "2025-08-29" +end = "2026-11-26" + +[[trusted.libc]] +criteria = "safe-to-deploy" +user-id = 55123 # rust-lang-owner +start = "2024-08-15" +end = "2026-11-26" + +[[trusted.loom]] +criteria = "safe-to-deploy" +user-id = 6741 # Alice Ryhl (Darksonn) +start = "2021-04-12" +end = "2026-11-26" + +[[trusted.memchr]] +criteria = "safe-to-deploy" +user-id = 189 # Andrew Gallant (BurntSushi) +start = "2019-07-07" +end = "2026-11-26" + +[[trusted.paste]] +criteria = "safe-to-deploy" +user-id = 3618 # David Tolnay (dtolnay) +start = "2019-03-19" +end = "2026-11-26" + +[[trusted.regex-automata]] +criteria = "safe-to-deploy" +user-id = 189 # Andrew Gallant (BurntSushi) +start = "2019-02-25" +end = "2026-11-26" + +[[trusted.regex-syntax]] +criteria = "safe-to-deploy" +user-id = 189 # Andrew Gallant (BurntSushi) +start = "2019-03-30" +end = "2026-11-26" + +[[trusted.rustversion]] +criteria = "safe-to-deploy" +user-id = 3618 # David Tolnay (dtolnay) +start = "2019-07-08" +end = "2026-11-26" + +[[trusted.scoped-tls]] +criteria = "safe-to-deploy" +user-id = 1 # Alex Crichton (alexcrichton) +start = "2019-02-26" +end = "2026-11-26" + +[[trusted.thread_local]] +criteria = "safe-to-deploy" +user-id = 2915 # Amanieu d'Antras (Amanieu) +start = "2019-09-07" +end = "2026-11-26" + +[[trusted.tracing-subscriber]] +criteria = "safe-to-deploy" +user-id = 10 # Carl Lerche (carllerche) +start = "2025-08-29" +end = "2026-11-26" + +[[trusted.valuable]] +criteria = "safe-to-deploy" +user-id = 10 # Carl Lerche (carllerche) +start = "2022-01-03" +end = "2026-11-26" + +[[trusted.windows]] +criteria = "safe-to-deploy" +user-id = 64539 # Kenny Kerr (kennykerr) +start = "2021-01-15" +end = "2026-11-26" + +[[trusted.windows-collections]] +criteria = "safe-to-deploy" +user-id = 64539 # Kenny Kerr (kennykerr) +start = "2025-02-06" +end = "2026-11-26" + +[[trusted.windows-core]] +criteria = "safe-to-deploy" +user-id = 64539 # Kenny Kerr (kennykerr) +start = "2021-11-15" +end = "2026-11-26" + +[[trusted.windows-future]] +criteria = "safe-to-deploy" +user-id = 64539 # Kenny Kerr (kennykerr) +start = "2025-02-10" +end = "2026-11-26" + +[[trusted.windows-implement]] +criteria = "safe-to-deploy" +user-id = 64539 # Kenny Kerr (kennykerr) +start = "2022-01-27" +end = "2026-11-26" + +[[trusted.windows-interface]] +criteria = "safe-to-deploy" +user-id = 64539 # Kenny Kerr (kennykerr) +start = "2022-02-18" +end = "2026-11-26" + +[[trusted.windows-link]] +criteria = "safe-to-deploy" +user-id = 64539 # Kenny Kerr (kennykerr) +start = "2024-07-17" +end = "2026-11-26" + +[[trusted.windows-numerics]] +criteria = "safe-to-deploy" +user-id = 64539 # Kenny Kerr (kennykerr) +start = "2023-05-15" +end = "2026-11-26" + +[[trusted.windows-result]] +criteria = "safe-to-deploy" +user-id = 64539 # Kenny Kerr (kennykerr) +start = "2024-02-02" +end = "2026-11-26" + +[[trusted.windows-strings]] +criteria = "safe-to-deploy" +user-id = 64539 # Kenny Kerr (kennykerr) +start = "2024-02-02" +end = "2026-11-26" + +[[trusted.windows-sys]] +criteria = "safe-to-deploy" +user-id = 64539 # Kenny Kerr (kennykerr) +start = "2021-11-15" +end = "2026-11-26" + +[[trusted.windows-threading]] +criteria = "safe-to-deploy" +user-id = 64539 # Kenny Kerr (kennykerr) +start = "2025-04-29" +end = "2026-11-26" diff --git a/embassy-mcxa/supply-chain/config.toml b/embassy-mcxa/supply-chain/config.toml new file mode 100644 index 000000000..5682db9ea --- /dev/null +++ b/embassy-mcxa/supply-chain/config.toml @@ -0,0 +1,141 @@ + +# cargo-vet config file + +[cargo-vet] +version = "0.10" + +[imports.OpenDevicePartnership] +url = "https://raw.githubusercontent.com/OpenDevicePartnership/rust-crate-audits/main/audits.toml" + +[imports.bytecode-alliance] +url = "https://raw.githubusercontent.com/bytecodealliance/wasmtime/main/supply-chain/audits.toml" + +[imports.google] +url = "https://raw.githubusercontent.com/google/rust-crate-audits/main/audits.toml" + +[imports.mozilla] +url = "https://raw.githubusercontent.com/mozilla/supply-chain/main/audits.toml" + +[[exemptions.bare-metal]] +version = "0.2.5" +criteria = "safe-to-deploy" + +[[exemptions.bitfield]] +version = "0.13.2" +criteria = "safe-to-deploy" + +[[exemptions.cortex-m]] +version = "0.7.7" +criteria = "safe-to-deploy" + +[[exemptions.cortex-m-rt]] +version = "0.7.5" +criteria = "safe-to-deploy" + +[[exemptions.cortex-m-rt-macros]] +version = "0.7.5" +criteria = "safe-to-deploy" + +[[exemptions.critical-section]] +version = "1.2.0" +criteria = "safe-to-deploy" + +[[exemptions.defmt]] +version = "1.0.1" +criteria = "safe-to-deploy" + +[[exemptions.defmt-macros]] +version = "1.0.1" +criteria = "safe-to-deploy" + +[[exemptions.defmt-parser]] +version = "1.0.0" +criteria = "safe-to-deploy" + +[[exemptions.embassy-embedded-hal]] +version = "0.5.0" +criteria = "safe-to-deploy" + +[[exemptions.embassy-futures]] +version = "0.1.2" +criteria = "safe-to-deploy" + +[[exemptions.embassy-hal-internal]] +version = "0.3.0" +criteria = "safe-to-deploy" + +[[exemptions.embassy-sync]] +version = "0.7.2" +criteria = "safe-to-deploy" + +[[exemptions.embassy-time]] +version = "0.5.0" +criteria = "safe-to-deploy" + +[[exemptions.embassy-time-driver]] +version = "0.2.1" +criteria = "safe-to-deploy" + +[[exemptions.embedded-hal]] +version = "0.2.7" +criteria = "safe-to-deploy" + +[[exemptions.embedded-hal]] +version = "1.0.0" +criteria = "safe-to-deploy" + +[[exemptions.embedded-hal-async]] +version = "1.0.0" +criteria = "safe-to-deploy" + +[[exemptions.embedded-hal-nb]] +version = "1.0.0" +criteria = "safe-to-deploy" + +[[exemptions.embedded-io-async]] +version = "0.6.1" +criteria = "safe-to-deploy" + +[[exemptions.embedded-storage]] +version = "0.3.1" +criteria = "safe-to-deploy" + +[[exemptions.embedded-storage-async]] +version = "0.4.1" +criteria = "safe-to-deploy" + +[[exemptions.hash32]] +version = "0.3.1" +criteria = "safe-to-deploy" + +[[exemptions.heapless]] +version = "0.8.0" +criteria = "safe-to-deploy" + +[[exemptions.proc-macro-error-attr2]] +version = "2.0.0" +criteria = "safe-to-deploy" + +[[exemptions.proc-macro-error2]] +version = "2.0.1" +criteria = "safe-to-deploy" + +[[exemptions.rustc_version]] +version = "0.2.3" +criteria = "safe-to-deploy" + +[[exemptions.semver]] +version = "0.9.0" +criteria = "safe-to-deploy" + +[[exemptions.semver-parser]] +version = "0.7.0" +criteria = "safe-to-deploy" + +[[exemptions.vcell]] +version = "0.1.3" +criteria = "safe-to-deploy" + +[[exemptions.volatile-register]] +version = "0.2.2" +criteria = "safe-to-deploy" diff --git a/embassy-mcxa/supply-chain/imports.lock b/embassy-mcxa/supply-chain/imports.lock new file mode 100644 index 000000000..dbd1235b0 --- /dev/null +++ b/embassy-mcxa/supply-chain/imports.lock @@ -0,0 +1,523 @@ + +# cargo-vet imports lock + +[[publisher.aho-corasick]] +version = "1.1.4" +when = "2025-10-28" +user-id = 189 +user-login = "BurntSushi" +user-name = "Andrew Gallant" + +[[publisher.libc]] +version = "0.2.177" +when = "2025-10-09" +user-id = 55123 +user-login = "rust-lang-owner" + +[[publisher.loom]] +version = "0.7.2" +when = "2024-04-23" +user-id = 6741 +user-login = "Darksonn" +user-name = "Alice Ryhl" + +[[publisher.memchr]] +version = "2.7.6" +when = "2025-09-25" +user-id = 189 +user-login = "BurntSushi" +user-name = "Andrew Gallant" + +[[publisher.paste]] +version = "1.0.15" +when = "2024-05-07" +user-id = 3618 +user-login = "dtolnay" +user-name = "David Tolnay" + +[[publisher.regex-automata]] +version = "0.4.13" +when = "2025-10-13" +user-id = 189 +user-login = "BurntSushi" +user-name = "Andrew Gallant" + +[[publisher.regex-syntax]] +version = "0.8.8" +when = "2025-10-13" +user-id = 189 +user-login = "BurntSushi" +user-name = "Andrew Gallant" + +[[publisher.rustversion]] +version = "1.0.22" +when = "2025-08-08" +user-id = 3618 +user-login = "dtolnay" +user-name = "David Tolnay" + +[[publisher.scoped-tls]] +version = "1.0.1" +when = "2022-10-31" +user-id = 1 +user-login = "alexcrichton" +user-name = "Alex Crichton" + +[[publisher.thread_local]] +version = "1.1.9" +when = "2025-06-12" +user-id = 2915 +user-login = "Amanieu" +user-name = "Amanieu d'Antras" + +[[publisher.tracing-subscriber]] +version = "0.3.20" +when = "2025-08-29" +user-id = 10 +user-login = "carllerche" +user-name = "Carl Lerche" + +[[publisher.windows]] +version = "0.61.3" +when = "2025-06-12" +user-id = 64539 +user-login = "kennykerr" +user-name = "Kenny Kerr" + +[[publisher.windows-collections]] +version = "0.2.0" +when = "2025-03-18" +user-id = 64539 +user-login = "kennykerr" +user-name = "Kenny Kerr" + +[[publisher.windows-core]] +version = "0.61.2" +when = "2025-05-19" +user-id = 64539 +user-login = "kennykerr" +user-name = "Kenny Kerr" + +[[publisher.windows-future]] +version = "0.2.1" +when = "2025-05-15" +user-id = 64539 +user-login = "kennykerr" +user-name = "Kenny Kerr" + +[[publisher.windows-implement]] +version = "0.60.2" +when = "2025-10-06" +user-id = 64539 +user-login = "kennykerr" +user-name = "Kenny Kerr" + +[[publisher.windows-interface]] +version = "0.59.3" +when = "2025-10-06" +user-id = 64539 +user-login = "kennykerr" +user-name = "Kenny Kerr" + +[[publisher.windows-link]] +version = "0.1.3" +when = "2025-06-12" +user-id = 64539 +user-login = "kennykerr" +user-name = "Kenny Kerr" + +[[publisher.windows-link]] +version = "0.2.1" +when = "2025-10-06" +user-id = 64539 +user-login = "kennykerr" +user-name = "Kenny Kerr" + +[[publisher.windows-numerics]] +version = "0.2.0" +when = "2025-03-18" +user-id = 64539 +user-login = "kennykerr" +user-name = "Kenny Kerr" + +[[publisher.windows-result]] +version = "0.3.4" +when = "2025-05-19" +user-id = 64539 +user-login = "kennykerr" +user-name = "Kenny Kerr" + +[[publisher.windows-strings]] +version = "0.4.2" +when = "2025-05-19" +user-id = 64539 +user-login = "kennykerr" +user-name = "Kenny Kerr" + +[[publisher.windows-sys]] +version = "0.61.2" +when = "2025-10-06" +user-id = 64539 +user-login = "kennykerr" +user-name = "Kenny Kerr" + +[[publisher.windows-threading]] +version = "0.1.0" +when = "2025-05-15" +user-id = 64539 +user-login = "kennykerr" +user-name = "Kenny Kerr" + +[audits.OpenDevicePartnership.audits] + +[[audits.bytecode-alliance.audits.embedded-io]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +version = "0.4.0" +notes = "No `unsafe` code and only uses `std` in ways one would expect the crate to do so." + +[[audits.bytecode-alliance.audits.embedded-io]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.4.0 -> 0.6.1" +notes = "Major updates, but almost all safe code. Lots of pruning/deletions, nothing out of the ordrinary." + +[[audits.bytecode-alliance.audits.futures-core]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +version = "0.3.27" +notes = "Unsafe used to implement a concurrency primitive AtomicWaker. Well-commented and not obviously incorrect. Like my other audits of these concurrency primitives inside the futures family, I couldn't certify that it is correct without formal methods, but that is out of scope for this vetting." + +[[audits.bytecode-alliance.audits.futures-core]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +delta = "0.3.28 -> 0.3.31" + +[[audits.bytecode-alliance.audits.futures-sink]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +version = "0.3.27" + +[[audits.bytecode-alliance.audits.futures-sink]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +delta = "0.3.28 -> 0.3.31" + +[[audits.bytecode-alliance.audits.log]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.4.22 -> 0.4.27" +notes = "Lots of minor updates to macros and such, nothing touching `unsafe`" + +[[audits.bytecode-alliance.audits.log]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.4.27 -> 0.4.28" +notes = "Minor doc updates and lots new tests, nothing out of the ordinary." + +[[audits.bytecode-alliance.audits.matchers]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +version = "0.1.0" + +[[audits.bytecode-alliance.audits.matchers]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.1.0 -> 0.2.0" +notes = "Some unsafe code, but not more than before. Nothing awry." + +[[audits.bytecode-alliance.audits.nu-ansi-term]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +version = "0.46.0" +notes = "one use of unsafe to call windows specific api to get console handle." + +[[audits.bytecode-alliance.audits.nu-ansi-term]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.46.0 -> 0.50.1" +notes = "Lots of stylistic/rust-related chanegs, plus new features, but nothing out of the ordrinary." + +[[audits.bytecode-alliance.audits.nu-ansi-term]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.50.1 -> 0.50.3" +notes = "CI changes, Rust changes, nothing out of the ordinary." + +[[audits.bytecode-alliance.audits.sharded-slab]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +version = "0.1.4" +notes = "I always really enjoy reading eliza's code, she left perfect comments at every use of unsafe." + +[[audits.bytecode-alliance.audits.shlex]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +version = "1.1.0" +notes = "Only minor `unsafe` code blocks which look valid and otherwise does what it says on the tin." + +[[audits.bytecode-alliance.audits.tracing-attributes]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.1.28 -> 0.1.30" +notes = "Few code changes, a pretty minor update." + +[[audits.bytecode-alliance.audits.tracing-core]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.1.33 -> 0.1.34" +notes = "Mostly just an update with Rust stylistic conventions changing. Nothing awry." + +[[audits.bytecode-alliance.audits.tracing-log]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +version = "0.1.3" +notes = """ +This is a standard adapter between the `log` ecosystem and the `tracing` +ecosystem. There's one `unsafe` block in this crate and it's well-scoped. +""" + +[[audits.bytecode-alliance.audits.tracing-log]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +delta = "0.1.3 -> 0.2.0" +notes = "Nothing out of the ordinary, a typical major version update and nothing awry." + +[[audits.google.audits.bitflags]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +version = "1.3.2" +notes = """ +Security review of earlier versions of the crate can be found at +(Google-internal, sorry): go/image-crate-chromium-security-review + +The crate exposes a function marked as `unsafe`, but doesn't use any +`unsafe` blocks (except for tests of the single `unsafe` function). I +think this justifies marking this crate as `ub-risk-1`. + +Additional review comments can be found at https://crrev.com/c/4723145/31 +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.byteorder]] +who = "danakj " +criteria = "safe-to-deploy" +version = "1.5.0" +notes = "Unsafe review in https://crrev.com/c/5838022" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.lazy_static]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +version = "1.4.0" +notes = ''' +I grepped for \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits. + +There are two places where `unsafe` is used. Unsafe review notes can be found +in https://crrev.com/c/5347418. + +This crate has been added to Chromium in https://crrev.com/c/3321895. +''' +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.lazy_static]] +who = "Lukasz Anforowicz " +criteria = "safe-to-deploy" +delta = "1.4.0 -> 1.5.0" +notes = "Unsafe review notes: https://crrev.com/c/5650836" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.log]] +who = "danakj " +criteria = "safe-to-deploy" +version = "0.4.22" +notes = """ +Unsafe review in https://docs.google.com/document/d/1IXQbD1GhTRqNHIGxq6yy7qHqxeO4CwN5noMFXnqyDIM/edit?usp=sharing + +Unsafety is generally very well-documented, with one exception, which we +describe in the review doc. +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.nb]] +who = "George Burgess IV " +criteria = "safe-to-deploy" +version = "1.0.0" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.google.audits.nb]] +who = "George Burgess IV " +criteria = "safe-to-deploy" +delta = "1.0.0 -> 0.1.3" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.google.audits.nb]] +who = "George Burgess IV " +criteria = "safe-to-deploy" +delta = "1.0.0 -> 1.1.0" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.google.audits.num-traits]] +who = "Manish Goregaokar " +criteria = "safe-to-deploy" +version = "0.2.19" +notes = "Contains a single line of float-to-int unsafe with decent safety comments" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.pin-project-lite]] +who = "David Koloski " +criteria = "safe-to-deploy" +version = "0.2.9" +notes = "Reviewed on https://fxrev.dev/824504" +aggregated-from = "https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/third_party/rust_crates/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.pin-project-lite]] +who = "David Koloski " +criteria = "safe-to-deploy" +delta = "0.2.9 -> 0.2.13" +notes = "Audited at https://fxrev.dev/946396" +aggregated-from = "https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/third_party/rust_crates/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.smallvec]] +who = "Manish Goregaokar " +criteria = "safe-to-deploy" +version = "1.13.2" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.smallvec]] +who = "Jonathan Hao " +criteria = "safe-to-deploy" +delta = "1.13.2 -> 1.14.0" +notes = """ +WARNING: This certification is a result of a **partial** audit. The +`malloc_size_of` feature has **not** been audited. This feature does +not explicitly document its safety requirements. +See also https://chromium-review.googlesource.com/c/chromium/src/+/6275133/comment/ea0d7a93_98051a2e/ +and https://github.com/servo/malloc_size_of/issues/8. +This feature is banned in gnrt_config.toml. +""" +aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" + +[[audits.google.audits.void]] +who = "George Burgess IV " +criteria = "safe-to-deploy" +version = "1.0.2" +aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" + +[[audits.mozilla.audits.futures-core]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "0.3.27 -> 0.3.28" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.futures-sink]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "0.3.27 -> 0.3.28" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.once_cell]] +who = "Erich Gubler " +criteria = "safe-to-deploy" +delta = "1.20.1 -> 1.20.2" +notes = "This update works around a Cargo bug that forces the addition of `portable-atomic` into a lockfile, which we have never needed to use." +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.once_cell]] +who = "Erich Gubler " +criteria = "safe-to-deploy" +delta = "1.20.2 -> 1.20.3" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.once_cell]] +who = "Erich Gubler " +criteria = "safe-to-deploy" +delta = "1.20.3 -> 1.21.1" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.once_cell]] +who = "Erich Gubler " +criteria = "safe-to-deploy" +delta = "1.21.1 -> 1.21.3" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.pin-project-lite]] +who = "Mike Hommey " +criteria = "safe-to-deploy" +delta = "0.2.13 -> 0.2.14" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.pin-project-lite]] +who = "Nika Layzell " +criteria = "safe-to-deploy" +delta = "0.2.14 -> 0.2.16" +notes = """ +Only functional change is to work around a bug in the negative_impls feature +(https://github.com/taiki-e/pin-project/issues/340#issuecomment-2432146009) +""" +aggregated-from = "https://raw.githubusercontent.com/mozilla/cargo-vet/main/supply-chain/audits.toml" + +[[audits.mozilla.audits.sharded-slab]] +who = "Mark Hammond " +criteria = "safe-to-deploy" +delta = "0.1.4 -> 0.1.7" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.shlex]] +who = "Max Inden " +criteria = "safe-to-deploy" +delta = "1.1.0 -> 1.3.0" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.smallvec]] +who = "Erich Gubler " +criteria = "safe-to-deploy" +delta = "1.14.0 -> 1.15.1" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.tracing]] +who = "Alex Franchuk " +criteria = "safe-to-deploy" +version = "0.1.37" +notes = """ +There's only one unsafe impl, and its purpose is to ensure correct behavior by +creating a non-Send marker type (it has nothing to do with soundness). All +dependencies make sense, and no side-effectful std functions are used. +""" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.tracing]] +who = "Mark Hammond " +criteria = "safe-to-deploy" +delta = "0.1.37 -> 0.1.41" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.tracing-attributes]] +who = "Alex Franchuk " +criteria = "safe-to-deploy" +version = "0.1.24" +notes = "No unsafe code, macros extensively tested and produce reasonable code." +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.tracing-attributes]] +who = "Mark Hammond " +criteria = "safe-to-deploy" +delta = "0.1.24 -> 0.1.28" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.tracing-core]] +who = "Alex Franchuk " +criteria = "safe-to-deploy" +version = "0.1.30" +notes = """ +Most unsafe code is in implementing non-std sync primitives. Unsafe impls are +logically correct and justified in comments, and unsafe code is sound and +justified in comments. +""" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.tracing-core]] +who = "Mark Hammond " +criteria = "safe-to-deploy" +delta = "0.1.30 -> 0.1.33" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" diff --git a/embassy-mcxa/tools/run_and_attach_rtt.sh b/embassy-mcxa/tools/run_and_attach_rtt.sh new file mode 100644 index 000000000..13041d06b --- /dev/null +++ b/embassy-mcxa/tools/run_and_attach_rtt.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +ELF="${1:-target/thumbv8m.main-none-eabihf/debug/examples/hello}" +PROBE_ID="${2:-1fc9:0143:H3AYDQVQMTROB}" +CHIP="${3:-MCXA276}" +SPEED="${4:-1000}" + +# 1) Flash & run using the existing run.sh (probe is in use only during this step) +./run.sh "$ELF" + +# 2) Give target a short moment to boot and set up RTT CB in RAM +sleep 0.5 + +# 3) Attach RTT/defmt using probe-rs (no flashing) +exec probe-rs attach \ + --chip "$CHIP" \ + --probe "$PROBE_ID" \ + --protocol swd \ + --speed "$SPEED" \ + "$ELF" \ + --rtt-scan-memory \ + --log-format oneline + diff --git a/embassy-mcxa/tools/run_jlink_noblock.sh b/embassy-mcxa/tools/run_jlink_noblock.sh new file mode 100644 index 000000000..3ea1f2b4b --- /dev/null +++ b/embassy-mcxa/tools/run_jlink_noblock.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +set -euo pipefail + +ELF="${1:-}" +PROBE_ID="${2:-1366:0101:000600110607}" # default to your J-Link +CHIP="${3:-MCXA276}" +SPEED="${4:-1000}" +PORT="${PROBE_RS_GDB_PORT:-1337}" + +if [[ -z "${ELF}" || ! -f "${ELF}" ]]; then + echo "Usage: $0 [probe-id] [chip] [speed-khz]" >&2 + exit 1 +fi + +if ! command -v probe-rs >/dev/null 2>&1; then + echo "probe-rs not found (cargo install probe-rs --features cli)" >&2 + exit 1 +fi +if ! command -v gdb-multiarch >/dev/null 2>&1; then + echo "gdb-multiarch not found; install it (e.g., sudo apt install gdb-multiarch)." >&2 + exit 1 +fi + +# Start probe-rs GDB server +SERVER_LOG=$(mktemp) +probe-rs gdb --chip "${CHIP}" --protocol swd --speed "${SPEED}" --non-interactive "${ELF}" --probe "${PROBE_ID}" \ + >"${SERVER_LOG}" 2>&1 & +GDB_SERVER_PID=$! + +# Wait for readiness +for _ in {1..50}; do + if grep -q "Firing up GDB stub" "${SERVER_LOG}"; then break; fi + if grep -q "Connecting to the chip was unsuccessful" "${SERVER_LOG}"; then + echo "probe-rs gdb server failed. Log:" >&2 + sed -e 's/^/ /' "${SERVER_LOG}" >&2 || true + kill "${GDB_SERVER_PID}" 2>/dev/null || true + exit 1 + fi + sleep 0.1 +done + +# GDB script: load, resume, detach +GDB_SCRIPT=$(mktemp) +cat >"${GDB_SCRIPT}" <&2 + sed -e 's/^/ /' "${SERVER_LOG}" >&2 || true + kill "${GDB_SERVER_PID}" 2>/dev/null || true + exit 1 +fi + +# Stop server now that we've detached +kill "${GDB_SERVER_PID}" 2>/dev/null || true +rm -f "${GDB_SCRIPT}" "${SERVER_LOG}" || true + +echo "Flashed, resumed, and detached (probe free)." + diff --git a/examples/.cargo/config.toml b/examples/.cargo/config.toml deleted file mode 100644 index aedc55b06..000000000 --- a/examples/.cargo/config.toml +++ /dev/null @@ -1,17 +0,0 @@ -[target.thumbv8m.main-none-eabihf] -runner = 'probe-rs run --chip MCXA276 --preverify --verify --protocol swd --speed 12000' - -rustflags = [ - "-C", "linker=flip-link", - "-C", "link-arg=-Tlink.x", - "-C", "link-arg=-Tdefmt.x", - # 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", -] - -[build] -target = "thumbv8m.main-none-eabihf" # Cortex-M33 - -[env] -DEFMT_LOG = "trace" diff --git a/examples/.gitignore b/examples/.gitignore deleted file mode 100644 index 2f7896d1d..000000000 --- a/examples/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target/ diff --git a/examples/Cargo.lock b/examples/Cargo.lock deleted file mode 100644 index c6e864df2..000000000 --- a/examples/Cargo.lock +++ /dev/null @@ -1,1555 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "anyhow" -version = "1.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" - -[[package]] -name = "askama" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" -dependencies = [ - "askama_derive", - "itoa", - "percent-encoding", - "serde", - "serde_json", -] - -[[package]] -name = "askama_derive" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" -dependencies = [ - "askama_parser", - "memchr", - "proc-macro2", - "quote", - "rustc-hash", - "syn 2.0.110", -] - -[[package]] -name = "askama_parser" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" -dependencies = [ - "memchr", - "winnow 0.7.13", -] - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "bare-metal" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "bitfield" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "cc" -version = "1.2.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cordyceps" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" -dependencies = [ - "loom", - "tracing", -] - -[[package]] -name = "cortex-m" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" -dependencies = [ - "bare-metal", - "bitfield", - "critical-section", - "embedded-hal 0.2.7", - "volatile-register", -] - -[[package]] -name = "cortex-m-rt" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" -dependencies = [ - "cortex-m-rt-macros", -] - -[[package]] -name = "cortex-m-rt-macros" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.110", -] - -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.110", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.110", -] - -[[package]] -name = "dd-manifest-tree" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5793572036e0a6638977c7370c6afc423eac848ee8495f079b8fd3964de7b9f9" -dependencies = [ - "toml", -] - -[[package]] -name = "defmt" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" -dependencies = [ - "defmt-parser", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.110", -] - -[[package]] -name = "defmt-parser" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" -dependencies = [ - "thiserror", -] - -[[package]] -name = "defmt-rtt" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" -dependencies = [ - "critical-section", - "defmt", -] - -[[package]] -name = "device-driver" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af0e43acfcbb0bb3b7435cc1b1dbb33596cacfec1eb243336b74a398e0bd6cbf" -dependencies = [ - "device-driver-macros", - "embedded-io", - "embedded-io-async", -] - -[[package]] -name = "device-driver-generation" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3935aec9cf5bb2ab927f59ca69faecf976190390b0ce34c6023889e9041040c0" -dependencies = [ - "anyhow", - "askama", - "bitvec", - "convert_case", - "dd-manifest-tree", - "itertools", - "kdl", - "proc-macro2", - "quote", - "syn 2.0.110", -] - -[[package]] -name = "device-driver-macros" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fdc68ed515c4eddff2e95371185b4becba066085bf36d50f07f09782af98e17" -dependencies = [ - "device-driver-generation", - "proc-macro2", - "syn 2.0.110", -] - -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "embassy-embedded-hal" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "554e3e840696f54b4c9afcf28a0f24da431c927f4151040020416e7393d6d0d8" -dependencies = [ - "embassy-futures", - "embassy-hal-internal", - "embassy-sync", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "embedded-storage", - "embedded-storage-async", - "nb 1.1.0", -] - -[[package]] -name = "embassy-executor" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06070468370195e0e86f241c8e5004356d696590a678d47d6676795b2e439c6b" -dependencies = [ - "cortex-m", - "critical-section", - "document-features", - "embassy-executor-macros", - "embassy-executor-timer-queue", -] - -[[package]] -name = "embassy-executor-macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfdddc3a04226828316bf31393b6903ee162238576b1584ee2669af215d55472" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.110", -] - -[[package]] -name = "embassy-executor-timer-queue" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c" - -[[package]] -name = "embassy-futures" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" - -[[package]] -name = "embassy-hal-internal" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" -dependencies = [ - "cortex-m", - "critical-section", - "num-traits", -] - -[[package]] -name = "embassy-mcxa" -version = "0.1.0" -dependencies = [ - "cortex-m", - "cortex-m-rt", - "critical-section", - "defmt", - "embassy-embedded-hal", - "embassy-hal-internal", - "embassy-sync", - "embassy-time", - "embassy-time-driver", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "embedded-hal-nb", - "embedded-io", - "embedded-io-async", - "heapless 0.8.0", - "maitake-sync", - "mcxa-pac", - "nb 1.1.0", - "paste", -] - -[[package]] -name = "embassy-mcxa-examples" -version = "0.1.0" -dependencies = [ - "cortex-m", - "cortex-m-rt", - "critical-section", - "defmt", - "defmt-rtt", - "embassy-embedded-hal", - "embassy-executor", - "embassy-mcxa", - "embassy-sync", - "embassy-time", - "embassy-time-driver", - "embedded-io-async", - "heapless 0.9.2", - "panic-probe", - "tmp108", -] - -[[package]] -name = "embassy-sync" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" -dependencies = [ - "cfg-if", - "critical-section", - "embedded-io-async", - "futures-core", - "futures-sink", - "heapless 0.8.0", -] - -[[package]] -name = "embassy-time" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" -dependencies = [ - "cfg-if", - "critical-section", - "document-features", - "embassy-time-driver", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "futures-core", -] - -[[package]] -name = "embassy-time-driver" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" -dependencies = [ - "document-features", -] - -[[package]] -name = "embedded-hal" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" -dependencies = [ - "nb 0.1.3", - "void", -] - -[[package]] -name = "embedded-hal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" - -[[package]] -name = "embedded-hal-async" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" -dependencies = [ - "embedded-hal 1.0.0", -] - -[[package]] -name = "embedded-hal-nb" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" -dependencies = [ - "embedded-hal 1.0.0", - "nb 1.1.0", -] - -[[package]] -name = "embedded-io" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" - -[[package]] -name = "embedded-io-async" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" -dependencies = [ - "embedded-io", -] - -[[package]] -name = "embedded-storage" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" - -[[package]] -name = "embedded-storage-async" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1763775e2323b7d5f0aa6090657f5e21cfa02ede71f5dc40eead06d64dcd15cc" -dependencies = [ - "embedded-storage", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "find-msvc-tools" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "generator" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows", -] - -[[package]] -name = "hash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" -dependencies = [ - "byteorder", -] - -[[package]] -name = "hashbrown" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" - -[[package]] -name = "heapless" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" -dependencies = [ - "hash32", - "stable_deref_trait", -] - -[[package]] -name = "heapless" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" -dependencies = [ - "hash32", - "stable_deref_trait", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "indexmap" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "kdl" -version = "6.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a29e7b50079ff44549f68c0becb1c73d7f6de2a4ea952da77966daf3d4761e" -dependencies = [ - "miette", - "num", - "winnow 0.6.24", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.177" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" - -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - -[[package]] -name = "log" -version = "0.4.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" - -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "maitake-sync" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "748f86d9befd480b602c3bebc9ef30dbf2f3dfc8acc4a73d07b90f0117e6de3f" -dependencies = [ - "cordyceps", - "critical-section", - "loom", - "mutex-traits", - "mycelium-bitfield", - "pin-project", - "portable-atomic", - "tracing", -] - -[[package]] -name = "manyhow" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587" -dependencies = [ - "manyhow-macros", - "proc-macro2", - "quote", - "syn 1.0.109", - "syn 2.0.110", -] - -[[package]] -name = "manyhow-macros" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495" -dependencies = [ - "proc-macro-utils", - "proc-macro2", - "quote", -] - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "maybe-async-cfg" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dbfaa67a76e2623580df07d6bb5e7956c0a4bae4b418314083a9c619bd66627" -dependencies = [ - "manyhow", - "proc-macro2", - "pulldown-cmark", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "mcxa-pac" -version = "0.1.0" -source = "git+https://github.com/OpenDevicePartnership/mcxa-pac#e18dfb52500ca77b8d8326662b966a80251182ca" -dependencies = [ - "cortex-m", - "cortex-m-rt", - "critical-section", - "defmt", - "vcell", -] - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "miette" -version = "7.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" -dependencies = [ - "cfg-if", - "unicode-width", -] - -[[package]] -name = "mutex-traits" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3929f2b5633d29cf7b6624992e5f3c1e9334f1193423e12d17be4faf678cde3f" - -[[package]] -name = "mycelium-bitfield" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc" - -[[package]] -name = "nb" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" -dependencies = [ - "nb 1.1.0", -] - -[[package]] -name = "nb" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "panic-probe" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" -dependencies = [ - "cortex-m", - "defmt", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pin-project" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.110", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "portable-atomic" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" -dependencies = [ - "critical-section", -] - -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.110", -] - -[[package]] -name = "proc-macro-utils" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" -dependencies = [ - "proc-macro2", - "quote", - "smallvec", -] - -[[package]] -name = "proc-macro2" -version = "1.0.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "pulldown-cmark" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625" -dependencies = [ - "bitflags 2.10.0", - "memchr", - "unicase", -] - -[[package]] -name = "quote" -version = "1.0.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "regex-automata" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.110", -] - -[[package]] -name = "serde_json" -version = "1.0.145" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", - "serde_core", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.110" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "thiserror" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.110", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "tmp108" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0d644cc97d3cee96793f454b834881f78b5d4e89c90ecf26b3690f42004d111" -dependencies = [ - "device-driver", - "embedded-hal 1.0.0", - "maybe-async-cfg", -] - -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow 0.7.13", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - -[[package]] -name = "tracing" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb41cbdb933e23b7929f47bb577710643157d7602ef3a2ebd3902b13ac5eda6" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.110", -] - -[[package]] -name = "tracing-core" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - -[[package]] -name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "vcell" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" - -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - -[[package]] -name = "volatile-register" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" -dependencies = [ - "vcell", -] - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.110", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.110", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core", - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "winnow" -version = "0.6.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" -dependencies = [ - "memchr", -] - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] diff --git a/examples/Cargo.toml b/examples/Cargo.toml deleted file mode 100644 index a1092c416..000000000 --- a/examples/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "embassy-mcxa-examples" -version = "0.1.0" -edition = "2021" -license = "MIT OR Apache-2.0" - -[dependencies] -cortex-m = { version = "0.7", features = ["critical-section-single-core"] } -cortex-m-rt = { version = "0.7", features = ["set-sp", "set-vtor"] } -critical-section = "1.2.0" -defmt = "1.0" -defmt-rtt = "1.0" -embassy-embedded-hal = "0.5.0" -embassy-executor = { version = "0.9.0", features = ["arch-cortex-m", "executor-interrupt", "executor-thread"], default-features = false } -embassy-mcxa = { path = "../", features = ["defmt", "unstable-pac", "time"] } -embassy-sync = "0.7.2" -embassy-time = "0.5.0" -embassy-time-driver = "0.2.1" -embedded-io-async = "0.6.1" -heapless = "0.9.2" -panic-probe = { version = "1.0", features = ["print-defmt"] } -tmp108 = "0.4.0" - -[profile.release] -lto = true # better optimizations -debug = 2 # enough information for defmt/rtt locations diff --git a/examples/build.rs b/examples/build.rs deleted file mode 100644 index f076bba9f..000000000 --- a/examples/build.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::env; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; - -fn main() { - let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); - - // Generate memory.x - put "FLASH" at start of RAM, RAM after "FLASH" - // cortex-m-rt expects FLASH for code, RAM for data/bss/stack - // Both are in RAM, but separated to satisfy cortex-m-rt's expectations - // MCXA256 has 128KB RAM total - File::create(out.join("memory.x")) - .unwrap() - .write_all(include_bytes!("memory.x")) - .unwrap(); - - println!("cargo:rustc-link-search={}", out.display()); - println!("cargo:rerun-if-changed=memory.x"); -} diff --git a/examples/mcxa/.cargo/config.toml b/examples/mcxa/.cargo/config.toml new file mode 100644 index 000000000..aedc55b06 --- /dev/null +++ b/examples/mcxa/.cargo/config.toml @@ -0,0 +1,17 @@ +[target.thumbv8m.main-none-eabihf] +runner = 'probe-rs run --chip MCXA276 --preverify --verify --protocol swd --speed 12000' + +rustflags = [ + "-C", "linker=flip-link", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + # 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", +] + +[build] +target = "thumbv8m.main-none-eabihf" # Cortex-M33 + +[env] +DEFMT_LOG = "trace" diff --git a/examples/mcxa/.gitignore b/examples/mcxa/.gitignore new file mode 100644 index 000000000..2f7896d1d --- /dev/null +++ b/examples/mcxa/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/examples/mcxa/Cargo.lock b/examples/mcxa/Cargo.lock new file mode 100644 index 000000000..c6e864df2 --- /dev/null +++ b/examples/mcxa/Cargo.lock @@ -0,0 +1,1555 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "askama" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" +dependencies = [ + "askama_derive", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" +dependencies = [ + "askama_parser", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "syn 2.0.110", +] + +[[package]] +name = "askama_parser" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" +dependencies = [ + "memchr", + "winnow 0.7.13", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cordyceps" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" +dependencies = [ + "loom", + "tracing", +] + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield", + "critical-section", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.110", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "dd-manifest-tree" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5793572036e0a6638977c7370c6afc423eac848ee8495f079b8fd3964de7b9f9" +dependencies = [ + "toml", +] + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" +dependencies = [ + "critical-section", + "defmt", +] + +[[package]] +name = "device-driver" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af0e43acfcbb0bb3b7435cc1b1dbb33596cacfec1eb243336b74a398e0bd6cbf" +dependencies = [ + "device-driver-macros", + "embedded-io", + "embedded-io-async", +] + +[[package]] +name = "device-driver-generation" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3935aec9cf5bb2ab927f59ca69faecf976190390b0ce34c6023889e9041040c0" +dependencies = [ + "anyhow", + "askama", + "bitvec", + "convert_case", + "dd-manifest-tree", + "itertools", + "kdl", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "device-driver-macros" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fdc68ed515c4eddff2e95371185b4becba066085bf36d50f07f09782af98e17" +dependencies = [ + "device-driver-generation", + "proc-macro2", + "syn 2.0.110", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embassy-embedded-hal" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "554e3e840696f54b4c9afcf28a0f24da431c927f4151040020416e7393d6d0d8" +dependencies = [ + "embassy-futures", + "embassy-hal-internal", + "embassy-sync", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-storage", + "embedded-storage-async", + "nb 1.1.0", +] + +[[package]] +name = "embassy-executor" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06070468370195e0e86f241c8e5004356d696590a678d47d6676795b2e439c6b" +dependencies = [ + "cortex-m", + "critical-section", + "document-features", + "embassy-executor-macros", + "embassy-executor-timer-queue", +] + +[[package]] +name = "embassy-executor-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfdddc3a04226828316bf31393b6903ee162238576b1584ee2669af215d55472" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "embassy-executor-timer-queue" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c" + +[[package]] +name = "embassy-futures" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" + +[[package]] +name = "embassy-hal-internal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" +dependencies = [ + "cortex-m", + "critical-section", + "num-traits", +] + +[[package]] +name = "embassy-mcxa" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "defmt", + "embassy-embedded-hal", + "embassy-hal-internal", + "embassy-sync", + "embassy-time", + "embassy-time-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "embedded-io-async", + "heapless 0.8.0", + "maitake-sync", + "mcxa-pac", + "nb 1.1.0", + "paste", +] + +[[package]] +name = "embassy-mcxa-examples" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "defmt", + "defmt-rtt", + "embassy-embedded-hal", + "embassy-executor", + "embassy-mcxa", + "embassy-sync", + "embassy-time", + "embassy-time-driver", + "embedded-io-async", + "heapless 0.9.2", + "panic-probe", + "tmp108", +] + +[[package]] +name = "embassy-sync" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io-async", + "futures-core", + "futures-sink", + "heapless 0.8.0", +] + +[[package]] +name = "embassy-time" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" +dependencies = [ + "cfg-if", + "critical-section", + "document-features", + "embassy-time-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-core", +] + +[[package]] +name = "embassy-time-driver" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" +dependencies = [ + "document-features", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "embedded-io", +] + +[[package]] +name = "embedded-storage" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" + +[[package]] +name = "embedded-storage-async" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1763775e2323b7d5f0aa6090657f5e21cfa02ede71f5dc40eead06d64dcd15cc" +dependencies = [ + "embedded-storage", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "generator" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "kdl" +version = "6.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81a29e7b50079ff44549f68c0becb1c73d7f6de2a4ea952da77966daf3d4761e" +dependencies = [ + "miette", + "num", + "winnow 0.6.24", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "maitake-sync" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "748f86d9befd480b602c3bebc9ef30dbf2f3dfc8acc4a73d07b90f0117e6de3f" +dependencies = [ + "cordyceps", + "critical-section", + "loom", + "mutex-traits", + "mycelium-bitfield", + "pin-project", + "portable-atomic", + "tracing", +] + +[[package]] +name = "manyhow" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587" +dependencies = [ + "manyhow-macros", + "proc-macro2", + "quote", + "syn 1.0.109", + "syn 2.0.110", +] + +[[package]] +name = "manyhow-macros" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495" +dependencies = [ + "proc-macro-utils", + "proc-macro2", + "quote", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "maybe-async-cfg" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dbfaa67a76e2623580df07d6bb5e7956c0a4bae4b418314083a9c619bd66627" +dependencies = [ + "manyhow", + "proc-macro2", + "pulldown-cmark", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "mcxa-pac" +version = "0.1.0" +source = "git+https://github.com/OpenDevicePartnership/mcxa-pac#e18dfb52500ca77b8d8326662b966a80251182ca" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "defmt", + "vcell", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "unicode-width", +] + +[[package]] +name = "mutex-traits" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3929f2b5633d29cf7b6624992e5f3c1e9334f1193423e12d17be4faf678cde3f" + +[[package]] +name = "mycelium-bitfield" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc" + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "panic-probe" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" +dependencies = [ + "cortex-m", + "defmt", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +dependencies = [ + "critical-section", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "proc-macro-utils" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pulldown-cmark" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625" +dependencies = [ + "bitflags 2.10.0", + "memchr", + "unicase", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tmp108" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0d644cc97d3cee96793f454b834881f78b5d4e89c90ecf26b3690f42004d111" +dependencies = [ + "device-driver", + "embedded-hal 1.0.0", + "maybe-async-cfg", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow 0.7.13", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "tracing" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb41cbdb933e23b7929f47bb577710643157d7602ef3a2ebd3902b13ac5eda6" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "tracing-core" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "winnow" +version = "0.6.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] diff --git a/examples/mcxa/Cargo.toml b/examples/mcxa/Cargo.toml new file mode 100644 index 000000000..a1092c416 --- /dev/null +++ b/examples/mcxa/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "embassy-mcxa-examples" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +cortex-m = { version = "0.7", features = ["critical-section-single-core"] } +cortex-m-rt = { version = "0.7", features = ["set-sp", "set-vtor"] } +critical-section = "1.2.0" +defmt = "1.0" +defmt-rtt = "1.0" +embassy-embedded-hal = "0.5.0" +embassy-executor = { version = "0.9.0", features = ["arch-cortex-m", "executor-interrupt", "executor-thread"], default-features = false } +embassy-mcxa = { path = "../", features = ["defmt", "unstable-pac", "time"] } +embassy-sync = "0.7.2" +embassy-time = "0.5.0" +embassy-time-driver = "0.2.1" +embedded-io-async = "0.6.1" +heapless = "0.9.2" +panic-probe = { version = "1.0", features = ["print-defmt"] } +tmp108 = "0.4.0" + +[profile.release] +lto = true # better optimizations +debug = 2 # enough information for defmt/rtt locations diff --git a/examples/mcxa/build.rs b/examples/mcxa/build.rs new file mode 100644 index 000000000..f076bba9f --- /dev/null +++ b/examples/mcxa/build.rs @@ -0,0 +1,20 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + + // Generate memory.x - put "FLASH" at start of RAM, RAM after "FLASH" + // cortex-m-rt expects FLASH for code, RAM for data/bss/stack + // Both are in RAM, but separated to satisfy cortex-m-rt's expectations + // MCXA256 has 128KB RAM total + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/examples/mcxa/memory.x b/examples/mcxa/memory.x new file mode 100644 index 000000000..315ced58a --- /dev/null +++ b/examples/mcxa/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 1M + RAM : ORIGIN = 0x20000000, LENGTH = 128K +} diff --git a/examples/mcxa/src/bin/adc_interrupt.rs b/examples/mcxa/src/bin/adc_interrupt.rs new file mode 100644 index 000000000..83d8046b3 --- /dev/null +++ b/examples/mcxa/src/bin/adc_interrupt.rs @@ -0,0 +1,84 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa_examples::init_adc_pins; +use hal::adc::{LpadcConfig, TriggerPriorityPolicy}; +use hal::clocks::periph_helpers::{AdcClockSel, Div4}; +use hal::clocks::PoweredClock; +use hal::pac::adc1::cfg::{Pwrsel, Refsel}; +use hal::pac::adc1::cmdl1::{Adch, Mode}; +use hal::pac::adc1::ctrl::CalAvgs; +use hal::pac::adc1::tctrl::Tcmd; +use hal::{bind_interrupts, InterruptExt}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ADC1 => hal::adc::AdcHandler; +}); + +#[used] +#[no_mangle] +static KEEP_ADC: unsafe extern "C" fn() = ADC1; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = hal::init(hal::config::Config::default()); + + defmt::info!("ADC interrupt Example"); + + unsafe { + init_adc_pins(); + } + + let adc_config = LpadcConfig { + enable_in_doze_mode: true, + conversion_average_mode: CalAvgs::Average128, + enable_analog_preliminary: true, + power_up_delay: 0x80, + reference_voltage_source: Refsel::Option3, + power_level_mode: Pwrsel::Lowest, + trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, + enable_conv_pause: false, + conv_pause_delay: 0, + fifo_watermark: 0, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: AdcClockSel::FroLfDiv, + div: Div4::no_div(), + }; + let adc = hal::adc::Adc::::new(p.ADC1, adc_config); + + adc.do_offset_calibration(); + adc.do_auto_calibration(); + + let mut conv_command_config = adc.get_default_conv_command_config(); + conv_command_config.channel_number = Adch::SelectCorrespondingChannel8; + conv_command_config.conversion_resolution_mode = Mode::Data16Bits; + adc.set_conv_command_config(1, &conv_command_config); + + let mut conv_trigger_config = adc.get_default_conv_trigger_config(); + conv_trigger_config.target_command_id = Tcmd::ExecuteCmd1; + conv_trigger_config.enable_hardware_trigger = false; + adc.set_conv_trigger_config(0, &conv_trigger_config); + + defmt::info!("ADC configuration done..."); + + adc.enable_interrupt(0x1); + + unsafe { + hal::interrupt::ADC1.enable(); + } + + unsafe { + cortex_m::interrupt::enable(); + } + + loop { + adc.do_software_trigger(1); + while !adc.is_interrupt_triggered() { + // Wait until the interrupt is triggered + } + defmt::info!("*** ADC interrupt TRIGGERED! ***"); + //TBD need to print the value + } +} diff --git a/examples/mcxa/src/bin/adc_polling.rs b/examples/mcxa/src/bin/adc_polling.rs new file mode 100644 index 000000000..ddf3f586b --- /dev/null +++ b/examples/mcxa/src/bin/adc_polling.rs @@ -0,0 +1,68 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa_examples::init_adc_pins; +use hal::adc::{ConvResult, LpadcConfig, TriggerPriorityPolicy}; +use hal::clocks::periph_helpers::{AdcClockSel, Div4}; +use hal::clocks::PoweredClock; +use hal::pac::adc1::cfg::{Pwrsel, Refsel}; +use hal::pac::adc1::cmdl1::{Adch, Mode}; +use hal::pac::adc1::ctrl::CalAvgs; +use hal::pac::adc1::tctrl::Tcmd; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +const G_LPADC_RESULT_SHIFT: u32 = 0; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = hal::init(hal::config::Config::default()); + + unsafe { + init_adc_pins(); + } + + defmt::info!("=== ADC polling Example ==="); + + let adc_config = LpadcConfig { + enable_in_doze_mode: true, + conversion_average_mode: CalAvgs::Average128, + enable_analog_preliminary: true, + power_up_delay: 0x80, + reference_voltage_source: Refsel::Option3, + power_level_mode: Pwrsel::Lowest, + trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, + enable_conv_pause: false, + conv_pause_delay: 0, + fifo_watermark: 0, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: AdcClockSel::FroLfDiv, + div: Div4::no_div(), + }; + let adc = hal::adc::Adc::::new(p.ADC1, adc_config); + + adc.do_offset_calibration(); + adc.do_auto_calibration(); + + let mut conv_command_config = adc.get_default_conv_command_config(); + conv_command_config.channel_number = Adch::SelectCorrespondingChannel8; + conv_command_config.conversion_resolution_mode = Mode::Data16Bits; + adc.set_conv_command_config(1, &conv_command_config); + + let mut conv_trigger_config = adc.get_default_conv_trigger_config(); + conv_trigger_config.target_command_id = Tcmd::ExecuteCmd1; + conv_trigger_config.enable_hardware_trigger = false; + adc.set_conv_trigger_config(0, &conv_trigger_config); + + defmt::info!("=== ADC configuration done... ==="); + + loop { + adc.do_software_trigger(1); + let mut result: Option = None; + while result.is_none() { + result = hal::adc::get_conv_result(); + } + let value = result.unwrap().conv_value >> G_LPADC_RESULT_SHIFT; + defmt::info!("value: {=u16}", value); + } +} diff --git a/examples/mcxa/src/bin/blinky.rs b/examples/mcxa/src/bin/blinky.rs new file mode 100644 index 000000000..dd08ec0d9 --- /dev/null +++ b/examples/mcxa/src/bin/blinky.rs @@ -0,0 +1,36 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_time::Timer; +use hal::gpio::{DriveStrength, Level, Output, SlewRate}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = hal::init(hal::config::Config::default()); + + defmt::info!("Blink example"); + + let mut red = Output::new(p.P3_18, Level::High, DriveStrength::Normal, SlewRate::Fast); + let mut green = Output::new(p.P3_19, Level::High, DriveStrength::Normal, SlewRate::Fast); + let mut blue = Output::new(p.P3_21, Level::High, DriveStrength::Normal, SlewRate::Fast); + + loop { + defmt::info!("Toggle LEDs"); + + red.toggle(); + Timer::after_millis(250).await; + + red.toggle(); + green.toggle(); + Timer::after_millis(250).await; + + green.toggle(); + blue.toggle(); + Timer::after_millis(250).await; + blue.toggle(); + + Timer::after_millis(250).await; + } +} diff --git a/examples/mcxa/src/bin/button.rs b/examples/mcxa/src/bin/button.rs new file mode 100644 index 000000000..943edbb15 --- /dev/null +++ b/examples/mcxa/src/bin/button.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_time::Timer; +use hal::gpio::{Input, Pull}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = hal::init(hal::config::Config::default()); + + defmt::info!("Button example"); + + // This button is labeled "WAKEUP" on the FRDM-MCXA276 + // The board already has a 10K pullup + let monitor = Input::new(p.P1_7, Pull::Disabled); + + loop { + defmt::info!("Pin level is {:?}", monitor.get_level()); + Timer::after_millis(1000).await; + } +} diff --git a/examples/mcxa/src/bin/button_async.rs b/examples/mcxa/src/bin/button_async.rs new file mode 100644 index 000000000..6cc7b62cd --- /dev/null +++ b/examples/mcxa/src/bin/button_async.rs @@ -0,0 +1,29 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_time::Timer; +use hal::gpio::{Input, Pull}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = hal::init(hal::config::Config::default()); + + defmt::info!("GPIO interrupt example"); + + // This button is labeled "WAKEUP" on the FRDM-MCXA276 + // The board already has a 10K pullup + let mut pin = Input::new(p.P1_7, Pull::Disabled); + + let mut press_count = 0u32; + + loop { + pin.wait_for_falling_edge().await; + + press_count += 1; + + defmt::info!("Button pressed! Count: {}", press_count); + Timer::after_millis(50).await; + } +} diff --git a/examples/mcxa/src/bin/clkout.rs b/examples/mcxa/src/bin/clkout.rs new file mode 100644 index 000000000..bfd963540 --- /dev/null +++ b/examples/mcxa/src/bin/clkout.rs @@ -0,0 +1,69 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clkout::{ClockOut, ClockOutSel, Config, Div4}; +use embassy_mcxa::clocks::PoweredClock; +use embassy_mcxa::gpio::{DriveStrength, SlewRate}; +use embassy_mcxa::{Level, Output}; +use embassy_time::Timer; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +/// Demonstrate CLKOUT, using Pin P4.2 +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = hal::init(hal::config::Config::default()); + let mut pin = p.P4_2; + let mut clkout = p.CLKOUT; + + loop { + defmt::info!("Set Low..."); + let mut output = Output::new(pin.reborrow(), Level::Low, DriveStrength::Normal, SlewRate::Slow); + Timer::after_millis(500).await; + + defmt::info!("Set High..."); + output.set_high(); + Timer::after_millis(400).await; + + defmt::info!("Set Low..."); + output.set_low(); + Timer::after_millis(500).await; + + defmt::info!("16k..."); + // Run Clock Out with the 16K clock + let _clock_out = ClockOut::new( + clkout.reborrow(), + pin.reborrow(), + Config { + sel: ClockOutSel::Clk16K, + div: Div4::no_div(), + level: PoweredClock::NormalEnabledDeepSleepDisabled, + }, + ) + .unwrap(); + + Timer::after_millis(3000).await; + + defmt::info!("Set Low..."); + drop(_clock_out); + + let _output = Output::new(pin.reborrow(), Level::Low, DriveStrength::Normal, SlewRate::Slow); + Timer::after_millis(500).await; + + // Run Clock Out with the 12M clock, divided by 3 + defmt::info!("4M..."); + let _clock_out = ClockOut::new( + clkout.reborrow(), + pin.reborrow(), + Config { + sel: ClockOutSel::Fro12M, + div: const { Div4::from_divisor(3).unwrap() }, + level: PoweredClock::NormalEnabledDeepSleepDisabled, + }, + ) + .unwrap(); + + // Let it run for 3 seconds... + Timer::after_millis(3000).await; + } +} diff --git a/examples/mcxa/src/bin/dma_channel_link.rs b/examples/mcxa/src/bin/dma_channel_link.rs new file mode 100644 index 000000000..92c7a9681 --- /dev/null +++ b/examples/mcxa/src/bin/dma_channel_link.rs @@ -0,0 +1,372 @@ +//! DMA channel linking example for MCXA276. +//! +//! This example demonstrates DMA channel linking (minor and major loop linking): +//! - Channel 0: Transfers SRC_BUFFER to DEST_BUFFER0, with: +//! - Minor Link to Channel 1 (triggers CH1 after each minor loop) +//! - Major Link to Channel 2 (triggers CH2 after major loop completes) +//! - Channel 1: Transfers SRC_BUFFER to DEST_BUFFER1 (triggered by CH0 minor link) +//! - Channel 2: Transfers SRC_BUFFER to DEST_BUFFER2 (triggered by CH0 major link) +//! +//! # Embassy-style features demonstrated: +//! - `DmaChannel::new()` for channel creation +//! - `DmaChannel::is_done()` and `clear_done()` helper methods +//! - Channel linking with `set_minor_link()` and `set_major_link()` +//! - Standard `DmaCh*InterruptHandler` with `bind_interrupts!` macro + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler, DmaCh2InterruptHandler, DmaChannel}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Buffers +static mut SRC_BUFFER: [u32; 4] = [1, 2, 3, 4]; +static mut DEST_BUFFER0: [u32; 4] = [0; 4]; +static mut DEST_BUFFER1: [u32; 4] = [0; 4]; +static mut DEST_BUFFER2: [u32; 4] = [0; 4]; + +// Bind DMA channel interrupts using Embassy-style macro +// The standard handlers call on_interrupt() which wakes wakers and clears flags +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; + DMA_CH1 => DmaCh1InterruptHandler; + DMA_CH2 => DmaCh2InterruptHandler; +}); + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA channel link example starting..."); + + // DMA is initialized during hal::init() - no need to call ensure_init() + + let pac_periphs = unsafe { pac::Peripherals::steal() }; + let dma0 = &pac_periphs.dma0; + let edma = unsafe { &*pac::Edma0Tcd0::ptr() }; + + // Clear any residual state + for i in 0..3 { + let t = edma.tcd(i); + t.ch_csr().write(|w| w.erq().disable().done().clear_bit_by_one()); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + t.ch_es().write(|w| w.err().clear_bit_by_one()); + t.ch_mux().write(|w| unsafe { w.bits(0) }); + } + + // Clear Global Halt/Error state + dma0.mp_csr().modify(|_, w| { + w.halt() + .normal_operation() + .hae() + .normal_operation() + .ecx() + .normal_operation() + .cx() + .normal_operation() + }); + + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH2); + } + + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"EDMA channel link example begin.\r\n\r\n").unwrap(); + + // Initialize buffers + unsafe { + SRC_BUFFER = [1, 2, 3, 4]; + DEST_BUFFER0 = [0; 4]; + DEST_BUFFER1 = [0; 4]; + DEST_BUFFER2 = [0; 4]; + } + + tx.blocking_write(b"Source Buffer: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC_BUFFER) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"DEST0 (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER0) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"DEST1 (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER1) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"DEST2 (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER2) as *const u32, 4); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + tx.blocking_write(b"Configuring DMA channels with Embassy-style API...\r\n") + .unwrap(); + + let ch0 = DmaChannel::new(p.DMA_CH0); + let ch1 = DmaChannel::new(p.DMA_CH1); + let ch2 = DmaChannel::new(p.DMA_CH2); + + // Configure channels using direct TCD access (advanced feature demo) + // This example demonstrates channel linking which requires direct TCD manipulation + + // Helper to configure TCD for memory-to-memory transfer + // Parameters: channel, src, dst, width, nbytes (minor loop), count (major loop), interrupt + #[allow(clippy::too_many_arguments)] + unsafe fn configure_tcd( + edma: &embassy_mcxa::pac::edma_0_tcd0::RegisterBlock, + ch: usize, + src: u32, + dst: u32, + width: u8, + nbytes: u32, + count: u16, + enable_int: bool, + ) { + let t = edma.tcd(ch); + + // Reset channel state + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .ebw() + .disable() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Source/destination addresses + t.tcd_saddr().write(|w| w.saddr().bits(src)); + t.tcd_daddr().write(|w| w.daddr().bits(dst)); + + // Offsets: increment by width + t.tcd_soff().write(|w| w.soff().bits(width as u16)); + t.tcd_doff().write(|w| w.doff().bits(width as u16)); + + // Attributes: size = log2(width) + let size = match width { + 1 => 0, + 2 => 1, + 4 => 2, + _ => 0, + }; + t.tcd_attr().write(|w| w.ssize().bits(size).dsize().bits(size)); + + // Number of bytes per minor loop + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); + + // Major loop: reset source address after major loop + let total_bytes = nbytes * count as u32; + t.tcd_slast_sda() + .write(|w| w.slast_sda().bits(-(total_bytes as i32) as u32)); + t.tcd_dlast_sga() + .write(|w| w.dlast_sga().bits(-(total_bytes as i32) as u32)); + + // Major loop count + t.tcd_biter_elinkno().write(|w| w.biter().bits(count)); + t.tcd_citer_elinkno().write(|w| w.citer().bits(count)); + + // Control/status: enable interrupt if requested + if enable_int { + t.tcd_csr().write(|w| w.intmajor().set_bit()); + } else { + t.tcd_csr().write(|w| w.intmajor().clear_bit()); + } + + cortex_m::asm::dsb(); + } + + unsafe { + // Channel 0: Transfer 16 bytes total (8 bytes per minor loop, 2 major iterations) + // Minor Link -> Channel 1 + // Major Link -> Channel 2 + configure_tcd( + edma, + 0, + core::ptr::addr_of!(SRC_BUFFER) as u32, + core::ptr::addr_of_mut!(DEST_BUFFER0) as u32, + 4, // src width + 8, // nbytes (minor loop = 2 words) + 2, // count (major loop = 2 iterations) + false, // no interrupt + ); + ch0.set_minor_link(1); // Link to CH1 after each minor loop + ch0.set_major_link(2); // Link to CH2 after major loop + + // Channel 1: Transfer 16 bytes (triggered by CH0 minor link) + configure_tcd( + edma, + 1, + core::ptr::addr_of!(SRC_BUFFER) as u32, + core::ptr::addr_of_mut!(DEST_BUFFER1) as u32, + 4, + 16, // full buffer in one minor loop + 1, // 1 major iteration + false, + ); + + // Channel 2: Transfer 16 bytes (triggered by CH0 major link) + configure_tcd( + edma, + 2, + core::ptr::addr_of!(SRC_BUFFER) as u32, + core::ptr::addr_of_mut!(DEST_BUFFER2) as u32, + 4, + 16, // full buffer in one minor loop + 1, // 1 major iteration + true, // enable interrupt + ); + } + + tx.blocking_write(b"Triggering Channel 0 (1st minor loop)...\r\n") + .unwrap(); + + // Trigger first minor loop of CH0 + unsafe { + ch0.trigger_start(); + } + + // Wait for CH1 to complete (triggered by CH0 minor link) + while !ch1.is_done() { + cortex_m::asm::nop(); + } + unsafe { + ch1.clear_done(); + } + + tx.blocking_write(b"CH1 done (via minor link).\r\n").unwrap(); + tx.blocking_write(b"Triggering Channel 0 (2nd minor loop)...\r\n") + .unwrap(); + + // Trigger second minor loop of CH0 + unsafe { + ch0.trigger_start(); + } + + // Wait for CH0 major loop to complete + while !ch0.is_done() { + cortex_m::asm::nop(); + } + unsafe { + ch0.clear_done(); + } + + tx.blocking_write(b"CH0 major loop done.\r\n").unwrap(); + + // Wait for CH2 to complete (triggered by CH0 major link) + // Using is_done() instead of AtomicBool - the standard interrupt handler + // clears the interrupt flag and wakes wakers, but DONE bit remains set + while !ch2.is_done() { + cortex_m::asm::nop(); + } + unsafe { + ch2.clear_done(); + } + + tx.blocking_write(b"CH2 done (via major link).\r\n\r\n").unwrap(); + + tx.blocking_write(b"EDMA channel link example finish.\r\n\r\n").unwrap(); + + tx.blocking_write(b"DEST0 (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER0) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"DEST1 (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER1) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"DEST2 (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER2) as *const u32, 4); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify all buffers match source + let mut success = true; + unsafe { + let src_ptr = core::ptr::addr_of!(SRC_BUFFER) as *const u32; + let dst0_ptr = core::ptr::addr_of!(DEST_BUFFER0) as *const u32; + let dst1_ptr = core::ptr::addr_of!(DEST_BUFFER1) as *const u32; + let dst2_ptr = core::ptr::addr_of!(DEST_BUFFER2) as *const u32; + + for i in 0..4 { + if *dst0_ptr.add(i) != *src_ptr.add(i) { + success = false; + } + if *dst1_ptr.add(i) != *src_ptr.add(i) { + success = false; + } + if *dst2_ptr.add(i) != *src_ptr.add(i) { + success = false; + } + } + } + + if success { + tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); + defmt::info!("PASS: Data verified."); + } else { + tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); + defmt::error!("FAIL: Mismatch detected!"); + } + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/dma_interleave_transfer.rs b/examples/mcxa/src/bin/dma_interleave_transfer.rs new file mode 100644 index 000000000..7876e8978 --- /dev/null +++ b/examples/mcxa/src/bin/dma_interleave_transfer.rs @@ -0,0 +1,215 @@ +//! DMA interleaved transfer example for MCXA276. +//! +//! This example demonstrates using DMA with custom source/destination offsets +//! to interleave data during transfer. +//! +//! # Embassy-style features demonstrated: +//! - `TransferOptions::default()` for configuration (used internally) +//! - DMA channel with `DmaChannel::new()` + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel 0 interrupt using Embassy-style macro +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; +}); + +const BUFFER_LENGTH: usize = 16; +const HALF_BUFF_LENGTH: usize = BUFFER_LENGTH / 2; + +// Buffers in RAM +static mut SRC_BUFFER: [u32; HALF_BUFF_LENGTH] = [0; HALF_BUFF_LENGTH]; +static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA interleave transfer example starting..."); + + // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"EDMA interleave transfer example begin.\r\n\r\n") + .unwrap(); + + // Initialize buffers + unsafe { + SRC_BUFFER = [1, 2, 3, 4, 5, 6, 7, 8]; + DEST_BUFFER = [0; BUFFER_LENGTH]; + } + + tx.blocking_write(b"Source Buffer: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC_BUFFER) as *const u32, HALF_BUFF_LENGTH); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Destination Buffer (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") + .unwrap(); + + // Create DMA channel using Embassy-style API + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Configure interleaved transfer using direct TCD access: + // - src_offset = 4: advance source by 4 bytes after each read + // - dst_offset = 8: advance dest by 8 bytes after each write + // This spreads source data across every other word in destination + unsafe { + let t = dma_ch0.tcd(); + + // Reset channel state + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .ebw() + .disable() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Source/destination addresses + t.tcd_saddr() + .write(|w| w.saddr().bits(core::ptr::addr_of_mut!(SRC_BUFFER) as u32)); + t.tcd_daddr() + .write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DEST_BUFFER) as u32)); + + // Custom offsets for interleaving + t.tcd_soff().write(|w| w.soff().bits(4)); // src: +4 bytes per read + t.tcd_doff().write(|w| w.doff().bits(8)); // dst: +8 bytes per write + + // Attributes: 32-bit transfers (size = 2) + t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); + + // Transfer entire source buffer in one minor loop + let nbytes = (HALF_BUFF_LENGTH * 4) as u32; + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); + + // Reset source address after major loop + t.tcd_slast_sda().write(|w| w.slast_sda().bits(-(nbytes as i32) as u32)); + // Destination uses 2x offset, so adjust accordingly + let dst_total = (HALF_BUFF_LENGTH * 8) as u32; + t.tcd_dlast_sga() + .write(|w| w.dlast_sga().bits(-(dst_total as i32) as u32)); + + // Major loop count = 1 + t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); + t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); + + // Enable interrupt on major loop completion + t.tcd_csr().write(|w| w.intmajor().set_bit()); + + cortex_m::asm::dsb(); + + tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); + dma_ch0.trigger_start(); + } + + // Wait for completion using channel helper method + while !dma_ch0.is_done() { + cortex_m::asm::nop(); + } + unsafe { + dma_ch0.clear_done(); + } + + tx.blocking_write(b"\r\nEDMA interleave transfer example finish.\r\n\r\n") + .unwrap(); + tx.blocking_write(b"Destination Buffer (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify: Even indices should match SRC_BUFFER[i/2], odd indices should be 0 + let mut mismatch = false; + unsafe { + for i in 0..BUFFER_LENGTH { + if i % 2 == 0 { + if DEST_BUFFER[i] != SRC_BUFFER[i / 2] { + mismatch = true; + } + } else if DEST_BUFFER[i] != 0 { + mismatch = true; + } + } + } + + if mismatch { + tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); + defmt::error!("FAIL: Mismatch detected!"); + } else { + tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); + defmt::info!("PASS: Data verified."); + } + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/dma_mem_to_mem.rs b/examples/mcxa/src/bin/dma_mem_to_mem.rs new file mode 100644 index 000000000..68f70e742 --- /dev/null +++ b/examples/mcxa/src/bin/dma_mem_to_mem.rs @@ -0,0 +1,229 @@ +//! DMA memory-to-memory transfer example for MCXA276. +//! +//! This example demonstrates using DMA to copy data between memory buffers +//! using the Embassy-style async API with type-safe transfers. +//! +//! # Embassy-style features demonstrated: +//! - `TransferOptions` for configuration +//! - Type-safe `mem_to_mem()` method with async `.await` +//! - `Transfer` Future that can be `.await`ed +//! - `Word` trait for automatic transfer width detection +//! - `memset()` method for filling memory with a pattern + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel, TransferOptions}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel 0 interrupt using Embassy-style macro +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; +}); + +const BUFFER_LENGTH: usize = 4; + +// Buffers in RAM (static mut is automatically placed in .bss/.data) +static mut SRC_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; +static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; +static mut MEMSET_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; // u32 max is 4294967295 (10 digits) + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer as [v1, v2, v3, v4] to UART +/// Takes a raw pointer to avoid warnings about shared references to mutable statics +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const [u32; BUFFER_LENGTH]) { + tx.blocking_write(b"[").ok(); + unsafe { + let buf = &*buf_ptr; + for (i, val) in buf.iter().enumerate() { + write_u32(tx, *val); + if i < buf.len() - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA memory-to-memory example starting..."); + + // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + // Create UART for debug output + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"EDMA memory to memory example begin.\r\n\r\n") + .unwrap(); + + // Initialize buffers + unsafe { + SRC_BUFFER = [1, 2, 3, 4]; + DEST_BUFFER = [0; BUFFER_LENGTH]; + } + + tx.blocking_write(b"Source Buffer: ").unwrap(); + print_buffer(&mut tx, &raw const SRC_BUFFER); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Destination Buffer (before): ").unwrap(); + print_buffer(&mut tx, &raw const DEST_BUFFER); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") + .unwrap(); + + // Create DMA channel + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Configure transfer options (Embassy-style) + // TransferOptions defaults to: complete_transfer_interrupt = true + let options = TransferOptions::default(); + + // ========================================================================= + // Part 1: Embassy-style async API demonstration (mem_to_mem) + // ========================================================================= + // + // Use the new type-safe `mem_to_mem()` method: + // - Automatically determines transfer width from buffer element type (u32) + // - Returns a `Transfer` future that can be `.await`ed + // - Uses TransferOptions for consistent configuration + // + // Using async `.await` - the executor can run other tasks while waiting! + + // Perform type-safe memory-to-memory transfer using Embassy-style async API + unsafe { + let src = &*core::ptr::addr_of!(SRC_BUFFER); + let dst = &mut *core::ptr::addr_of_mut!(DEST_BUFFER); + + // Using async `.await` - the executor can run other tasks while waiting! + let transfer = dma_ch0.mem_to_mem(src, dst, options); + transfer.await; + } + + tx.blocking_write(b"DMA mem-to-mem transfer complete!\r\n\r\n").unwrap(); + tx.blocking_write(b"Destination Buffer (after): ").unwrap(); + print_buffer(&mut tx, &raw const DEST_BUFFER); + tx.blocking_write(b"\r\n").unwrap(); + + // Verify data + let mut mismatch = false; + unsafe { + for i in 0..BUFFER_LENGTH { + if SRC_BUFFER[i] != DEST_BUFFER[i] { + mismatch = true; + break; + } + } + } + + if mismatch { + tx.blocking_write(b"FAIL: mem_to_mem mismatch!\r\n").unwrap(); + defmt::error!("FAIL: mem_to_mem mismatch!"); + } else { + tx.blocking_write(b"PASS: mem_to_mem verified.\r\n\r\n").unwrap(); + defmt::info!("PASS: mem_to_mem verified."); + } + + // ========================================================================= + // Part 2: memset() demonstration + // ========================================================================= + // + // The `memset()` method fills a buffer with a pattern value: + // - Fixed source address (pattern is read repeatedly) + // - Incrementing destination address + // - Uses the same Transfer future pattern + + tx.blocking_write(b"--- Demonstrating memset() feature ---\r\n\r\n") + .unwrap(); + + tx.blocking_write(b"Memset Buffer (before): ").unwrap(); + print_buffer(&mut tx, &raw const MEMSET_BUFFER); + tx.blocking_write(b"\r\n").unwrap(); + + // Fill buffer with a pattern value using DMA memset + let pattern: u32 = 0xDEADBEEF; + tx.blocking_write(b"Filling with pattern 0xDEADBEEF...\r\n").unwrap(); + + unsafe { + let dst = &mut *core::ptr::addr_of_mut!(MEMSET_BUFFER); + + // Using blocking_wait() for demonstration - also shows non-async usage + let transfer = dma_ch0.memset(&pattern, dst, options); + transfer.blocking_wait(); + } + + tx.blocking_write(b"DMA memset complete!\r\n\r\n").unwrap(); + tx.blocking_write(b"Memset Buffer (after): ").unwrap(); + print_buffer(&mut tx, &raw const MEMSET_BUFFER); + tx.blocking_write(b"\r\n").unwrap(); + + // Verify memset result + let mut memset_ok = true; + unsafe { + #[allow(clippy::needless_range_loop)] + for i in 0..BUFFER_LENGTH { + if MEMSET_BUFFER[i] != pattern { + memset_ok = false; + break; + } + } + } + + if !memset_ok { + tx.blocking_write(b"FAIL: memset mismatch!\r\n").unwrap(); + defmt::error!("FAIL: memset mismatch!"); + } else { + tx.blocking_write(b"PASS: memset verified.\r\n\r\n").unwrap(); + defmt::info!("PASS: memset verified."); + } + + tx.blocking_write(b"=== All DMA tests complete ===\r\n").unwrap(); + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/dma_memset.rs b/examples/mcxa/src/bin/dma_memset.rs new file mode 100644 index 000000000..95e365e47 --- /dev/null +++ b/examples/mcxa/src/bin/dma_memset.rs @@ -0,0 +1,218 @@ +//! DMA memset example for MCXA276. +//! +//! This example demonstrates using DMA to fill a buffer with a repeated pattern. +//! The source address stays fixed while the destination increments. +//! +//! # Embassy-style features demonstrated: +//! - `DmaChannel::is_done()` and `clear_done()` helper methods +//! - No need to pass register block around + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel 0 interrupt using Embassy-style macro +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; +}); + +const BUFFER_LENGTH: usize = 4; + +// Buffers in RAM +static mut PATTERN: u32 = 0; +static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA memset example starting..."); + + // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"EDMA memset example begin.\r\n\r\n").unwrap(); + + // Initialize buffers + unsafe { + PATTERN = 0xDEADBEEF; + DEST_BUFFER = [0; BUFFER_LENGTH]; + } + + tx.blocking_write(b"Pattern value: 0x").unwrap(); + // Print pattern in hex + unsafe { + let hex_chars = b"0123456789ABCDEF"; + let mut hex_buf = [0u8; 8]; + let mut val = PATTERN; + for i in (0..8).rev() { + hex_buf[i] = hex_chars[(val & 0xF) as usize]; + val >>= 4; + } + tx.blocking_write(&hex_buf).ok(); + } + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Destination Buffer (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") + .unwrap(); + + // Create DMA channel using Embassy-style API + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Configure memset transfer using direct TCD access: + // Source stays fixed (soff = 0, reads same pattern repeatedly) + // Destination increments (doff = 4) + unsafe { + let t = dma_ch0.tcd(); + + // Reset channel state + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .ebw() + .disable() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Source address (pattern) - fixed + t.tcd_saddr() + .write(|w| w.saddr().bits(core::ptr::addr_of_mut!(PATTERN) as u32)); + // Destination address - increments + t.tcd_daddr() + .write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DEST_BUFFER) as u32)); + + // Source offset = 0 (stays fixed), Dest offset = 4 (increments) + t.tcd_soff().write(|w| w.soff().bits(0)); + t.tcd_doff().write(|w| w.doff().bits(4)); + + // Attributes: 32-bit transfers (size = 2) + t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); + + // Transfer entire buffer in one minor loop + let nbytes = (BUFFER_LENGTH * 4) as u32; + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); + + // Source doesn't need adjustment (stays fixed) + t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); + // Reset dest address after major loop + t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); + + // Major loop count = 1 + t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); + t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); + + // Enable interrupt on major loop completion + t.tcd_csr().write(|w| w.intmajor().set_bit()); + + cortex_m::asm::dsb(); + + tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); + dma_ch0.trigger_start(); + } + + // Wait for completion using channel helper method + while !dma_ch0.is_done() { + cortex_m::asm::nop(); + } + unsafe { + dma_ch0.clear_done(); + } + + tx.blocking_write(b"\r\nEDMA memset example finish.\r\n\r\n").unwrap(); + tx.blocking_write(b"Destination Buffer (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify: All elements should equal PATTERN + let mut mismatch = false; + unsafe { + #[allow(clippy::needless_range_loop)] + for i in 0..BUFFER_LENGTH { + if DEST_BUFFER[i] != PATTERN { + mismatch = true; + break; + } + } + } + + if mismatch { + tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); + defmt::error!("FAIL: Mismatch detected!"); + } else { + tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); + defmt::info!("PASS: Data verified."); + } + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/dma_ping_pong_transfer.rs b/examples/mcxa/src/bin/dma_ping_pong_transfer.rs new file mode 100644 index 000000000..f8f543382 --- /dev/null +++ b/examples/mcxa/src/bin/dma_ping_pong_transfer.rs @@ -0,0 +1,376 @@ +//! DMA ping-pong/double-buffer transfer example for MCXA276. +//! +//! This example demonstrates two approaches for ping-pong/double-buffering: +//! +//! ## Approach 1: Scatter/Gather with linked TCDs (manual) +//! - Two TCDs link to each other for alternating transfers +//! - Uses custom handler that delegates to on_interrupt() then signals completion +//! - Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, +//! so we need an AtomicBool to track completion +//! +//! ## Approach 2: Half-transfer interrupt with wait_half() (NEW!) +//! - Single continuous transfer over entire buffer +//! - Uses half-transfer interrupt to know when first half is ready +//! - Application can process first half while second half is being filled +//! +//! # Embassy-style features demonstrated: +//! - `DmaChannel::new()` for channel creation +//! - Scatter/gather with linked TCDs +//! - Custom handler that delegates to HAL's `on_interrupt()` (best practice) +//! - Standard `DmaCh1InterruptHandler` with `bind_interrupts!` macro +//! - NEW: `wait_half()` for half-transfer interrupt handling + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicBool, Ordering}; + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{self, DmaCh1InterruptHandler, DmaChannel, Tcd, TransferOptions}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Source and destination buffers for Approach 1 (scatter/gather) +static mut SRC: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; +static mut DST: [u32; 8] = [0; 8]; + +// Source and destination buffers for Approach 2 (wait_half) +static mut SRC2: [u32; 8] = [0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]; +static mut DST2: [u32; 8] = [0; 8]; + +// TCD pool for scatter/gather - must be 32-byte aligned +#[repr(C, align(32))] +struct TcdPool([Tcd; 2]); + +static mut TCD_POOL: TcdPool = TcdPool( + [Tcd { + saddr: 0, + soff: 0, + attr: 0, + nbytes: 0, + slast: 0, + daddr: 0, + doff: 0, + citer: 0, + dlast_sga: 0, + csr: 0, + biter: 0, + }; 2], +); + +// AtomicBool to track scatter/gather completion +// Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, +// so we need this flag to detect when each transfer completes +static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); + +// Custom handler for scatter/gather that delegates to HAL's on_interrupt() +// This follows the "interrupts as threads" pattern - the handler does minimal work +// (delegates to HAL + sets a flag) and the main task does the actual processing +pub struct PingPongDmaHandler; + +impl embassy_mcxa::interrupt::typelevel::Handler for PingPongDmaHandler { + unsafe fn on_interrupt() { + // Delegate to HAL's on_interrupt() which clears INT flag and wakes wakers + dma::on_interrupt(0); + // Signal completion for polling (needed because ESG clears DONE bit) + TRANSFER_DONE.store(true, Ordering::Release); + } +} + +// Bind DMA channel interrupts +// CH0: Custom handler for scatter/gather (delegates to on_interrupt + sets flag) +// CH1: Standard handler for wait_half() demo +bind_interrupts!(struct Irqs { + DMA_CH0 => PingPongDmaHandler; + DMA_CH1 => DmaCh1InterruptHandler; +}); + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA ping-pong transfer example starting..."); + + // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"EDMA ping-pong transfer example begin.\r\n\r\n") + .unwrap(); + + // Initialize buffers + unsafe { + SRC = [1, 2, 3, 4, 5, 6, 7, 8]; + DST = [0; 8]; + } + + tx.blocking_write(b"Source Buffer: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC) as *const u32, 8); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Destination Buffer (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Configuring ping-pong DMA with Embassy-style API...\r\n") + .unwrap(); + + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Configure ping-pong transfer using direct TCD access: + // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. + // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), links to TCD1. + // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), links to TCD0. + unsafe { + let tcds = &mut *core::ptr::addr_of_mut!(TCD_POOL.0); + let src_ptr = core::ptr::addr_of!(SRC) as *const u32; + let dst_ptr = core::ptr::addr_of_mut!(DST) as *mut u32; + + let half_len = 4usize; + let half_bytes = (half_len * 4) as u32; + + let tcd0_addr = &tcds[0] as *const _ as u32; + let tcd1_addr = &tcds[1] as *const _ as u32; + + // TCD0: First half -> Links to TCD1 + tcds[0] = Tcd { + saddr: src_ptr as u32, + soff: 4, + attr: 0x0202, // 32-bit src/dst + nbytes: half_bytes, + slast: 0, + daddr: dst_ptr as u32, + doff: 4, + citer: 1, + dlast_sga: tcd1_addr as i32, + csr: 0x0012, // ESG | INTMAJOR + biter: 1, + }; + + // TCD1: Second half -> Links to TCD0 + tcds[1] = Tcd { + saddr: src_ptr.add(half_len) as u32, + soff: 4, + attr: 0x0202, + nbytes: half_bytes, + slast: 0, + daddr: dst_ptr.add(half_len) as u32, + doff: 4, + citer: 1, + dlast_sga: tcd0_addr as i32, + csr: 0x0012, + biter: 1, + }; + + // Load TCD0 into hardware registers + dma_ch0.load_tcd(&tcds[0]); + } + + tx.blocking_write(b"Triggering first half transfer...\r\n").unwrap(); + + // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) + unsafe { + dma_ch0.trigger_start(); + } + + // Wait for first half + while !TRANSFER_DONE.load(Ordering::Acquire) { + cortex_m::asm::nop(); + } + TRANSFER_DONE.store(false, Ordering::Release); + + tx.blocking_write(b"First half transferred.\r\n").unwrap(); + tx.blocking_write(b"Triggering second half transfer...\r\n").unwrap(); + + // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) + unsafe { + dma_ch0.trigger_start(); + } + + // Wait for second half + while !TRANSFER_DONE.load(Ordering::Acquire) { + cortex_m::asm::nop(); + } + TRANSFER_DONE.store(false, Ordering::Release); + + tx.blocking_write(b"Second half transferred.\r\n\r\n").unwrap(); + + tx.blocking_write(b"EDMA ping-pong transfer example finish.\r\n\r\n") + .unwrap(); + tx.blocking_write(b"Destination Buffer (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify: DST should match SRC + let mut mismatch = false; + unsafe { + let src_ptr = core::ptr::addr_of!(SRC) as *const u32; + let dst_ptr = core::ptr::addr_of!(DST) as *const u32; + for i in 0..8 { + if *src_ptr.add(i) != *dst_ptr.add(i) { + mismatch = true; + break; + } + } + } + + if mismatch { + tx.blocking_write(b"FAIL: Approach 1 mismatch detected!\r\n").unwrap(); + defmt::error!("FAIL: Approach 1 mismatch detected!"); + } else { + tx.blocking_write(b"PASS: Approach 1 data verified.\r\n\r\n").unwrap(); + defmt::info!("PASS: Approach 1 data verified."); + } + + // ========================================================================= + // Approach 2: Half-Transfer Interrupt with wait_half() (NEW!) + // ========================================================================= + // + // This approach uses a single continuous DMA transfer with half-transfer + // interrupt enabled. The wait_half() method allows you to be notified + // when the first half of the buffer is complete, so you can process it + // while the second half is still being filled. + // + // Benefits: + // - Simpler setup (no TCD pool needed) + // - True async/await support + // - Good for streaming data processing + + tx.blocking_write(b"--- Approach 2: wait_half() demo ---\r\n\r\n") + .unwrap(); + + // Enable DMA CH1 interrupt + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); + } + + // Initialize approach 2 buffers + unsafe { + SRC2 = [0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]; + DST2 = [0; 8]; + } + + tx.blocking_write(b"SRC2: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC2) as *const u32, 8); + tx.blocking_write(b"\r\n").unwrap(); + + let dma_ch1 = DmaChannel::new(p.DMA_CH1); + + // Configure transfer with half-transfer interrupt enabled + let mut options = TransferOptions::default(); + options.half_transfer_interrupt = true; // Enable half-transfer interrupt + options.complete_transfer_interrupt = true; + + tx.blocking_write(b"Starting transfer with half_transfer_interrupt...\r\n") + .unwrap(); + + unsafe { + let src = &*core::ptr::addr_of!(SRC2); + let dst = &mut *core::ptr::addr_of_mut!(DST2); + + // Create the transfer + let mut transfer = dma_ch1.mem_to_mem(src, dst, options); + + // Wait for half-transfer (first 4 elements) + tx.blocking_write(b"Waiting for first half...\r\n").unwrap(); + let half_ok = transfer.wait_half().await; + + if half_ok { + tx.blocking_write(b"Half-transfer complete! First half of DST2: ") + .unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b"(Processing first half while second half transfers...)\r\n") + .unwrap(); + } + + // Wait for complete transfer + tx.blocking_write(b"Waiting for second half...\r\n").unwrap(); + transfer.await; + } + + tx.blocking_write(b"Transfer complete! Full DST2: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 8); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify approach 2 + let mut mismatch2 = false; + unsafe { + let src_ptr = core::ptr::addr_of!(SRC2) as *const u32; + let dst_ptr = core::ptr::addr_of!(DST2) as *const u32; + for i in 0..8 { + if *src_ptr.add(i) != *dst_ptr.add(i) { + mismatch2 = true; + break; + } + } + } + + if mismatch2 { + tx.blocking_write(b"FAIL: Approach 2 mismatch!\r\n").unwrap(); + defmt::error!("FAIL: Approach 2 mismatch!"); + } else { + tx.blocking_write(b"PASS: Approach 2 verified.\r\n").unwrap(); + defmt::info!("PASS: Approach 2 verified."); + } + + tx.blocking_write(b"\r\n=== All ping-pong demos complete ===\r\n") + .unwrap(); + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/dma_scatter_gather.rs b/examples/mcxa/src/bin/dma_scatter_gather.rs new file mode 100644 index 000000000..4b26bc2ed --- /dev/null +++ b/examples/mcxa/src/bin/dma_scatter_gather.rs @@ -0,0 +1,262 @@ +//! DMA scatter-gather transfer example for MCXA276. +//! +//! This example demonstrates using DMA with scatter/gather to chain multiple +//! transfer descriptors. The first TCD transfers the first half of the buffer, +//! then automatically loads the second TCD to transfer the second half. +//! +//! # Embassy-style features demonstrated: +//! - `DmaChannel::new()` for channel creation +//! - Scatter/gather with chained TCDs +//! - Custom handler that delegates to HAL's `on_interrupt()` (best practice) + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicBool, Ordering}; + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{self, DmaChannel, Tcd}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Source and destination buffers +static mut SRC: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; +static mut DST: [u32; 8] = [0; 8]; + +// TCD pool for scatter/gather - must be 32-byte aligned +#[repr(C, align(32))] +struct TcdPool([Tcd; 2]); + +static mut TCD_POOL: TcdPool = TcdPool( + [Tcd { + saddr: 0, + soff: 0, + attr: 0, + nbytes: 0, + slast: 0, + daddr: 0, + doff: 0, + citer: 0, + dlast_sga: 0, + csr: 0, + biter: 0, + }; 2], +); + +// AtomicBool to track scatter/gather completion +// Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, +// so we need this flag to detect when each transfer completes +static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); + +// Custom handler for scatter/gather that delegates to HAL's on_interrupt() +// This follows the "interrupts as threads" pattern - the handler does minimal work +// (delegates to HAL + sets a flag) and the main task does the actual processing +pub struct ScatterGatherDmaHandler; + +impl embassy_mcxa::interrupt::typelevel::Handler + for ScatterGatherDmaHandler +{ + unsafe fn on_interrupt() { + // Delegate to HAL's on_interrupt() which clears INT flag and wakes wakers + dma::on_interrupt(0); + // Signal completion for polling (needed because ESG clears DONE bit) + TRANSFER_DONE.store(true, Ordering::Release); + } +} + +// Bind DMA channel interrupt +// Custom handler for scatter/gather (delegates to on_interrupt + sets flag) +bind_interrupts!(struct Irqs { + DMA_CH0 => ScatterGatherDmaHandler; +}); + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA scatter-gather transfer example starting..."); + + // DMA is initialized during hal::init() - no need to call ensure_init() + + // Enable DMA interrupt + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"EDMA scatter-gather transfer example begin.\r\n\r\n") + .unwrap(); + + // Initialize buffers + unsafe { + SRC = [1, 2, 3, 4, 5, 6, 7, 8]; + DST = [0; 8]; + } + + tx.blocking_write(b"Source Buffer: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC) as *const u32, 8); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Destination Buffer (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Configuring scatter-gather DMA with Embassy-style API...\r\n") + .unwrap(); + + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Configure scatter-gather transfer using direct TCD access: + // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. + // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), then loads TCD1. + // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), last TCD. + unsafe { + let tcds = core::slice::from_raw_parts_mut(core::ptr::addr_of_mut!(TCD_POOL.0) as *mut Tcd, 2); + let src_ptr = core::ptr::addr_of!(SRC) as *const u32; + let dst_ptr = core::ptr::addr_of_mut!(DST) as *mut u32; + + let num_tcds = 2usize; + let chunk_len = 4usize; // 8 / 2 + let chunk_bytes = (chunk_len * 4) as u32; + + for i in 0..num_tcds { + let is_last = i == num_tcds - 1; + let next_tcd_addr = if is_last { + 0 // No next TCD + } else { + &tcds[i + 1] as *const _ as u32 + }; + + tcds[i] = Tcd { + saddr: src_ptr.add(i * chunk_len) as u32, + soff: 4, + attr: 0x0202, // 32-bit src/dst + nbytes: chunk_bytes, + slast: 0, + daddr: dst_ptr.add(i * chunk_len) as u32, + doff: 4, + citer: 1, + dlast_sga: next_tcd_addr as i32, + // ESG (scatter/gather) for non-last, INTMAJOR for all + csr: if is_last { 0x0002 } else { 0x0012 }, + biter: 1, + }; + } + + // Load TCD0 into hardware registers + dma_ch0.load_tcd(&tcds[0]); + } + + tx.blocking_write(b"Triggering first half transfer...\r\n").unwrap(); + + // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) + // TCD0 is currently loaded. + unsafe { + dma_ch0.trigger_start(); + } + + // Wait for first half + while !TRANSFER_DONE.load(Ordering::Acquire) { + cortex_m::asm::nop(); + } + TRANSFER_DONE.store(false, Ordering::Release); + + tx.blocking_write(b"First half transferred.\r\n").unwrap(); + tx.blocking_write(b"Triggering second half transfer...\r\n").unwrap(); + + // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) + // TCD1 should have been loaded by the scatter/gather engine. + unsafe { + dma_ch0.trigger_start(); + } + + // Wait for second half + while !TRANSFER_DONE.load(Ordering::Acquire) { + cortex_m::asm::nop(); + } + TRANSFER_DONE.store(false, Ordering::Release); + + tx.blocking_write(b"Second half transferred.\r\n\r\n").unwrap(); + + tx.blocking_write(b"EDMA scatter-gather transfer example finish.\r\n\r\n") + .unwrap(); + tx.blocking_write(b"Destination Buffer (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify: DST should match SRC + let mut mismatch = false; + unsafe { + let src_ptr = core::ptr::addr_of!(SRC) as *const u32; + let dst_ptr = core::ptr::addr_of!(DST) as *const u32; + for i in 0..8 { + if *src_ptr.add(i) != *dst_ptr.add(i) { + mismatch = true; + break; + } + } + } + + if mismatch { + tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); + defmt::error!("FAIL: Mismatch detected!"); + } else { + tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); + defmt::info!("PASS: Data verified."); + } + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/dma_scatter_gather_builder.rs b/examples/mcxa/src/bin/dma_scatter_gather_builder.rs new file mode 100644 index 000000000..e483bb81f --- /dev/null +++ b/examples/mcxa/src/bin/dma_scatter_gather_builder.rs @@ -0,0 +1,231 @@ +//! DMA Scatter-Gather Builder example for MCXA276. +//! +//! This example demonstrates using the new `ScatterGatherBuilder` API for +//! chaining multiple DMA transfers with a type-safe builder pattern. +//! +//! # Features demonstrated: +//! - `ScatterGatherBuilder::new()` for creating a builder +//! - `add_transfer()` for adding memory-to-memory segments +//! - `build()` to start the chained transfer +//! - Automatic TCD linking and ESG bit management +//! +//! # Comparison with manual scatter-gather: +//! The manual approach (see `dma_scatter_gather.rs`) requires: +//! - Manual TCD pool allocation and alignment +//! - Manual CSR/ESG/INTMAJOR bit manipulation +//! - Manual dlast_sga address calculations +//! +//! The builder approach handles all of this automatically! + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel, ScatterGatherBuilder}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel 0 interrupt +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; +}); + +// Source buffers (multiple segments) +static mut SRC1: [u32; 4] = [0x11111111, 0x22222222, 0x33333333, 0x44444444]; +static mut SRC2: [u32; 4] = [0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC, 0xDDDDDDDD]; +static mut SRC3: [u32; 4] = [0x12345678, 0x9ABCDEF0, 0xFEDCBA98, 0x76543210]; + +// Destination buffers (one per segment) +static mut DST1: [u32; 4] = [0; 4]; +static mut DST2: [u32; 4] = [0; 4]; +static mut DST3: [u32; 4] = [0; 4]; + +/// Helper to write a u32 as hex to UART +fn write_hex(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + const HEX: &[u8; 16] = b"0123456789ABCDEF"; + for i in (0..8).rev() { + let nibble = ((val >> (i * 4)) & 0xF) as usize; + tx.blocking_write(&[HEX[nibble]]).ok(); + } +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_hex(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA Scatter-Gather Builder example starting..."); + + // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + // Create UART for debug output + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"DMA Scatter-Gather Builder Example\r\n").unwrap(); + tx.blocking_write(b"===================================\r\n\r\n") + .unwrap(); + + // Show source buffers + tx.blocking_write(b"Source buffers:\r\n").unwrap(); + tx.blocking_write(b" SRC1: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC1) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b" SRC2: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC2) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b" SRC3: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(SRC3) as *const u32, 4); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + tx.blocking_write(b"Destination buffers (before):\r\n").unwrap(); + tx.blocking_write(b" DST1: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST1) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b" DST2: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b" DST3: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST3) as *const u32, 4); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Create DMA channel + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + tx.blocking_write(b"Building scatter-gather chain with builder API...\r\n") + .unwrap(); + + // ========================================================================= + // ScatterGatherBuilder API demonstration + // ========================================================================= + // + // The builder pattern makes scatter-gather transfers much easier: + // 1. Create a builder + // 2. Add transfer segments with add_transfer() + // 3. Call build() to start the entire chain + // No manual TCD manipulation required! + + let mut builder = ScatterGatherBuilder::::new(); + + // Add three transfer segments - the builder handles TCD linking automatically + unsafe { + let src1 = &*core::ptr::addr_of!(SRC1); + let dst1 = &mut *core::ptr::addr_of_mut!(DST1); + builder.add_transfer(src1, dst1); + } + + unsafe { + let src2 = &*core::ptr::addr_of!(SRC2); + let dst2 = &mut *core::ptr::addr_of_mut!(DST2); + builder.add_transfer(src2, dst2); + } + + unsafe { + let src3 = &*core::ptr::addr_of!(SRC3); + let dst3 = &mut *core::ptr::addr_of_mut!(DST3); + builder.add_transfer(src3, dst3); + } + + tx.blocking_write(b"Added 3 transfer segments to chain.\r\n").unwrap(); + tx.blocking_write(b"Starting scatter-gather transfer with .await...\r\n\r\n") + .unwrap(); + + // Build and execute the scatter-gather chain + // The build() method: + // - Links all TCDs together with ESG bit + // - Sets INTMAJOR on all TCDs + // - Loads the first TCD into hardware + // - Returns a Transfer future + unsafe { + let transfer = builder.build(&dma_ch0).expect("Failed to build scatter-gather"); + transfer.blocking_wait(); + } + + tx.blocking_write(b"Scatter-gather transfer complete!\r\n\r\n").unwrap(); + + // Show results + tx.blocking_write(b"Destination buffers (after):\r\n").unwrap(); + tx.blocking_write(b" DST1: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST1) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b" DST2: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + tx.blocking_write(b" DST3: ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST3) as *const u32, 4); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify all three segments + let mut all_ok = true; + unsafe { + let src1 = core::ptr::addr_of!(SRC1) as *const u32; + let dst1 = core::ptr::addr_of!(DST1) as *const u32; + for i in 0..4 { + if *src1.add(i) != *dst1.add(i) { + all_ok = false; + } + } + + let src2 = core::ptr::addr_of!(SRC2) as *const u32; + let dst2 = core::ptr::addr_of!(DST2) as *const u32; + for i in 0..4 { + if *src2.add(i) != *dst2.add(i) { + all_ok = false; + } + } + + let src3 = core::ptr::addr_of!(SRC3) as *const u32; + let dst3 = core::ptr::addr_of!(DST3) as *const u32; + for i in 0..4 { + if *src3.add(i) != *dst3.add(i) { + all_ok = false; + } + } + } + + if all_ok { + tx.blocking_write(b"PASS: All segments verified!\r\n").unwrap(); + defmt::info!("PASS: All segments verified!"); + } else { + tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); + defmt::error!("FAIL: Mismatch detected!"); + } + + tx.blocking_write(b"\r\n=== Scatter-Gather Builder example complete ===\r\n") + .unwrap(); + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/dma_wrap_transfer.rs b/examples/mcxa/src/bin/dma_wrap_transfer.rs new file mode 100644 index 000000000..82936d9d0 --- /dev/null +++ b/examples/mcxa/src/bin/dma_wrap_transfer.rs @@ -0,0 +1,222 @@ +//! DMA wrap transfer example for MCXA276. +//! +//! This example demonstrates using DMA with modulo addressing to wrap around +//! a source buffer, effectively repeating the source data in the destination. +//! +//! # Embassy-style features demonstrated: +//! - `DmaChannel::is_done()` and `clear_done()` helper methods +//! - No need to pass register block around + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel 0 interrupt using Embassy-style macro +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; +}); + +// Source buffer: 4 words (16 bytes), aligned to 16 bytes for modulo +#[repr(align(16))] +struct AlignedSrc([u32; 4]); + +static mut SRC: AlignedSrc = AlignedSrc([0; 4]); +static mut DST: [u32; 8] = [0; 8]; + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA wrap transfer example starting..."); + + // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"EDMA wrap transfer example begin.\r\n\r\n").unwrap(); + + // Initialize buffers + unsafe { + SRC.0 = [1, 2, 3, 4]; + DST = [0; 8]; + } + + tx.blocking_write(b"Source Buffer: ").unwrap(); + print_buffer(&mut tx, unsafe { core::ptr::addr_of!(SRC.0) } as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Destination Buffer (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") + .unwrap(); + + // Create DMA channel using Embassy-style API + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Configure wrap transfer using direct TCD access: + // SRC is 16 bytes (4 * u32). We want to transfer 32 bytes (8 * u32). + // SRC modulo is 16 bytes (2^4 = 16) - wraps source address. + // DST modulo is 0 (disabled). + // This causes the source address to wrap around after 16 bytes, + // effectively repeating the source data. + unsafe { + let t = dma_ch0.tcd(); + + // Reset channel state + t.ch_csr().write(|w| { + w.erq() + .disable() + .earq() + .disable() + .eei() + .no_error() + .ebw() + .disable() + .done() + .clear_bit_by_one() + }); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Source/destination addresses + t.tcd_saddr() + .write(|w| w.saddr().bits(core::ptr::addr_of!(SRC.0) as u32)); + t.tcd_daddr() + .write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DST) as u32)); + + // Offsets: both increment by 4 bytes + t.tcd_soff().write(|w| w.soff().bits(4)); + t.tcd_doff().write(|w| w.doff().bits(4)); + + // Attributes: 32-bit transfers (size = 2) + // SMOD = 4 (2^4 = 16 byte modulo for source), DMOD = 0 (disabled) + t.tcd_attr().write(|w| { + w.ssize() + .bits(2) + .dsize() + .bits(2) + .smod() + .bits(4) // Source modulo: 2^4 = 16 bytes + .dmod() + .bits(0) // Dest modulo: disabled + }); + + // Transfer 32 bytes total in one minor loop + let nbytes = 32u32; + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); + + // Source wraps via modulo, no adjustment needed + t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); + // Reset dest address after major loop + t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); + + // Major loop count = 1 + t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); + t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); + + // Enable interrupt on major loop completion + t.tcd_csr().write(|w| w.intmajor().set_bit()); + + cortex_m::asm::dsb(); + + tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); + dma_ch0.trigger_start(); + } + + // Wait for completion using channel helper method + while !dma_ch0.is_done() { + cortex_m::asm::nop(); + } + unsafe { + dma_ch0.clear_done(); + } + + tx.blocking_write(b"\r\nEDMA wrap transfer example finish.\r\n\r\n") + .unwrap(); + tx.blocking_write(b"Destination Buffer (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify: DST should be [1, 2, 3, 4, 1, 2, 3, 4] + let expected = [1u32, 2, 3, 4, 1, 2, 3, 4]; + let mut mismatch = false; + unsafe { + for i in 0..8 { + if DST[i] != expected[i] { + mismatch = true; + break; + } + } + } + + if mismatch { + tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); + defmt::error!("FAIL: Mismatch detected!"); + } else { + tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); + defmt::info!("PASS: Data verified."); + } + + loop { + cortex_m::asm::wfe(); + } +} diff --git a/examples/mcxa/src/bin/hello.rs b/examples/mcxa/src/bin/hello.rs new file mode 100644 index 000000000..e371d9413 --- /dev/null +++ b/examples/mcxa/src/bin/hello.rs @@ -0,0 +1,119 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use hal::lpuart::{Blocking, Config, Lpuart}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +/// Simple helper to write a byte as hex to UART +fn write_hex_byte(uart: &mut Lpuart<'_, Blocking>, byte: u8) { + const HEX_DIGITS: &[u8] = b"0123456789ABCDEF"; + let _ = uart.write_byte(HEX_DIGITS[(byte >> 4) as usize]); + let _ = uart.write_byte(HEX_DIGITS[(byte & 0xF) as usize]); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("boot"); + + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + // Create UART instance using LPUART2 with P2_2 as TX and P2_3 as RX + let mut uart = Lpuart::new_blocking( + p.LPUART2, // Peripheral + p.P2_2, // TX pin + p.P2_3, // RX pin + config, + ) + .unwrap(); + + // Print welcome message before any async delays to guarantee early console output + uart.write_str_blocking("\r\n=== MCXA276 UART Echo Demo ===\r\n"); + uart.write_str_blocking("Available commands:\r\n"); + uart.write_str_blocking(" help - Show this help\r\n"); + uart.write_str_blocking(" echo - Echo back the text\r\n"); + uart.write_str_blocking(" hex - Display byte in hex (0-255)\r\n"); + uart.write_str_blocking("Type a command: "); + + let mut buffer = [0u8; 64]; + let mut buf_idx = 0; + + loop { + // Read a byte from UART + let byte = uart.read_byte_blocking(); + + // Echo the character back + if byte == b'\r' || byte == b'\n' { + // Enter pressed - process command + uart.write_str_blocking("\r\n"); + + if buf_idx > 0 { + let command = &buffer[0..buf_idx]; + + if command == b"help" { + uart.write_str_blocking("Available commands:\r\n"); + uart.write_str_blocking(" help - Show this help\r\n"); + uart.write_str_blocking(" echo - Echo back the text\r\n"); + uart.write_str_blocking(" hex - Display byte in hex (0-255)\r\n"); + } else if command.starts_with(b"echo ") && command.len() > 5 { + uart.write_str_blocking("Echo: "); + uart.write_str_blocking(core::str::from_utf8(&command[5..]).unwrap_or("")); + uart.write_str_blocking("\r\n"); + } else if command.starts_with(b"hex ") && command.len() > 4 { + // Parse the byte value + let num_str = &command[4..]; + if let Ok(num) = parse_u8(num_str) { + uart.write_str_blocking("Hex: 0x"); + write_hex_byte(&mut uart, num); + uart.write_str_blocking("\r\n"); + } else { + uart.write_str_blocking("Invalid number for hex command\r\n"); + } + } else if !command.is_empty() { + uart.write_str_blocking("Unknown command: "); + uart.write_str_blocking(core::str::from_utf8(command).unwrap_or("")); + uart.write_str_blocking("\r\n"); + } + } + + // Reset buffer and prompt + buf_idx = 0; + uart.write_str_blocking("Type a command: "); + } else if byte == 8 || byte == 127 { + // Backspace + if buf_idx > 0 { + buf_idx -= 1; + uart.write_str_blocking("\x08 \x08"); // Erase character + } + } else if buf_idx < buffer.len() - 1 { + // Regular character + buffer[buf_idx] = byte; + buf_idx += 1; + let _ = uart.write_byte(byte); + } + } +} + +/// Simple parser for u8 from ASCII bytes +fn parse_u8(bytes: &[u8]) -> Result { + let mut result = 0u8; + for &b in bytes { + if b.is_ascii_digit() { + result = result.checked_mul(10).ok_or(())?; + result = result.checked_add(b - b'0').ok_or(())?; + } else { + return Err(()); + } + } + Ok(result) +} diff --git a/examples/mcxa/src/bin/i2c-blocking.rs b/examples/mcxa/src/bin/i2c-blocking.rs new file mode 100644 index 000000000..0f6c8cbae --- /dev/null +++ b/examples/mcxa/src/bin/i2c-blocking.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_time::Timer; +use hal::clocks::config::Div8; +use hal::config::Config; +use hal::i2c::controller::{self, I2c, Speed}; +use tmp108::Tmp108; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); + + let p = hal::init(config); + + defmt::info!("I2C example"); + + let mut config = controller::Config::default(); + config.speed = Speed::Standard; + let i2c = I2c::new_blocking(p.LPI2C3, p.P3_27, p.P3_28, config).unwrap(); + let mut tmp = Tmp108::new_with_a0_gnd(i2c); + + loop { + let temperature = tmp.temperature().unwrap(); + defmt::info!("Temperature: {}C", temperature); + Timer::after_secs(1).await; + } +} diff --git a/examples/mcxa/src/bin/i2c-scan-blocking.rs b/examples/mcxa/src/bin/i2c-scan-blocking.rs new file mode 100644 index 000000000..4e203597b --- /dev/null +++ b/examples/mcxa/src/bin/i2c-scan-blocking.rs @@ -0,0 +1,41 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::gpio::Pull; +use embassy_mcxa::Input; +use embassy_time::Timer; +use hal::clocks::config::Div8; +use hal::config::Config; +use hal::i2c::controller::{self, I2c, Speed}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); + + let p = hal::init(config); + + defmt::info!("I2C example"); + + let mut config = controller::Config::default(); + config.speed = Speed::Standard; + + // Note: P0_2 is connected to P1_8 on the FRDM_MCXA276 via a resistor, and + // defaults to SWO on the debug peripheral. Explicitly make it a high-z + // input. + let _pin = Input::new(p.P0_2, Pull::Disabled); + let mut i2c = I2c::new_blocking(p.LPI2C2, p.P1_9, p.P1_8, config).unwrap(); + + for addr in 0x01..=0x7f { + let result = i2c.blocking_write(addr, &[]); + if result.is_ok() { + defmt::info!("Device found at addr {:02x}", addr); + } + } + + loop { + Timer::after_secs(10).await; + } +} diff --git a/examples/mcxa/src/bin/lpuart_buffered.rs b/examples/mcxa/src/bin/lpuart_buffered.rs new file mode 100644 index 000000000..420589d00 --- /dev/null +++ b/examples/mcxa/src/bin/lpuart_buffered.rs @@ -0,0 +1,62 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::lpuart::buffered::BufferedLpuart; +use embassy_mcxa::lpuart::Config; +use embassy_mcxa::{bind_interrupts, lpuart}; +use embedded_io_async::Write; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind OS_EVENT for timers plus LPUART2 IRQ for the buffered driver +bind_interrupts!(struct Irqs { + LPUART2 => lpuart::buffered::BufferedInterruptHandler::; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + // Configure NVIC for LPUART2 + hal::interrupt::LPUART2.configure_for_uart(hal::interrupt::Priority::P3); + + // UART configuration (enable both TX and RX) + let config = Config { + baudrate_bps: 115_200, + rx_fifo_watermark: 0, + tx_fifo_watermark: 0, + ..Default::default() + }; + + let mut tx_buf = [0u8; 256]; + let mut rx_buf = [0u8; 256]; + + // Create a buffered LPUART2 instance with both TX and RX + let mut uart = BufferedLpuart::new( + p.LPUART2, + p.P2_2, // TX pin + p.P2_3, // RX pin + Irqs, + &mut tx_buf, + &mut rx_buf, + config, + ) + .unwrap(); + + // Split into TX and RX parts + let (tx, rx) = uart.split_ref(); + + tx.write(b"Hello buffered LPUART.\r\n").await.unwrap(); + tx.write(b"Type characters to echo them back.\r\n").await.unwrap(); + + // Echo loop + let mut buf = [0u8; 4]; + loop { + let used = rx.read(&mut buf).await.unwrap(); + tx.write_all(&buf[..used]).await.unwrap(); + } +} diff --git a/examples/mcxa/src/bin/lpuart_dma.rs b/examples/mcxa/src/bin/lpuart_dma.rs new file mode 100644 index 000000000..5497f8646 --- /dev/null +++ b/examples/mcxa/src/bin/lpuart_dma.rs @@ -0,0 +1,81 @@ +//! LPUART DMA example for MCXA276. +//! +//! This example demonstrates using DMA for UART TX and RX operations. +//! It sends a message using DMA, then waits for 16 characters to be received +//! via DMA and echoes them back. +//! +//! The DMA request sources are automatically derived from the LPUART instance type. +//! DMA clock/reset/init is handled automatically by the HAL. + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler}; +use embassy_mcxa::lpuart::{Config, LpuartDma}; +use embassy_mcxa::{bind_interrupts, pac}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel interrupts using Embassy-style macro +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; + DMA_CH1 => DmaCh1InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("LPUART DMA example starting..."); + + // Enable DMA interrupts (per-channel, as needed) + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); + } + + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + // Create UART instance with DMA channels + let mut lpuart = LpuartDma::new( + p.LPUART2, p.P2_2, // TX pin + p.P2_3, // RX pin + p.DMA_CH0, // TX DMA channel + p.DMA_CH1, // RX DMA channel + config, + ) + .unwrap(); + + // Send a message using DMA (DMA request source is automatically derived from LPUART2) + let tx_msg = b"Hello from LPUART2 DMA TX!\r\n"; + lpuart.write_dma(tx_msg).await.unwrap(); + + defmt::info!("TX DMA complete"); + + // Send prompt + let prompt = b"Type 16 characters to echo via DMA:\r\n"; + lpuart.write_dma(prompt).await.unwrap(); + + // Receive 16 characters using DMA + let mut rx_buf = [0u8; 16]; + lpuart.read_dma(&mut rx_buf).await.unwrap(); + + defmt::info!("RX DMA complete"); + + // Echo back the received data + let echo_prefix = b"\r\nReceived: "; + lpuart.write_dma(echo_prefix).await.unwrap(); + lpuart.write_dma(&rx_buf).await.unwrap(); + let done_msg = b"\r\nDone!\r\n"; + lpuart.write_dma(done_msg).await.unwrap(); + + defmt::info!("Example complete"); +} diff --git a/examples/mcxa/src/bin/lpuart_polling.rs b/examples/mcxa/src/bin/lpuart_polling.rs new file mode 100644 index 000000000..b80668834 --- /dev/null +++ b/examples/mcxa/src/bin/lpuart_polling.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +use crate::hal::lpuart::{Config, Lpuart}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("boot"); + + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + // Create UART instance using LPUART2 with P2_2 as TX and P2_3 as RX + let lpuart = Lpuart::new_blocking( + p.LPUART2, // Peripheral + p.P2_2, // TX pin + p.P2_3, // RX pin + config, + ) + .unwrap(); + + // Split into separate TX and RX parts + let (mut tx, mut rx) = lpuart.split(); + + // Write hello messages + tx.blocking_write(b"Hello world.\r\n").unwrap(); + tx.blocking_write(b"Echoing. Type characters...\r\n").unwrap(); + + // Echo loop + loop { + let mut buf = [0u8; 1]; + rx.blocking_read(&mut buf).unwrap(); + tx.blocking_write(&buf).unwrap(); + } +} diff --git a/examples/mcxa/src/bin/lpuart_ring_buffer.rs b/examples/mcxa/src/bin/lpuart_ring_buffer.rs new file mode 100644 index 000000000..1d1a51970 --- /dev/null +++ b/examples/mcxa/src/bin/lpuart_ring_buffer.rs @@ -0,0 +1,130 @@ +//! LPUART Ring Buffer DMA example for MCXA276. +//! +//! This example demonstrates using the high-level `LpuartRxDma::setup_ring_buffer()` +//! API for continuous circular DMA reception from a UART peripheral. +//! +//! # Features demonstrated: +//! - `LpuartRxDma::setup_ring_buffer()` for continuous peripheral-to-memory DMA +//! - `RingBuffer` for async reading of received data +//! - Handling of potential overrun conditions +//! - Half-transfer and complete-transfer interrupts for timely wakeups +//! +//! # How it works: +//! 1. Create an `LpuartRxDma` driver with a DMA channel +//! 2. Call `setup_ring_buffer()` which handles all low-level DMA configuration +//! 3. Application asynchronously reads data as it arrives via `ring_buf.read()` +//! 4. Both half-transfer and complete-transfer interrupts wake the reader + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::bind_interrupts; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler}; +use embassy_mcxa::lpuart::{Config, LpuartDma, LpuartTxDma}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel interrupts +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; + DMA_CH1 => DmaCh1InterruptHandler; +}); + +// Ring buffer for RX - power of 2 is ideal for modulo efficiency +static mut RX_RING_BUFFER: [u8; 64] = [0; 64]; + +/// Helper to write a byte as hex to UART +fn write_hex( + tx: &mut LpuartTxDma<'_, T, C>, + byte: u8, +) { + const HEX: &[u8; 16] = b"0123456789ABCDEF"; + let buf = [HEX[(byte >> 4) as usize], HEX[(byte & 0x0F) as usize]]; + tx.blocking_write(&buf).ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("LPUART Ring Buffer DMA example starting..."); + + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + ..Default::default() + }; + + // Create LPUART with DMA support for both TX and RX, then split + // This is the proper Embassy pattern - create once, split into TX and RX + let lpuart = LpuartDma::new(p.LPUART2, p.P2_2, p.P2_3, p.DMA_CH1, p.DMA_CH0, config).unwrap(); + let (mut tx, rx) = lpuart.split(); + + tx.blocking_write(b"LPUART Ring Buffer DMA Example\r\n").unwrap(); + tx.blocking_write(b"==============================\r\n\r\n").unwrap(); + + tx.blocking_write(b"Setting up circular DMA for UART RX...\r\n") + .unwrap(); + + // Set up the ring buffer with circular DMA + // The HAL handles: DMA request source, RDMAE enable, circular transfer config, NVIC enable + let ring_buf = unsafe { + let buf = &mut *core::ptr::addr_of_mut!(RX_RING_BUFFER); + rx.setup_ring_buffer(buf) + }; + + // Enable DMA requests to start continuous reception + unsafe { + rx.enable_dma_request(); + } + + tx.blocking_write(b"Ring buffer ready! Type characters to see them echoed.\r\n") + .unwrap(); + tx.blocking_write(b"The DMA continuously receives in the background.\r\n\r\n") + .unwrap(); + + // Main loop: read from ring buffer and echo back + let mut read_buf = [0u8; 16]; + let mut total_received: usize = 0; + + loop { + // Async read - waits until data is available + match ring_buf.read(&mut read_buf).await { + Ok(n) if n > 0 => { + total_received += n; + + // Echo back what we received + tx.blocking_write(b"RX[").unwrap(); + for (i, &byte) in read_buf.iter().enumerate().take(n) { + write_hex(&mut tx, byte); + if i < n - 1 { + tx.blocking_write(b" ").unwrap(); + } + } + tx.blocking_write(b"]: ").unwrap(); + tx.blocking_write(&read_buf[..n]).unwrap(); + tx.blocking_write(b"\r\n").unwrap(); + + defmt::info!("Received {} bytes, total: {}", n, total_received); + } + Ok(_) => { + // No data, shouldn't happen with async read + } + Err(_) => { + // Overrun detected + tx.blocking_write(b"ERROR: Ring buffer overrun!\r\n").unwrap(); + defmt::error!("Ring buffer overrun!"); + ring_buf.clear(); + } + } + } +} diff --git a/examples/mcxa/src/bin/rtc_alarm.rs b/examples/mcxa/src/bin/rtc_alarm.rs new file mode 100644 index 000000000..a7800a2d1 --- /dev/null +++ b/examples/mcxa/src/bin/rtc_alarm.rs @@ -0,0 +1,74 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa as hal; +use hal::rtc::{RtcDateTime, RtcInterruptEnable}; +use hal::InterruptExt; + +type MyRtc = hal::rtc::Rtc<'static, hal::rtc::Rtc0>; + +use embassy_mcxa::bind_interrupts; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RTC => hal::rtc::RtcHandler; +}); + +#[used] +#[no_mangle] +static KEEP_RTC: unsafe extern "C" fn() = RTC; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = hal::init(hal::config::Config::default()); + + defmt::info!("=== RTC Alarm Example ==="); + + let rtc_config = hal::rtc::get_default_config(); + + let rtc = MyRtc::new(p.RTC0, rtc_config); + + let now = RtcDateTime { + year: 2025, + month: 10, + day: 15, + hour: 14, + minute: 30, + second: 0, + }; + + rtc.stop(); + + defmt::info!("Time set to: 2025-10-15 14:30:00"); + rtc.set_datetime(now); + + let mut alarm = now; + alarm.second += 10; + + rtc.set_alarm(alarm); + defmt::info!("Alarm set for: 2025-10-15 14:30:10 (+10 seconds)"); + + rtc.set_interrupt(RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE); + + unsafe { + hal::interrupt::RTC.enable(); + } + + unsafe { + cortex_m::interrupt::enable(); + } + + rtc.start(); + + defmt::info!("RTC started, waiting for alarm..."); + + loop { + if rtc.is_alarm_triggered() { + defmt::info!("*** ALARM TRIGGERED! ***"); + break; + } + } + + defmt::info!("Example complete - Test PASSED!"); +} diff --git a/examples/mcxa/src/lib.rs b/examples/mcxa/src/lib.rs new file mode 100644 index 000000000..2573a6adc --- /dev/null +++ b/examples/mcxa/src/lib.rs @@ -0,0 +1,16 @@ +#![no_std] +#![allow(clippy::missing_safety_doc)] + +//! Shared board-specific helpers for the FRDM-MCXA276 examples. +//! These live with the examples so the HAL stays generic. + +use hal::{clocks, pins}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +/// Initialize clocks and pin muxing for ADC. +pub unsafe fn init_adc_pins() { + // NOTE: Lpuart has been updated to properly enable + reset its own clocks. + // GPIO has not. + _ = clocks::enable_and_reset::(&clocks::periph_helpers::NoConfig); + pins::configure_adc_pins(); +} diff --git a/examples/memory.x b/examples/memory.x deleted file mode 100644 index 315ced58a..000000000 --- a/examples/memory.x +++ /dev/null @@ -1,5 +0,0 @@ -MEMORY -{ - FLASH : ORIGIN = 0x00000000, LENGTH = 1M - RAM : ORIGIN = 0x20000000, LENGTH = 128K -} diff --git a/examples/src/bin/adc_interrupt.rs b/examples/src/bin/adc_interrupt.rs deleted file mode 100644 index 83d8046b3..000000000 --- a/examples/src/bin/adc_interrupt.rs +++ /dev/null @@ -1,84 +0,0 @@ -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa_examples::init_adc_pins; -use hal::adc::{LpadcConfig, TriggerPriorityPolicy}; -use hal::clocks::periph_helpers::{AdcClockSel, Div4}; -use hal::clocks::PoweredClock; -use hal::pac::adc1::cfg::{Pwrsel, Refsel}; -use hal::pac::adc1::cmdl1::{Adch, Mode}; -use hal::pac::adc1::ctrl::CalAvgs; -use hal::pac::adc1::tctrl::Tcmd; -use hal::{bind_interrupts, InterruptExt}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -bind_interrupts!(struct Irqs { - ADC1 => hal::adc::AdcHandler; -}); - -#[used] -#[no_mangle] -static KEEP_ADC: unsafe extern "C" fn() = ADC1; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = hal::init(hal::config::Config::default()); - - defmt::info!("ADC interrupt Example"); - - unsafe { - init_adc_pins(); - } - - let adc_config = LpadcConfig { - enable_in_doze_mode: true, - conversion_average_mode: CalAvgs::Average128, - enable_analog_preliminary: true, - power_up_delay: 0x80, - reference_voltage_source: Refsel::Option3, - power_level_mode: Pwrsel::Lowest, - trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, - enable_conv_pause: false, - conv_pause_delay: 0, - fifo_watermark: 0, - power: PoweredClock::NormalEnabledDeepSleepDisabled, - source: AdcClockSel::FroLfDiv, - div: Div4::no_div(), - }; - let adc = hal::adc::Adc::::new(p.ADC1, adc_config); - - adc.do_offset_calibration(); - adc.do_auto_calibration(); - - let mut conv_command_config = adc.get_default_conv_command_config(); - conv_command_config.channel_number = Adch::SelectCorrespondingChannel8; - conv_command_config.conversion_resolution_mode = Mode::Data16Bits; - adc.set_conv_command_config(1, &conv_command_config); - - let mut conv_trigger_config = adc.get_default_conv_trigger_config(); - conv_trigger_config.target_command_id = Tcmd::ExecuteCmd1; - conv_trigger_config.enable_hardware_trigger = false; - adc.set_conv_trigger_config(0, &conv_trigger_config); - - defmt::info!("ADC configuration done..."); - - adc.enable_interrupt(0x1); - - unsafe { - hal::interrupt::ADC1.enable(); - } - - unsafe { - cortex_m::interrupt::enable(); - } - - loop { - adc.do_software_trigger(1); - while !adc.is_interrupt_triggered() { - // Wait until the interrupt is triggered - } - defmt::info!("*** ADC interrupt TRIGGERED! ***"); - //TBD need to print the value - } -} diff --git a/examples/src/bin/adc_polling.rs b/examples/src/bin/adc_polling.rs deleted file mode 100644 index ddf3f586b..000000000 --- a/examples/src/bin/adc_polling.rs +++ /dev/null @@ -1,68 +0,0 @@ -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa_examples::init_adc_pins; -use hal::adc::{ConvResult, LpadcConfig, TriggerPriorityPolicy}; -use hal::clocks::periph_helpers::{AdcClockSel, Div4}; -use hal::clocks::PoweredClock; -use hal::pac::adc1::cfg::{Pwrsel, Refsel}; -use hal::pac::adc1::cmdl1::{Adch, Mode}; -use hal::pac::adc1::ctrl::CalAvgs; -use hal::pac::adc1::tctrl::Tcmd; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -const G_LPADC_RESULT_SHIFT: u32 = 0; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = hal::init(hal::config::Config::default()); - - unsafe { - init_adc_pins(); - } - - defmt::info!("=== ADC polling Example ==="); - - let adc_config = LpadcConfig { - enable_in_doze_mode: true, - conversion_average_mode: CalAvgs::Average128, - enable_analog_preliminary: true, - power_up_delay: 0x80, - reference_voltage_source: Refsel::Option3, - power_level_mode: Pwrsel::Lowest, - trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, - enable_conv_pause: false, - conv_pause_delay: 0, - fifo_watermark: 0, - power: PoweredClock::NormalEnabledDeepSleepDisabled, - source: AdcClockSel::FroLfDiv, - div: Div4::no_div(), - }; - let adc = hal::adc::Adc::::new(p.ADC1, adc_config); - - adc.do_offset_calibration(); - adc.do_auto_calibration(); - - let mut conv_command_config = adc.get_default_conv_command_config(); - conv_command_config.channel_number = Adch::SelectCorrespondingChannel8; - conv_command_config.conversion_resolution_mode = Mode::Data16Bits; - adc.set_conv_command_config(1, &conv_command_config); - - let mut conv_trigger_config = adc.get_default_conv_trigger_config(); - conv_trigger_config.target_command_id = Tcmd::ExecuteCmd1; - conv_trigger_config.enable_hardware_trigger = false; - adc.set_conv_trigger_config(0, &conv_trigger_config); - - defmt::info!("=== ADC configuration done... ==="); - - loop { - adc.do_software_trigger(1); - let mut result: Option = None; - while result.is_none() { - result = hal::adc::get_conv_result(); - } - let value = result.unwrap().conv_value >> G_LPADC_RESULT_SHIFT; - defmt::info!("value: {=u16}", value); - } -} diff --git a/examples/src/bin/blinky.rs b/examples/src/bin/blinky.rs deleted file mode 100644 index dd08ec0d9..000000000 --- a/examples/src/bin/blinky.rs +++ /dev/null @@ -1,36 +0,0 @@ -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_time::Timer; -use hal::gpio::{DriveStrength, Level, Output, SlewRate}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = hal::init(hal::config::Config::default()); - - defmt::info!("Blink example"); - - let mut red = Output::new(p.P3_18, Level::High, DriveStrength::Normal, SlewRate::Fast); - let mut green = Output::new(p.P3_19, Level::High, DriveStrength::Normal, SlewRate::Fast); - let mut blue = Output::new(p.P3_21, Level::High, DriveStrength::Normal, SlewRate::Fast); - - loop { - defmt::info!("Toggle LEDs"); - - red.toggle(); - Timer::after_millis(250).await; - - red.toggle(); - green.toggle(); - Timer::after_millis(250).await; - - green.toggle(); - blue.toggle(); - Timer::after_millis(250).await; - blue.toggle(); - - Timer::after_millis(250).await; - } -} diff --git a/examples/src/bin/button.rs b/examples/src/bin/button.rs deleted file mode 100644 index 943edbb15..000000000 --- a/examples/src/bin/button.rs +++ /dev/null @@ -1,23 +0,0 @@ -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_time::Timer; -use hal::gpio::{Input, Pull}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = hal::init(hal::config::Config::default()); - - defmt::info!("Button example"); - - // This button is labeled "WAKEUP" on the FRDM-MCXA276 - // The board already has a 10K pullup - let monitor = Input::new(p.P1_7, Pull::Disabled); - - loop { - defmt::info!("Pin level is {:?}", monitor.get_level()); - Timer::after_millis(1000).await; - } -} diff --git a/examples/src/bin/button_async.rs b/examples/src/bin/button_async.rs deleted file mode 100644 index 6cc7b62cd..000000000 --- a/examples/src/bin/button_async.rs +++ /dev/null @@ -1,29 +0,0 @@ -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_time::Timer; -use hal::gpio::{Input, Pull}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = hal::init(hal::config::Config::default()); - - defmt::info!("GPIO interrupt example"); - - // This button is labeled "WAKEUP" on the FRDM-MCXA276 - // The board already has a 10K pullup - let mut pin = Input::new(p.P1_7, Pull::Disabled); - - let mut press_count = 0u32; - - loop { - pin.wait_for_falling_edge().await; - - press_count += 1; - - defmt::info!("Button pressed! Count: {}", press_count); - Timer::after_millis(50).await; - } -} diff --git a/examples/src/bin/clkout.rs b/examples/src/bin/clkout.rs deleted file mode 100644 index bfd963540..000000000 --- a/examples/src/bin/clkout.rs +++ /dev/null @@ -1,69 +0,0 @@ -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa::clkout::{ClockOut, ClockOutSel, Config, Div4}; -use embassy_mcxa::clocks::PoweredClock; -use embassy_mcxa::gpio::{DriveStrength, SlewRate}; -use embassy_mcxa::{Level, Output}; -use embassy_time::Timer; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -/// Demonstrate CLKOUT, using Pin P4.2 -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = hal::init(hal::config::Config::default()); - let mut pin = p.P4_2; - let mut clkout = p.CLKOUT; - - loop { - defmt::info!("Set Low..."); - let mut output = Output::new(pin.reborrow(), Level::Low, DriveStrength::Normal, SlewRate::Slow); - Timer::after_millis(500).await; - - defmt::info!("Set High..."); - output.set_high(); - Timer::after_millis(400).await; - - defmt::info!("Set Low..."); - output.set_low(); - Timer::after_millis(500).await; - - defmt::info!("16k..."); - // Run Clock Out with the 16K clock - let _clock_out = ClockOut::new( - clkout.reborrow(), - pin.reborrow(), - Config { - sel: ClockOutSel::Clk16K, - div: Div4::no_div(), - level: PoweredClock::NormalEnabledDeepSleepDisabled, - }, - ) - .unwrap(); - - Timer::after_millis(3000).await; - - defmt::info!("Set Low..."); - drop(_clock_out); - - let _output = Output::new(pin.reborrow(), Level::Low, DriveStrength::Normal, SlewRate::Slow); - Timer::after_millis(500).await; - - // Run Clock Out with the 12M clock, divided by 3 - defmt::info!("4M..."); - let _clock_out = ClockOut::new( - clkout.reborrow(), - pin.reborrow(), - Config { - sel: ClockOutSel::Fro12M, - div: const { Div4::from_divisor(3).unwrap() }, - level: PoweredClock::NormalEnabledDeepSleepDisabled, - }, - ) - .unwrap(); - - // Let it run for 3 seconds... - Timer::after_millis(3000).await; - } -} diff --git a/examples/src/bin/dma_channel_link.rs b/examples/src/bin/dma_channel_link.rs deleted file mode 100644 index 92c7a9681..000000000 --- a/examples/src/bin/dma_channel_link.rs +++ /dev/null @@ -1,372 +0,0 @@ -//! DMA channel linking example for MCXA276. -//! -//! This example demonstrates DMA channel linking (minor and major loop linking): -//! - Channel 0: Transfers SRC_BUFFER to DEST_BUFFER0, with: -//! - Minor Link to Channel 1 (triggers CH1 after each minor loop) -//! - Major Link to Channel 2 (triggers CH2 after major loop completes) -//! - Channel 1: Transfers SRC_BUFFER to DEST_BUFFER1 (triggered by CH0 minor link) -//! - Channel 2: Transfers SRC_BUFFER to DEST_BUFFER2 (triggered by CH0 major link) -//! -//! # Embassy-style features demonstrated: -//! - `DmaChannel::new()` for channel creation -//! - `DmaChannel::is_done()` and `clear_done()` helper methods -//! - Channel linking with `set_minor_link()` and `set_major_link()` -//! - Standard `DmaCh*InterruptHandler` with `bind_interrupts!` macro - -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler, DmaCh2InterruptHandler, DmaChannel}; -use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; -use embassy_mcxa::{bind_interrupts, pac}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -// Buffers -static mut SRC_BUFFER: [u32; 4] = [1, 2, 3, 4]; -static mut DEST_BUFFER0: [u32; 4] = [0; 4]; -static mut DEST_BUFFER1: [u32; 4] = [0; 4]; -static mut DEST_BUFFER2: [u32; 4] = [0; 4]; - -// Bind DMA channel interrupts using Embassy-style macro -// The standard handlers call on_interrupt() which wakes wakers and clears flags -bind_interrupts!(struct Irqs { - DMA_CH0 => DmaCh0InterruptHandler; - DMA_CH1 => DmaCh1InterruptHandler; - DMA_CH2 => DmaCh2InterruptHandler; -}); - -/// Helper to write a u32 as decimal ASCII to UART -fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { - let mut buf = [0u8; 10]; - let mut n = val; - let mut i = buf.len(); - - if n == 0 { - tx.blocking_write(b"0").ok(); - return; - } - - while n > 0 { - i -= 1; - buf[i] = b'0' + (n % 10) as u8; - n /= 10; - } - - tx.blocking_write(&buf[i..]).ok(); -} - -/// Helper to print a buffer to UART -fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { - tx.blocking_write(b"[").ok(); - unsafe { - for i in 0..len { - write_u32(tx, *buf_ptr.add(i)); - if i < len - 1 { - tx.blocking_write(b", ").ok(); - } - } - } - tx.blocking_write(b"]").ok(); -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - // Small delay to allow probe-rs to attach after reset - for _ in 0..100_000 { - cortex_m::asm::nop(); - } - - let mut cfg = hal::config::Config::default(); - cfg.clock_cfg.sirc.fro_12m_enabled = true; - cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); - let p = hal::init(cfg); - - defmt::info!("DMA channel link example starting..."); - - // DMA is initialized during hal::init() - no need to call ensure_init() - - let pac_periphs = unsafe { pac::Peripherals::steal() }; - let dma0 = &pac_periphs.dma0; - let edma = unsafe { &*pac::Edma0Tcd0::ptr() }; - - // Clear any residual state - for i in 0..3 { - let t = edma.tcd(i); - t.ch_csr().write(|w| w.erq().disable().done().clear_bit_by_one()); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - t.ch_es().write(|w| w.err().clear_bit_by_one()); - t.ch_mux().write(|w| unsafe { w.bits(0) }); - } - - // Clear Global Halt/Error state - dma0.mp_csr().modify(|_, w| { - w.halt() - .normal_operation() - .hae() - .normal_operation() - .ecx() - .normal_operation() - .cx() - .normal_operation() - }); - - unsafe { - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH2); - } - - let config = Config { - baudrate_bps: 115_200, - ..Default::default() - }; - - let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); - let (mut tx, _rx) = lpuart.split(); - - tx.blocking_write(b"EDMA channel link example begin.\r\n\r\n").unwrap(); - - // Initialize buffers - unsafe { - SRC_BUFFER = [1, 2, 3, 4]; - DEST_BUFFER0 = [0; 4]; - DEST_BUFFER1 = [0; 4]; - DEST_BUFFER2 = [0; 4]; - } - - tx.blocking_write(b"Source Buffer: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(SRC_BUFFER) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"DEST0 (before): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER0) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"DEST1 (before): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER1) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"DEST2 (before): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER2) as *const u32, 4); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - tx.blocking_write(b"Configuring DMA channels with Embassy-style API...\r\n") - .unwrap(); - - let ch0 = DmaChannel::new(p.DMA_CH0); - let ch1 = DmaChannel::new(p.DMA_CH1); - let ch2 = DmaChannel::new(p.DMA_CH2); - - // Configure channels using direct TCD access (advanced feature demo) - // This example demonstrates channel linking which requires direct TCD manipulation - - // Helper to configure TCD for memory-to-memory transfer - // Parameters: channel, src, dst, width, nbytes (minor loop), count (major loop), interrupt - #[allow(clippy::too_many_arguments)] - unsafe fn configure_tcd( - edma: &embassy_mcxa::pac::edma_0_tcd0::RegisterBlock, - ch: usize, - src: u32, - dst: u32, - width: u8, - nbytes: u32, - count: u16, - enable_int: bool, - ) { - let t = edma.tcd(ch); - - // Reset channel state - t.ch_csr().write(|w| { - w.erq() - .disable() - .earq() - .disable() - .eei() - .no_error() - .ebw() - .disable() - .done() - .clear_bit_by_one() - }); - t.ch_es().write(|w| w.bits(0)); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Source/destination addresses - t.tcd_saddr().write(|w| w.saddr().bits(src)); - t.tcd_daddr().write(|w| w.daddr().bits(dst)); - - // Offsets: increment by width - t.tcd_soff().write(|w| w.soff().bits(width as u16)); - t.tcd_doff().write(|w| w.doff().bits(width as u16)); - - // Attributes: size = log2(width) - let size = match width { - 1 => 0, - 2 => 1, - 4 => 2, - _ => 0, - }; - t.tcd_attr().write(|w| w.ssize().bits(size).dsize().bits(size)); - - // Number of bytes per minor loop - t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); - - // Major loop: reset source address after major loop - let total_bytes = nbytes * count as u32; - t.tcd_slast_sda() - .write(|w| w.slast_sda().bits(-(total_bytes as i32) as u32)); - t.tcd_dlast_sga() - .write(|w| w.dlast_sga().bits(-(total_bytes as i32) as u32)); - - // Major loop count - t.tcd_biter_elinkno().write(|w| w.biter().bits(count)); - t.tcd_citer_elinkno().write(|w| w.citer().bits(count)); - - // Control/status: enable interrupt if requested - if enable_int { - t.tcd_csr().write(|w| w.intmajor().set_bit()); - } else { - t.tcd_csr().write(|w| w.intmajor().clear_bit()); - } - - cortex_m::asm::dsb(); - } - - unsafe { - // Channel 0: Transfer 16 bytes total (8 bytes per minor loop, 2 major iterations) - // Minor Link -> Channel 1 - // Major Link -> Channel 2 - configure_tcd( - edma, - 0, - core::ptr::addr_of!(SRC_BUFFER) as u32, - core::ptr::addr_of_mut!(DEST_BUFFER0) as u32, - 4, // src width - 8, // nbytes (minor loop = 2 words) - 2, // count (major loop = 2 iterations) - false, // no interrupt - ); - ch0.set_minor_link(1); // Link to CH1 after each minor loop - ch0.set_major_link(2); // Link to CH2 after major loop - - // Channel 1: Transfer 16 bytes (triggered by CH0 minor link) - configure_tcd( - edma, - 1, - core::ptr::addr_of!(SRC_BUFFER) as u32, - core::ptr::addr_of_mut!(DEST_BUFFER1) as u32, - 4, - 16, // full buffer in one minor loop - 1, // 1 major iteration - false, - ); - - // Channel 2: Transfer 16 bytes (triggered by CH0 major link) - configure_tcd( - edma, - 2, - core::ptr::addr_of!(SRC_BUFFER) as u32, - core::ptr::addr_of_mut!(DEST_BUFFER2) as u32, - 4, - 16, // full buffer in one minor loop - 1, // 1 major iteration - true, // enable interrupt - ); - } - - tx.blocking_write(b"Triggering Channel 0 (1st minor loop)...\r\n") - .unwrap(); - - // Trigger first minor loop of CH0 - unsafe { - ch0.trigger_start(); - } - - // Wait for CH1 to complete (triggered by CH0 minor link) - while !ch1.is_done() { - cortex_m::asm::nop(); - } - unsafe { - ch1.clear_done(); - } - - tx.blocking_write(b"CH1 done (via minor link).\r\n").unwrap(); - tx.blocking_write(b"Triggering Channel 0 (2nd minor loop)...\r\n") - .unwrap(); - - // Trigger second minor loop of CH0 - unsafe { - ch0.trigger_start(); - } - - // Wait for CH0 major loop to complete - while !ch0.is_done() { - cortex_m::asm::nop(); - } - unsafe { - ch0.clear_done(); - } - - tx.blocking_write(b"CH0 major loop done.\r\n").unwrap(); - - // Wait for CH2 to complete (triggered by CH0 major link) - // Using is_done() instead of AtomicBool - the standard interrupt handler - // clears the interrupt flag and wakes wakers, but DONE bit remains set - while !ch2.is_done() { - cortex_m::asm::nop(); - } - unsafe { - ch2.clear_done(); - } - - tx.blocking_write(b"CH2 done (via major link).\r\n\r\n").unwrap(); - - tx.blocking_write(b"EDMA channel link example finish.\r\n\r\n").unwrap(); - - tx.blocking_write(b"DEST0 (after): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER0) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"DEST1 (after): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER1) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"DEST2 (after): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER2) as *const u32, 4); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - // Verify all buffers match source - let mut success = true; - unsafe { - let src_ptr = core::ptr::addr_of!(SRC_BUFFER) as *const u32; - let dst0_ptr = core::ptr::addr_of!(DEST_BUFFER0) as *const u32; - let dst1_ptr = core::ptr::addr_of!(DEST_BUFFER1) as *const u32; - let dst2_ptr = core::ptr::addr_of!(DEST_BUFFER2) as *const u32; - - for i in 0..4 { - if *dst0_ptr.add(i) != *src_ptr.add(i) { - success = false; - } - if *dst1_ptr.add(i) != *src_ptr.add(i) { - success = false; - } - if *dst2_ptr.add(i) != *src_ptr.add(i) { - success = false; - } - } - } - - if success { - tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); - defmt::info!("PASS: Data verified."); - } else { - tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); - defmt::error!("FAIL: Mismatch detected!"); - } - - loop { - cortex_m::asm::wfe(); - } -} diff --git a/examples/src/bin/dma_interleave_transfer.rs b/examples/src/bin/dma_interleave_transfer.rs deleted file mode 100644 index 7876e8978..000000000 --- a/examples/src/bin/dma_interleave_transfer.rs +++ /dev/null @@ -1,215 +0,0 @@ -//! DMA interleaved transfer example for MCXA276. -//! -//! This example demonstrates using DMA with custom source/destination offsets -//! to interleave data during transfer. -//! -//! # Embassy-style features demonstrated: -//! - `TransferOptions::default()` for configuration (used internally) -//! - DMA channel with `DmaChannel::new()` - -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel}; -use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; -use embassy_mcxa::{bind_interrupts, pac}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -// Bind DMA channel 0 interrupt using Embassy-style macro -bind_interrupts!(struct Irqs { - DMA_CH0 => DmaCh0InterruptHandler; -}); - -const BUFFER_LENGTH: usize = 16; -const HALF_BUFF_LENGTH: usize = BUFFER_LENGTH / 2; - -// Buffers in RAM -static mut SRC_BUFFER: [u32; HALF_BUFF_LENGTH] = [0; HALF_BUFF_LENGTH]; -static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; - -/// Helper to write a u32 as decimal ASCII to UART -fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { - let mut buf = [0u8; 10]; - let mut n = val; - let mut i = buf.len(); - - if n == 0 { - tx.blocking_write(b"0").ok(); - return; - } - - while n > 0 { - i -= 1; - buf[i] = b'0' + (n % 10) as u8; - n /= 10; - } - - tx.blocking_write(&buf[i..]).ok(); -} - -/// Helper to print a buffer to UART -fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { - tx.blocking_write(b"[").ok(); - unsafe { - for i in 0..len { - write_u32(tx, *buf_ptr.add(i)); - if i < len - 1 { - tx.blocking_write(b", ").ok(); - } - } - } - tx.blocking_write(b"]").ok(); -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - // Small delay to allow probe-rs to attach after reset - for _ in 0..100_000 { - cortex_m::asm::nop(); - } - - let mut cfg = hal::config::Config::default(); - cfg.clock_cfg.sirc.fro_12m_enabled = true; - cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); - let p = hal::init(cfg); - - defmt::info!("DMA interleave transfer example starting..."); - - // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) - unsafe { - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); - } - - let config = Config { - baudrate_bps: 115_200, - ..Default::default() - }; - - let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); - let (mut tx, _rx) = lpuart.split(); - - tx.blocking_write(b"EDMA interleave transfer example begin.\r\n\r\n") - .unwrap(); - - // Initialize buffers - unsafe { - SRC_BUFFER = [1, 2, 3, 4, 5, 6, 7, 8]; - DEST_BUFFER = [0; BUFFER_LENGTH]; - } - - tx.blocking_write(b"Source Buffer: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(SRC_BUFFER) as *const u32, HALF_BUFF_LENGTH); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Destination Buffer (before): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") - .unwrap(); - - // Create DMA channel using Embassy-style API - let dma_ch0 = DmaChannel::new(p.DMA_CH0); - - // Configure interleaved transfer using direct TCD access: - // - src_offset = 4: advance source by 4 bytes after each read - // - dst_offset = 8: advance dest by 8 bytes after each write - // This spreads source data across every other word in destination - unsafe { - let t = dma_ch0.tcd(); - - // Reset channel state - t.ch_csr().write(|w| { - w.erq() - .disable() - .earq() - .disable() - .eei() - .no_error() - .ebw() - .disable() - .done() - .clear_bit_by_one() - }); - t.ch_es().write(|w| w.bits(0)); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Source/destination addresses - t.tcd_saddr() - .write(|w| w.saddr().bits(core::ptr::addr_of_mut!(SRC_BUFFER) as u32)); - t.tcd_daddr() - .write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DEST_BUFFER) as u32)); - - // Custom offsets for interleaving - t.tcd_soff().write(|w| w.soff().bits(4)); // src: +4 bytes per read - t.tcd_doff().write(|w| w.doff().bits(8)); // dst: +8 bytes per write - - // Attributes: 32-bit transfers (size = 2) - t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); - - // Transfer entire source buffer in one minor loop - let nbytes = (HALF_BUFF_LENGTH * 4) as u32; - t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); - - // Reset source address after major loop - t.tcd_slast_sda().write(|w| w.slast_sda().bits(-(nbytes as i32) as u32)); - // Destination uses 2x offset, so adjust accordingly - let dst_total = (HALF_BUFF_LENGTH * 8) as u32; - t.tcd_dlast_sga() - .write(|w| w.dlast_sga().bits(-(dst_total as i32) as u32)); - - // Major loop count = 1 - t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); - t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); - - // Enable interrupt on major loop completion - t.tcd_csr().write(|w| w.intmajor().set_bit()); - - cortex_m::asm::dsb(); - - tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); - dma_ch0.trigger_start(); - } - - // Wait for completion using channel helper method - while !dma_ch0.is_done() { - cortex_m::asm::nop(); - } - unsafe { - dma_ch0.clear_done(); - } - - tx.blocking_write(b"\r\nEDMA interleave transfer example finish.\r\n\r\n") - .unwrap(); - tx.blocking_write(b"Destination Buffer (after): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - // Verify: Even indices should match SRC_BUFFER[i/2], odd indices should be 0 - let mut mismatch = false; - unsafe { - for i in 0..BUFFER_LENGTH { - if i % 2 == 0 { - if DEST_BUFFER[i] != SRC_BUFFER[i / 2] { - mismatch = true; - } - } else if DEST_BUFFER[i] != 0 { - mismatch = true; - } - } - } - - if mismatch { - tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); - defmt::error!("FAIL: Mismatch detected!"); - } else { - tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); - defmt::info!("PASS: Data verified."); - } - - loop { - cortex_m::asm::wfe(); - } -} diff --git a/examples/src/bin/dma_mem_to_mem.rs b/examples/src/bin/dma_mem_to_mem.rs deleted file mode 100644 index 68f70e742..000000000 --- a/examples/src/bin/dma_mem_to_mem.rs +++ /dev/null @@ -1,229 +0,0 @@ -//! DMA memory-to-memory transfer example for MCXA276. -//! -//! This example demonstrates using DMA to copy data between memory buffers -//! using the Embassy-style async API with type-safe transfers. -//! -//! # Embassy-style features demonstrated: -//! - `TransferOptions` for configuration -//! - Type-safe `mem_to_mem()` method with async `.await` -//! - `Transfer` Future that can be `.await`ed -//! - `Word` trait for automatic transfer width detection -//! - `memset()` method for filling memory with a pattern - -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel, TransferOptions}; -use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; -use embassy_mcxa::{bind_interrupts, pac}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -// Bind DMA channel 0 interrupt using Embassy-style macro -bind_interrupts!(struct Irqs { - DMA_CH0 => DmaCh0InterruptHandler; -}); - -const BUFFER_LENGTH: usize = 4; - -// Buffers in RAM (static mut is automatically placed in .bss/.data) -static mut SRC_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; -static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; -static mut MEMSET_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; - -/// Helper to write a u32 as decimal ASCII to UART -fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { - let mut buf = [0u8; 10]; // u32 max is 4294967295 (10 digits) - let mut n = val; - let mut i = buf.len(); - - if n == 0 { - tx.blocking_write(b"0").ok(); - return; - } - - while n > 0 { - i -= 1; - buf[i] = b'0' + (n % 10) as u8; - n /= 10; - } - - tx.blocking_write(&buf[i..]).ok(); -} - -/// Helper to print a buffer as [v1, v2, v3, v4] to UART -/// Takes a raw pointer to avoid warnings about shared references to mutable statics -fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const [u32; BUFFER_LENGTH]) { - tx.blocking_write(b"[").ok(); - unsafe { - let buf = &*buf_ptr; - for (i, val) in buf.iter().enumerate() { - write_u32(tx, *val); - if i < buf.len() - 1 { - tx.blocking_write(b", ").ok(); - } - } - } - tx.blocking_write(b"]").ok(); -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - // Small delay to allow probe-rs to attach after reset - for _ in 0..100_000 { - cortex_m::asm::nop(); - } - - let mut cfg = hal::config::Config::default(); - cfg.clock_cfg.sirc.fro_12m_enabled = true; - cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); - let p = hal::init(cfg); - - defmt::info!("DMA memory-to-memory example starting..."); - - // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) - unsafe { - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); - } - - // Create UART for debug output - let config = Config { - baudrate_bps: 115_200, - ..Default::default() - }; - - let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); - let (mut tx, _rx) = lpuart.split(); - - tx.blocking_write(b"EDMA memory to memory example begin.\r\n\r\n") - .unwrap(); - - // Initialize buffers - unsafe { - SRC_BUFFER = [1, 2, 3, 4]; - DEST_BUFFER = [0; BUFFER_LENGTH]; - } - - tx.blocking_write(b"Source Buffer: ").unwrap(); - print_buffer(&mut tx, &raw const SRC_BUFFER); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Destination Buffer (before): ").unwrap(); - print_buffer(&mut tx, &raw const DEST_BUFFER); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") - .unwrap(); - - // Create DMA channel - let dma_ch0 = DmaChannel::new(p.DMA_CH0); - - // Configure transfer options (Embassy-style) - // TransferOptions defaults to: complete_transfer_interrupt = true - let options = TransferOptions::default(); - - // ========================================================================= - // Part 1: Embassy-style async API demonstration (mem_to_mem) - // ========================================================================= - // - // Use the new type-safe `mem_to_mem()` method: - // - Automatically determines transfer width from buffer element type (u32) - // - Returns a `Transfer` future that can be `.await`ed - // - Uses TransferOptions for consistent configuration - // - // Using async `.await` - the executor can run other tasks while waiting! - - // Perform type-safe memory-to-memory transfer using Embassy-style async API - unsafe { - let src = &*core::ptr::addr_of!(SRC_BUFFER); - let dst = &mut *core::ptr::addr_of_mut!(DEST_BUFFER); - - // Using async `.await` - the executor can run other tasks while waiting! - let transfer = dma_ch0.mem_to_mem(src, dst, options); - transfer.await; - } - - tx.blocking_write(b"DMA mem-to-mem transfer complete!\r\n\r\n").unwrap(); - tx.blocking_write(b"Destination Buffer (after): ").unwrap(); - print_buffer(&mut tx, &raw const DEST_BUFFER); - tx.blocking_write(b"\r\n").unwrap(); - - // Verify data - let mut mismatch = false; - unsafe { - for i in 0..BUFFER_LENGTH { - if SRC_BUFFER[i] != DEST_BUFFER[i] { - mismatch = true; - break; - } - } - } - - if mismatch { - tx.blocking_write(b"FAIL: mem_to_mem mismatch!\r\n").unwrap(); - defmt::error!("FAIL: mem_to_mem mismatch!"); - } else { - tx.blocking_write(b"PASS: mem_to_mem verified.\r\n\r\n").unwrap(); - defmt::info!("PASS: mem_to_mem verified."); - } - - // ========================================================================= - // Part 2: memset() demonstration - // ========================================================================= - // - // The `memset()` method fills a buffer with a pattern value: - // - Fixed source address (pattern is read repeatedly) - // - Incrementing destination address - // - Uses the same Transfer future pattern - - tx.blocking_write(b"--- Demonstrating memset() feature ---\r\n\r\n") - .unwrap(); - - tx.blocking_write(b"Memset Buffer (before): ").unwrap(); - print_buffer(&mut tx, &raw const MEMSET_BUFFER); - tx.blocking_write(b"\r\n").unwrap(); - - // Fill buffer with a pattern value using DMA memset - let pattern: u32 = 0xDEADBEEF; - tx.blocking_write(b"Filling with pattern 0xDEADBEEF...\r\n").unwrap(); - - unsafe { - let dst = &mut *core::ptr::addr_of_mut!(MEMSET_BUFFER); - - // Using blocking_wait() for demonstration - also shows non-async usage - let transfer = dma_ch0.memset(&pattern, dst, options); - transfer.blocking_wait(); - } - - tx.blocking_write(b"DMA memset complete!\r\n\r\n").unwrap(); - tx.blocking_write(b"Memset Buffer (after): ").unwrap(); - print_buffer(&mut tx, &raw const MEMSET_BUFFER); - tx.blocking_write(b"\r\n").unwrap(); - - // Verify memset result - let mut memset_ok = true; - unsafe { - #[allow(clippy::needless_range_loop)] - for i in 0..BUFFER_LENGTH { - if MEMSET_BUFFER[i] != pattern { - memset_ok = false; - break; - } - } - } - - if !memset_ok { - tx.blocking_write(b"FAIL: memset mismatch!\r\n").unwrap(); - defmt::error!("FAIL: memset mismatch!"); - } else { - tx.blocking_write(b"PASS: memset verified.\r\n\r\n").unwrap(); - defmt::info!("PASS: memset verified."); - } - - tx.blocking_write(b"=== All DMA tests complete ===\r\n").unwrap(); - - loop { - cortex_m::asm::wfe(); - } -} diff --git a/examples/src/bin/dma_memset.rs b/examples/src/bin/dma_memset.rs deleted file mode 100644 index 95e365e47..000000000 --- a/examples/src/bin/dma_memset.rs +++ /dev/null @@ -1,218 +0,0 @@ -//! DMA memset example for MCXA276. -//! -//! This example demonstrates using DMA to fill a buffer with a repeated pattern. -//! The source address stays fixed while the destination increments. -//! -//! # Embassy-style features demonstrated: -//! - `DmaChannel::is_done()` and `clear_done()` helper methods -//! - No need to pass register block around - -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel}; -use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; -use embassy_mcxa::{bind_interrupts, pac}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -// Bind DMA channel 0 interrupt using Embassy-style macro -bind_interrupts!(struct Irqs { - DMA_CH0 => DmaCh0InterruptHandler; -}); - -const BUFFER_LENGTH: usize = 4; - -// Buffers in RAM -static mut PATTERN: u32 = 0; -static mut DEST_BUFFER: [u32; BUFFER_LENGTH] = [0; BUFFER_LENGTH]; - -/// Helper to write a u32 as decimal ASCII to UART -fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { - let mut buf = [0u8; 10]; - let mut n = val; - let mut i = buf.len(); - - if n == 0 { - tx.blocking_write(b"0").ok(); - return; - } - - while n > 0 { - i -= 1; - buf[i] = b'0' + (n % 10) as u8; - n /= 10; - } - - tx.blocking_write(&buf[i..]).ok(); -} - -/// Helper to print a buffer to UART -fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { - tx.blocking_write(b"[").ok(); - unsafe { - for i in 0..len { - write_u32(tx, *buf_ptr.add(i)); - if i < len - 1 { - tx.blocking_write(b", ").ok(); - } - } - } - tx.blocking_write(b"]").ok(); -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - // Small delay to allow probe-rs to attach after reset - for _ in 0..100_000 { - cortex_m::asm::nop(); - } - - let mut cfg = hal::config::Config::default(); - cfg.clock_cfg.sirc.fro_12m_enabled = true; - cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); - let p = hal::init(cfg); - - defmt::info!("DMA memset example starting..."); - - // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) - unsafe { - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); - } - - let config = Config { - baudrate_bps: 115_200, - ..Default::default() - }; - - let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); - let (mut tx, _rx) = lpuart.split(); - - tx.blocking_write(b"EDMA memset example begin.\r\n\r\n").unwrap(); - - // Initialize buffers - unsafe { - PATTERN = 0xDEADBEEF; - DEST_BUFFER = [0; BUFFER_LENGTH]; - } - - tx.blocking_write(b"Pattern value: 0x").unwrap(); - // Print pattern in hex - unsafe { - let hex_chars = b"0123456789ABCDEF"; - let mut hex_buf = [0u8; 8]; - let mut val = PATTERN; - for i in (0..8).rev() { - hex_buf[i] = hex_chars[(val & 0xF) as usize]; - val >>= 4; - } - tx.blocking_write(&hex_buf).ok(); - } - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Destination Buffer (before): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") - .unwrap(); - - // Create DMA channel using Embassy-style API - let dma_ch0 = DmaChannel::new(p.DMA_CH0); - - // Configure memset transfer using direct TCD access: - // Source stays fixed (soff = 0, reads same pattern repeatedly) - // Destination increments (doff = 4) - unsafe { - let t = dma_ch0.tcd(); - - // Reset channel state - t.ch_csr().write(|w| { - w.erq() - .disable() - .earq() - .disable() - .eei() - .no_error() - .ebw() - .disable() - .done() - .clear_bit_by_one() - }); - t.ch_es().write(|w| w.bits(0)); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Source address (pattern) - fixed - t.tcd_saddr() - .write(|w| w.saddr().bits(core::ptr::addr_of_mut!(PATTERN) as u32)); - // Destination address - increments - t.tcd_daddr() - .write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DEST_BUFFER) as u32)); - - // Source offset = 0 (stays fixed), Dest offset = 4 (increments) - t.tcd_soff().write(|w| w.soff().bits(0)); - t.tcd_doff().write(|w| w.doff().bits(4)); - - // Attributes: 32-bit transfers (size = 2) - t.tcd_attr().write(|w| w.ssize().bits(2).dsize().bits(2)); - - // Transfer entire buffer in one minor loop - let nbytes = (BUFFER_LENGTH * 4) as u32; - t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); - - // Source doesn't need adjustment (stays fixed) - t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); - // Reset dest address after major loop - t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); - - // Major loop count = 1 - t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); - t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); - - // Enable interrupt on major loop completion - t.tcd_csr().write(|w| w.intmajor().set_bit()); - - cortex_m::asm::dsb(); - - tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); - dma_ch0.trigger_start(); - } - - // Wait for completion using channel helper method - while !dma_ch0.is_done() { - cortex_m::asm::nop(); - } - unsafe { - dma_ch0.clear_done(); - } - - tx.blocking_write(b"\r\nEDMA memset example finish.\r\n\r\n").unwrap(); - tx.blocking_write(b"Destination Buffer (after): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DEST_BUFFER) as *const u32, BUFFER_LENGTH); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - // Verify: All elements should equal PATTERN - let mut mismatch = false; - unsafe { - #[allow(clippy::needless_range_loop)] - for i in 0..BUFFER_LENGTH { - if DEST_BUFFER[i] != PATTERN { - mismatch = true; - break; - } - } - } - - if mismatch { - tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); - defmt::error!("FAIL: Mismatch detected!"); - } else { - tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); - defmt::info!("PASS: Data verified."); - } - - loop { - cortex_m::asm::wfe(); - } -} diff --git a/examples/src/bin/dma_ping_pong_transfer.rs b/examples/src/bin/dma_ping_pong_transfer.rs deleted file mode 100644 index f8f543382..000000000 --- a/examples/src/bin/dma_ping_pong_transfer.rs +++ /dev/null @@ -1,376 +0,0 @@ -//! DMA ping-pong/double-buffer transfer example for MCXA276. -//! -//! This example demonstrates two approaches for ping-pong/double-buffering: -//! -//! ## Approach 1: Scatter/Gather with linked TCDs (manual) -//! - Two TCDs link to each other for alternating transfers -//! - Uses custom handler that delegates to on_interrupt() then signals completion -//! - Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, -//! so we need an AtomicBool to track completion -//! -//! ## Approach 2: Half-transfer interrupt with wait_half() (NEW!) -//! - Single continuous transfer over entire buffer -//! - Uses half-transfer interrupt to know when first half is ready -//! - Application can process first half while second half is being filled -//! -//! # Embassy-style features demonstrated: -//! - `DmaChannel::new()` for channel creation -//! - Scatter/gather with linked TCDs -//! - Custom handler that delegates to HAL's `on_interrupt()` (best practice) -//! - Standard `DmaCh1InterruptHandler` with `bind_interrupts!` macro -//! - NEW: `wait_half()` for half-transfer interrupt handling - -#![no_std] -#![no_main] - -use core::sync::atomic::{AtomicBool, Ordering}; - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::dma::{self, DmaCh1InterruptHandler, DmaChannel, Tcd, TransferOptions}; -use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; -use embassy_mcxa::{bind_interrupts, pac}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -// Source and destination buffers for Approach 1 (scatter/gather) -static mut SRC: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; -static mut DST: [u32; 8] = [0; 8]; - -// Source and destination buffers for Approach 2 (wait_half) -static mut SRC2: [u32; 8] = [0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]; -static mut DST2: [u32; 8] = [0; 8]; - -// TCD pool for scatter/gather - must be 32-byte aligned -#[repr(C, align(32))] -struct TcdPool([Tcd; 2]); - -static mut TCD_POOL: TcdPool = TcdPool( - [Tcd { - saddr: 0, - soff: 0, - attr: 0, - nbytes: 0, - slast: 0, - daddr: 0, - doff: 0, - citer: 0, - dlast_sga: 0, - csr: 0, - biter: 0, - }; 2], -); - -// AtomicBool to track scatter/gather completion -// Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, -// so we need this flag to detect when each transfer completes -static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); - -// Custom handler for scatter/gather that delegates to HAL's on_interrupt() -// This follows the "interrupts as threads" pattern - the handler does minimal work -// (delegates to HAL + sets a flag) and the main task does the actual processing -pub struct PingPongDmaHandler; - -impl embassy_mcxa::interrupt::typelevel::Handler for PingPongDmaHandler { - unsafe fn on_interrupt() { - // Delegate to HAL's on_interrupt() which clears INT flag and wakes wakers - dma::on_interrupt(0); - // Signal completion for polling (needed because ESG clears DONE bit) - TRANSFER_DONE.store(true, Ordering::Release); - } -} - -// Bind DMA channel interrupts -// CH0: Custom handler for scatter/gather (delegates to on_interrupt + sets flag) -// CH1: Standard handler for wait_half() demo -bind_interrupts!(struct Irqs { - DMA_CH0 => PingPongDmaHandler; - DMA_CH1 => DmaCh1InterruptHandler; -}); - -/// Helper to write a u32 as decimal ASCII to UART -fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { - let mut buf = [0u8; 10]; - let mut n = val; - let mut i = buf.len(); - - if n == 0 { - tx.blocking_write(b"0").ok(); - return; - } - - while n > 0 { - i -= 1; - buf[i] = b'0' + (n % 10) as u8; - n /= 10; - } - - tx.blocking_write(&buf[i..]).ok(); -} - -/// Helper to print a buffer to UART -fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { - tx.blocking_write(b"[").ok(); - unsafe { - for i in 0..len { - write_u32(tx, *buf_ptr.add(i)); - if i < len - 1 { - tx.blocking_write(b", ").ok(); - } - } - } - tx.blocking_write(b"]").ok(); -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - // Small delay to allow probe-rs to attach after reset - for _ in 0..100_000 { - cortex_m::asm::nop(); - } - - let mut cfg = hal::config::Config::default(); - cfg.clock_cfg.sirc.fro_12m_enabled = true; - cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); - let p = hal::init(cfg); - - defmt::info!("DMA ping-pong transfer example starting..."); - - // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) - unsafe { - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); - } - - let config = Config { - baudrate_bps: 115_200, - ..Default::default() - }; - - let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); - let (mut tx, _rx) = lpuart.split(); - - tx.blocking_write(b"EDMA ping-pong transfer example begin.\r\n\r\n") - .unwrap(); - - // Initialize buffers - unsafe { - SRC = [1, 2, 3, 4, 5, 6, 7, 8]; - DST = [0; 8]; - } - - tx.blocking_write(b"Source Buffer: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(SRC) as *const u32, 8); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Destination Buffer (before): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Configuring ping-pong DMA with Embassy-style API...\r\n") - .unwrap(); - - let dma_ch0 = DmaChannel::new(p.DMA_CH0); - - // Configure ping-pong transfer using direct TCD access: - // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. - // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), links to TCD1. - // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), links to TCD0. - unsafe { - let tcds = &mut *core::ptr::addr_of_mut!(TCD_POOL.0); - let src_ptr = core::ptr::addr_of!(SRC) as *const u32; - let dst_ptr = core::ptr::addr_of_mut!(DST) as *mut u32; - - let half_len = 4usize; - let half_bytes = (half_len * 4) as u32; - - let tcd0_addr = &tcds[0] as *const _ as u32; - let tcd1_addr = &tcds[1] as *const _ as u32; - - // TCD0: First half -> Links to TCD1 - tcds[0] = Tcd { - saddr: src_ptr as u32, - soff: 4, - attr: 0x0202, // 32-bit src/dst - nbytes: half_bytes, - slast: 0, - daddr: dst_ptr as u32, - doff: 4, - citer: 1, - dlast_sga: tcd1_addr as i32, - csr: 0x0012, // ESG | INTMAJOR - biter: 1, - }; - - // TCD1: Second half -> Links to TCD0 - tcds[1] = Tcd { - saddr: src_ptr.add(half_len) as u32, - soff: 4, - attr: 0x0202, - nbytes: half_bytes, - slast: 0, - daddr: dst_ptr.add(half_len) as u32, - doff: 4, - citer: 1, - dlast_sga: tcd0_addr as i32, - csr: 0x0012, - biter: 1, - }; - - // Load TCD0 into hardware registers - dma_ch0.load_tcd(&tcds[0]); - } - - tx.blocking_write(b"Triggering first half transfer...\r\n").unwrap(); - - // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) - unsafe { - dma_ch0.trigger_start(); - } - - // Wait for first half - while !TRANSFER_DONE.load(Ordering::Acquire) { - cortex_m::asm::nop(); - } - TRANSFER_DONE.store(false, Ordering::Release); - - tx.blocking_write(b"First half transferred.\r\n").unwrap(); - tx.blocking_write(b"Triggering second half transfer...\r\n").unwrap(); - - // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) - unsafe { - dma_ch0.trigger_start(); - } - - // Wait for second half - while !TRANSFER_DONE.load(Ordering::Acquire) { - cortex_m::asm::nop(); - } - TRANSFER_DONE.store(false, Ordering::Release); - - tx.blocking_write(b"Second half transferred.\r\n\r\n").unwrap(); - - tx.blocking_write(b"EDMA ping-pong transfer example finish.\r\n\r\n") - .unwrap(); - tx.blocking_write(b"Destination Buffer (after): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - // Verify: DST should match SRC - let mut mismatch = false; - unsafe { - let src_ptr = core::ptr::addr_of!(SRC) as *const u32; - let dst_ptr = core::ptr::addr_of!(DST) as *const u32; - for i in 0..8 { - if *src_ptr.add(i) != *dst_ptr.add(i) { - mismatch = true; - break; - } - } - } - - if mismatch { - tx.blocking_write(b"FAIL: Approach 1 mismatch detected!\r\n").unwrap(); - defmt::error!("FAIL: Approach 1 mismatch detected!"); - } else { - tx.blocking_write(b"PASS: Approach 1 data verified.\r\n\r\n").unwrap(); - defmt::info!("PASS: Approach 1 data verified."); - } - - // ========================================================================= - // Approach 2: Half-Transfer Interrupt with wait_half() (NEW!) - // ========================================================================= - // - // This approach uses a single continuous DMA transfer with half-transfer - // interrupt enabled. The wait_half() method allows you to be notified - // when the first half of the buffer is complete, so you can process it - // while the second half is still being filled. - // - // Benefits: - // - Simpler setup (no TCD pool needed) - // - True async/await support - // - Good for streaming data processing - - tx.blocking_write(b"--- Approach 2: wait_half() demo ---\r\n\r\n") - .unwrap(); - - // Enable DMA CH1 interrupt - unsafe { - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); - } - - // Initialize approach 2 buffers - unsafe { - SRC2 = [0xA1, 0xA2, 0xA3, 0xA4, 0xB1, 0xB2, 0xB3, 0xB4]; - DST2 = [0; 8]; - } - - tx.blocking_write(b"SRC2: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(SRC2) as *const u32, 8); - tx.blocking_write(b"\r\n").unwrap(); - - let dma_ch1 = DmaChannel::new(p.DMA_CH1); - - // Configure transfer with half-transfer interrupt enabled - let mut options = TransferOptions::default(); - options.half_transfer_interrupt = true; // Enable half-transfer interrupt - options.complete_transfer_interrupt = true; - - tx.blocking_write(b"Starting transfer with half_transfer_interrupt...\r\n") - .unwrap(); - - unsafe { - let src = &*core::ptr::addr_of!(SRC2); - let dst = &mut *core::ptr::addr_of_mut!(DST2); - - // Create the transfer - let mut transfer = dma_ch1.mem_to_mem(src, dst, options); - - // Wait for half-transfer (first 4 elements) - tx.blocking_write(b"Waiting for first half...\r\n").unwrap(); - let half_ok = transfer.wait_half().await; - - if half_ok { - tx.blocking_write(b"Half-transfer complete! First half of DST2: ") - .unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - tx.blocking_write(b"(Processing first half while second half transfers...)\r\n") - .unwrap(); - } - - // Wait for complete transfer - tx.blocking_write(b"Waiting for second half...\r\n").unwrap(); - transfer.await; - } - - tx.blocking_write(b"Transfer complete! Full DST2: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 8); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - // Verify approach 2 - let mut mismatch2 = false; - unsafe { - let src_ptr = core::ptr::addr_of!(SRC2) as *const u32; - let dst_ptr = core::ptr::addr_of!(DST2) as *const u32; - for i in 0..8 { - if *src_ptr.add(i) != *dst_ptr.add(i) { - mismatch2 = true; - break; - } - } - } - - if mismatch2 { - tx.blocking_write(b"FAIL: Approach 2 mismatch!\r\n").unwrap(); - defmt::error!("FAIL: Approach 2 mismatch!"); - } else { - tx.blocking_write(b"PASS: Approach 2 verified.\r\n").unwrap(); - defmt::info!("PASS: Approach 2 verified."); - } - - tx.blocking_write(b"\r\n=== All ping-pong demos complete ===\r\n") - .unwrap(); - - loop { - cortex_m::asm::wfe(); - } -} diff --git a/examples/src/bin/dma_scatter_gather.rs b/examples/src/bin/dma_scatter_gather.rs deleted file mode 100644 index 4b26bc2ed..000000000 --- a/examples/src/bin/dma_scatter_gather.rs +++ /dev/null @@ -1,262 +0,0 @@ -//! DMA scatter-gather transfer example for MCXA276. -//! -//! This example demonstrates using DMA with scatter/gather to chain multiple -//! transfer descriptors. The first TCD transfers the first half of the buffer, -//! then automatically loads the second TCD to transfer the second half. -//! -//! # Embassy-style features demonstrated: -//! - `DmaChannel::new()` for channel creation -//! - Scatter/gather with chained TCDs -//! - Custom handler that delegates to HAL's `on_interrupt()` (best practice) - -#![no_std] -#![no_main] - -use core::sync::atomic::{AtomicBool, Ordering}; - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::dma::{self, DmaChannel, Tcd}; -use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; -use embassy_mcxa::{bind_interrupts, pac}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -// Source and destination buffers -static mut SRC: [u32; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; -static mut DST: [u32; 8] = [0; 8]; - -// TCD pool for scatter/gather - must be 32-byte aligned -#[repr(C, align(32))] -struct TcdPool([Tcd; 2]); - -static mut TCD_POOL: TcdPool = TcdPool( - [Tcd { - saddr: 0, - soff: 0, - attr: 0, - nbytes: 0, - slast: 0, - daddr: 0, - doff: 0, - citer: 0, - dlast_sga: 0, - csr: 0, - biter: 0, - }; 2], -); - -// AtomicBool to track scatter/gather completion -// Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, -// so we need this flag to detect when each transfer completes -static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); - -// Custom handler for scatter/gather that delegates to HAL's on_interrupt() -// This follows the "interrupts as threads" pattern - the handler does minimal work -// (delegates to HAL + sets a flag) and the main task does the actual processing -pub struct ScatterGatherDmaHandler; - -impl embassy_mcxa::interrupt::typelevel::Handler - for ScatterGatherDmaHandler -{ - unsafe fn on_interrupt() { - // Delegate to HAL's on_interrupt() which clears INT flag and wakes wakers - dma::on_interrupt(0); - // Signal completion for polling (needed because ESG clears DONE bit) - TRANSFER_DONE.store(true, Ordering::Release); - } -} - -// Bind DMA channel interrupt -// Custom handler for scatter/gather (delegates to on_interrupt + sets flag) -bind_interrupts!(struct Irqs { - DMA_CH0 => ScatterGatherDmaHandler; -}); - -/// Helper to write a u32 as decimal ASCII to UART -fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { - let mut buf = [0u8; 10]; - let mut n = val; - let mut i = buf.len(); - - if n == 0 { - tx.blocking_write(b"0").ok(); - return; - } - - while n > 0 { - i -= 1; - buf[i] = b'0' + (n % 10) as u8; - n /= 10; - } - - tx.blocking_write(&buf[i..]).ok(); -} - -/// Helper to print a buffer to UART -fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { - tx.blocking_write(b"[").ok(); - unsafe { - for i in 0..len { - write_u32(tx, *buf_ptr.add(i)); - if i < len - 1 { - tx.blocking_write(b", ").ok(); - } - } - } - tx.blocking_write(b"]").ok(); -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - // Small delay to allow probe-rs to attach after reset - for _ in 0..100_000 { - cortex_m::asm::nop(); - } - - let mut cfg = hal::config::Config::default(); - cfg.clock_cfg.sirc.fro_12m_enabled = true; - cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); - let p = hal::init(cfg); - - defmt::info!("DMA scatter-gather transfer example starting..."); - - // DMA is initialized during hal::init() - no need to call ensure_init() - - // Enable DMA interrupt - unsafe { - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); - } - - let config = Config { - baudrate_bps: 115_200, - ..Default::default() - }; - - let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); - let (mut tx, _rx) = lpuart.split(); - - tx.blocking_write(b"EDMA scatter-gather transfer example begin.\r\n\r\n") - .unwrap(); - - // Initialize buffers - unsafe { - SRC = [1, 2, 3, 4, 5, 6, 7, 8]; - DST = [0; 8]; - } - - tx.blocking_write(b"Source Buffer: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(SRC) as *const u32, 8); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Destination Buffer (before): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Configuring scatter-gather DMA with Embassy-style API...\r\n") - .unwrap(); - - let dma_ch0 = DmaChannel::new(p.DMA_CH0); - - // Configure scatter-gather transfer using direct TCD access: - // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. - // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), then loads TCD1. - // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), last TCD. - unsafe { - let tcds = core::slice::from_raw_parts_mut(core::ptr::addr_of_mut!(TCD_POOL.0) as *mut Tcd, 2); - let src_ptr = core::ptr::addr_of!(SRC) as *const u32; - let dst_ptr = core::ptr::addr_of_mut!(DST) as *mut u32; - - let num_tcds = 2usize; - let chunk_len = 4usize; // 8 / 2 - let chunk_bytes = (chunk_len * 4) as u32; - - for i in 0..num_tcds { - let is_last = i == num_tcds - 1; - let next_tcd_addr = if is_last { - 0 // No next TCD - } else { - &tcds[i + 1] as *const _ as u32 - }; - - tcds[i] = Tcd { - saddr: src_ptr.add(i * chunk_len) as u32, - soff: 4, - attr: 0x0202, // 32-bit src/dst - nbytes: chunk_bytes, - slast: 0, - daddr: dst_ptr.add(i * chunk_len) as u32, - doff: 4, - citer: 1, - dlast_sga: next_tcd_addr as i32, - // ESG (scatter/gather) for non-last, INTMAJOR for all - csr: if is_last { 0x0002 } else { 0x0012 }, - biter: 1, - }; - } - - // Load TCD0 into hardware registers - dma_ch0.load_tcd(&tcds[0]); - } - - tx.blocking_write(b"Triggering first half transfer...\r\n").unwrap(); - - // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) - // TCD0 is currently loaded. - unsafe { - dma_ch0.trigger_start(); - } - - // Wait for first half - while !TRANSFER_DONE.load(Ordering::Acquire) { - cortex_m::asm::nop(); - } - TRANSFER_DONE.store(false, Ordering::Release); - - tx.blocking_write(b"First half transferred.\r\n").unwrap(); - tx.blocking_write(b"Triggering second half transfer...\r\n").unwrap(); - - // Trigger second transfer (second half: SRC[4..8] -> DST[4..8]) - // TCD1 should have been loaded by the scatter/gather engine. - unsafe { - dma_ch0.trigger_start(); - } - - // Wait for second half - while !TRANSFER_DONE.load(Ordering::Acquire) { - cortex_m::asm::nop(); - } - TRANSFER_DONE.store(false, Ordering::Release); - - tx.blocking_write(b"Second half transferred.\r\n\r\n").unwrap(); - - tx.blocking_write(b"EDMA scatter-gather transfer example finish.\r\n\r\n") - .unwrap(); - tx.blocking_write(b"Destination Buffer (after): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - // Verify: DST should match SRC - let mut mismatch = false; - unsafe { - let src_ptr = core::ptr::addr_of!(SRC) as *const u32; - let dst_ptr = core::ptr::addr_of!(DST) as *const u32; - for i in 0..8 { - if *src_ptr.add(i) != *dst_ptr.add(i) { - mismatch = true; - break; - } - } - } - - if mismatch { - tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); - defmt::error!("FAIL: Mismatch detected!"); - } else { - tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); - defmt::info!("PASS: Data verified."); - } - - loop { - cortex_m::asm::wfe(); - } -} diff --git a/examples/src/bin/dma_scatter_gather_builder.rs b/examples/src/bin/dma_scatter_gather_builder.rs deleted file mode 100644 index e483bb81f..000000000 --- a/examples/src/bin/dma_scatter_gather_builder.rs +++ /dev/null @@ -1,231 +0,0 @@ -//! DMA Scatter-Gather Builder example for MCXA276. -//! -//! This example demonstrates using the new `ScatterGatherBuilder` API for -//! chaining multiple DMA transfers with a type-safe builder pattern. -//! -//! # Features demonstrated: -//! - `ScatterGatherBuilder::new()` for creating a builder -//! - `add_transfer()` for adding memory-to-memory segments -//! - `build()` to start the chained transfer -//! - Automatic TCD linking and ESG bit management -//! -//! # Comparison with manual scatter-gather: -//! The manual approach (see `dma_scatter_gather.rs`) requires: -//! - Manual TCD pool allocation and alignment -//! - Manual CSR/ESG/INTMAJOR bit manipulation -//! - Manual dlast_sga address calculations -//! -//! The builder approach handles all of this automatically! - -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel, ScatterGatherBuilder}; -use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; -use embassy_mcxa::{bind_interrupts, pac}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -// Bind DMA channel 0 interrupt -bind_interrupts!(struct Irqs { - DMA_CH0 => DmaCh0InterruptHandler; -}); - -// Source buffers (multiple segments) -static mut SRC1: [u32; 4] = [0x11111111, 0x22222222, 0x33333333, 0x44444444]; -static mut SRC2: [u32; 4] = [0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC, 0xDDDDDDDD]; -static mut SRC3: [u32; 4] = [0x12345678, 0x9ABCDEF0, 0xFEDCBA98, 0x76543210]; - -// Destination buffers (one per segment) -static mut DST1: [u32; 4] = [0; 4]; -static mut DST2: [u32; 4] = [0; 4]; -static mut DST3: [u32; 4] = [0; 4]; - -/// Helper to write a u32 as hex to UART -fn write_hex(tx: &mut LpuartTx<'_, Blocking>, val: u32) { - const HEX: &[u8; 16] = b"0123456789ABCDEF"; - for i in (0..8).rev() { - let nibble = ((val >> (i * 4)) & 0xF) as usize; - tx.blocking_write(&[HEX[nibble]]).ok(); - } -} - -/// Helper to print a buffer to UART -fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { - tx.blocking_write(b"[").ok(); - unsafe { - for i in 0..len { - write_hex(tx, *buf_ptr.add(i)); - if i < len - 1 { - tx.blocking_write(b", ").ok(); - } - } - } - tx.blocking_write(b"]").ok(); -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - // Small delay to allow probe-rs to attach after reset - for _ in 0..100_000 { - cortex_m::asm::nop(); - } - - let mut cfg = hal::config::Config::default(); - cfg.clock_cfg.sirc.fro_12m_enabled = true; - cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); - let p = hal::init(cfg); - - defmt::info!("DMA Scatter-Gather Builder example starting..."); - - // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) - unsafe { - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); - } - - // Create UART for debug output - let config = Config { - baudrate_bps: 115_200, - ..Default::default() - }; - - let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); - let (mut tx, _rx) = lpuart.split(); - - tx.blocking_write(b"DMA Scatter-Gather Builder Example\r\n").unwrap(); - tx.blocking_write(b"===================================\r\n\r\n") - .unwrap(); - - // Show source buffers - tx.blocking_write(b"Source buffers:\r\n").unwrap(); - tx.blocking_write(b" SRC1: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(SRC1) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - tx.blocking_write(b" SRC2: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(SRC2) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - tx.blocking_write(b" SRC3: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(SRC3) as *const u32, 4); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - tx.blocking_write(b"Destination buffers (before):\r\n").unwrap(); - tx.blocking_write(b" DST1: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST1) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - tx.blocking_write(b" DST2: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - tx.blocking_write(b" DST3: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST3) as *const u32, 4); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - // Create DMA channel - let dma_ch0 = DmaChannel::new(p.DMA_CH0); - - tx.blocking_write(b"Building scatter-gather chain with builder API...\r\n") - .unwrap(); - - // ========================================================================= - // ScatterGatherBuilder API demonstration - // ========================================================================= - // - // The builder pattern makes scatter-gather transfers much easier: - // 1. Create a builder - // 2. Add transfer segments with add_transfer() - // 3. Call build() to start the entire chain - // No manual TCD manipulation required! - - let mut builder = ScatterGatherBuilder::::new(); - - // Add three transfer segments - the builder handles TCD linking automatically - unsafe { - let src1 = &*core::ptr::addr_of!(SRC1); - let dst1 = &mut *core::ptr::addr_of_mut!(DST1); - builder.add_transfer(src1, dst1); - } - - unsafe { - let src2 = &*core::ptr::addr_of!(SRC2); - let dst2 = &mut *core::ptr::addr_of_mut!(DST2); - builder.add_transfer(src2, dst2); - } - - unsafe { - let src3 = &*core::ptr::addr_of!(SRC3); - let dst3 = &mut *core::ptr::addr_of_mut!(DST3); - builder.add_transfer(src3, dst3); - } - - tx.blocking_write(b"Added 3 transfer segments to chain.\r\n").unwrap(); - tx.blocking_write(b"Starting scatter-gather transfer with .await...\r\n\r\n") - .unwrap(); - - // Build and execute the scatter-gather chain - // The build() method: - // - Links all TCDs together with ESG bit - // - Sets INTMAJOR on all TCDs - // - Loads the first TCD into hardware - // - Returns a Transfer future - unsafe { - let transfer = builder.build(&dma_ch0).expect("Failed to build scatter-gather"); - transfer.blocking_wait(); - } - - tx.blocking_write(b"Scatter-gather transfer complete!\r\n\r\n").unwrap(); - - // Show results - tx.blocking_write(b"Destination buffers (after):\r\n").unwrap(); - tx.blocking_write(b" DST1: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST1) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - tx.blocking_write(b" DST2: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST2) as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - tx.blocking_write(b" DST3: ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST3) as *const u32, 4); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - // Verify all three segments - let mut all_ok = true; - unsafe { - let src1 = core::ptr::addr_of!(SRC1) as *const u32; - let dst1 = core::ptr::addr_of!(DST1) as *const u32; - for i in 0..4 { - if *src1.add(i) != *dst1.add(i) { - all_ok = false; - } - } - - let src2 = core::ptr::addr_of!(SRC2) as *const u32; - let dst2 = core::ptr::addr_of!(DST2) as *const u32; - for i in 0..4 { - if *src2.add(i) != *dst2.add(i) { - all_ok = false; - } - } - - let src3 = core::ptr::addr_of!(SRC3) as *const u32; - let dst3 = core::ptr::addr_of!(DST3) as *const u32; - for i in 0..4 { - if *src3.add(i) != *dst3.add(i) { - all_ok = false; - } - } - } - - if all_ok { - tx.blocking_write(b"PASS: All segments verified!\r\n").unwrap(); - defmt::info!("PASS: All segments verified!"); - } else { - tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); - defmt::error!("FAIL: Mismatch detected!"); - } - - tx.blocking_write(b"\r\n=== Scatter-Gather Builder example complete ===\r\n") - .unwrap(); - - loop { - cortex_m::asm::wfe(); - } -} diff --git a/examples/src/bin/dma_wrap_transfer.rs b/examples/src/bin/dma_wrap_transfer.rs deleted file mode 100644 index 82936d9d0..000000000 --- a/examples/src/bin/dma_wrap_transfer.rs +++ /dev/null @@ -1,222 +0,0 @@ -//! DMA wrap transfer example for MCXA276. -//! -//! This example demonstrates using DMA with modulo addressing to wrap around -//! a source buffer, effectively repeating the source data in the destination. -//! -//! # Embassy-style features demonstrated: -//! - `DmaChannel::is_done()` and `clear_done()` helper methods -//! - No need to pass register block around - -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaChannel}; -use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; -use embassy_mcxa::{bind_interrupts, pac}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -// Bind DMA channel 0 interrupt using Embassy-style macro -bind_interrupts!(struct Irqs { - DMA_CH0 => DmaCh0InterruptHandler; -}); - -// Source buffer: 4 words (16 bytes), aligned to 16 bytes for modulo -#[repr(align(16))] -struct AlignedSrc([u32; 4]); - -static mut SRC: AlignedSrc = AlignedSrc([0; 4]); -static mut DST: [u32; 8] = [0; 8]; - -/// Helper to write a u32 as decimal ASCII to UART -fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { - let mut buf = [0u8; 10]; - let mut n = val; - let mut i = buf.len(); - - if n == 0 { - tx.blocking_write(b"0").ok(); - return; - } - - while n > 0 { - i -= 1; - buf[i] = b'0' + (n % 10) as u8; - n /= 10; - } - - tx.blocking_write(&buf[i..]).ok(); -} - -/// Helper to print a buffer to UART -fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { - tx.blocking_write(b"[").ok(); - unsafe { - for i in 0..len { - write_u32(tx, *buf_ptr.add(i)); - if i < len - 1 { - tx.blocking_write(b", ").ok(); - } - } - } - tx.blocking_write(b"]").ok(); -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - // Small delay to allow probe-rs to attach after reset - for _ in 0..100_000 { - cortex_m::asm::nop(); - } - - let mut cfg = hal::config::Config::default(); - cfg.clock_cfg.sirc.fro_12m_enabled = true; - cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); - let p = hal::init(cfg); - - defmt::info!("DMA wrap transfer example starting..."); - - // Enable DMA interrupt (DMA clock/reset/init is handled automatically by HAL) - unsafe { - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); - } - - let config = Config { - baudrate_bps: 115_200, - ..Default::default() - }; - - let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); - let (mut tx, _rx) = lpuart.split(); - - tx.blocking_write(b"EDMA wrap transfer example begin.\r\n\r\n").unwrap(); - - // Initialize buffers - unsafe { - SRC.0 = [1, 2, 3, 4]; - DST = [0; 8]; - } - - tx.blocking_write(b"Source Buffer: ").unwrap(); - print_buffer(&mut tx, unsafe { core::ptr::addr_of!(SRC.0) } as *const u32, 4); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Destination Buffer (before): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); - tx.blocking_write(b"\r\n").unwrap(); - - tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") - .unwrap(); - - // Create DMA channel using Embassy-style API - let dma_ch0 = DmaChannel::new(p.DMA_CH0); - - // Configure wrap transfer using direct TCD access: - // SRC is 16 bytes (4 * u32). We want to transfer 32 bytes (8 * u32). - // SRC modulo is 16 bytes (2^4 = 16) - wraps source address. - // DST modulo is 0 (disabled). - // This causes the source address to wrap around after 16 bytes, - // effectively repeating the source data. - unsafe { - let t = dma_ch0.tcd(); - - // Reset channel state - t.ch_csr().write(|w| { - w.erq() - .disable() - .earq() - .disable() - .eei() - .no_error() - .ebw() - .disable() - .done() - .clear_bit_by_one() - }); - t.ch_es().write(|w| w.bits(0)); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Source/destination addresses - t.tcd_saddr() - .write(|w| w.saddr().bits(core::ptr::addr_of!(SRC.0) as u32)); - t.tcd_daddr() - .write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DST) as u32)); - - // Offsets: both increment by 4 bytes - t.tcd_soff().write(|w| w.soff().bits(4)); - t.tcd_doff().write(|w| w.doff().bits(4)); - - // Attributes: 32-bit transfers (size = 2) - // SMOD = 4 (2^4 = 16 byte modulo for source), DMOD = 0 (disabled) - t.tcd_attr().write(|w| { - w.ssize() - .bits(2) - .dsize() - .bits(2) - .smod() - .bits(4) // Source modulo: 2^4 = 16 bytes - .dmod() - .bits(0) // Dest modulo: disabled - }); - - // Transfer 32 bytes total in one minor loop - let nbytes = 32u32; - t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); - - // Source wraps via modulo, no adjustment needed - t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); - // Reset dest address after major loop - t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); - - // Major loop count = 1 - t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); - t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); - - // Enable interrupt on major loop completion - t.tcd_csr().write(|w| w.intmajor().set_bit()); - - cortex_m::asm::dsb(); - - tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); - dma_ch0.trigger_start(); - } - - // Wait for completion using channel helper method - while !dma_ch0.is_done() { - cortex_m::asm::nop(); - } - unsafe { - dma_ch0.clear_done(); - } - - tx.blocking_write(b"\r\nEDMA wrap transfer example finish.\r\n\r\n") - .unwrap(); - tx.blocking_write(b"Destination Buffer (after): ").unwrap(); - print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); - tx.blocking_write(b"\r\n\r\n").unwrap(); - - // Verify: DST should be [1, 2, 3, 4, 1, 2, 3, 4] - let expected = [1u32, 2, 3, 4, 1, 2, 3, 4]; - let mut mismatch = false; - unsafe { - for i in 0..8 { - if DST[i] != expected[i] { - mismatch = true; - break; - } - } - } - - if mismatch { - tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); - defmt::error!("FAIL: Mismatch detected!"); - } else { - tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); - defmt::info!("PASS: Data verified."); - } - - loop { - cortex_m::asm::wfe(); - } -} diff --git a/examples/src/bin/hello.rs b/examples/src/bin/hello.rs deleted file mode 100644 index e371d9413..000000000 --- a/examples/src/bin/hello.rs +++ /dev/null @@ -1,119 +0,0 @@ -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::config::Div8; -use hal::lpuart::{Blocking, Config, Lpuart}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -/// Simple helper to write a byte as hex to UART -fn write_hex_byte(uart: &mut Lpuart<'_, Blocking>, byte: u8) { - const HEX_DIGITS: &[u8] = b"0123456789ABCDEF"; - let _ = uart.write_byte(HEX_DIGITS[(byte >> 4) as usize]); - let _ = uart.write_byte(HEX_DIGITS[(byte & 0xF) as usize]); -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let mut cfg = hal::config::Config::default(); - cfg.clock_cfg.sirc.fro_12m_enabled = true; - cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); - let p = hal::init(cfg); - - defmt::info!("boot"); - - // Create UART configuration - let config = Config { - baudrate_bps: 115_200, - ..Default::default() - }; - - // Create UART instance using LPUART2 with P2_2 as TX and P2_3 as RX - let mut uart = Lpuart::new_blocking( - p.LPUART2, // Peripheral - p.P2_2, // TX pin - p.P2_3, // RX pin - config, - ) - .unwrap(); - - // Print welcome message before any async delays to guarantee early console output - uart.write_str_blocking("\r\n=== MCXA276 UART Echo Demo ===\r\n"); - uart.write_str_blocking("Available commands:\r\n"); - uart.write_str_blocking(" help - Show this help\r\n"); - uart.write_str_blocking(" echo - Echo back the text\r\n"); - uart.write_str_blocking(" hex - Display byte in hex (0-255)\r\n"); - uart.write_str_blocking("Type a command: "); - - let mut buffer = [0u8; 64]; - let mut buf_idx = 0; - - loop { - // Read a byte from UART - let byte = uart.read_byte_blocking(); - - // Echo the character back - if byte == b'\r' || byte == b'\n' { - // Enter pressed - process command - uart.write_str_blocking("\r\n"); - - if buf_idx > 0 { - let command = &buffer[0..buf_idx]; - - if command == b"help" { - uart.write_str_blocking("Available commands:\r\n"); - uart.write_str_blocking(" help - Show this help\r\n"); - uart.write_str_blocking(" echo - Echo back the text\r\n"); - uart.write_str_blocking(" hex - Display byte in hex (0-255)\r\n"); - } else if command.starts_with(b"echo ") && command.len() > 5 { - uart.write_str_blocking("Echo: "); - uart.write_str_blocking(core::str::from_utf8(&command[5..]).unwrap_or("")); - uart.write_str_blocking("\r\n"); - } else if command.starts_with(b"hex ") && command.len() > 4 { - // Parse the byte value - let num_str = &command[4..]; - if let Ok(num) = parse_u8(num_str) { - uart.write_str_blocking("Hex: 0x"); - write_hex_byte(&mut uart, num); - uart.write_str_blocking("\r\n"); - } else { - uart.write_str_blocking("Invalid number for hex command\r\n"); - } - } else if !command.is_empty() { - uart.write_str_blocking("Unknown command: "); - uart.write_str_blocking(core::str::from_utf8(command).unwrap_or("")); - uart.write_str_blocking("\r\n"); - } - } - - // Reset buffer and prompt - buf_idx = 0; - uart.write_str_blocking("Type a command: "); - } else if byte == 8 || byte == 127 { - // Backspace - if buf_idx > 0 { - buf_idx -= 1; - uart.write_str_blocking("\x08 \x08"); // Erase character - } - } else if buf_idx < buffer.len() - 1 { - // Regular character - buffer[buf_idx] = byte; - buf_idx += 1; - let _ = uart.write_byte(byte); - } - } -} - -/// Simple parser for u8 from ASCII bytes -fn parse_u8(bytes: &[u8]) -> Result { - let mut result = 0u8; - for &b in bytes { - if b.is_ascii_digit() { - result = result.checked_mul(10).ok_or(())?; - result = result.checked_add(b - b'0').ok_or(())?; - } else { - return Err(()); - } - } - Ok(result) -} diff --git a/examples/src/bin/i2c-blocking.rs b/examples/src/bin/i2c-blocking.rs deleted file mode 100644 index 0f6c8cbae..000000000 --- a/examples/src/bin/i2c-blocking.rs +++ /dev/null @@ -1,31 +0,0 @@ -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_time::Timer; -use hal::clocks::config::Div8; -use hal::config::Config; -use hal::i2c::controller::{self, I2c, Speed}; -use tmp108::Tmp108; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let mut config = Config::default(); - config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); - - let p = hal::init(config); - - defmt::info!("I2C example"); - - let mut config = controller::Config::default(); - config.speed = Speed::Standard; - let i2c = I2c::new_blocking(p.LPI2C3, p.P3_27, p.P3_28, config).unwrap(); - let mut tmp = Tmp108::new_with_a0_gnd(i2c); - - loop { - let temperature = tmp.temperature().unwrap(); - defmt::info!("Temperature: {}C", temperature); - Timer::after_secs(1).await; - } -} diff --git a/examples/src/bin/i2c-scan-blocking.rs b/examples/src/bin/i2c-scan-blocking.rs deleted file mode 100644 index 4e203597b..000000000 --- a/examples/src/bin/i2c-scan-blocking.rs +++ /dev/null @@ -1,41 +0,0 @@ -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa::gpio::Pull; -use embassy_mcxa::Input; -use embassy_time::Timer; -use hal::clocks::config::Div8; -use hal::config::Config; -use hal::i2c::controller::{self, I2c, Speed}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let mut config = Config::default(); - config.clock_cfg.sirc.fro_lf_div = Div8::from_divisor(1); - - let p = hal::init(config); - - defmt::info!("I2C example"); - - let mut config = controller::Config::default(); - config.speed = Speed::Standard; - - // Note: P0_2 is connected to P1_8 on the FRDM_MCXA276 via a resistor, and - // defaults to SWO on the debug peripheral. Explicitly make it a high-z - // input. - let _pin = Input::new(p.P0_2, Pull::Disabled); - let mut i2c = I2c::new_blocking(p.LPI2C2, p.P1_9, p.P1_8, config).unwrap(); - - for addr in 0x01..=0x7f { - let result = i2c.blocking_write(addr, &[]); - if result.is_ok() { - defmt::info!("Device found at addr {:02x}", addr); - } - } - - loop { - Timer::after_secs(10).await; - } -} diff --git a/examples/src/bin/lpuart_buffered.rs b/examples/src/bin/lpuart_buffered.rs deleted file mode 100644 index 420589d00..000000000 --- a/examples/src/bin/lpuart_buffered.rs +++ /dev/null @@ -1,62 +0,0 @@ -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::lpuart::buffered::BufferedLpuart; -use embassy_mcxa::lpuart::Config; -use embassy_mcxa::{bind_interrupts, lpuart}; -use embedded_io_async::Write; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -// Bind OS_EVENT for timers plus LPUART2 IRQ for the buffered driver -bind_interrupts!(struct Irqs { - LPUART2 => lpuart::buffered::BufferedInterruptHandler::; -}); - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let mut cfg = hal::config::Config::default(); - cfg.clock_cfg.sirc.fro_12m_enabled = true; - cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); - let p = hal::init(cfg); - - // Configure NVIC for LPUART2 - hal::interrupt::LPUART2.configure_for_uart(hal::interrupt::Priority::P3); - - // UART configuration (enable both TX and RX) - let config = Config { - baudrate_bps: 115_200, - rx_fifo_watermark: 0, - tx_fifo_watermark: 0, - ..Default::default() - }; - - let mut tx_buf = [0u8; 256]; - let mut rx_buf = [0u8; 256]; - - // Create a buffered LPUART2 instance with both TX and RX - let mut uart = BufferedLpuart::new( - p.LPUART2, - p.P2_2, // TX pin - p.P2_3, // RX pin - Irqs, - &mut tx_buf, - &mut rx_buf, - config, - ) - .unwrap(); - - // Split into TX and RX parts - let (tx, rx) = uart.split_ref(); - - tx.write(b"Hello buffered LPUART.\r\n").await.unwrap(); - tx.write(b"Type characters to echo them back.\r\n").await.unwrap(); - - // Echo loop - let mut buf = [0u8; 4]; - loop { - let used = rx.read(&mut buf).await.unwrap(); - tx.write_all(&buf[..used]).await.unwrap(); - } -} diff --git a/examples/src/bin/lpuart_dma.rs b/examples/src/bin/lpuart_dma.rs deleted file mode 100644 index 5497f8646..000000000 --- a/examples/src/bin/lpuart_dma.rs +++ /dev/null @@ -1,81 +0,0 @@ -//! LPUART DMA example for MCXA276. -//! -//! This example demonstrates using DMA for UART TX and RX operations. -//! It sends a message using DMA, then waits for 16 characters to be received -//! via DMA and echoes them back. -//! -//! The DMA request sources are automatically derived from the LPUART instance type. -//! DMA clock/reset/init is handled automatically by the HAL. - -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler}; -use embassy_mcxa::lpuart::{Config, LpuartDma}; -use embassy_mcxa::{bind_interrupts, pac}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -// Bind DMA channel interrupts using Embassy-style macro -bind_interrupts!(struct Irqs { - DMA_CH0 => DmaCh0InterruptHandler; - DMA_CH1 => DmaCh1InterruptHandler; -}); - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let mut cfg = hal::config::Config::default(); - cfg.clock_cfg.sirc.fro_12m_enabled = true; - cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); - let p = hal::init(cfg); - - defmt::info!("LPUART DMA example starting..."); - - // Enable DMA interrupts (per-channel, as needed) - unsafe { - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); - cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1); - } - - // Create UART configuration - let config = Config { - baudrate_bps: 115_200, - ..Default::default() - }; - - // Create UART instance with DMA channels - let mut lpuart = LpuartDma::new( - p.LPUART2, p.P2_2, // TX pin - p.P2_3, // RX pin - p.DMA_CH0, // TX DMA channel - p.DMA_CH1, // RX DMA channel - config, - ) - .unwrap(); - - // Send a message using DMA (DMA request source is automatically derived from LPUART2) - let tx_msg = b"Hello from LPUART2 DMA TX!\r\n"; - lpuart.write_dma(tx_msg).await.unwrap(); - - defmt::info!("TX DMA complete"); - - // Send prompt - let prompt = b"Type 16 characters to echo via DMA:\r\n"; - lpuart.write_dma(prompt).await.unwrap(); - - // Receive 16 characters using DMA - let mut rx_buf = [0u8; 16]; - lpuart.read_dma(&mut rx_buf).await.unwrap(); - - defmt::info!("RX DMA complete"); - - // Echo back the received data - let echo_prefix = b"\r\nReceived: "; - lpuart.write_dma(echo_prefix).await.unwrap(); - lpuart.write_dma(&rx_buf).await.unwrap(); - let done_msg = b"\r\nDone!\r\n"; - lpuart.write_dma(done_msg).await.unwrap(); - - defmt::info!("Example complete"); -} diff --git a/examples/src/bin/lpuart_polling.rs b/examples/src/bin/lpuart_polling.rs deleted file mode 100644 index b80668834..000000000 --- a/examples/src/bin/lpuart_polling.rs +++ /dev/null @@ -1,47 +0,0 @@ -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::config::Div8; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -use crate::hal::lpuart::{Config, Lpuart}; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let mut cfg = hal::config::Config::default(); - cfg.clock_cfg.sirc.fro_12m_enabled = true; - cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); - let p = hal::init(cfg); - - defmt::info!("boot"); - - // Create UART configuration - let config = Config { - baudrate_bps: 115_200, - ..Default::default() - }; - - // Create UART instance using LPUART2 with P2_2 as TX and P2_3 as RX - let lpuart = Lpuart::new_blocking( - p.LPUART2, // Peripheral - p.P2_2, // TX pin - p.P2_3, // RX pin - config, - ) - .unwrap(); - - // Split into separate TX and RX parts - let (mut tx, mut rx) = lpuart.split(); - - // Write hello messages - tx.blocking_write(b"Hello world.\r\n").unwrap(); - tx.blocking_write(b"Echoing. Type characters...\r\n").unwrap(); - - // Echo loop - loop { - let mut buf = [0u8; 1]; - rx.blocking_read(&mut buf).unwrap(); - tx.blocking_write(&buf).unwrap(); - } -} diff --git a/examples/src/bin/lpuart_ring_buffer.rs b/examples/src/bin/lpuart_ring_buffer.rs deleted file mode 100644 index 1d1a51970..000000000 --- a/examples/src/bin/lpuart_ring_buffer.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! LPUART Ring Buffer DMA example for MCXA276. -//! -//! This example demonstrates using the high-level `LpuartRxDma::setup_ring_buffer()` -//! API for continuous circular DMA reception from a UART peripheral. -//! -//! # Features demonstrated: -//! - `LpuartRxDma::setup_ring_buffer()` for continuous peripheral-to-memory DMA -//! - `RingBuffer` for async reading of received data -//! - Handling of potential overrun conditions -//! - Half-transfer and complete-transfer interrupts for timely wakeups -//! -//! # How it works: -//! 1. Create an `LpuartRxDma` driver with a DMA channel -//! 2. Call `setup_ring_buffer()` which handles all low-level DMA configuration -//! 3. Application asynchronously reads data as it arrives via `ring_buf.read()` -//! 4. Both half-transfer and complete-transfer interrupts wake the reader - -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa::bind_interrupts; -use embassy_mcxa::clocks::config::Div8; -use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler}; -use embassy_mcxa::lpuart::{Config, LpuartDma, LpuartTxDma}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -// Bind DMA channel interrupts -bind_interrupts!(struct Irqs { - DMA_CH0 => DmaCh0InterruptHandler; - DMA_CH1 => DmaCh1InterruptHandler; -}); - -// Ring buffer for RX - power of 2 is ideal for modulo efficiency -static mut RX_RING_BUFFER: [u8; 64] = [0; 64]; - -/// Helper to write a byte as hex to UART -fn write_hex( - tx: &mut LpuartTxDma<'_, T, C>, - byte: u8, -) { - const HEX: &[u8; 16] = b"0123456789ABCDEF"; - let buf = [HEX[(byte >> 4) as usize], HEX[(byte & 0x0F) as usize]]; - tx.blocking_write(&buf).ok(); -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - // Small delay to allow probe-rs to attach after reset - for _ in 0..100_000 { - cortex_m::asm::nop(); - } - - let mut cfg = hal::config::Config::default(); - cfg.clock_cfg.sirc.fro_12m_enabled = true; - cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); - let p = hal::init(cfg); - - defmt::info!("LPUART Ring Buffer DMA example starting..."); - - // Create UART configuration - let config = Config { - baudrate_bps: 115_200, - ..Default::default() - }; - - // Create LPUART with DMA support for both TX and RX, then split - // This is the proper Embassy pattern - create once, split into TX and RX - let lpuart = LpuartDma::new(p.LPUART2, p.P2_2, p.P2_3, p.DMA_CH1, p.DMA_CH0, config).unwrap(); - let (mut tx, rx) = lpuart.split(); - - tx.blocking_write(b"LPUART Ring Buffer DMA Example\r\n").unwrap(); - tx.blocking_write(b"==============================\r\n\r\n").unwrap(); - - tx.blocking_write(b"Setting up circular DMA for UART RX...\r\n") - .unwrap(); - - // Set up the ring buffer with circular DMA - // The HAL handles: DMA request source, RDMAE enable, circular transfer config, NVIC enable - let ring_buf = unsafe { - let buf = &mut *core::ptr::addr_of_mut!(RX_RING_BUFFER); - rx.setup_ring_buffer(buf) - }; - - // Enable DMA requests to start continuous reception - unsafe { - rx.enable_dma_request(); - } - - tx.blocking_write(b"Ring buffer ready! Type characters to see them echoed.\r\n") - .unwrap(); - tx.blocking_write(b"The DMA continuously receives in the background.\r\n\r\n") - .unwrap(); - - // Main loop: read from ring buffer and echo back - let mut read_buf = [0u8; 16]; - let mut total_received: usize = 0; - - loop { - // Async read - waits until data is available - match ring_buf.read(&mut read_buf).await { - Ok(n) if n > 0 => { - total_received += n; - - // Echo back what we received - tx.blocking_write(b"RX[").unwrap(); - for (i, &byte) in read_buf.iter().enumerate().take(n) { - write_hex(&mut tx, byte); - if i < n - 1 { - tx.blocking_write(b" ").unwrap(); - } - } - tx.blocking_write(b"]: ").unwrap(); - tx.blocking_write(&read_buf[..n]).unwrap(); - tx.blocking_write(b"\r\n").unwrap(); - - defmt::info!("Received {} bytes, total: {}", n, total_received); - } - Ok(_) => { - // No data, shouldn't happen with async read - } - Err(_) => { - // Overrun detected - tx.blocking_write(b"ERROR: Ring buffer overrun!\r\n").unwrap(); - defmt::error!("Ring buffer overrun!"); - ring_buf.clear(); - } - } - } -} diff --git a/examples/src/bin/rtc_alarm.rs b/examples/src/bin/rtc_alarm.rs deleted file mode 100644 index a7800a2d1..000000000 --- a/examples/src/bin/rtc_alarm.rs +++ /dev/null @@ -1,74 +0,0 @@ -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa as hal; -use hal::rtc::{RtcDateTime, RtcInterruptEnable}; -use hal::InterruptExt; - -type MyRtc = hal::rtc::Rtc<'static, hal::rtc::Rtc0>; - -use embassy_mcxa::bind_interrupts; -use {defmt_rtt as _, panic_probe as _}; - -bind_interrupts!(struct Irqs { - RTC => hal::rtc::RtcHandler; -}); - -#[used] -#[no_mangle] -static KEEP_RTC: unsafe extern "C" fn() = RTC; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = hal::init(hal::config::Config::default()); - - defmt::info!("=== RTC Alarm Example ==="); - - let rtc_config = hal::rtc::get_default_config(); - - let rtc = MyRtc::new(p.RTC0, rtc_config); - - let now = RtcDateTime { - year: 2025, - month: 10, - day: 15, - hour: 14, - minute: 30, - second: 0, - }; - - rtc.stop(); - - defmt::info!("Time set to: 2025-10-15 14:30:00"); - rtc.set_datetime(now); - - let mut alarm = now; - alarm.second += 10; - - rtc.set_alarm(alarm); - defmt::info!("Alarm set for: 2025-10-15 14:30:10 (+10 seconds)"); - - rtc.set_interrupt(RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE); - - unsafe { - hal::interrupt::RTC.enable(); - } - - unsafe { - cortex_m::interrupt::enable(); - } - - rtc.start(); - - defmt::info!("RTC started, waiting for alarm..."); - - loop { - if rtc.is_alarm_triggered() { - defmt::info!("*** ALARM TRIGGERED! ***"); - break; - } - } - - defmt::info!("Example complete - Test PASSED!"); -} diff --git a/examples/src/lib.rs b/examples/src/lib.rs deleted file mode 100644 index 2573a6adc..000000000 --- a/examples/src/lib.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_std] -#![allow(clippy::missing_safety_doc)] - -//! Shared board-specific helpers for the FRDM-MCXA276 examples. -//! These live with the examples so the HAL stays generic. - -use hal::{clocks, pins}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -/// Initialize clocks and pin muxing for ADC. -pub unsafe fn init_adc_pins() { - // NOTE: Lpuart has been updated to properly enable + reset its own clocks. - // GPIO has not. - _ = clocks::enable_and_reset::(&clocks::periph_helpers::NoConfig); - pins::configure_adc_pins(); -} diff --git a/ram.ld b/ram.ld deleted file mode 100644 index 816ab6819..000000000 --- a/ram.ld +++ /dev/null @@ -1,109 +0,0 @@ -/* Simple RAM execution linker script for MCXA276 */ -MEMORY -{ - RAM : ORIGIN = 0x20000000, LENGTH = 128K -} - -/* Pull in device default interrupt symbol aliases (e.g., CMC = DefaultHandler) */ -INCLUDE device.x - - -/* Provide core exception weak aliases if not supplied by cortex-m-rt's link.x */ -PROVIDE(NonMaskableInt = DefaultHandler); -PROVIDE(HardFault = DefaultHandler); -PROVIDE(MemoryManagement = DefaultHandler); -PROVIDE(BusFault = DefaultHandler); -PROVIDE(UsageFault = DefaultHandler); -PROVIDE(SecureFault = DefaultHandler); -PROVIDE(SVCall = DefaultHandler); -PROVIDE(DebugMonitor = DefaultHandler); -PROVIDE(PendSV = DefaultHandler); -PROVIDE(SysTick = DefaultHandler); - -/* In RAM-run we have no FLASH sidata; copy from sdata */ -__sidata = __sdata; - -/* Ensure the PAC interrupt table is kept */ -EXTERN(__INTERRUPTS); - - -/* Pull in defmt's linker script to generate the defmt table that host decoders expect */ -INCLUDE defmt.x - -ENTRY(Reset) -EXTERN(VECTOR_TABLE) -EXTERN(Reset) -EXTERN(main) - -/* Define _stack_start at end of RAM BEFORE it's used in vector table */ -_stack_start = ORIGIN(RAM) + LENGTH(RAM); - -SECTIONS -{ - .vector_table ORIGIN(RAM) : - { - /* Slot 0: Initial stack pointer - use our explicitly set _stack_start */ - LONG(_stack_start); - /* Slot 1: Reset vector - address of Reset function with Thumb bit set */ - LONG(Reset | 1); - /* Cortex-M33 core exceptions (slots 2-14) */ - KEEP(*(.vector_table.exceptions)); - /* Peripheral interrupt vectors provided by PAC (slots 16+) */ - KEEP(*(.vector_table.interrupts)); - } > RAM - - .text : - { - KEEP(*(.text.Reset)); - KEEP(*(.text.main)); - *(.text .text.*); - } > RAM - - /* Keep defmt table and fragments so host decoders can find metadata */ - .defmt : - { - KEEP(*(.defmt)); - KEEP(*(.defmt.*)); - } > RAM - - .rodata : - { - *(.rodata .rodata.*); - } > RAM - - .data : - { - . = ALIGN(4); - __sdata = .; - *(.data .data.*); - . = ALIGN(4); - __edata = .; - } > RAM - - /* Ensure RTT control block with "SEGGER RTT" signature is loaded to RAM */ - .rtt : - { - KEEP(*(.rtt)); - } > RAM - - /* Place uninitialized buffers (like defmt-rtt) in RAM; load is fine for RAM-run */ - .uninit : - { - *(.uninit .uninit.*); - } > RAM - - .bss : - { - . = ALIGN(4); - __sbss = .; - *(.bss .bss.*); - . = ALIGN(4); - __ebss = .; - } > RAM - - /* Discard exception unwinding info */ - /DISCARD/ : - { - *(.ARM.exidx .ARM.exidx.*); - } -} diff --git a/run.sh b/run.sh deleted file mode 100644 index 418dc8a24..000000000 --- a/run.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ELF="${1:-}" -if [[ -z "${ELF}" ]]; then - echo "Usage: $0 " - exit 1 -fi -if [[ ! -f "${ELF}" ]]; then - echo "ELF not found: ${ELF}" - exit 1 -fi - -# Configurable via env -CHIP="${CHIP:-MCXA276}" -SPEED="${PROBE_SPEED:-1000}" # kHz -# Default to J-Link if PROBE not provided -PROBE_OPT=(--probe "${PROBE:-1366:0101:000600110607}") -PORT="${PROBE_RS_GDB_PORT:-1337}" - -cleanup() { - if [[ -n "${GDB_SERVER_PID:-}" ]]; then kill "${GDB_SERVER_PID}" 2>/dev/null || true; fi - [[ -n "${GDB_SCRIPT:-}" ]] && rm -f "${GDB_SCRIPT}" || true - [[ -n "${SERVER_LOG:-}" ]] && rm -f "${SERVER_LOG}" || true -} -trap cleanup EXIT - -if ! command -v probe-rs >/dev/null 2>&1; then - echo "probe-rs not found (cargo install probe-rs --features cli)" - exit 1 -fi -if ! command -v gdb-multiarch >/dev/null 2>&1; then - echo "gdb-multiarch not found; install it (e.g., sudo apt install gdb-multiarch)." - exit 1 -fi - -# Start probe-rs GDB server and capture its output to a log (do not hide errors) -SERVER_LOG=$(mktemp) -set +e -probe-rs gdb --chip "${CHIP}" --protocol swd --speed "${SPEED}" --non-interactive "${ELF}" "${PROBE_OPT[@]}" \ - >"${SERVER_LOG}" 2>&1 & -GDB_SERVER_PID=$! -set -e - -# Wait for server readiness without touching the TCP port to avoid corrupting the GDB protocol -ready="" -for _ in {1..50}; do - if grep -q "Firing up GDB stub" "${SERVER_LOG}"; then ready=1; break; fi - if grep -q "Connecting to the chip was unsuccessful" "${SERVER_LOG}"; then - echo "probe-rs gdb server failed to connect to target. Log:" >&2 - echo "----- probe-rs gdb log -----" >&2 - sed -e 's/^/ /' "${SERVER_LOG}" >&2 || true - exit 1 - fi - sleep 0.1 -done -if [[ -z "${ready}" ]]; then - echo "probe-rs gdb server did not report readiness. Log:" >&2 - echo "----- probe-rs gdb log -----" >&2 - sed -e 's/^/ /' "${SERVER_LOG}" >&2 || true - exit 1 -fi - -# GDB script: load to RAM and run, no reset -GDB_SCRIPT=$(mktemp) -cat >"${GDB_SCRIPT}" <&2 - echo "----- probe-rs gdb log -----" >&2 - sed -e 's/^/ /' "${SERVER_LOG}" >&2 || true - exit 1 -fi - -echo "Program loaded and started (no reset)" diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 9eb3c3b4f..000000000 --- a/rustfmt.toml +++ /dev/null @@ -1,3 +0,0 @@ -group_imports = "StdExternalCrate" -imports_granularity = "Module" -max_width = 120 diff --git a/src/adc.rs b/src/adc.rs deleted file mode 100644 index b5ec5983f..000000000 --- a/src/adc.rs +++ /dev/null @@ -1,409 +0,0 @@ -//! ADC driver -use core::sync::atomic::{AtomicBool, Ordering}; - -use embassy_hal_internal::{Peri, PeripheralType}; - -use crate::clocks::periph_helpers::{AdcClockSel, AdcConfig, Div4}; -use crate::clocks::{enable_and_reset, Gate, PoweredClock}; -use crate::pac; -use crate::pac::adc1::cfg::{HptExdi, Pwrsel, Refsel, Tcmdres, Tprictrl, Tres}; -use crate::pac::adc1::cmdh1::{Avgs, Cmpen, Next, Sts}; -use crate::pac::adc1::cmdl1::{Adch, Ctype, Mode}; -use crate::pac::adc1::ctrl::CalAvgs; -use crate::pac::adc1::tctrl::{Tcmd, Tpri}; - -type Regs = pac::adc1::RegisterBlock; - -static INTERRUPT_TRIGGERED: AtomicBool = AtomicBool::new(false); -// Token-based instance pattern like embassy-imxrt -pub trait Instance: Gate + PeripheralType { - fn ptr() -> *const Regs; -} - -/// Token for ADC1 -pub type Adc1 = crate::peripherals::ADC1; -impl Instance for crate::peripherals::ADC1 { - #[inline(always)] - fn ptr() -> *const Regs { - pac::Adc1::ptr() - } -} - -// Also implement Instance for the Peri wrapper type -// impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::ADC1> { -// #[inline(always)] -// fn ptr() -> *const Regs { -// pac::Adc1::ptr() -// } -// } - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum TriggerPriorityPolicy { - ConvPreemptImmediatelyNotAutoResumed = 0, - ConvPreemptSoftlyNotAutoResumed = 1, - ConvPreemptImmediatelyAutoRestarted = 4, - ConvPreemptSoftlyAutoRestarted = 5, - ConvPreemptImmediatelyAutoResumed = 12, - ConvPreemptSoftlyAutoResumed = 13, - ConvPreemptSubsequentlyNotAutoResumed = 2, - ConvPreemptSubsequentlyAutoRestarted = 6, - ConvPreemptSubsequentlyAutoResumed = 14, - TriggerPriorityExceptionDisabled = 16, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct LpadcConfig { - pub enable_in_doze_mode: bool, - pub conversion_average_mode: CalAvgs, - pub enable_analog_preliminary: bool, - pub power_up_delay: u8, - pub reference_voltage_source: Refsel, - pub power_level_mode: Pwrsel, - pub trigger_priority_policy: TriggerPriorityPolicy, - pub enable_conv_pause: bool, - pub conv_pause_delay: u16, - pub fifo_watermark: u8, - pub power: PoweredClock, - pub source: AdcClockSel, - pub div: Div4, -} - -impl Default for LpadcConfig { - fn default() -> Self { - LpadcConfig { - enable_in_doze_mode: true, - conversion_average_mode: CalAvgs::NoAverage, - enable_analog_preliminary: false, - power_up_delay: 0x80, - reference_voltage_source: Refsel::Option1, - power_level_mode: Pwrsel::Lowest, - trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, - enable_conv_pause: false, - conv_pause_delay: 0, - fifo_watermark: 0, - power: PoweredClock::NormalEnabledDeepSleepDisabled, - source: AdcClockSel::FroLfDiv, - div: Div4::no_div(), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ConvCommandConfig { - pub sample_channel_mode: Ctype, - pub channel_number: Adch, - pub chained_next_command_number: Next, - pub enable_auto_channel_increment: bool, - pub loop_count: u8, - pub hardware_average_mode: Avgs, - pub sample_time_mode: Sts, - pub hardware_compare_mode: Cmpen, - pub hardware_compare_value_high: u32, - pub hardware_compare_value_low: u32, - pub conversion_resolution_mode: Mode, - pub enable_wait_trigger: bool, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ConvTriggerConfig { - pub target_command_id: Tcmd, - pub delay_power: u8, - pub priority: Tpri, - pub enable_hardware_trigger: bool, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ConvResult { - pub command_id_source: u32, - pub loop_count_index: u32, - pub trigger_id_source: u32, - pub conv_value: u16, -} - -pub struct Adc<'a, I: Instance> { - _inst: core::marker::PhantomData<&'a mut I>, -} - -impl<'a, I: Instance> Adc<'a, I> { - /// initialize ADC - pub fn new(_inst: Peri<'a, I>, config: LpadcConfig) -> Self { - let adc = unsafe { &*I::ptr() }; - - let _clock_freq = unsafe { - enable_and_reset::(&AdcConfig { - power: config.power, - source: config.source, - div: config.div, - }) - .expect("Adc Init should not fail") - }; - - /* Reset the module. */ - adc.ctrl().modify(|_, w| w.rst().held_in_reset()); - adc.ctrl().modify(|_, w| w.rst().released_from_reset()); - - adc.ctrl().modify(|_, w| w.rstfifo0().trigger_reset()); - - /* Disable the module before setting configuration. */ - adc.ctrl().modify(|_, w| w.adcen().disabled()); - - /* Configure the module generally. */ - if config.enable_in_doze_mode { - adc.ctrl().modify(|_, w| w.dozen().enabled()); - } else { - adc.ctrl().modify(|_, w| w.dozen().disabled()); - } - - /* Set calibration average mode. */ - adc.ctrl() - .modify(|_, w| w.cal_avgs().variant(config.conversion_average_mode)); - - adc.cfg().write(|w| unsafe { - let w = if config.enable_analog_preliminary { - w.pwren().pre_enabled() - } else { - w - }; - - w.pudly() - .bits(config.power_up_delay) - .refsel() - .variant(config.reference_voltage_source) - .pwrsel() - .variant(config.power_level_mode) - .tprictrl() - .variant(match config.trigger_priority_policy { - TriggerPriorityPolicy::ConvPreemptSoftlyNotAutoResumed - | TriggerPriorityPolicy::ConvPreemptSoftlyAutoRestarted - | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed => Tprictrl::FinishCurrentOnPriority, - TriggerPriorityPolicy::ConvPreemptSubsequentlyNotAutoResumed - | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoRestarted - | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed => Tprictrl::FinishSequenceOnPriority, - _ => Tprictrl::AbortCurrentOnPriority, - }) - .tres() - .variant(match config.trigger_priority_policy { - TriggerPriorityPolicy::ConvPreemptImmediatelyAutoRestarted - | TriggerPriorityPolicy::ConvPreemptSoftlyAutoRestarted - | TriggerPriorityPolicy::ConvPreemptImmediatelyAutoResumed - | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed - | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoRestarted - | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed => Tres::Enabled, - _ => Tres::Disabled, - }) - .tcmdres() - .variant(match config.trigger_priority_policy { - TriggerPriorityPolicy::ConvPreemptImmediatelyAutoResumed - | TriggerPriorityPolicy::ConvPreemptSoftlyAutoResumed - | TriggerPriorityPolicy::ConvPreemptSubsequentlyAutoResumed - | TriggerPriorityPolicy::TriggerPriorityExceptionDisabled => Tcmdres::Enabled, - _ => Tcmdres::Disabled, - }) - .hpt_exdi() - .variant(match config.trigger_priority_policy { - TriggerPriorityPolicy::TriggerPriorityExceptionDisabled => HptExdi::Disabled, - _ => HptExdi::Enabled, - }) - }); - - if config.enable_conv_pause { - adc.pause() - .modify(|_, w| unsafe { w.pauseen().enabled().pausedly().bits(config.conv_pause_delay) }); - } else { - adc.pause().write(|w| unsafe { w.bits(0) }); - } - - adc.fctrl0() - .write(|w| unsafe { w.fwmark().bits(config.fifo_watermark) }); - - // Enable ADC - adc.ctrl().modify(|_, w| w.adcen().enabled()); - - Self { - _inst: core::marker::PhantomData, - } - } - - pub fn deinit(&self) { - let adc = unsafe { &*I::ptr() }; - adc.ctrl().modify(|_, w| w.adcen().disabled()); - } - - pub fn do_offset_calibration(&self) { - let adc = unsafe { &*I::ptr() }; - // Enable calibration mode - adc.ctrl() - .modify(|_, w| w.calofs().offset_calibration_request_pending()); - - // Wait for calibration to complete (polling status register) - while adc.stat().read().cal_rdy().is_not_set() {} - } - - pub fn get_gain_conv_result(&self, mut gain_adjustment: f32) -> u32 { - let mut gcra_array = [0u32; 17]; - let mut gcalr: u32 = 0; - - for i in (1..=17).rev() { - let shift = 16 - (i - 1); - let step = 1.0 / (1u32 << shift) as f32; - let tmp = (gain_adjustment / step) as u32; - gcra_array[i - 1] = tmp; - gain_adjustment -= tmp as f32 * step; - } - - for i in (1..=17).rev() { - gcalr += gcra_array[i - 1] << (i - 1); - } - gcalr - } - - pub fn do_auto_calibration(&self) { - let adc = unsafe { &*I::ptr() }; - adc.ctrl().modify(|_, w| w.cal_req().calibration_request_pending()); - - while adc.gcc0().read().rdy().is_gain_cal_not_valid() {} - - let mut gcca = adc.gcc0().read().gain_cal().bits() as u32; - if gcca & ((0xFFFF + 1) >> 1) != 0 { - gcca |= !0xFFFF; - } - - let gcra = 131072.0 / (131072.0 - gcca as f32); - - // Write to GCR0 - adc.gcr0().write(|w| unsafe { w.bits(self.get_gain_conv_result(gcra)) }); - - adc.gcr0().modify(|_, w| w.rdy().set_bit()); - - // Wait for calibration to complete (polling status register) - while adc.stat().read().cal_rdy().is_not_set() {} - } - - pub fn do_software_trigger(&self, trigger_id_mask: u32) { - let adc = unsafe { &*I::ptr() }; - adc.swtrig().write(|w| unsafe { w.bits(trigger_id_mask) }); - } - - pub fn get_default_conv_command_config(&self) -> ConvCommandConfig { - ConvCommandConfig { - sample_channel_mode: Ctype::SingleEndedASideChannel, - channel_number: Adch::SelectCh0, - chained_next_command_number: Next::NoNextCmdTerminateOnFinish, - enable_auto_channel_increment: false, - loop_count: 0, - hardware_average_mode: Avgs::NoAverage, - sample_time_mode: Sts::Sample3p5, - hardware_compare_mode: Cmpen::DisabledAlwaysStoreResult, - hardware_compare_value_high: 0, - hardware_compare_value_low: 0, - conversion_resolution_mode: Mode::Data12Bits, - enable_wait_trigger: false, - } - } - - //TBD Need to add cmdlx and cmdhx with x {2..7} - pub fn set_conv_command_config(&self, index: u32, config: &ConvCommandConfig) { - let adc = unsafe { &*I::ptr() }; - - match index { - 1 => { - adc.cmdl1().write(|w| { - w.adch() - .variant(config.channel_number) - .mode() - .variant(config.conversion_resolution_mode) - }); - adc.cmdh1().write(|w| unsafe { - w.next() - .variant(config.chained_next_command_number) - .loop_() - .bits(config.loop_count) - .avgs() - .variant(config.hardware_average_mode) - .sts() - .variant(config.sample_time_mode) - .cmpen() - .variant(config.hardware_compare_mode); - if config.enable_wait_trigger { - w.wait_trig().enabled(); - } - if config.enable_auto_channel_increment { - w.lwi().enabled(); - } - w - }); - } - _ => panic!("Invalid command index: must be between 1 and 7"), - } - } - - pub fn get_default_conv_trigger_config(&self) -> ConvTriggerConfig { - ConvTriggerConfig { - target_command_id: Tcmd::NotValid, - delay_power: 0, - priority: Tpri::HighestPriority, - enable_hardware_trigger: false, - } - } - - pub fn set_conv_trigger_config(&self, trigger_id: usize, config: &ConvTriggerConfig) { - let adc = unsafe { &*I::ptr() }; - let tctrl = &adc.tctrl(trigger_id); - - tctrl.write(|w| unsafe { - let w = w.tcmd().variant(config.target_command_id); - let w = w.tdly().bits(config.delay_power); - w.tpri().variant(config.priority); - if config.enable_hardware_trigger { - w.hten().enabled() - } else { - w - } - }); - } - - pub fn do_reset_fifo(&self) { - let adc = unsafe { &*I::ptr() }; - adc.ctrl().modify(|_, w| w.rstfifo0().trigger_reset()); - } - - pub fn enable_interrupt(&self, mask: u32) { - let adc = unsafe { &*I::ptr() }; - adc.ie().modify(|r, w| unsafe { w.bits(r.bits() | mask) }); - INTERRUPT_TRIGGERED.store(false, Ordering::SeqCst); - } - - pub fn is_interrupt_triggered(&self) -> bool { - INTERRUPT_TRIGGERED.load(Ordering::Relaxed) - } -} - -pub fn get_conv_result() -> Option { - let adc = unsafe { &*pac::Adc1::ptr() }; - let fifo = adc.resfifo0().read().bits(); - const VALID_MASK: u32 = 1 << 31; - if fifo & VALID_MASK == 0 { - return None; - } - - Some(ConvResult { - command_id_source: (fifo >> 24) & 0x0F, - loop_count_index: (fifo >> 20) & 0x0F, - trigger_id_source: (fifo >> 16) & 0x0F, - conv_value: (fifo & 0xFFFF) as u16, - }) -} - -pub fn on_interrupt() { - if get_conv_result().is_some() { - INTERRUPT_TRIGGERED.store(true, Ordering::SeqCst); - } -} - -pub struct AdcHandler; -impl crate::interrupt::typelevel::Handler for AdcHandler { - unsafe fn on_interrupt() { - on_interrupt(); - } -} diff --git a/src/clkout.rs b/src/clkout.rs deleted file mode 100644 index 88c731df1..000000000 --- a/src/clkout.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! CLKOUT pseudo-peripheral -//! -//! CLKOUT is a part of the clock generation subsystem, and can be used -//! either to generate arbitrary waveforms, or to debug the state of -//! internal oscillators. - -use core::marker::PhantomData; - -use embassy_hal_internal::Peri; - -pub use crate::clocks::periph_helpers::Div4; -use crate::clocks::{with_clocks, ClockError, PoweredClock}; -use crate::pac::mrcc0::mrcc_clkout_clksel::Mux; -use crate::peripherals::CLKOUT; - -/// A peripheral representing the CLKOUT pseudo-peripheral -pub struct ClockOut<'a> { - _p: PhantomData<&'a mut CLKOUT>, - freq: u32, -} - -/// Selected clock source to output -pub enum ClockOutSel { - /// 12MHz Internal Oscillator - Fro12M, - /// FRO180M Internal Oscillator, via divisor - FroHfDiv, - /// External Oscillator - ClkIn, - /// 16KHz oscillator - Clk16K, - /// Output of PLL1 - Pll1Clk, - /// Main System CPU clock, divided by 6 - SlowClk, -} - -/// Configuration for the ClockOut -pub struct Config { - /// Selected Source Clock - pub sel: ClockOutSel, - /// Selected division level - pub div: Div4, - /// Selected power level - pub level: PoweredClock, -} - -impl<'a> ClockOut<'a> { - /// Create a new ClockOut pin. On success, the clock signal will begin immediately - /// on the given pin. - pub fn new( - _peri: Peri<'a, CLKOUT>, - pin: Peri<'a, impl sealed::ClockOutPin>, - cfg: Config, - ) -> Result { - // There's no MRCC enable bit, so we check the validity of the clocks here - // - // TODO: Should we check that the frequency is suitably low? - let (freq, mux) = check_sel(cfg.sel, cfg.level)?; - - // All good! Apply requested config, starting with the pin. - pin.mux(); - - setup_clkout(mux, cfg.div); - - Ok(Self { - _p: PhantomData, - freq: freq / cfg.div.into_divisor(), - }) - } - - /// Frequency of the clkout pin - #[inline] - pub fn frequency(&self) -> u32 { - self.freq - } -} - -impl Drop for ClockOut<'_> { - fn drop(&mut self) { - disable_clkout(); - } -} - -/// Check whether the given clock selection is valid -fn check_sel(sel: ClockOutSel, level: PoweredClock) -> Result<(u32, Mux), ClockError> { - let res = with_clocks(|c| { - Ok(match sel { - ClockOutSel::Fro12M => (c.ensure_fro_hf_active(&level)?, Mux::Clkroot12m), - ClockOutSel::FroHfDiv => (c.ensure_fro_hf_div_active(&level)?, Mux::ClkrootFircDiv), - ClockOutSel::ClkIn => (c.ensure_clk_in_active(&level)?, Mux::ClkrootSosc), - ClockOutSel::Clk16K => (c.ensure_clk_16k_vdd_core_active(&level)?, Mux::Clkroot16k), - ClockOutSel::Pll1Clk => (c.ensure_pll1_clk_active(&level)?, Mux::ClkrootSpll), - ClockOutSel::SlowClk => (c.ensure_slow_clk_active(&level)?, Mux::ClkrootSlow), - }) - }); - let Some(res) = res else { - return Err(ClockError::NeverInitialized); - }; - res -} - -/// Set up the clkout pin using the given mux and div settings -fn setup_clkout(mux: Mux, div: Div4) { - let mrcc = unsafe { crate::pac::Mrcc0::steal() }; - - mrcc.mrcc_clkout_clksel().write(|w| w.mux().variant(mux)); - - // Set up clkdiv - mrcc.mrcc_clkout_clkdiv().write(|w| { - w.halt().set_bit(); - w.reset().set_bit(); - unsafe { w.div().bits(div.into_bits()) }; - w - }); - mrcc.mrcc_clkout_clkdiv().write(|w| { - w.halt().clear_bit(); - w.reset().clear_bit(); - unsafe { w.div().bits(div.into_bits()) }; - w - }); - - while mrcc.mrcc_clkout_clkdiv().read().unstab().bit_is_set() {} -} - -/// Stop the clkout -fn disable_clkout() { - // Stop the output by selecting the "none" clock - // - // TODO: restore the pin to hi-z or something? - let mrcc = unsafe { crate::pac::Mrcc0::steal() }; - mrcc.mrcc_clkout_clkdiv().write(|w| { - w.reset().set_bit(); - w.halt().set_bit(); - unsafe { - w.div().bits(0); - } - w - }); - mrcc.mrcc_clkout_clksel().write(|w| unsafe { w.bits(0b111) }); -} - -mod sealed { - use embassy_hal_internal::PeripheralType; - - use crate::gpio::{Pull, SealedPin}; - - /// Sealed marker trait for clockout pins - pub trait ClockOutPin: PeripheralType { - /// Set the given pin to the correct muxing state - fn mux(&self); - } - - macro_rules! impl_pin { - ($pin:ident, $func:ident) => { - impl ClockOutPin for crate::peripherals::$pin { - fn mux(&self) { - self.set_function(crate::pac::port0::pcr0::Mux::$func); - self.set_pull(Pull::Disabled); - } - } - }; - } - - impl_pin!(P0_6, Mux12); - impl_pin!(P3_6, Mux1); - impl_pin!(P3_8, Mux12); - impl_pin!(P4_2, Mux1); -} diff --git a/src/clocks/config.rs b/src/clocks/config.rs deleted file mode 100644 index 0563b8917..000000000 --- a/src/clocks/config.rs +++ /dev/null @@ -1,204 +0,0 @@ -//! Clock Configuration -//! -//! This module holds configuration types used for the system clocks. For -//! configuration of individual peripherals, see [`super::periph_helpers`]. - -use super::PoweredClock; - -/// This type represents a divider in the range 1..=256. -/// -/// At a hardware level, this is an 8-bit register from 0..=255, -/// which adds one. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct Div8(pub(super) u8); - -impl Div8 { - /// Store a "raw" divisor value that will divide the source by - /// `(n + 1)`, e.g. `Div8::from_raw(0)` will divide the source - /// by 1, and `Div8::from_raw(255)` will divide the source by - /// 256. - pub const fn from_raw(n: u8) -> Self { - Self(n) - } - - /// Divide by one, or no division - pub const fn no_div() -> Self { - Self(0) - } - - /// Store a specific divisor value that will divide the source - /// by `n`. e.g. `Div8::from_divisor(1)` will divide the source - /// by 1, and `Div8::from_divisor(256)` will divide the source - /// by 256. - /// - /// Will return `None` if `n` is not in the range `1..=256`. - /// Consider [`Self::from_raw`] for an infallible version. - pub const fn from_divisor(n: u16) -> Option { - let Some(n) = n.checked_sub(1) else { - return None; - }; - if n > (u8::MAX as u16) { - return None; - } - Some(Self(n as u8)) - } - - /// Convert into "raw" bits form - #[inline(always)] - pub const fn into_bits(self) -> u8 { - self.0 - } - - /// Convert into "divisor" form, as a u32 for convenient frequency math - #[inline(always)] - pub const fn into_divisor(self) -> u32 { - self.0 as u32 + 1 - } -} - -/// ```text -/// ┌─────────────────────────────────────────────────────────┐ -/// │ │ -/// │ ┌───────────┐ clk_out ┌─────────┐ │ -/// XTAL ──────┼──▷│ System │───────────▷│ │ clk_in │ -/// │ │ OSC │ clkout_byp │ MUX │──────────────────┼──────▷ -/// EXTAL ──────┼──▷│ │───────────▷│ │ │ -/// │ └───────────┘ └─────────┘ │ -/// │ │ -/// │ ┌───────────┐ fro_hf_root ┌────┐ fro_hf │ -/// │ │ FRO180 ├───────┬─────▷│ CG │─────────────────────┼──────▷ -/// │ │ │ │ ├────┤ clk_45m │ -/// │ │ │ └─────▷│ CG │─────────────────────┼──────▷ -/// │ └───────────┘ └────┘ │ -/// │ ┌───────────┐ fro_12m_root ┌────┐ fro_12m │ -/// │ │ FRO12M │────────┬─────▷│ CG │────────────────────┼──────▷ -/// │ │ │ │ ├────┤ clk_1m │ -/// │ │ │ └─────▷│1/12│────────────────────┼──────▷ -/// │ └───────────┘ └────┘ │ -/// │ │ -/// │ ┌──────────┐ │ -/// │ │000 │ │ -/// │ clk_in │ │ │ -/// │ ───────────────▷│001 │ │ -/// │ fro_12m │ │ │ -/// │ ───────────────▷│010 │ │ -/// │ fro_hf_root │ │ │ -/// │ ───────────────▷│011 │ main_clk │ -/// │ │ │───────────────────────────┼──────▷ -/// clk_16k ──────┼─────────────────▷│100 │ │ -/// │ none │ │ │ -/// │ ───────────────▷│101 │ │ -/// │ pll1_clk │ │ │ -/// │ ───────────────▷│110 │ │ -/// │ none │ │ │ -/// │ ───────────────▷│111 │ │ -/// │ └──────────┘ │ -/// │ ▲ │ -/// │ │ │ -/// │ SCG SCS │ -/// │ SCG-Lite │ -/// └─────────────────────────────────────────────────────────┘ -/// -/// -/// clk_in ┌─────┐ -/// ───────────────▷│00 │ -/// clk_45m │ │ -/// ───────────────▷│01 │ ┌───────────┐ pll1_clk -/// none │ │─────▷│ SPLL │───────────────▷ -/// ───────────────▷│10 │ └───────────┘ -/// fro_12m │ │ -/// ───────────────▷│11 │ -/// └─────┘ -/// ``` -#[non_exhaustive] -pub struct ClocksConfig { - /// FIRC, FRO180, 45/60/90/180M clock source - pub firc: Option, - /// SIRC, FRO12M, clk_12m clock source - // NOTE: I don't think we *can* disable the SIRC? - pub sirc: SircConfig, - /// FRO16K clock source - pub fro16k: Option, -} - -// FIRC/FRO180M - -/// ```text -/// ┌───────────┐ fro_hf_root ┌────┐ fro_hf -/// │ FRO180M ├───────┬─────▷│GATE│──────────▷ -/// │ │ │ ├────┤ clk_45m -/// │ │ └─────▷│GATE│──────────▷ -/// └───────────┘ └────┘ -/// ``` -#[non_exhaustive] -pub struct FircConfig { - /// Selected clock frequency - pub frequency: FircFreqSel, - /// Selected power state of the clock - pub power: PoweredClock, - /// Is the "fro_hf" gated clock enabled? - pub fro_hf_enabled: bool, - /// Is the "clk_45m" gated clock enabled? - pub clk_45m_enabled: bool, - /// Is the "fro_hf_div" clock enabled? Requires `fro_hf`! - pub fro_hf_div: Option, -} - -/// Selected FIRC frequency -pub enum FircFreqSel { - /// 45MHz Output - Mhz45, - /// 60MHz Output - Mhz60, - /// 90MHz Output - Mhz90, - /// 180MHz Output - Mhz180, -} - -// SIRC/FRO12M - -/// ```text -/// ┌───────────┐ fro_12m_root ┌────┐ fro_12m -/// │ FRO12M │────────┬─────▷│ CG │──────────▷ -/// │ │ │ ├────┤ clk_1m -/// │ │ └─────▷│1/12│──────────▷ -/// └───────────┘ └────┘ -/// ``` -#[non_exhaustive] -pub struct SircConfig { - pub power: PoweredClock, - // peripheral output, aka sirc_12mhz - pub fro_12m_enabled: bool, - /// Is the "fro_lf_div" clock enabled? Requires `fro_12m`! - pub fro_lf_div: Option, -} - -#[non_exhaustive] -pub struct Fro16KConfig { - pub vsys_domain_active: bool, - pub vdd_core_domain_active: bool, -} - -impl Default for ClocksConfig { - fn default() -> Self { - Self { - firc: Some(FircConfig { - frequency: FircFreqSel::Mhz45, - power: PoweredClock::NormalEnabledDeepSleepDisabled, - fro_hf_enabled: true, - clk_45m_enabled: true, - fro_hf_div: None, - }), - sirc: SircConfig { - power: PoweredClock::AlwaysEnabled, - fro_12m_enabled: true, - fro_lf_div: None, - }, - fro16k: Some(Fro16KConfig { - vsys_domain_active: true, - vdd_core_domain_active: true, - }), - } - } -} diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs deleted file mode 100644 index ac30115f6..000000000 --- a/src/clocks/mod.rs +++ /dev/null @@ -1,950 +0,0 @@ -//! # Clock Module -//! -//! For the MCX-A, we separate clock and peripheral control into two main stages: -//! -//! 1. At startup, e.g. when `embassy_mcxa::init()` is called, we configure the -//! core system clocks, including external and internal oscillators. This -//! configuration is then largely static for the duration of the program. -//! 2. When HAL drivers are created, e.g. `Lpuart::new()` is called, the driver -//! is responsible for two main things: -//! * Ensuring that any required "upstream" core system clocks necessary for -//! clocking the peripheral is active and configured to a reasonable value -//! * Enabling the clock gates for that peripheral, and resetting the peripheral -//! -//! From a user perspective, only step 1 is visible. Step 2 is automatically handled -//! by HAL drivers, using interfaces defined in this module. -//! -//! It is also possible to *view* the state of the clock configuration after [`init()`] -//! has been called, using the [`with_clocks()`] function, which provides a view of the -//! [`Clocks`] structure. -//! -//! ## For HAL driver implementors -//! -//! The majority of peripherals in the MCXA chip are fed from either a "hard-coded" or -//! configurable clock source, e.g. selecting the FROM12M or `clk_1m` as a source. This -//! selection, as well as often any pre-scaler division from that source clock, is made -//! through MRCC registers. -//! -//! Any peripheral that is controlled through the MRCC register can automatically implement -//! the necessary APIs using the `impl_cc_gate!` macro in this module. You will also need -//! to define the configuration surface and steps necessary to fully configure that peripheral -//! from a clocks perspective by: -//! -//! 1. Defining a configuration type in the [`periph_helpers`] module that contains any selects -//! or divisions available to the HAL driver -//! 2. Implementing the [`periph_helpers::SPConfHelper`] trait, which should check that the -//! necessary input clocks are reasonable - -use core::cell::RefCell; - -use config::{ClocksConfig, FircConfig, FircFreqSel, Fro16KConfig, SircConfig}; -use mcxa_pac::scg0::firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten}; -use mcxa_pac::scg0::sirccsr::{SircClkPeriphEn, Sircsten}; -use periph_helpers::SPConfHelper; - -use crate::pac; -pub mod config; -pub mod periph_helpers; - -// -// Statics/Consts -// - -/// The state of system core clocks. -/// -/// Initialized by [`init()`], and then unchanged for the remainder of the program. -static CLOCKS: critical_section::Mutex>> = critical_section::Mutex::new(RefCell::new(None)); - -// -// Free functions -// - -/// Initialize the core system clocks with the given [`ClocksConfig`]. -/// -/// This function should be called EXACTLY once at start-up, usually via a -/// call to [`embassy_mcxa::init()`](crate::init()). Subsequent calls will -/// return an error. -pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { - critical_section::with(|cs| { - if CLOCKS.borrow_ref(cs).is_some() { - Err(ClockError::AlreadyInitialized) - } else { - Ok(()) - } - })?; - - let mut clocks = Clocks::default(); - let mut operator = ClockOperator { - clocks: &mut clocks, - config: &settings, - - _mrcc0: unsafe { pac::Mrcc0::steal() }, - scg0: unsafe { pac::Scg0::steal() }, - syscon: unsafe { pac::Syscon::steal() }, - vbat0: unsafe { pac::Vbat0::steal() }, - }; - - operator.configure_firc_clocks()?; - operator.configure_sirc_clocks()?; - operator.configure_fro16k_clocks()?; - - // For now, just use FIRC as the main/cpu clock, which should already be - // the case on reset - assert!(operator.scg0.rccr().read().scs().is_firc()); - let input = operator.clocks.fro_hf_root.clone().unwrap(); - operator.clocks.main_clk = Some(input.clone()); - // We can also assume cpu/system clk == fro_hf because div is /1. - assert_eq!(operator.syscon.ahbclkdiv().read().div().bits(), 0); - operator.clocks.cpu_system_clk = Some(input); - - critical_section::with(|cs| { - let mut clks = CLOCKS.borrow_ref_mut(cs); - assert!(clks.is_none(), "Clock setup race!"); - *clks = Some(clocks); - }); - - Ok(()) -} - -/// Obtain the full clocks structure, calling the given closure in a critical section. -/// -/// The given closure will be called with read-only access to the state of the system -/// clocks. This can be used to query and return the state of a given clock. -/// -/// As the caller's closure will be called in a critical section, care must be taken -/// not to block or cause any other undue delays while accessing. -/// -/// Calls to this function will not succeed until after a successful call to `init()`, -/// and will always return None. -pub fn with_clocks R>(f: F) -> Option { - critical_section::with(|cs| { - let c = CLOCKS.borrow_ref(cs); - let c = c.as_ref()?; - Some(f(c)) - }) -} - -// -// Structs/Enums -// - -/// The `Clocks` structure contains the initialized state of the core system clocks -/// -/// These values are configured by providing [`config::ClocksConfig`] to the [`init()`] function -/// at boot time. -#[derive(Default, Debug, Clone)] -#[non_exhaustive] -pub struct Clocks { - /// The `clk_in` is a clock provided by an external oscillator - pub clk_in: Option, - - // FRO180M stuff - // - /// `fro_hf_root` is the direct output of the `FRO180M` internal oscillator - /// - /// It is used to feed downstream clocks, such as `fro_hf`, `clk_45m`, - /// and `fro_hf_div`. - pub fro_hf_root: Option, - - /// `fro_hf` is the same frequency as `fro_hf_root`, but behind a gate. - pub fro_hf: Option, - - /// `clk_45` is a 45MHz clock, sourced from `fro_hf`. - pub clk_45m: Option, - - /// `fro_hf_div` is a configurable frequency clock, sourced from `fro_hf`. - pub fro_hf_div: Option, - - // - // End FRO180M - - // FRO12M stuff - // - /// `fro_12m_root` is the direct output of the `FRO12M` internal oscillator - /// - /// It is used to feed downstream clocks, such as `fro_12m`, `clk_1m`, - /// `and `fro_lf_div`. - pub fro_12m_root: Option, - - /// `fro_12m` is the same frequency as `fro_12m_root`, but behind a gate. - pub fro_12m: Option, - - /// `clk_1m` is a 1MHz clock, sourced from `fro_12m` - pub clk_1m: Option, - - /// `fro_lf_div` is a configurable frequency clock, sourced from `fro_12m` - pub fro_lf_div: Option, - // - // End FRO12M stuff - /// `clk_16k_vsys` is one of two outputs of the `FRO16K` internal oscillator. - /// - /// Also referred to as `clk_16k[0]` in the datasheet, it feeds peripherals in - /// the system domain, such as the CMP and RTC. - pub clk_16k_vsys: Option, - - /// `clk_16k_vdd_core` is one of two outputs of the `FRO16K` internal oscillator. - /// - /// Also referred to as `clk_16k[1]` in the datasheet, it feeds peripherals in - /// the VDD Core domain, such as the OSTimer or LPUarts. - pub clk_16k_vdd_core: Option, - - /// `main_clk` is the main clock used by the CPU, AHB, APB, IPS bus, and some - /// peripherals. - pub main_clk: Option, - - /// `CPU_CLK` or `SYSTEM_CLK` is the output of `main_clk`, run through the `AHBCLKDIV` - pub cpu_system_clk: Option, - - /// `pll1_clk` is the output of the main system PLL, `pll1`. - pub pll1_clk: Option, -} - -/// `ClockError` is the main error returned when configuring or checking clock state -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[non_exhaustive] -pub enum ClockError { - /// The system clocks were never initialized by calling [`init()`] - NeverInitialized, - /// The [`init()`] function was called more than once - AlreadyInitialized, - /// The requested configuration was not possible to fulfill, as the system clocks - /// were not configured in a compatible way - BadConfig { clock: &'static str, reason: &'static str }, - /// The requested configuration was not possible to fulfill, as the required system - /// clocks have not yet been implemented. - NotImplemented { clock: &'static str }, - /// The requested peripheral could not be configured, as the steps necessary to - /// enable it have not yet been implemented. - UnimplementedConfig, -} - -/// Information regarding a system clock -#[derive(Debug, Clone)] -pub struct Clock { - /// The frequency, in Hz, of the given clock - pub frequency: u32, - /// The power state of the clock, e.g. whether it is active in deep sleep mode - /// or not. - pub power: PoweredClock, -} - -/// The power state of a given clock. -/// -/// On the MCX-A, when Deep-Sleep is entered, any clock not configured for Deep Sleep -/// mode will be stopped. This means that any downstream usage, e.g. by peripherals, -/// will also stop. -/// -/// In the future, we will provide an API for entering Deep Sleep, and if there are -/// any peripherals that are NOT using an `AlwaysEnabled` clock active, entry into -/// Deep Sleep will be prevented, in order to avoid misbehaving peripherals. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PoweredClock { - /// The given clock will NOT continue running in Deep Sleep mode - NormalEnabledDeepSleepDisabled, - /// The given clock WILL continue running in Deep Sleep mode - AlwaysEnabled, -} - -/// The ClockOperator is a private helper type that contains the methods used -/// during system clock initialization. -/// -/// # SAFETY -/// -/// Concurrent access to clock-relevant peripheral registers, such as `MRCC`, `SCG`, -/// `SYSCON`, and `VBAT` should not be allowed for the duration of the [`init()`] function. -struct ClockOperator<'a> { - /// A mutable reference to the current state of system clocks - clocks: &'a mut Clocks, - /// A reference to the requested configuration provided by the caller of [`init()`] - config: &'a ClocksConfig, - - // We hold on to stolen peripherals - _mrcc0: pac::Mrcc0, - scg0: pac::Scg0, - syscon: pac::Syscon, - vbat0: pac::Vbat0, -} - -/// Trait describing an AHB clock gate that can be toggled through MRCC. -pub trait Gate { - type MrccPeriphConfig: SPConfHelper; - - /// Enable the clock gate. - /// - /// # SAFETY - /// - /// The current peripheral must be disabled prior to calling this method - unsafe fn enable_clock(); - - /// Disable the clock gate. - /// - /// # SAFETY - /// - /// There must be no active user of this peripheral when calling this method - unsafe fn disable_clock(); - - /// Drive the peripheral into reset. - /// - /// # SAFETY - /// - /// There must be no active user of this peripheral when calling this method - unsafe fn assert_reset(); - - /// Drive the peripheral out of reset. - /// - /// # SAFETY - /// - /// There must be no active user of this peripheral when calling this method - unsafe fn release_reset(); - - /// Return whether the clock gate for this peripheral is currently enabled. - fn is_clock_enabled() -> bool; - - /// Return whether the peripheral is currently held in reset. - fn is_reset_released() -> bool; -} - -/// This is the primary helper method HAL drivers are expected to call when creating -/// an instance of the peripheral. -/// -/// This method: -/// -/// 1. Enables the MRCC clock gate for this peripheral -/// 2. Calls the `G::MrccPeriphConfig::post_enable_config()` method, returning an error -/// and re-disabling the peripheral if this fails. -/// 3. Pulses the MRCC reset line, to reset the peripheral to the default state -/// 4. Returns the frequency, in Hz that is fed into the peripheral, taking into account -/// the selected upstream clock, as well as any division specified by `cfg`. -/// -/// NOTE: if a clock is disabled, sourced from an "ambient" clock source, this method -/// may return `Ok(0)`. In the future, this might be updated to return the correct -/// "ambient" clock, e.g. the AHB/APB frequency. -/// -/// # SAFETY -/// -/// This peripheral must not yet be in use prior to calling `enable_and_reset`. -#[inline] -pub unsafe fn enable_and_reset(cfg: &G::MrccPeriphConfig) -> Result { - let freq = enable::(cfg).inspect_err(|_| disable::())?; - pulse_reset::(); - Ok(freq) -} - -/// Enable the clock gate for the given peripheral. -/// -/// Prefer [`enable_and_reset`] unless you are specifically avoiding a pulse of the reset, or need -/// to control the duration of the pulse more directly. -/// -/// # SAFETY -/// -/// This peripheral must not yet be in use prior to calling `enable`. -#[inline] -pub unsafe fn enable(cfg: &G::MrccPeriphConfig) -> Result { - G::enable_clock(); - while !G::is_clock_enabled() {} - core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); - - let freq = critical_section::with(|cs| { - let clocks = CLOCKS.borrow_ref(cs); - let clocks = clocks.as_ref().ok_or(ClockError::NeverInitialized)?; - cfg.post_enable_config(clocks) - }); - - freq.inspect_err(|_e| { - G::disable_clock(); - }) -} - -/// Disable the clock gate for the given peripheral. -/// -/// # SAFETY -/// -/// This peripheral must no longer be in use prior to calling `enable`. -#[allow(dead_code)] -#[inline] -pub unsafe fn disable() { - G::disable_clock(); -} - -/// Check whether a gate is currently enabled. -#[allow(dead_code)] -#[inline] -pub fn is_clock_enabled() -> bool { - G::is_clock_enabled() -} - -/// Release a reset line for the given peripheral set. -/// -/// Prefer [`enable_and_reset`]. -/// -/// # SAFETY -/// -/// This peripheral must not yet be in use prior to calling `release_reset`. -#[inline] -pub unsafe fn release_reset() { - G::release_reset(); -} - -/// Assert a reset line for the given peripheral set. -/// -/// Prefer [`enable_and_reset`]. -/// -/// # SAFETY -/// -/// This peripheral must not yet be in use prior to calling `assert_reset`. -#[inline] -pub unsafe fn assert_reset() { - G::assert_reset(); -} - -/// Check whether the peripheral is held in reset. -/// -/// # Safety -/// -/// Must be called with a valid peripheral gate type. -#[inline] -pub unsafe fn is_reset_released() -> bool { - G::is_reset_released() -} - -/// Pulse a reset line (assert then release) with a short delay. -/// -/// Prefer [`enable_and_reset`]. -/// -/// # SAFETY -/// -/// This peripheral must not yet be in use prior to calling `release_reset`. -#[inline] -pub unsafe fn pulse_reset() { - G::assert_reset(); - cortex_m::asm::nop(); - cortex_m::asm::nop(); - G::release_reset(); -} - -// -// `impl`s for structs/enums -// - -/// The [`Clocks`] type's methods generally take the form of "ensure X clock is active". -/// -/// These methods are intended to be used by HAL peripheral implementors to ensure that their -/// selected clocks are active at a suitable level at time of construction. These methods -/// return the frequency of the requested clock, in Hertz, or a [`ClockError`]. -impl Clocks { - /// Ensure the `fro_lf_div` clock is active and valid at the given power state. - pub fn ensure_fro_lf_div_active(&self, at_level: &PoweredClock) -> Result { - let Some(clk) = self.fro_lf_div.as_ref() else { - return Err(ClockError::BadConfig { - clock: "fro_lf_div", - reason: "required but not active", - }); - }; - if !clk.power.meets_requirement_of(at_level) { - return Err(ClockError::BadConfig { - clock: "fro_lf_div", - reason: "not low power active", - }); - } - Ok(clk.frequency) - } - - /// Ensure the `fro_hf` clock is active and valid at the given power state. - pub fn ensure_fro_hf_active(&self, at_level: &PoweredClock) -> Result { - let Some(clk) = self.fro_hf.as_ref() else { - return Err(ClockError::BadConfig { - clock: "fro_hf", - reason: "required but not active", - }); - }; - if !clk.power.meets_requirement_of(at_level) { - return Err(ClockError::BadConfig { - clock: "fro_hf", - reason: "not low power active", - }); - } - Ok(clk.frequency) - } - - /// Ensure the `fro_hf_div` clock is active and valid at the given power state. - pub fn ensure_fro_hf_div_active(&self, at_level: &PoweredClock) -> Result { - let Some(clk) = self.fro_hf_div.as_ref() else { - return Err(ClockError::BadConfig { - clock: "fro_hf_div", - reason: "required but not active", - }); - }; - if !clk.power.meets_requirement_of(at_level) { - return Err(ClockError::BadConfig { - clock: "fro_hf_div", - reason: "not low power active", - }); - } - Ok(clk.frequency) - } - - /// Ensure the `clk_in` clock is active and valid at the given power state. - pub fn ensure_clk_in_active(&self, _at_level: &PoweredClock) -> Result { - Err(ClockError::NotImplemented { clock: "clk_in" }) - } - - /// Ensure the `clk_16k_vsys` clock is active and valid at the given power state. - pub fn ensure_clk_16k_vsys_active(&self, _at_level: &PoweredClock) -> Result { - // NOTE: clk_16k is always active in low power mode - Ok(self - .clk_16k_vsys - .as_ref() - .ok_or(ClockError::BadConfig { - clock: "clk_16k_vsys", - reason: "required but not active", - })? - .frequency) - } - - /// Ensure the `clk_16k_vdd_core` clock is active and valid at the given power state. - pub fn ensure_clk_16k_vdd_core_active(&self, _at_level: &PoweredClock) -> Result { - // NOTE: clk_16k is always active in low power mode - Ok(self - .clk_16k_vdd_core - .as_ref() - .ok_or(ClockError::BadConfig { - clock: "clk_16k_vdd_core", - reason: "required but not active", - })? - .frequency) - } - - /// Ensure the `clk_1m` clock is active and valid at the given power state. - pub fn ensure_clk_1m_active(&self, at_level: &PoweredClock) -> Result { - let Some(clk) = self.clk_1m.as_ref() else { - return Err(ClockError::BadConfig { - clock: "clk_1m", - reason: "required but not active", - }); - }; - if !clk.power.meets_requirement_of(at_level) { - return Err(ClockError::BadConfig { - clock: "clk_1m", - reason: "not low power active", - }); - } - Ok(clk.frequency) - } - - /// Ensure the `pll1_clk` clock is active and valid at the given power state. - pub fn ensure_pll1_clk_active(&self, _at_level: &PoweredClock) -> Result { - Err(ClockError::NotImplemented { clock: "pll1_clk" }) - } - - /// Ensure the `pll1_clk_div` clock is active and valid at the given power state. - pub fn ensure_pll1_clk_div_active(&self, _at_level: &PoweredClock) -> Result { - Err(ClockError::NotImplemented { clock: "pll1_clk_div" }) - } - - /// Ensure the `CPU_CLK` or `SYSTEM_CLK` is active - pub fn ensure_cpu_system_clk_active(&self, at_level: &PoweredClock) -> Result { - let Some(clk) = self.cpu_system_clk.as_ref() else { - return Err(ClockError::BadConfig { - clock: "cpu_system_clk", - reason: "required but not active", - }); - }; - // Can the main_clk ever be active in deep sleep? I think it is gated? - match at_level { - PoweredClock::NormalEnabledDeepSleepDisabled => {} - PoweredClock::AlwaysEnabled => { - return Err(ClockError::BadConfig { - clock: "main_clk", - reason: "not low power active", - }); - } - } - - Ok(clk.frequency) - } - - pub fn ensure_slow_clk_active(&self, at_level: &PoweredClock) -> Result { - let freq = self.ensure_cpu_system_clk_active(at_level)?; - - Ok(freq / 6) - } -} - -impl PoweredClock { - /// Does THIS clock meet the power requirements of the OTHER clock? - pub fn meets_requirement_of(&self, other: &Self) -> bool { - match (self, other) { - (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::AlwaysEnabled) => false, - (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true, - (PoweredClock::AlwaysEnabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true, - (PoweredClock::AlwaysEnabled, PoweredClock::AlwaysEnabled) => true, - } - } -} - -impl ClockOperator<'_> { - /// Configure the FIRC/FRO180M clock family - /// - /// NOTE: Currently we require this to be a fairly hardcoded value, as this clock is used - /// as the main clock used for the CPU, AHB, APB, etc. - fn configure_firc_clocks(&mut self) -> Result<(), ClockError> { - const HARDCODED_ERR: Result<(), ClockError> = Err(ClockError::BadConfig { - clock: "firc", - reason: "For now, FIRC must be enabled and in default state!", - }); - - // Did the user give us a FIRC config? - let Some(firc) = self.config.firc.as_ref() else { - return HARDCODED_ERR; - }; - // Is the FIRC set to 45MHz (should be reset default) - if !matches!(firc.frequency, FircFreqSel::Mhz45) { - return HARDCODED_ERR; - } - let base_freq = 45_000_000; - - // Now, check if the FIRC as expected for our hardcoded value - let mut firc_ok = true; - - // Is the hardware currently set to the default 45MHz? - // - // NOTE: the SVD currently has the wrong(?) values for these: - // 45 -> 48 - // 60 -> 64 - // 90 -> 96 - // 180 -> 192 - // Probably correct-ish, but for a different trim value? - firc_ok &= self.scg0.firccfg().read().freq_sel().is_firc_48mhz_192s(); - - // Check some values in the CSR - let csr = self.scg0.firccsr().read(); - // Is it enabled? - firc_ok &= csr.fircen().is_enabled(); - // Is it accurate? - firc_ok &= csr.fircacc().is_enabled_and_valid(); - // Is there no error? - firc_ok &= csr.fircerr().is_error_not_detected(); - // Is the FIRC the system clock? - firc_ok &= csr.fircsel().is_firc(); - // Is it valid? - firc_ok &= csr.fircvld().is_enabled_and_valid(); - - // Are we happy with the current (hardcoded) state? - if !firc_ok { - return HARDCODED_ERR; - } - - // Note that the fro_hf_root is active - self.clocks.fro_hf_root = Some(Clock { - frequency: base_freq, - power: firc.power, - }); - - // Okay! Now we're past that, let's enable all the downstream clocks. - let FircConfig { - frequency: _, - power, - fro_hf_enabled, - clk_45m_enabled, - fro_hf_div, - } = firc; - - // When is the FRO enabled? - let pow_set = match power { - PoweredClock::NormalEnabledDeepSleepDisabled => Fircsten::DisabledInStopModes, - PoweredClock::AlwaysEnabled => Fircsten::EnabledInStopModes, - }; - - // Do we enable the `fro_hf` output? - let fro_hf_set = if *fro_hf_enabled { - self.clocks.fro_hf = Some(Clock { - frequency: base_freq, - power: *power, - }); - FircFclkPeriphEn::Enabled - } else { - FircFclkPeriphEn::Disabled - }; - - // Do we enable the `clk_45m` output? - let clk_45m_set = if *clk_45m_enabled { - self.clocks.clk_45m = Some(Clock { - frequency: 45_000_000, - power: *power, - }); - FircSclkPeriphEn::Enabled - } else { - FircSclkPeriphEn::Disabled - }; - - self.scg0.firccsr().modify(|_r, w| { - w.fircsten().variant(pow_set); - w.firc_fclk_periph_en().variant(fro_hf_set); - w.firc_sclk_periph_en().variant(clk_45m_set); - w - }); - - // Do we enable the `fro_hf_div` output? - if let Some(d) = fro_hf_div.as_ref() { - // We need `fro_hf` to be enabled - if !*fro_hf_enabled { - return Err(ClockError::BadConfig { - clock: "fro_hf_div", - reason: "fro_hf not enabled", - }); - } - - // Halt and reset the div; then set our desired div. - self.syscon.frohfdiv().write(|w| { - w.halt().halt(); - w.reset().asserted(); - unsafe { w.div().bits(d.into_bits()) }; - w - }); - // Then unhalt it, and reset it - self.syscon.frohfdiv().write(|w| { - w.halt().run(); - w.reset().released(); - w - }); - - // Wait for clock to stabilize - while self.syscon.frohfdiv().read().unstab().is_ongoing() {} - - // Store off the clock info - self.clocks.fro_hf_div = Some(Clock { - frequency: base_freq / d.into_divisor(), - power: *power, - }); - } - - Ok(()) - } - - /// Configure the SIRC/FRO12M clock family - fn configure_sirc_clocks(&mut self) -> Result<(), ClockError> { - let SircConfig { - power, - fro_12m_enabled, - fro_lf_div, - } = &self.config.sirc; - let base_freq = 12_000_000; - - // Allow writes - self.scg0.sirccsr().modify(|_r, w| w.lk().write_enabled()); - self.clocks.fro_12m_root = Some(Clock { - frequency: base_freq, - power: *power, - }); - - let deep = match power { - PoweredClock::NormalEnabledDeepSleepDisabled => Sircsten::Disabled, - PoweredClock::AlwaysEnabled => Sircsten::Enabled, - }; - let pclk = if *fro_12m_enabled { - self.clocks.fro_12m = Some(Clock { - frequency: base_freq, - power: *power, - }); - self.clocks.clk_1m = Some(Clock { - frequency: base_freq / 12, - power: *power, - }); - SircClkPeriphEn::Enabled - } else { - SircClkPeriphEn::Disabled - }; - - // Set sleep/peripheral usage - self.scg0.sirccsr().modify(|_r, w| { - w.sircsten().variant(deep); - w.sirc_clk_periph_en().variant(pclk); - w - }); - - while self.scg0.sirccsr().read().sircvld().is_disabled_or_not_valid() {} - if self.scg0.sirccsr().read().sircerr().is_error_detected() { - return Err(ClockError::BadConfig { - clock: "sirc", - reason: "error set", - }); - } - - // reset lock - self.scg0.sirccsr().modify(|_r, w| w.lk().write_disabled()); - - // Do we enable the `fro_lf_div` output? - if let Some(d) = fro_lf_div.as_ref() { - // We need `fro_lf` to be enabled - if !*fro_12m_enabled { - return Err(ClockError::BadConfig { - clock: "fro_lf_div", - reason: "fro_12m not enabled", - }); - } - - // Halt and reset the div; then set our desired div. - self.syscon.frolfdiv().write(|w| { - w.halt().halt(); - w.reset().asserted(); - unsafe { w.div().bits(d.into_bits()) }; - w - }); - // Then unhalt it, and reset it - self.syscon.frolfdiv().modify(|_r, w| { - w.halt().run(); - w.reset().released(); - w - }); - - // Wait for clock to stabilize - while self.syscon.frolfdiv().read().unstab().is_ongoing() {} - - // Store off the clock info - self.clocks.fro_lf_div = Some(Clock { - frequency: base_freq / d.into_divisor(), - power: *power, - }); - } - - Ok(()) - } - - /// Configure the FRO16K/clk_16k clock family - fn configure_fro16k_clocks(&mut self) -> Result<(), ClockError> { - let Some(fro16k) = self.config.fro16k.as_ref() else { - return Ok(()); - }; - // Enable FRO16K oscillator - self.vbat0.froctla().modify(|_, w| w.fro_en().set_bit()); - - // Lock the control register - self.vbat0.frolcka().modify(|_, w| w.lock().set_bit()); - - let Fro16KConfig { - vsys_domain_active, - vdd_core_domain_active, - } = fro16k; - - // Enable clock outputs to both VSYS and VDD_CORE domains - // Bit 0: clk_16k0 to VSYS domain - // Bit 1: clk_16k1 to VDD_CORE domain - // - // TODO: Define sub-fields for this register with a PAC patch? - let mut bits = 0; - if *vsys_domain_active { - bits |= 0b01; - self.clocks.clk_16k_vsys = Some(Clock { - frequency: 16_384, - power: PoweredClock::AlwaysEnabled, - }); - } - if *vdd_core_domain_active { - bits |= 0b10; - self.clocks.clk_16k_vdd_core = Some(Clock { - frequency: 16_384, - power: PoweredClock::AlwaysEnabled, - }); - } - self.vbat0.froclke().modify(|_r, w| unsafe { w.clke().bits(bits) }); - - Ok(()) - } -} - -// -// Macros/macro impls -// - -/// This macro is used to implement the [`Gate`] trait for a given peripheral -/// that is controlled by the MRCC peripheral. -macro_rules! impl_cc_gate { - ($name:ident, $clk_reg:ident, $rst_reg:ident, $field:ident, $config:ty) => { - impl Gate for crate::peripherals::$name { - type MrccPeriphConfig = $config; - - #[inline] - unsafe fn enable_clock() { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$clk_reg().modify(|_, w| w.$field().enabled()); - } - - #[inline] - unsafe fn disable_clock() { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$clk_reg().modify(|_r, w| w.$field().disabled()); - } - - #[inline] - fn is_clock_enabled() -> bool { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$clk_reg().read().$field().is_enabled() - } - - #[inline] - unsafe fn release_reset() { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$rst_reg().modify(|_, w| w.$field().enabled()); - } - - #[inline] - unsafe fn assert_reset() { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$rst_reg().modify(|_, w| w.$field().disabled()); - } - - #[inline] - fn is_reset_released() -> bool { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$rst_reg().read().$field().is_enabled() - } - } - }; -} - -/// This module contains implementations of MRCC APIs, specifically of the [`Gate`] trait, -/// for various low level peripherals. -pub(crate) mod gate { - #[cfg(not(feature = "time"))] - use super::periph_helpers::OsTimerConfig; - use super::periph_helpers::{AdcConfig, Lpi2cConfig, LpuartConfig, NoConfig}; - use super::*; - - // These peripherals have no additional upstream clocks or configuration required - // other than enabling through the MRCC gate. Currently, these peripherals will - // ALWAYS return `Ok(0)` when calling [`enable_and_reset()`] and/or - // [`SPConfHelper::post_enable_config()`]. - impl_cc_gate!(PORT0, mrcc_glb_cc1, mrcc_glb_rst1, port0, NoConfig); - impl_cc_gate!(PORT1, mrcc_glb_cc1, mrcc_glb_rst1, port1, NoConfig); - impl_cc_gate!(PORT2, mrcc_glb_cc1, mrcc_glb_rst1, port2, NoConfig); - impl_cc_gate!(PORT3, mrcc_glb_cc1, mrcc_glb_rst1, port3, NoConfig); - impl_cc_gate!(PORT4, mrcc_glb_cc1, mrcc_glb_rst1, port4, NoConfig); - - impl_cc_gate!(GPIO0, mrcc_glb_cc2, mrcc_glb_rst2, gpio0, NoConfig); - impl_cc_gate!(GPIO1, mrcc_glb_cc2, mrcc_glb_rst2, gpio1, NoConfig); - impl_cc_gate!(GPIO2, mrcc_glb_cc2, mrcc_glb_rst2, gpio2, NoConfig); - impl_cc_gate!(GPIO3, mrcc_glb_cc2, mrcc_glb_rst2, gpio3, NoConfig); - impl_cc_gate!(GPIO4, mrcc_glb_cc2, mrcc_glb_rst2, gpio4, NoConfig); - - // These peripherals DO have meaningful configuration, and could fail if the system - // clocks do not match their needs. - #[cfg(not(feature = "time"))] - impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig); - - impl_cc_gate!(LPI2C0, mrcc_glb_cc0, mrcc_glb_rst0, lpi2c0, Lpi2cConfig); - impl_cc_gate!(LPI2C1, mrcc_glb_cc0, mrcc_glb_rst0, lpi2c1, Lpi2cConfig); - impl_cc_gate!(LPI2C2, mrcc_glb_cc1, mrcc_glb_rst1, lpi2c2, Lpi2cConfig); - impl_cc_gate!(LPI2C3, mrcc_glb_cc1, mrcc_glb_rst1, lpi2c3, Lpi2cConfig); - - impl_cc_gate!(LPUART0, mrcc_glb_cc0, mrcc_glb_rst0, lpuart0, LpuartConfig); - impl_cc_gate!(LPUART1, mrcc_glb_cc0, mrcc_glb_rst0, lpuart1, LpuartConfig); - impl_cc_gate!(LPUART2, mrcc_glb_cc0, mrcc_glb_rst0, lpuart2, LpuartConfig); - impl_cc_gate!(LPUART3, mrcc_glb_cc0, mrcc_glb_rst0, lpuart3, LpuartConfig); - impl_cc_gate!(LPUART4, mrcc_glb_cc0, mrcc_glb_rst0, lpuart4, LpuartConfig); - impl_cc_gate!(LPUART5, mrcc_glb_cc1, mrcc_glb_rst1, lpuart5, LpuartConfig); - impl_cc_gate!(ADC1, mrcc_glb_cc1, mrcc_glb_rst1, adc1, AdcConfig); - - // DMA0 peripheral - uses NoConfig since it has no selectable clock source - impl_cc_gate!(DMA0, mrcc_glb_cc0, mrcc_glb_rst0, dma0, NoConfig); -} diff --git a/src/clocks/periph_helpers.rs b/src/clocks/periph_helpers.rs deleted file mode 100644 index 24d074e8a..000000000 --- a/src/clocks/periph_helpers.rs +++ /dev/null @@ -1,506 +0,0 @@ -//! Peripheral Helpers -//! -//! The purpose of this module is to define the per-peripheral special handling -//! required from a clocking perspective. Different peripherals have different -//! selectable source clocks, and some peripherals have additional pre-dividers -//! that can be used. -//! -//! See the docs of [`SPConfHelper`] for more details. - -use super::{ClockError, Clocks, PoweredClock}; -use crate::pac; - -/// Sealed Peripheral Configuration Helper -/// -/// NOTE: the name "sealed" doesn't *totally* make sense because its not sealed yet in the -/// embassy-mcxa project, but it derives from embassy-imxrt where it is. We should -/// fix the name, or actually do the sealing of peripherals. -/// -/// This trait serves to act as a per-peripheral customization for clocking behavior. -/// -/// This trait should be implemented on a configuration type for a given peripheral, and -/// provide the methods that will be called by the higher level operations like -/// `embassy_mcxa::clocks::enable_and_reset()`. -pub trait SPConfHelper { - /// This method is called AFTER a given MRCC peripheral has been enabled (e.g. un-gated), - /// but BEFORE the peripheral reset line is reset. - /// - /// This function should check that any relevant upstream clocks are enabled, are in a - /// reasonable power state, and that the requested configuration can be made. If any of - /// these checks fail, an `Err(ClockError)` should be returned, likely `ClockError::BadConfig`. - /// - /// This function SHOULD NOT make any changes to the system clock configuration, even - /// unsafely, as this should remain static for the duration of the program. - /// - /// This function WILL be called in a critical section, care should be taken not to delay - /// for an unreasonable amount of time. - /// - /// On success, this function MUST return an `Ok(freq)`, where `freq` is the frequency - /// fed into the peripheral, taking into account the selected source clock, as well as - /// any pre-divisors. - fn post_enable_config(&self, clocks: &Clocks) -> Result; -} - -// config types - -/// This type represents a divider in the range 1..=16. -/// -/// At a hardware level, this is an 8-bit register from 0..=15, -/// which adds one. -/// -/// While the *clock* domain seems to use 8-bit dividers, the *peripheral* domain -/// seems to use 4 bit dividers! -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct Div4(pub(super) u8); - -impl Div4 { - /// Divide by one, or no division - pub const fn no_div() -> Self { - Self(0) - } - - /// Store a "raw" divisor value that will divide the source by - /// `(n + 1)`, e.g. `Div4::from_raw(0)` will divide the source - /// by 1, and `Div4::from_raw(15)` will divide the source by - /// 16. - pub const fn from_raw(n: u8) -> Option { - if n > 0b1111 { - None - } else { - Some(Self(n)) - } - } - - /// Store a specific divisor value that will divide the source - /// by `n`. e.g. `Div4::from_divisor(1)` will divide the source - /// by 1, and `Div4::from_divisor(16)` will divide the source - /// by 16. - /// - /// Will return `None` if `n` is not in the range `1..=16`. - /// Consider [`Self::from_raw`] for an infallible version. - pub const fn from_divisor(n: u8) -> Option { - let Some(n) = n.checked_sub(1) else { - return None; - }; - if n > 0b1111 { - return None; - } - Some(Self(n)) - } - - /// Convert into "raw" bits form - #[inline(always)] - pub const fn into_bits(self) -> u8 { - self.0 - } - - /// Convert into "divisor" form, as a u32 for convenient frequency math - #[inline(always)] - pub const fn into_divisor(self) -> u32 { - self.0 as u32 + 1 - } -} - -/// A basic type that always returns an error when `post_enable_config` is called. -/// -/// Should only be used as a placeholder. -pub struct UnimplementedConfig; - -impl SPConfHelper for UnimplementedConfig { - fn post_enable_config(&self, _clocks: &Clocks) -> Result { - Err(ClockError::UnimplementedConfig) - } -} - -/// A basic type that always returns `Ok(0)` when `post_enable_config` is called. -/// -/// This should only be used for peripherals that are "ambiently" clocked, like `PORTn` -/// peripherals, which have no selectable/configurable source clock. -pub struct NoConfig; -impl SPConfHelper for NoConfig { - fn post_enable_config(&self, _clocks: &Clocks) -> Result { - Ok(0) - } -} - -// -// LPI2c -// - -/// Selectable clocks for `Lpi2c` peripherals -#[derive(Debug, Clone, Copy)] -pub enum Lpi2cClockSel { - /// FRO12M/FRO_LF/SIRC clock source, passed through divider - /// "fro_lf_div" - FroLfDiv, - /// FRO180M/FRO_HF/FIRC clock source, passed through divider - /// "fro_hf_div" - FroHfDiv, - /// SOSC/XTAL/EXTAL clock source - ClkIn, - /// clk_1m/FRO_LF divided by 12 - Clk1M, - /// Output of PLL1, passed through clock divider, - /// "pll1_clk_div", maybe "pll1_lf_div"? - Pll1ClkDiv, - /// Disabled - None, -} - -/// Which instance of the `Lpi2c` is this? -/// -/// Should not be directly selectable by end-users. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Lpi2cInstance { - /// Instance 0 - Lpi2c0, - /// Instance 1 - Lpi2c1, - /// Instance 2 - Lpi2c2, - /// Instance 3 - Lpi2c3, -} - -/// Top level configuration for `Lpi2c` instances. -pub struct Lpi2cConfig { - /// Power state required for this peripheral - pub power: PoweredClock, - /// Clock source - pub source: Lpi2cClockSel, - /// Clock divisor - pub div: Div4, - /// Which instance is this? - // NOTE: should not be user settable - pub(crate) instance: Lpi2cInstance, -} - -impl SPConfHelper for Lpi2cConfig { - fn post_enable_config(&self, clocks: &Clocks) -> Result { - // check that source is suitable - let mrcc0 = unsafe { pac::Mrcc0::steal() }; - use mcxa_pac::mrcc0::mrcc_lpi2c0_clksel::Mux; - - let (clkdiv, clksel) = match self.instance { - Lpi2cInstance::Lpi2c0 => (mrcc0.mrcc_lpi2c0_clkdiv(), mrcc0.mrcc_lpi2c0_clksel()), - Lpi2cInstance::Lpi2c1 => (mrcc0.mrcc_lpi2c1_clkdiv(), mrcc0.mrcc_lpi2c1_clksel()), - Lpi2cInstance::Lpi2c2 => (mrcc0.mrcc_lpi2c2_clkdiv(), mrcc0.mrcc_lpi2c2_clksel()), - Lpi2cInstance::Lpi2c3 => (mrcc0.mrcc_lpi2c3_clkdiv(), mrcc0.mrcc_lpi2c3_clksel()), - }; - - let (freq, variant) = match self.source { - Lpi2cClockSel::FroLfDiv => { - let freq = clocks.ensure_fro_lf_div_active(&self.power)?; - (freq, Mux::ClkrootFunc0) - } - Lpi2cClockSel::FroHfDiv => { - let freq = clocks.ensure_fro_hf_div_active(&self.power)?; - (freq, Mux::ClkrootFunc2) - } - Lpi2cClockSel::ClkIn => { - let freq = clocks.ensure_clk_in_active(&self.power)?; - (freq, Mux::ClkrootFunc3) - } - Lpi2cClockSel::Clk1M => { - let freq = clocks.ensure_clk_1m_active(&self.power)?; - (freq, Mux::ClkrootFunc5) - } - Lpi2cClockSel::Pll1ClkDiv => { - let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; - (freq, Mux::ClkrootFunc6) - } - Lpi2cClockSel::None => unsafe { - // no ClkrootFunc7, just write manually for now - clksel.write(|w| w.bits(0b111)); - clkdiv.modify(|_r, w| w.reset().asserted().halt().asserted()); - return Ok(0); - }, - }; - - // set clksel - clksel.modify(|_r, w| w.mux().variant(variant)); - - // Set up clkdiv - clkdiv.modify(|_r, w| { - unsafe { w.div().bits(self.div.into_bits()) } - .halt() - .asserted() - .reset() - .asserted() - }); - clkdiv.modify(|_r, w| w.halt().deasserted().reset().deasserted()); - - while clkdiv.read().unstab().is_unstable() {} - - Ok(freq / self.div.into_divisor()) - } -} - -// -// LPUart -// - -/// Selectable clocks for Lpuart peripherals -#[derive(Debug, Clone, Copy)] -pub enum LpuartClockSel { - /// FRO12M/FRO_LF/SIRC clock source, passed through divider - /// "fro_lf_div" - FroLfDiv, - /// FRO180M/FRO_HF/FIRC clock source, passed through divider - /// "fro_hf_div" - FroHfDiv, - /// SOSC/XTAL/EXTAL clock source - ClkIn, - /// FRO16K/clk_16k source - Clk16K, - /// clk_1m/FRO_LF divided by 12 - Clk1M, - /// Output of PLL1, passed through clock divider, - /// "pll1_clk_div", maybe "pll1_lf_div"? - Pll1ClkDiv, - /// Disabled - None, -} - -/// Which instance of the Lpuart is this? -/// -/// Should not be directly selectable by end-users. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum LpuartInstance { - /// Instance 0 - Lpuart0, - /// Instance 1 - Lpuart1, - /// Instance 2 - Lpuart2, - /// Instance 3 - Lpuart3, - /// Instance 4 - Lpuart4, - /// Instance 5 - Lpuart5, -} - -/// Top level configuration for `Lpuart` instances. -pub struct LpuartConfig { - /// Power state required for this peripheral - pub power: PoweredClock, - /// Clock source - pub source: LpuartClockSel, - /// Clock divisor - pub div: Div4, - /// Which instance is this? - // NOTE: should not be user settable - pub(crate) instance: LpuartInstance, -} - -impl SPConfHelper for LpuartConfig { - fn post_enable_config(&self, clocks: &Clocks) -> Result { - // check that source is suitable - let mrcc0 = unsafe { pac::Mrcc0::steal() }; - use mcxa_pac::mrcc0::mrcc_lpuart0_clksel::Mux; - - let (clkdiv, clksel) = match self.instance { - LpuartInstance::Lpuart0 => (mrcc0.mrcc_lpuart0_clkdiv(), mrcc0.mrcc_lpuart0_clksel()), - LpuartInstance::Lpuart1 => (mrcc0.mrcc_lpuart1_clkdiv(), mrcc0.mrcc_lpuart1_clksel()), - LpuartInstance::Lpuart2 => (mrcc0.mrcc_lpuart2_clkdiv(), mrcc0.mrcc_lpuart2_clksel()), - LpuartInstance::Lpuart3 => (mrcc0.mrcc_lpuart3_clkdiv(), mrcc0.mrcc_lpuart3_clksel()), - LpuartInstance::Lpuart4 => (mrcc0.mrcc_lpuart4_clkdiv(), mrcc0.mrcc_lpuart4_clksel()), - LpuartInstance::Lpuart5 => (mrcc0.mrcc_lpuart5_clkdiv(), mrcc0.mrcc_lpuart5_clksel()), - }; - - let (freq, variant) = match self.source { - LpuartClockSel::FroLfDiv => { - let freq = clocks.ensure_fro_lf_div_active(&self.power)?; - (freq, Mux::ClkrootFunc0) - } - LpuartClockSel::FroHfDiv => { - let freq = clocks.ensure_fro_hf_div_active(&self.power)?; - (freq, Mux::ClkrootFunc2) - } - LpuartClockSel::ClkIn => { - let freq = clocks.ensure_clk_in_active(&self.power)?; - (freq, Mux::ClkrootFunc3) - } - LpuartClockSel::Clk16K => { - let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?; - (freq, Mux::ClkrootFunc4) - } - LpuartClockSel::Clk1M => { - let freq = clocks.ensure_clk_1m_active(&self.power)?; - (freq, Mux::ClkrootFunc5) - } - LpuartClockSel::Pll1ClkDiv => { - let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; - (freq, Mux::ClkrootFunc6) - } - LpuartClockSel::None => unsafe { - // no ClkrootFunc7, just write manually for now - clksel.write(|w| w.bits(0b111)); - clkdiv.modify(|_r, w| { - w.reset().asserted(); - w.halt().asserted(); - w - }); - return Ok(0); - }, - }; - - // set clksel - clksel.modify(|_r, w| w.mux().variant(variant)); - - // Set up clkdiv - clkdiv.modify(|_r, w| { - w.halt().asserted(); - w.reset().asserted(); - unsafe { w.div().bits(self.div.into_bits()) }; - w - }); - clkdiv.modify(|_r, w| { - w.halt().deasserted(); - w.reset().deasserted(); - w - }); - - while clkdiv.read().unstab().is_unstable() {} - - Ok(freq / self.div.into_divisor()) - } -} - -// -// OSTimer -// - -/// Selectable clocks for the OSTimer peripheral -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum OstimerClockSel { - /// 16k clock, sourced from FRO16K (Vdd Core) - Clk16kVddCore, - /// 1 MHz Clock sourced from FRO12M - Clk1M, - /// Disabled - None, -} - -/// Top level configuration for the `OSTimer` peripheral -pub struct OsTimerConfig { - /// Power state required for this peripheral - pub power: PoweredClock, - /// Selected clock source for this peripheral - pub source: OstimerClockSel, -} - -impl SPConfHelper for OsTimerConfig { - fn post_enable_config(&self, clocks: &Clocks) -> Result { - let mrcc0 = unsafe { pac::Mrcc0::steal() }; - Ok(match self.source { - OstimerClockSel::Clk16kVddCore => { - let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?; - mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_16k()); - freq - } - OstimerClockSel::Clk1M => { - let freq = clocks.ensure_clk_1m_active(&self.power)?; - mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); - freq - } - OstimerClockSel::None => { - mrcc0.mrcc_ostimer0_clksel().write(|w| unsafe { w.mux().bits(0b11) }); - 0 - } - }) - } -} - -// -// Adc -// - -/// Selectable clocks for the ADC peripheral -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum AdcClockSel { - /// Divided `fro_lf`/`clk_12m`/FRO12M source - FroLfDiv, - /// Gated `fro_hf`/`FRO180M` source - FroHf, - /// External Clock Source - ClkIn, - /// 1MHz clock sourced by a divided `fro_lf`/`clk_12m` - Clk1M, - /// Internal PLL output, with configurable divisor - Pll1ClkDiv, - /// No clock/disabled - None, -} - -/// Top level configuration for the ADC peripheral -pub struct AdcConfig { - /// Power state required for this peripheral - pub power: PoweredClock, - /// Selected clock-source for this peripheral - pub source: AdcClockSel, - /// Pre-divisor, applied to the upstream clock output - pub div: Div4, -} - -impl SPConfHelper for AdcConfig { - fn post_enable_config(&self, clocks: &Clocks) -> Result { - use mcxa_pac::mrcc0::mrcc_adc_clksel::Mux; - let mrcc0 = unsafe { pac::Mrcc0::steal() }; - let (freq, variant) = match self.source { - AdcClockSel::FroLfDiv => { - let freq = clocks.ensure_fro_lf_div_active(&self.power)?; - (freq, Mux::ClkrootFunc0) - } - AdcClockSel::FroHf => { - let freq = clocks.ensure_fro_hf_active(&self.power)?; - (freq, Mux::ClkrootFunc1) - } - AdcClockSel::ClkIn => { - let freq = clocks.ensure_clk_in_active(&self.power)?; - (freq, Mux::ClkrootFunc3) - } - AdcClockSel::Clk1M => { - let freq = clocks.ensure_clk_1m_active(&self.power)?; - (freq, Mux::ClkrootFunc5) - } - AdcClockSel::Pll1ClkDiv => { - let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; - (freq, Mux::ClkrootFunc6) - } - AdcClockSel::None => { - mrcc0.mrcc_adc_clksel().write(|w| unsafe { - // no ClkrootFunc7, just write manually for now - w.mux().bits(0b111) - }); - mrcc0.mrcc_adc_clkdiv().modify(|_r, w| { - w.reset().asserted(); - w.halt().asserted(); - w - }); - return Ok(0); - } - }; - - // set clksel - mrcc0.mrcc_adc_clksel().modify(|_r, w| w.mux().variant(variant)); - - // Set up clkdiv - mrcc0.mrcc_adc_clkdiv().modify(|_r, w| { - w.halt().asserted(); - w.reset().asserted(); - unsafe { w.div().bits(self.div.into_bits()) }; - w - }); - mrcc0.mrcc_adc_clkdiv().modify(|_r, w| { - w.halt().deasserted(); - w.reset().deasserted(); - w - }); - - while mrcc0.mrcc_adc_clkdiv().read().unstab().is_unstable() {} - - Ok(freq / self.div.into_divisor()) - } -} diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index 8d52a1d44..000000000 --- a/src/config.rs +++ /dev/null @@ -1,27 +0,0 @@ -// HAL configuration (minimal), mirroring embassy-imxrt style - -use crate::clocks::config::ClocksConfig; -use crate::interrupt::Priority; - -#[non_exhaustive] -pub struct Config { - #[cfg(feature = "time")] - pub time_interrupt_priority: Priority, - pub rtc_interrupt_priority: Priority, - pub adc_interrupt_priority: Priority, - pub gpio_interrupt_priority: Priority, - pub clock_cfg: ClocksConfig, -} - -impl Default for Config { - fn default() -> Self { - Self { - #[cfg(feature = "time")] - time_interrupt_priority: Priority::from(0), - rtc_interrupt_priority: Priority::from(0), - adc_interrupt_priority: Priority::from(0), - gpio_interrupt_priority: Priority::from(0), - clock_cfg: ClocksConfig::default(), - } - } -} diff --git a/src/dma.rs b/src/dma.rs deleted file mode 100644 index 7d1588516..000000000 --- a/src/dma.rs +++ /dev/null @@ -1,2594 +0,0 @@ -//! DMA driver for MCXA276. -//! -//! This module provides a typed channel abstraction over the EDMA_0_TCD0 array -//! and helpers for configuring the channel MUX. The driver supports both -//! low-level TCD configuration and higher-level async transfer APIs. -//! -//! # Architecture -//! -//! The MCXA276 has 8 DMA channels (0-7), each with its own interrupt vector. -//! Each channel has a Transfer Control Descriptor (TCD) that defines the -//! transfer parameters. -//! -//! # Choosing the Right API -//! -//! This module provides several API levels to match different use cases: -//! -//! ## High-Level Async API (Recommended for Most Users) -//! -//! Use the async methods when you want simple, safe DMA transfers: -//! -//! | Method | Description | -//! |--------|-------------| -//! | [`DmaChannel::mem_to_mem()`] | Memory-to-memory copy | -//! | [`DmaChannel::memset()`] | Fill memory with a pattern | -//! | [`DmaChannel::write()`] | Memory-to-peripheral (TX) | -//! | [`DmaChannel::read()`] | Peripheral-to-memory (RX) | -//! -//! These return a [`Transfer`] future that can be `.await`ed: -//! -//! ```no_run -//! # use embassy_mcxa::dma::{DmaChannel, TransferOptions}; -//! # let dma_ch = DmaChannel::new(p.DMA_CH0); -//! # let src = [0u32; 4]; -//! # let mut dst = [0u32; 4]; -//! // Simple memory-to-memory transfer -//! unsafe { -//! dma_ch.mem_to_mem(&src, &mut dst, TransferOptions::default()).await; -//! } -//! ``` -//! -//! ## Setup Methods (For Peripheral Drivers) -//! -//! Use setup methods when you need manual lifecycle control: -//! -//! | Method | Description | -//! |--------|-------------| -//! | [`DmaChannel::setup_write()`] | Configure TX without starting | -//! | [`DmaChannel::setup_read()`] | Configure RX without starting | -//! -//! These configure the TCD but don't start the transfer. You control: -//! 1. When to call [`DmaChannel::enable_request()`] -//! 2. How to detect completion (polling or interrupts) -//! 3. When to clean up with [`DmaChannel::clear_done()`] -//! -//! ## Circular/Ring Buffer API (For Continuous Reception) -//! -//! Use [`DmaChannel::setup_circular_read()`] for continuous data reception: -//! -//! ```no_run -//! # use embassy_mcxa::dma::DmaChannel; -//! # let dma_ch = DmaChannel::new(p.DMA_CH0); -//! # let uart_rx_addr = 0x4000_0000 as *const u8; -//! static mut RX_BUF: [u8; 64] = [0; 64]; -//! -//! let ring_buf = unsafe { -//! dma_ch.setup_circular_read(uart_rx_addr, &mut RX_BUF) -//! }; -//! -//! // Read data as it arrives -//! let mut buf = [0u8; 16]; -//! let n = ring_buf.read(&mut buf).await.unwrap(); -//! ``` -//! -//! ## Scatter-Gather Builder (For Chained Transfers) -//! -//! Use [`ScatterGatherBuilder`] for complex multi-segment transfers: -//! -//! ```no_run -//! # use embassy_mcxa::dma::{DmaChannel, ScatterGatherBuilder}; -//! # let dma_ch = DmaChannel::new(p.DMA_CH0); -//! let mut builder = ScatterGatherBuilder::::new(); -//! builder.add_transfer(&src1, &mut dst1); -//! builder.add_transfer(&src2, &mut dst2); -//! -//! let transfer = unsafe { builder.build(&dma_ch).unwrap() }; -//! transfer.await; -//! ``` -//! -//! ## Direct TCD Access (For Advanced Use Cases) -//! -//! For full control, use the channel's `tcd()` method to access TCD registers directly. -//! See the `dma_*` examples for patterns. -//! -//! # Example -//! -//! ```no_run -//! use embassy_mcxa::dma::{DmaChannel, TransferOptions, Direction}; -//! -//! let dma_ch = DmaChannel::new(p.DMA_CH0); -//! // Configure and trigger a transfer... -//! ``` - -use core::future::Future; -use core::marker::PhantomData; -use core::pin::Pin; -use core::ptr::NonNull; -use core::sync::atomic::{fence, AtomicBool, AtomicUsize, Ordering}; -use core::task::{Context, Poll}; - -use embassy_hal_internal::PeripheralType; -use embassy_sync::waitqueue::AtomicWaker; - -use crate::clocks::Gate; -use crate::pac; -use crate::pac::Interrupt; -use crate::peripherals::DMA0; - -/// Static flag to track whether DMA has been initialized. -static DMA_INITIALIZED: AtomicBool = AtomicBool::new(false); - -/// Initialize DMA controller (clock enabled, reset released, controller configured). -/// -/// This function is intended to be called during HAL initialization (`hal::init()`). -/// It is idempotent - it will only initialize DMA once, even if called multiple times. -/// -/// The function enables the DMA0 clock, releases reset, and configures the controller -/// for normal operation with round-robin arbitration. -pub fn init() { - // Fast path: already initialized - if DMA_INITIALIZED.load(Ordering::Acquire) { - return; - } - - // Slow path: initialize DMA - // Use compare_exchange to ensure only one caller initializes - if DMA_INITIALIZED - .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) - .is_ok() - { - // We won the race - initialize DMA - unsafe { - // Enable DMA0 clock and release reset - DMA0::enable_clock(); - DMA0::release_reset(); - - // Configure DMA controller - let dma = &(*pac::Dma0::ptr()); - dma.mp_csr().modify(|_, w| { - w.edbg() - .enable() - .erca() - .enable() - .halt() - .normal_operation() - .gclc() - .available() - .gmrc() - .available() - }); - } - } -} - -// ============================================================================ -// Phase 1: Foundation Types (Embassy-aligned) -// ============================================================================ - -/// DMA transfer direction. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Direction { - /// Transfer from memory to memory. - MemoryToMemory, - /// Transfer from memory to a peripheral register. - MemoryToPeripheral, - /// Transfer from a peripheral register to memory. - PeripheralToMemory, -} - -/// DMA transfer priority. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Priority { - /// Low priority (channel priority 7). - Low, - /// Medium priority (channel priority 4). - Medium, - /// High priority (channel priority 1). - #[default] - High, - /// Highest priority (channel priority 0). - Highest, -} - -impl Priority { - /// Convert to hardware priority value (0 = highest, 7 = lowest). - pub fn to_hw_priority(self) -> u8 { - match self { - Priority::Low => 7, - Priority::Medium => 4, - Priority::High => 1, - Priority::Highest => 0, - } - } -} - -/// DMA transfer data width. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum WordSize { - /// 8-bit (1 byte) transfers. - OneByte, - /// 16-bit (2 byte) transfers. - TwoBytes, - /// 32-bit (4 byte) transfers. - #[default] - FourBytes, -} - -impl WordSize { - /// Size in bytes. - pub const fn bytes(self) -> usize { - match self { - WordSize::OneByte => 1, - WordSize::TwoBytes => 2, - WordSize::FourBytes => 4, - } - } - - /// Convert to hardware SSIZE/DSIZE field value. - pub const fn to_hw_size(self) -> u8 { - match self { - WordSize::OneByte => 0, - WordSize::TwoBytes => 1, - WordSize::FourBytes => 2, - } - } - - /// Create from byte width (1, 2, or 4). - pub const fn from_bytes(bytes: u8) -> Option { - match bytes { - 1 => Some(WordSize::OneByte), - 2 => Some(WordSize::TwoBytes), - 4 => Some(WordSize::FourBytes), - _ => None, - } - } -} - -/// Trait for types that can be transferred via DMA. -/// -/// This provides compile-time type safety for DMA transfers. -pub trait Word: Copy + 'static { - /// The word size for this type. - fn size() -> WordSize; -} - -impl Word for u8 { - fn size() -> WordSize { - WordSize::OneByte - } -} - -impl Word for u16 { - fn size() -> WordSize { - WordSize::TwoBytes - } -} - -impl Word for u32 { - fn size() -> WordSize { - WordSize::FourBytes - } -} - -/// DMA transfer options. -/// -/// This struct configures various aspects of a DMA transfer. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[non_exhaustive] -pub struct TransferOptions { - /// Transfer priority. - pub priority: Priority, - /// Enable circular (continuous) mode. - /// - /// When enabled, the transfer repeats automatically after completing. - pub circular: bool, - /// Enable interrupt on half transfer complete. - pub half_transfer_interrupt: bool, - /// Enable interrupt on transfer complete. - pub complete_transfer_interrupt: bool, -} - -impl Default for TransferOptions { - fn default() -> Self { - Self { - priority: Priority::High, - circular: false, - half_transfer_interrupt: false, - complete_transfer_interrupt: true, - } - } -} - -/// DMA error types. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - /// The DMA controller reported a bus error. - BusError, - /// The transfer was aborted. - Aborted, - /// Configuration error (e.g., invalid parameters). - Configuration, - /// Buffer overrun (for ring buffers). - Overrun, -} - -/// Whether to enable the major loop completion interrupt. -/// -/// This enum provides better readability than a boolean parameter -/// for functions that configure DMA interrupt behavior. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum EnableInterrupt { - /// Enable the interrupt on major loop completion. - Yes, - /// Do not enable the interrupt. - No, -} - -// ============================================================================ -// DMA Constants -// ============================================================================ - -/// Maximum bytes per DMA transfer (eDMA4 CITER/BITER are 15-bit fields). -/// -/// This is a hardware limitation of the eDMA4 controller. Transfers larger -/// than this must be split into multiple DMA operations. -pub const DMA_MAX_TRANSFER_SIZE: usize = 0x7FFF; - -// ============================================================================ -// DMA Request Source Types (Type-Safe API) -// ============================================================================ - -/// Trait for type-safe DMA request sources. -/// -/// Each peripheral that can trigger DMA requests implements this trait -/// with marker types that encode the correct request source number at -/// compile time. This prevents using the wrong request source for a -/// peripheral. -/// -/// # Example -/// -/// ```ignore -/// // The LPUART2 RX request source is automatically derived from the type: -/// channel.set_request_source::(); -/// ``` -/// -/// This trait is sealed and cannot be implemented outside this crate. -#[allow(private_bounds)] -pub trait DmaRequest: sealed::SealedDmaRequest { - /// The hardware request source number for the DMA mux. - const REQUEST_NUMBER: u8; -} - -/// Macro to define a DMA request type. -/// -/// Creates a zero-sized marker type that implements `DmaRequest` with -/// the specified request number. -macro_rules! define_dma_request { - ($(#[$meta:meta])* $name:ident = $num:expr) => { - $(#[$meta])* - #[derive(Debug, Copy, Clone)] - pub struct $name; - - impl sealed::SealedDmaRequest for $name {} - - impl DmaRequest for $name { - const REQUEST_NUMBER: u8 = $num; - } - }; -} - -// LPUART DMA request sources (from MCXA276 reference manual Table 4-8) -define_dma_request!( - /// DMA request source for LPUART0 RX. - Lpuart0RxRequest = 21 -); -define_dma_request!( - /// DMA request source for LPUART0 TX. - Lpuart0TxRequest = 22 -); -define_dma_request!( - /// DMA request source for LPUART1 RX. - Lpuart1RxRequest = 23 -); -define_dma_request!( - /// DMA request source for LPUART1 TX. - Lpuart1TxRequest = 24 -); -define_dma_request!( - /// DMA request source for LPUART2 RX. - Lpuart2RxRequest = 25 -); -define_dma_request!( - /// DMA request source for LPUART2 TX. - Lpuart2TxRequest = 26 -); -define_dma_request!( - /// DMA request source for LPUART3 RX. - Lpuart3RxRequest = 27 -); -define_dma_request!( - /// DMA request source for LPUART3 TX. - Lpuart3TxRequest = 28 -); -define_dma_request!( - /// DMA request source for LPUART4 RX. - Lpuart4RxRequest = 29 -); -define_dma_request!( - /// DMA request source for LPUART4 TX. - Lpuart4TxRequest = 30 -); -define_dma_request!( - /// DMA request source for LPUART5 RX. - Lpuart5RxRequest = 31 -); -define_dma_request!( - /// DMA request source for LPUART5 TX. - Lpuart5TxRequest = 32 -); - -// ============================================================================ -// Channel Trait (Sealed Pattern) -// ============================================================================ - -mod sealed { - use crate::pac::Interrupt; - - /// Sealed trait for DMA channels. - pub trait SealedChannel { - /// Zero-based channel index into the TCD array. - fn index(&self) -> usize; - /// Interrupt vector for this channel. - fn interrupt(&self) -> Interrupt; - } - - /// Sealed trait for DMA request sources. - pub trait SealedDmaRequest {} -} - -/// Marker trait implemented by HAL peripheral tokens that map to a DMA0 -/// channel backed by one EDMA_0_TCD0 TCD slot. -/// -/// This trait is sealed and cannot be implemented outside this crate. -#[allow(private_bounds)] -pub trait Channel: sealed::SealedChannel + PeripheralType + Into + 'static { - /// Zero-based channel index into the TCD array. - const INDEX: usize; - /// Interrupt vector for this channel. - const INTERRUPT: Interrupt; -} - -/// Type-erased DMA channel. -/// -/// This allows storing DMA channels in a uniform way regardless of their -/// concrete type, useful for async transfer futures and runtime channel selection. -#[derive(Debug, Clone, Copy)] -pub struct AnyChannel { - index: usize, - interrupt: Interrupt, -} - -impl AnyChannel { - /// Get the channel index. - #[inline] - pub const fn index(&self) -> usize { - self.index - } - - /// Get the channel interrupt. - #[inline] - pub const fn interrupt(&self) -> Interrupt { - self.interrupt - } - - /// Get a reference to the TCD register block for this channel. - /// - /// This steals the eDMA pointer internally since MCXA276 has only one eDMA instance. - #[inline] - fn tcd(&self) -> &'static pac::edma_0_tcd0::Tcd { - // Safety: MCXA276 has a single eDMA instance, and we're only accessing - // the TCD for this specific channel - let edma = unsafe { &*pac::Edma0Tcd0::ptr() }; - edma.tcd(self.index) - } - - /// Check if the channel's DONE flag is set. - pub fn is_done(&self) -> bool { - self.tcd().ch_csr().read().done().bit_is_set() - } - - /// Get the waker for this channel. - pub fn waker(&self) -> &'static AtomicWaker { - &STATES[self.index].waker - } -} - -impl sealed::SealedChannel for AnyChannel { - fn index(&self) -> usize { - self.index - } - - fn interrupt(&self) -> Interrupt { - self.interrupt - } -} - -/// Macro to implement Channel trait for a peripheral. -macro_rules! impl_channel { - ($peri:ident, $index:expr, $irq:ident) => { - impl sealed::SealedChannel for crate::peripherals::$peri { - fn index(&self) -> usize { - $index - } - - fn interrupt(&self) -> Interrupt { - Interrupt::$irq - } - } - - impl Channel for crate::peripherals::$peri { - const INDEX: usize = $index; - const INTERRUPT: Interrupt = Interrupt::$irq; - } - - impl From for AnyChannel { - fn from(_: crate::peripherals::$peri) -> Self { - AnyChannel { - index: $index, - interrupt: Interrupt::$irq, - } - } - } - }; -} - -impl_channel!(DMA_CH0, 0, DMA_CH0); -impl_channel!(DMA_CH1, 1, DMA_CH1); -impl_channel!(DMA_CH2, 2, DMA_CH2); -impl_channel!(DMA_CH3, 3, DMA_CH3); -impl_channel!(DMA_CH4, 4, DMA_CH4); -impl_channel!(DMA_CH5, 5, DMA_CH5); -impl_channel!(DMA_CH6, 6, DMA_CH6); -impl_channel!(DMA_CH7, 7, DMA_CH7); - -/// Strongly-typed handle to a DMA0 channel. -/// -/// The lifetime of this value is tied to the unique peripheral token -/// supplied by `embassy_hal_internal::peripherals!`, so safe code cannot -/// create two `DmaChannel` instances for the same hardware channel. -pub struct DmaChannel { - _ch: core::marker::PhantomData, -} - -// ============================================================================ -// DMA Transfer Methods - API Overview -// ============================================================================ -// -// The DMA API provides two categories of methods for configuring transfers: -// -// ## 1. Async Methods (Return `Transfer` Future) -// -// These methods return a [`Transfer`] Future that must be `.await`ed: -// -// - [`write()`](DmaChannel::write) - Memory-to-peripheral using default eDMA TCD block -// - [`read()`](DmaChannel::read) - Peripheral-to-memory using default eDMA TCD block -// - [`write_to_peripheral()`](DmaChannel::write_to_peripheral) - Memory-to-peripheral with custom eDMA TCD block -// - [`read_from_peripheral()`](DmaChannel::read_from_peripheral) - Peripheral-to-memory with custom eDMA TCD block -// - [`mem_to_mem()`](DmaChannel::mem_to_mem) - Memory-to-memory using default eDMA TCD block -// - [`transfer_mem_to_mem()`](DmaChannel::transfer_mem_to_mem) - Memory-to-memory with custom eDMA TCD block -// -// The `Transfer` manages the DMA lifecycle automatically: -// - Enables channel request -// - Waits for completion via async/await -// - Cleans up on completion -// -// **Important:** `Transfer::Drop` aborts the transfer if dropped before completion. -// This means you MUST `.await` the Transfer or it will be aborted when it goes out of scope. -// -// **Use case:** When you want to use async/await and let the Transfer handle lifecycle management. -// -// ## 2. Setup Methods (Configure TCD Only) -// -// These methods configure the TCD but do NOT return a `Transfer`: -// -// - [`setup_write()`](DmaChannel::setup_write) - Memory-to-peripheral using default eDMA TCD block -// - [`setup_read()`](DmaChannel::setup_read) - Peripheral-to-memory using default eDMA TCD block -// - [`setup_write_to_peripheral()`](DmaChannel::setup_write_to_peripheral) - Memory-to-peripheral with custom eDMA TCD block -// - [`setup_read_from_peripheral()`](DmaChannel::setup_read_from_peripheral) - Peripheral-to-memory with custom eDMA TCD block -// -// The caller is responsible for the complete DMA lifecycle: -// 1. Call [`enable_request()`](DmaChannel::enable_request) to start the transfer -// 2. Poll [`is_done()`](DmaChannel::is_done) or use interrupts to detect completion -// 3. Call [`disable_request()`](DmaChannel::disable_request), [`clear_done()`](DmaChannel::clear_done), -// [`clear_interrupt()`](DmaChannel::clear_interrupt) for cleanup -// -// **Use case:** Peripheral drivers (like LPUART) that need fine-grained control over -// DMA setup before starting a `Transfer`. -// -// ============================================================================ - -impl DmaChannel { - /// Wrap a DMA channel token (takes ownership of the Peri wrapper). - /// - /// Note: DMA is initialized during `hal::init()` via `dma::init()`. - #[inline] - pub fn new(_ch: embassy_hal_internal::Peri<'_, C>) -> Self { - Self { - _ch: core::marker::PhantomData, - } - } - - /// Wrap a DMA channel token directly (for internal use). - /// - /// Note: DMA is initialized during `hal::init()` via `dma::init()`. - #[inline] - pub fn from_token(_ch: C) -> Self { - Self { - _ch: core::marker::PhantomData, - } - } - - /// Channel index in the EDMA_0_TCD0 array. - #[inline] - pub const fn index(&self) -> usize { - C::INDEX - } - - /// Convert this typed channel into a type-erased `AnyChannel`. - #[inline] - pub fn into_any(self) -> AnyChannel { - AnyChannel { - index: C::INDEX, - interrupt: C::INTERRUPT, - } - } - - /// Get a reference to the type-erased channel info. - #[inline] - pub fn as_any(&self) -> AnyChannel { - AnyChannel { - index: C::INDEX, - interrupt: C::INTERRUPT, - } - } - - /// Return a reference to the underlying TCD register block. - /// - /// This steals the eDMA pointer internally since MCXA276 has only one eDMA instance. - /// - /// # Note - /// - /// This is exposed for advanced use cases that need direct TCD access. - /// For most use cases, prefer the higher-level transfer methods. - #[inline] - pub fn tcd(&self) -> &'static pac::edma_0_tcd0::Tcd { - // Safety: MCXA276 has a single eDMA instance - let edma = unsafe { &*pac::Edma0Tcd0::ptr() }; - edma.tcd(C::INDEX) - } - - /// Start an async transfer. - /// - /// The channel must already be configured. This enables the channel - /// request and returns a `Transfer` future that resolves when the - /// DMA transfer completes. - /// - /// # Safety - /// - /// The caller must ensure the DMA channel has been properly configured - /// and that source/destination buffers remain valid for the duration - /// of the transfer. - pub unsafe fn start_transfer(&self) -> Transfer<'_> { - // Clear any previous DONE/INT flags - let t = self.tcd(); - t.ch_csr().modify(|_, w| w.done().clear_bit_by_one()); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Enable the channel request - t.ch_csr().modify(|_, w| w.erq().enable()); - - Transfer::new(self.as_any()) - } - - // ======================================================================== - // Type-Safe Transfer Methods (Embassy-style API) - // ======================================================================== - - /// Perform a memory-to-memory DMA transfer (simplified API). - /// - /// This is a type-safe wrapper that uses the `Word` trait to determine - /// the correct transfer width automatically. Uses the global eDMA TCD - /// register accessor internally. - /// - /// # Arguments - /// - /// * `src` - Source buffer - /// * `dst` - Destination buffer (must be at least as large as src) - /// * `options` - Transfer configuration options - /// - /// # Safety - /// - /// The source and destination buffers must remain valid for the - /// duration of the transfer. - pub unsafe fn mem_to_mem(&self, src: &[W], dst: &mut [W], options: TransferOptions) -> Transfer<'_> { - self.transfer_mem_to_mem(src, dst, options) - } - - /// Perform a memory-to-memory DMA transfer. - /// - /// This is a type-safe wrapper that uses the `Word` trait to determine - /// the correct transfer width automatically. - /// - /// # Arguments - /// - /// * `edma` - Reference to the eDMA TCD register block - /// * `src` - Source buffer - /// * `dst` - Destination buffer (must be at least as large as src) - /// * `options` - Transfer configuration options - /// - /// # Safety - /// - /// The source and destination buffers must remain valid for the - /// duration of the transfer. - pub unsafe fn transfer_mem_to_mem( - &self, - src: &[W], - dst: &mut [W], - options: TransferOptions, - ) -> Transfer<'_> { - assert!(!src.is_empty()); - assert!(dst.len() >= src.len()); - assert!(src.len() <= 0x7fff); - - let size = W::size(); - let byte_count = (src.len() * size.bytes()) as u32; - - let t = self.tcd(); - - // Reset channel state - clear DONE, disable requests, clear errors - t.ch_csr().write(|w| { - w.erq() - .disable() - .earq() - .disable() - .eei() - .no_error() - .done() - .clear_bit_by_one() - }); - t.ch_es().write(|w| w.err().clear_bit_by_one()); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Memory barrier to ensure channel state is fully reset before touching TCD - cortex_m::asm::dsb(); - - // Full TCD reset following NXP SDK pattern (EDMA_TcdResetExt). - // Reset ALL TCD registers to 0 to clear any stale configuration from - // previous transfers. This is critical when reusing a channel. - t.tcd_saddr().write(|w| w.saddr().bits(0)); - t.tcd_soff().write(|w| w.soff().bits(0)); - t.tcd_attr().write(|w| w.bits(0)); - t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(0)); - t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); - t.tcd_daddr().write(|w| w.daddr().bits(0)); - t.tcd_doff().write(|w| w.doff().bits(0)); - t.tcd_citer_elinkno().write(|w| w.bits(0)); - t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(0)); - t.tcd_csr().write(|w| w.bits(0)); // Clear CSR completely - t.tcd_biter_elinkno().write(|w| w.bits(0)); - - // Memory barrier after TCD reset - cortex_m::asm::dsb(); - - // Note: Priority is managed by round-robin arbitration (set in init()) - // Per-channel priority can be configured via ch_pri() if needed - - // Now configure the new transfer - - // Source address and increment - t.tcd_saddr().write(|w| w.saddr().bits(src.as_ptr() as u32)); - t.tcd_soff().write(|w| w.soff().bits(size.bytes() as u16)); - - // Destination address and increment - t.tcd_daddr().write(|w| w.daddr().bits(dst.as_mut_ptr() as u32)); - t.tcd_doff().write(|w| w.doff().bits(size.bytes() as u16)); - - // Transfer attributes (size) - let hw_size = size.to_hw_size(); - t.tcd_attr().write(|w| w.ssize().bits(hw_size).dsize().bits(hw_size)); - - // Minor loop: transfer all bytes in one minor loop - t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(byte_count)); - - // No source/dest adjustment after major loop - t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); - t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(0)); - - // Major loop count = 1 (single major loop) - // Write BITER first, then CITER (CITER must match BITER at start) - t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); - t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); - - // Memory barrier before setting START - cortex_m::asm::dsb(); - - // Control/status: interrupt on major complete, start - // Write this last after all other TCD registers are configured - let int_major = options.complete_transfer_interrupt; - t.tcd_csr().write(|w| { - w.intmajor() - .bit(int_major) - .inthalf() - .bit(options.half_transfer_interrupt) - .dreq() - .set_bit() // Auto-disable request after major loop - .start() - .set_bit() // Start the channel - }); - - Transfer::new(self.as_any()) - } - - /// Fill a memory buffer with a pattern value (memset). - /// - /// This performs a DMA transfer where the source address remains fixed - /// (pattern value) while the destination address increments through the buffer. - /// It's useful for quickly filling large memory regions with a constant value. - /// - /// # Arguments - /// - /// * `pattern` - Reference to the pattern value (will be read repeatedly) - /// * `dst` - Destination buffer to fill - /// * `options` - Transfer configuration options - /// - /// # Example - /// - /// ```no_run - /// use embassy_mcxa::dma::{DmaChannel, TransferOptions}; - /// - /// let dma_ch = DmaChannel::new(p.DMA_CH0); - /// let pattern: u32 = 0xDEADBEEF; - /// let mut buffer = [0u32; 256]; - /// - /// unsafe { - /// dma_ch.memset(&pattern, &mut buffer, TransferOptions::default()).await; - /// } - /// // buffer is now filled with 0xDEADBEEF - /// ``` - /// - /// # Safety - /// - /// - The pattern and destination buffer must remain valid for the duration of the transfer. - pub unsafe fn memset(&self, pattern: &W, dst: &mut [W], options: TransferOptions) -> Transfer<'_> { - assert!(!dst.is_empty()); - assert!(dst.len() <= 0x7fff); - - let size = W::size(); - let byte_size = size.bytes(); - // Total bytes to transfer - all in one minor loop for software-triggered transfers - let total_bytes = (dst.len() * byte_size) as u32; - - let t = self.tcd(); - - // Reset channel state - clear DONE, disable requests, clear errors - t.ch_csr().write(|w| { - w.erq() - .disable() - .earq() - .disable() - .eei() - .no_error() - .done() - .clear_bit_by_one() - }); - t.ch_es().write(|w| w.err().clear_bit_by_one()); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Memory barrier to ensure channel state is fully reset before touching TCD - cortex_m::asm::dsb(); - - // Full TCD reset following NXP SDK pattern (EDMA_TcdResetExt). - // Reset ALL TCD registers to 0 to clear any stale configuration from - // previous transfers. This is critical when reusing a channel. - t.tcd_saddr().write(|w| w.saddr().bits(0)); - t.tcd_soff().write(|w| w.soff().bits(0)); - t.tcd_attr().write(|w| w.bits(0)); - t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(0)); - t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); - t.tcd_daddr().write(|w| w.daddr().bits(0)); - t.tcd_doff().write(|w| w.doff().bits(0)); - t.tcd_citer_elinkno().write(|w| w.bits(0)); - t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(0)); - t.tcd_csr().write(|w| w.bits(0)); // Clear CSR completely - t.tcd_biter_elinkno().write(|w| w.bits(0)); - - // Memory barrier after TCD reset - cortex_m::asm::dsb(); - - // Now configure the new transfer - // - // For software-triggered memset, we use a SINGLE minor loop that transfers - // all bytes at once. The source address stays fixed (SOFF=0) while the - // destination increments (DOFF=byte_size). The eDMA will read from the - // same source address for each destination word. - // - // This is necessary because the START bit only triggers ONE minor loop - // iteration. Using CITER>1 with software trigger would require multiple - // START triggers. - - // Source: pattern address, fixed (soff=0) - t.tcd_saddr().write(|w| w.saddr().bits(pattern as *const W as u32)); - t.tcd_soff().write(|w| w.soff().bits(0)); // Fixed source - reads pattern repeatedly - - // Destination: memory buffer, incrementing by word size - t.tcd_daddr().write(|w| w.daddr().bits(dst.as_mut_ptr() as u32)); - t.tcd_doff().write(|w| w.doff().bits(byte_size as u16)); - - // Transfer attributes - source and dest are same word size - let hw_size = size.to_hw_size(); - t.tcd_attr().write(|w| w.ssize().bits(hw_size).dsize().bits(hw_size)); - - // Minor loop: transfer ALL bytes in one minor loop (like mem_to_mem) - // This allows the entire transfer to complete with a single START trigger - t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(total_bytes)); - - // No address adjustment after major loop - t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); - t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(0)); - - // Major loop count = 1 (single major loop, all data in minor loop) - // Write BITER first, then CITER (CITER must match BITER at start) - t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); - t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); - - // Memory barrier before setting START - cortex_m::asm::dsb(); - - // Control/status: interrupt on major complete, start immediately - // Write this last after all other TCD registers are configured - let int_major = options.complete_transfer_interrupt; - t.tcd_csr().write(|w| { - w.intmajor() - .bit(int_major) - .inthalf() - .bit(options.half_transfer_interrupt) - .dreq() - .set_bit() // Auto-disable request after major loop - .start() - .set_bit() // Start the channel - }); - - Transfer::new(self.as_any()) - } - - /// Write data from memory to a peripheral register. - /// - /// The destination address remains fixed (peripheral register) while - /// the source address increments through the buffer. - /// - /// # Arguments - /// - /// * `buf` - Source buffer to write from - /// * `peri_addr` - Peripheral register address - /// * `options` - Transfer configuration options - /// - /// # Safety - /// - /// - The buffer must remain valid for the duration of the transfer. - /// - The peripheral address must be valid for writes. - pub unsafe fn write(&self, buf: &[W], peri_addr: *mut W, options: TransferOptions) -> Transfer<'_> { - self.write_to_peripheral(buf, peri_addr, options) - } - - /// Configure a memory-to-peripheral DMA transfer without starting it. - /// - /// This is a convenience wrapper around [`setup_write_to_peripheral()`](Self::setup_write_to_peripheral) - /// that uses the default eDMA TCD register block. - /// - /// This method configures the TCD but does NOT return a `Transfer`. The caller - /// is responsible for the complete DMA lifecycle: - /// 1. Call [`enable_request()`](Self::enable_request) to start the transfer - /// 2. Poll [`is_done()`](Self::is_done) or use interrupts to detect completion - /// 3. Call [`disable_request()`](Self::disable_request), [`clear_done()`](Self::clear_done), - /// [`clear_interrupt()`](Self::clear_interrupt) for cleanup - /// - /// # Example - /// - /// ```no_run - /// # use embassy_mcxa::dma::DmaChannel; - /// # let dma_ch = DmaChannel::new(p.DMA_CH0); - /// # let uart_tx_addr = 0x4000_0000 as *mut u8; - /// let data = [0x48, 0x65, 0x6c, 0x6c, 0x6f]; // "Hello" - /// - /// unsafe { - /// // Configure the transfer - /// dma_ch.setup_write(&data, uart_tx_addr, EnableInterrupt::Yes); - /// - /// // Start when peripheral is ready - /// dma_ch.enable_request(); - /// - /// // Wait for completion (or use interrupt) - /// while !dma_ch.is_done() {} - /// - /// // Clean up - /// dma_ch.clear_done(); - /// dma_ch.clear_interrupt(); - /// } - /// ``` - /// - /// # Arguments - /// - /// * `buf` - Source buffer to write from - /// * `peri_addr` - Peripheral register address - /// * `enable_interrupt` - Whether to enable interrupt on completion - /// - /// # Safety - /// - /// - The buffer must remain valid for the duration of the transfer. - /// - The peripheral address must be valid for writes. - pub unsafe fn setup_write(&self, buf: &[W], peri_addr: *mut W, enable_interrupt: EnableInterrupt) { - self.setup_write_to_peripheral(buf, peri_addr, enable_interrupt) - } - - /// Write data from memory to a peripheral register. - /// - /// The destination address remains fixed (peripheral register) while - /// the source address increments through the buffer. - /// - /// # Arguments - /// - /// * `buf` - Source buffer to write from - /// * `peri_addr` - Peripheral register address - /// * `options` - Transfer configuration options - /// - /// # Safety - /// - /// - The buffer must remain valid for the duration of the transfer. - /// - The peripheral address must be valid for writes. - pub unsafe fn write_to_peripheral( - &self, - buf: &[W], - peri_addr: *mut W, - options: TransferOptions, - ) -> Transfer<'_> { - assert!(!buf.is_empty()); - assert!(buf.len() <= 0x7fff); - - let size = W::size(); - let byte_size = size.bytes(); - - let t = self.tcd(); - - // Reset channel state - t.ch_csr().write(|w| w.erq().disable().done().clear_bit_by_one()); - t.ch_es().write(|w| w.bits(0)); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Addresses - t.tcd_saddr().write(|w| w.saddr().bits(buf.as_ptr() as u32)); - t.tcd_daddr().write(|w| w.daddr().bits(peri_addr as u32)); - - // Offsets: Source increments, Dest fixed - t.tcd_soff().write(|w| w.soff().bits(byte_size as u16)); - t.tcd_doff().write(|w| w.doff().bits(0)); - - // Attributes: set size and explicitly disable modulo - let hw_size = size.to_hw_size(); - t.tcd_attr().write(|w| { - w.ssize() - .bits(hw_size) - .dsize() - .bits(hw_size) - .smod() - .disable() - .dmod() - .bits(0) - }); - - // Minor loop: transfer one word per request (match old: only set nbytes) - t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(byte_size as u32)); - - // No final adjustments - t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); - t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(0)); - - // Major loop count = number of words - let count = buf.len() as u16; - t.tcd_citer_elinkno().write(|w| w.citer().bits(count).elink().disable()); - t.tcd_biter_elinkno().write(|w| w.biter().bits(count).elink().disable()); - - // CSR: interrupt on major loop complete and auto-clear ERQ - t.tcd_csr().write(|w| { - let w = if options.complete_transfer_interrupt { - w.intmajor().enable() - } else { - w.intmajor().disable() - }; - w.inthalf() - .disable() - .dreq() - .erq_field_clear() // Disable request when done - .esg() - .normal_format() - .majorelink() - .disable() - .eeop() - .disable() - .esda() - .disable() - .bwc() - .no_stall() - }); - - // Ensure all TCD writes have completed before DMA engine reads them - cortex_m::asm::dsb(); - - Transfer::new(self.as_any()) - } - - /// Read data from a peripheral register to memory. - /// - /// The source address remains fixed (peripheral register) while - /// the destination address increments through the buffer. - /// - /// # Arguments - /// - /// * `peri_addr` - Peripheral register address - /// * `buf` - Destination buffer to read into - /// * `options` - Transfer configuration options - /// - /// # Safety - /// - /// - The buffer must remain valid for the duration of the transfer. - /// - The peripheral address must be valid for reads. - pub unsafe fn read(&self, peri_addr: *const W, buf: &mut [W], options: TransferOptions) -> Transfer<'_> { - self.read_from_peripheral(peri_addr, buf, options) - } - - /// Configure a peripheral-to-memory DMA transfer without starting it. - /// - /// This is a convenience wrapper around [`setup_read_from_peripheral()`](Self::setup_read_from_peripheral) - /// that uses the default eDMA TCD register block. - /// - /// This method configures the TCD but does NOT return a `Transfer`. The caller - /// is responsible for the complete DMA lifecycle: - /// 1. Call [`enable_request()`](Self::enable_request) to start the transfer - /// 2. Poll [`is_done()`](Self::is_done) or use interrupts to detect completion - /// 3. Call [`disable_request()`](Self::disable_request), [`clear_done()`](Self::clear_done), - /// [`clear_interrupt()`](Self::clear_interrupt) for cleanup - /// - /// # Example - /// - /// ```no_run - /// # use embassy_mcxa::dma::DmaChannel; - /// # let dma_ch = DmaChannel::new(p.DMA_CH0); - /// # let uart_rx_addr = 0x4000_0000 as *const u8; - /// let mut buf = [0u8; 32]; - /// - /// unsafe { - /// // Configure the transfer - /// dma_ch.setup_read(uart_rx_addr, &mut buf, EnableInterrupt::Yes); - /// - /// // Start when peripheral is ready - /// dma_ch.enable_request(); - /// - /// // Wait for completion (or use interrupt) - /// while !dma_ch.is_done() {} - /// - /// // Clean up - /// dma_ch.clear_done(); - /// dma_ch.clear_interrupt(); - /// } - /// // buf now contains received data - /// ``` - /// - /// # Arguments - /// - /// * `peri_addr` - Peripheral register address - /// * `buf` - Destination buffer to read into - /// * `enable_interrupt` - Whether to enable interrupt on completion - /// - /// # Safety - /// - /// - The buffer must remain valid for the duration of the transfer. - /// - The peripheral address must be valid for reads. - pub unsafe fn setup_read(&self, peri_addr: *const W, buf: &mut [W], enable_interrupt: EnableInterrupt) { - self.setup_read_from_peripheral(peri_addr, buf, enable_interrupt) - } - - /// Read data from a peripheral register to memory. - /// - /// The source address remains fixed (peripheral register) while - /// the destination address increments through the buffer. - /// - /// # Arguments - /// - /// * `peri_addr` - Peripheral register address - /// * `buf` - Destination buffer to read into - /// * `options` - Transfer configuration options - /// - /// # Safety - /// - /// - The buffer must remain valid for the duration of the transfer. - /// - The peripheral address must be valid for reads. - pub unsafe fn read_from_peripheral( - &self, - peri_addr: *const W, - buf: &mut [W], - options: TransferOptions, - ) -> Transfer<'_> { - assert!(!buf.is_empty()); - assert!(buf.len() <= 0x7fff); - - let size = W::size(); - let byte_size = size.bytes(); - - let t = self.tcd(); - - // Reset channel control/error/interrupt state - t.ch_csr().write(|w| { - w.erq() - .disable() - .earq() - .disable() - .eei() - .no_error() - .ebw() - .disable() - .done() - .clear_bit_by_one() - }); - t.ch_es().write(|w| w.bits(0)); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Source: peripheral register, fixed - t.tcd_saddr().write(|w| w.saddr().bits(peri_addr as u32)); - t.tcd_soff().write(|w| w.soff().bits(0)); // No increment - - // Destination: memory buffer, incrementing - t.tcd_daddr().write(|w| w.daddr().bits(buf.as_mut_ptr() as u32)); - t.tcd_doff().write(|w| w.doff().bits(byte_size as u16)); - - // Transfer attributes: set size and explicitly disable modulo - let hw_size = size.to_hw_size(); - t.tcd_attr().write(|w| { - w.ssize() - .bits(hw_size) - .dsize() - .bits(hw_size) - .smod() - .disable() - .dmod() - .bits(0) - }); - - // Minor loop: transfer one word per request, no offsets - t.tcd_nbytes_mloffno().write(|w| { - w.nbytes() - .bits(byte_size as u32) - .dmloe() - .offset_not_applied() - .smloe() - .offset_not_applied() - }); - - // Major loop count = number of words - let count = buf.len() as u16; - t.tcd_citer_elinkno().write(|w| w.citer().bits(count).elink().disable()); - t.tcd_biter_elinkno().write(|w| w.biter().bits(count).elink().disable()); - - // No address adjustment after major loop - t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); - t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(0)); - - // Control/status: interrupt on major complete, auto-clear ERQ when done - t.tcd_csr().write(|w| { - let w = if options.complete_transfer_interrupt { - w.intmajor().enable() - } else { - w.intmajor().disable() - }; - let w = if options.half_transfer_interrupt { - w.inthalf().enable() - } else { - w.inthalf().disable() - }; - w.dreq() - .erq_field_clear() // Disable request when done (important for peripheral DMA) - .esg() - .normal_format() - .majorelink() - .disable() - .eeop() - .disable() - .esda() - .disable() - .bwc() - .no_stall() - }); - - // Ensure all TCD writes have completed before DMA engine reads them - cortex_m::asm::dsb(); - - Transfer::new(self.as_any()) - } - - /// Configure a memory-to-peripheral DMA transfer without starting it. - /// - /// This configures the TCD for a memory-to-peripheral transfer but does NOT - /// return a Transfer object. The caller is responsible for: - /// 1. Enabling the peripheral's DMA request - /// 2. Calling `enable_request()` to start the transfer - /// 3. Polling `is_done()` or using interrupts to detect completion - /// 4. Calling `disable_request()`, `clear_done()`, `clear_interrupt()` for cleanup - /// - /// Use this when you need manual control over the DMA lifecycle (e.g., in - /// peripheral drivers that have their own completion polling). - /// - /// # Arguments - /// - /// * `buf` - Source buffer to write from - /// * `peri_addr` - Peripheral register address - /// * `enable_interrupt` - Whether to enable interrupt on completion - /// - /// # Safety - /// - /// - The buffer must remain valid for the duration of the transfer. - /// - The peripheral address must be valid for writes. - pub unsafe fn setup_write_to_peripheral( - &self, - buf: &[W], - peri_addr: *mut W, - enable_interrupt: EnableInterrupt, - ) { - assert!(!buf.is_empty()); - assert!(buf.len() <= 0x7fff); - - let size = W::size(); - let byte_size = size.bytes(); - - let t = self.tcd(); - - // Reset channel state - t.ch_csr().write(|w| w.erq().disable().done().clear_bit_by_one()); - t.ch_es().write(|w| w.bits(0)); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Addresses - t.tcd_saddr().write(|w| w.saddr().bits(buf.as_ptr() as u32)); - t.tcd_daddr().write(|w| w.daddr().bits(peri_addr as u32)); - - // Offsets: Source increments, Dest fixed - t.tcd_soff().write(|w| w.soff().bits(byte_size as u16)); - t.tcd_doff().write(|w| w.doff().bits(0)); - - // Attributes: set size and explicitly disable modulo - let hw_size = size.to_hw_size(); - t.tcd_attr().write(|w| { - w.ssize() - .bits(hw_size) - .dsize() - .bits(hw_size) - .smod() - .disable() - .dmod() - .bits(0) - }); - - // Minor loop: transfer one word per request - t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(byte_size as u32)); - - // No final adjustments - t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); - t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(0)); - - // Major loop count = number of words - let count = buf.len() as u16; - t.tcd_citer_elinkno().write(|w| w.citer().bits(count).elink().disable()); - t.tcd_biter_elinkno().write(|w| w.biter().bits(count).elink().disable()); - - // CSR: optional interrupt on major loop complete and auto-clear ERQ - t.tcd_csr().write(|w| { - let w = match enable_interrupt { - EnableInterrupt::Yes => w.intmajor().enable(), - EnableInterrupt::No => w.intmajor().disable(), - }; - w.inthalf() - .disable() - .dreq() - .erq_field_clear() - .esg() - .normal_format() - .majorelink() - .disable() - .eeop() - .disable() - .esda() - .disable() - .bwc() - .no_stall() - }); - - // Ensure all TCD writes have completed before DMA engine reads them - cortex_m::asm::dsb(); - } - - /// Configure a peripheral-to-memory DMA transfer without starting it. - /// - /// This configures the TCD for a peripheral-to-memory transfer but does NOT - /// return a Transfer object. The caller is responsible for: - /// 1. Enabling the peripheral's DMA request - /// 2. Calling `enable_request()` to start the transfer - /// 3. Polling `is_done()` or using interrupts to detect completion - /// 4. Calling `disable_request()`, `clear_done()`, `clear_interrupt()` for cleanup - /// - /// Use this when you need manual control over the DMA lifecycle (e.g., in - /// peripheral drivers that have their own completion polling). - /// - /// # Arguments - /// - /// * `peri_addr` - Peripheral register address - /// * `buf` - Destination buffer to read into - /// * `enable_interrupt` - Whether to enable interrupt on completion - /// - /// # Safety - /// - /// - The buffer must remain valid for the duration of the transfer. - /// - The peripheral address must be valid for reads. - pub unsafe fn setup_read_from_peripheral( - &self, - peri_addr: *const W, - buf: &mut [W], - enable_interrupt: EnableInterrupt, - ) { - assert!(!buf.is_empty()); - assert!(buf.len() <= 0x7fff); - - let size = W::size(); - let byte_size = size.bytes(); - - let t = self.tcd(); - - // Reset channel control/error/interrupt state - t.ch_csr().write(|w| { - w.erq() - .disable() - .earq() - .disable() - .eei() - .no_error() - .ebw() - .disable() - .done() - .clear_bit_by_one() - }); - t.ch_es().write(|w| w.bits(0)); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Source: peripheral register, fixed - t.tcd_saddr().write(|w| w.saddr().bits(peri_addr as u32)); - t.tcd_soff().write(|w| w.soff().bits(0)); - - // Destination: memory buffer, incrementing - t.tcd_daddr().write(|w| w.daddr().bits(buf.as_mut_ptr() as u32)); - t.tcd_doff().write(|w| w.doff().bits(byte_size as u16)); - - // Attributes: set size and explicitly disable modulo - let hw_size = size.to_hw_size(); - t.tcd_attr().write(|w| { - w.ssize() - .bits(hw_size) - .dsize() - .bits(hw_size) - .smod() - .disable() - .dmod() - .bits(0) - }); - - // Minor loop: transfer one word per request - t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(byte_size as u32)); - - // No final adjustments - t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); - t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(0)); - - // Major loop count = number of words - let count = buf.len() as u16; - t.tcd_citer_elinkno().write(|w| w.citer().bits(count).elink().disable()); - t.tcd_biter_elinkno().write(|w| w.biter().bits(count).elink().disable()); - - // CSR: optional interrupt on major loop complete and auto-clear ERQ - t.tcd_csr().write(|w| { - let w = match enable_interrupt { - EnableInterrupt::Yes => w.intmajor().enable(), - EnableInterrupt::No => w.intmajor().disable(), - }; - w.inthalf() - .disable() - .dreq() - .erq_field_clear() - .esg() - .normal_format() - .majorelink() - .disable() - .eeop() - .disable() - .esda() - .disable() - .bwc() - .no_stall() - }); - - // Ensure all TCD writes have completed before DMA engine reads them - cortex_m::asm::dsb(); - } - - /// Configure the integrated channel MUX to use the given typed - /// DMA request source (e.g., [`Lpuart2TxRequest`] or [`Lpuart2RxRequest`]). - /// - /// This is the type-safe version that uses marker types to ensure - /// compile-time verification of request source validity. - /// - /// # Safety - /// - /// The channel must be properly configured before enabling requests. - /// The caller must ensure the DMA request source matches the peripheral - /// that will drive this channel. - /// - /// # Note - /// - /// The NXP SDK requires a two-step write sequence: first clear - /// the mux to 0, then set the actual source. This is a hardware - /// requirement on eDMA4 for the mux to properly latch. - /// - /// # Example - /// - /// ```ignore - /// use embassy_mcxa::dma::{DmaChannel, Lpuart2RxRequest}; - /// - /// // Type-safe: compiler verifies this is a valid DMA request type - /// unsafe { - /// channel.set_request_source::(); - /// } - /// ``` - #[inline] - pub unsafe fn set_request_source(&self) { - // Two-step write per NXP SDK: clear to 0, then set actual source. - self.tcd().ch_mux().write(|w| w.src().bits(0)); - cortex_m::asm::dsb(); // Ensure the clear completes before setting new source - self.tcd().ch_mux().write(|w| w.src().bits(R::REQUEST_NUMBER)); - } - - /// Enable hardware requests for this channel (ERQ=1). - /// - /// # Safety - /// - /// The channel must be properly configured before enabling requests. - pub unsafe fn enable_request(&self) { - let t = self.tcd(); - t.ch_csr().modify(|_, w| w.erq().enable()); - } - - /// Disable hardware requests for this channel (ERQ=0). - /// - /// # Safety - /// - /// Disabling requests on an active transfer may leave the transfer incomplete. - pub unsafe fn disable_request(&self) { - let t = self.tcd(); - t.ch_csr().modify(|_, w| w.erq().disable()); - } - - /// Return true if the channel's DONE flag is set. - pub fn is_done(&self) -> bool { - let t = self.tcd(); - t.ch_csr().read().done().bit_is_set() - } - - /// Clear the DONE flag for this channel. - /// - /// Uses modify to preserve other bits (especially ERQ) unlike write - /// which would clear ERQ and halt an active transfer. - /// - /// # Safety - /// - /// Clearing DONE while a transfer is in progress may cause undefined behavior. - pub unsafe fn clear_done(&self) { - let t = self.tcd(); - t.ch_csr().modify(|_, w| w.done().clear_bit_by_one()); - } - - /// Clear the channel interrupt flag (CH_INT.INT). - /// - /// # Safety - /// - /// Must be called from the correct interrupt context or with interrupts disabled. - pub unsafe fn clear_interrupt(&self) { - let t = self.tcd(); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - } - - /// Trigger a software start for this channel. - /// - /// # Safety - /// - /// The channel must be properly configured with a valid TCD before triggering. - pub unsafe fn trigger_start(&self) { - let t = self.tcd(); - t.tcd_csr().modify(|_, w| w.start().channel_started()); - } - - /// Get the waker for this channel - pub fn waker(&self) -> &'static AtomicWaker { - &STATES[C::INDEX].waker - } - - /// Enable the interrupt for this channel in the NVIC. - pub fn enable_interrupt(&self) { - unsafe { - cortex_m::peripheral::NVIC::unmask(C::INTERRUPT); - } - } - - /// Enable Major Loop Linking. - /// - /// When the major loop completes, the hardware will trigger a service request - /// on `link_ch`. - /// - /// # Arguments - /// - /// * `link_ch` - Target channel index (0-7) to link to - /// - /// # Safety - /// - /// The channel must be properly configured before setting up linking. - pub unsafe fn set_major_link(&self, link_ch: usize) { - let t = self.tcd(); - t.tcd_csr() - .modify(|_, w| w.majorelink().enable().majorlinkch().bits(link_ch as u8)); - } - - /// Disable Major Loop Linking. - /// - /// Removes any major loop channel linking previously configured. - /// - /// # Safety - /// - /// The caller must ensure this doesn't disrupt an active transfer that - /// depends on the linking. - pub unsafe fn clear_major_link(&self) { - let t = self.tcd(); - t.tcd_csr().modify(|_, w| w.majorelink().disable()); - } - - /// Enable Minor Loop Linking. - /// - /// After each minor loop, the hardware will trigger a service request - /// on `link_ch`. - /// - /// # Arguments - /// - /// * `link_ch` - Target channel index (0-7) to link to - /// - /// # Note - /// - /// This rewrites CITER and BITER registers to the ELINKYES format. - /// It preserves the current loop count. - /// - /// # Safety - /// - /// The channel must be properly configured before setting up linking. - pub unsafe fn set_minor_link(&self, link_ch: usize) { - let t = self.tcd(); - - // Read current CITER (assuming ELINKNO format initially) - let current_citer = t.tcd_citer_elinkno().read().citer().bits(); - let current_biter = t.tcd_biter_elinkno().read().biter().bits(); - - // Write back using ELINKYES format - t.tcd_citer_elinkyes().write(|w| { - w.citer() - .bits(current_citer) - .elink() - .enable() - .linkch() - .bits(link_ch as u8) - }); - - t.tcd_biter_elinkyes().write(|w| { - w.biter() - .bits(current_biter) - .elink() - .enable() - .linkch() - .bits(link_ch as u8) - }); - } - - /// Disable Minor Loop Linking. - /// - /// Removes any minor loop channel linking previously configured. - /// This rewrites CITER and BITER registers to the ELINKNO format, - /// preserving the current loop count. - /// - /// # Safety - /// - /// The caller must ensure this doesn't disrupt an active transfer that - /// depends on the linking. - pub unsafe fn clear_minor_link(&self) { - let t = self.tcd(); - - // Read current CITER (could be in either format, but we only need the count) - // Note: In ELINKYES format, citer is 9 bits; in ELINKNO, it's 15 bits. - // We read from ELINKNO which will give us the combined value. - let current_citer = t.tcd_citer_elinkno().read().citer().bits(); - let current_biter = t.tcd_biter_elinkno().read().biter().bits(); - - // Write back using ELINKNO format (disabling link) - t.tcd_citer_elinkno() - .write(|w| w.citer().bits(current_citer).elink().disable()); - - t.tcd_biter_elinkno() - .write(|w| w.biter().bits(current_biter).elink().disable()); - } - - /// Load a TCD from memory into the hardware channel registers. - /// - /// This is useful for scatter/gather and ping-pong transfers where - /// TCDs are prepared in RAM and then loaded into the hardware. - /// - /// # Safety - /// - /// - The TCD must be properly initialized. - /// - The caller must ensure no concurrent access to the same channel. - pub unsafe fn load_tcd(&self, tcd: &Tcd) { - let t = self.tcd(); - t.tcd_saddr().write(|w| w.saddr().bits(tcd.saddr)); - t.tcd_soff().write(|w| w.soff().bits(tcd.soff as u16)); - t.tcd_attr().write(|w| w.bits(tcd.attr)); - t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(tcd.nbytes)); - t.tcd_slast_sda().write(|w| w.slast_sda().bits(tcd.slast as u32)); - t.tcd_daddr().write(|w| w.daddr().bits(tcd.daddr)); - t.tcd_doff().write(|w| w.doff().bits(tcd.doff as u16)); - t.tcd_citer_elinkno().write(|w| w.citer().bits(tcd.citer)); - t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(tcd.dlast_sga as u32)); - t.tcd_csr().write(|w| w.bits(tcd.csr)); - t.tcd_biter_elinkno().write(|w| w.biter().bits(tcd.biter)); - } -} - -/// In-memory representation of a Transfer Control Descriptor (TCD). -/// -/// This matches the hardware layout (32 bytes). -#[repr(C, align(32))] -#[derive(Clone, Copy, Debug, Default)] -pub struct Tcd { - pub saddr: u32, - pub soff: i16, - pub attr: u16, - pub nbytes: u32, - pub slast: i32, - pub daddr: u32, - pub doff: i16, - pub citer: u16, - pub dlast_sga: i32, - pub csr: u16, - pub biter: u16, -} - -struct State { - /// Waker for transfer complete interrupt - waker: AtomicWaker, - /// Waker for half-transfer interrupt - half_waker: AtomicWaker, -} - -impl State { - const fn new() -> Self { - Self { - waker: AtomicWaker::new(), - half_waker: AtomicWaker::new(), - } - } -} - -static STATES: [State; 8] = [ - State::new(), - State::new(), - State::new(), - State::new(), - State::new(), - State::new(), - State::new(), - State::new(), -]; - -pub(crate) fn waker(idx: usize) -> &'static AtomicWaker { - &STATES[idx].waker -} - -pub(crate) fn half_waker(idx: usize) -> &'static AtomicWaker { - &STATES[idx].half_waker -} - -// ============================================================================ -// Async Transfer Future -// ============================================================================ - -/// An in-progress DMA transfer. -/// -/// This type implements `Future` and can be `.await`ed to wait for the -/// transfer to complete. Dropping the transfer will abort it. -#[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct Transfer<'a> { - channel: AnyChannel, - _phantom: core::marker::PhantomData<&'a ()>, -} - -impl<'a> Transfer<'a> { - /// Create a new transfer for the given channel. - /// - /// The caller must have already configured and started the DMA channel. - pub(crate) fn new(channel: AnyChannel) -> Self { - Self { - channel, - _phantom: core::marker::PhantomData, - } - } - - /// Check if the transfer is still running. - pub fn is_running(&self) -> bool { - !self.channel.is_done() - } - - /// Get the remaining transfer count. - pub fn remaining(&self) -> u16 { - let t = self.channel.tcd(); - t.tcd_citer_elinkno().read().citer().bits() - } - - /// Block until the transfer completes. - pub fn blocking_wait(self) { - while self.is_running() { - core::hint::spin_loop(); - } - - // Ensure all DMA writes are visible - fence(Ordering::SeqCst); - - // Don't run drop (which would abort) - core::mem::forget(self); - } - - /// Wait for the half-transfer interrupt asynchronously. - /// - /// This is useful for double-buffering scenarios where you want to process - /// the first half of the buffer while the second half is being filled. - /// - /// Returns `true` if the half-transfer occurred, `false` if the transfer - /// completed before the half-transfer interrupt. - /// - /// # Note - /// - /// The transfer must be configured with `TransferOptions::half_transfer_interrupt = true` - /// for this method to work correctly. - pub async fn wait_half(&mut self) -> bool { - use core::future::poll_fn; - - poll_fn(|cx| { - let state = &STATES[self.channel.index]; - - // Register the half-transfer waker - state.half_waker.register(cx.waker()); - - // Check if we're past the half-way point - let t = self.channel.tcd(); - let biter = t.tcd_biter_elinkno().read().biter().bits(); - let citer = t.tcd_citer_elinkno().read().citer().bits(); - let half_point = biter / 2; - - if self.channel.is_done() { - // Transfer completed before half-transfer - Poll::Ready(false) - } else if citer <= half_point { - // We're past the half-way point - fence(Ordering::SeqCst); - Poll::Ready(true) - } else { - Poll::Pending - } - }) - .await - } - - /// Abort the transfer. - fn abort(&mut self) { - let t = self.channel.tcd(); - - // Disable channel requests - t.ch_csr().modify(|_, w| w.erq().disable()); - - // Clear any pending interrupt - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Clear DONE flag - t.ch_csr().modify(|_, w| w.done().clear_bit_by_one()); - - fence(Ordering::SeqCst); - } -} - -impl<'a> Unpin for Transfer<'a> {} - -impl<'a> Future for Transfer<'a> { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let state = &STATES[self.channel.index]; - - // Register waker first - state.waker.register(cx.waker()); - - let done = self.channel.is_done(); - - if done { - // Ensure all DMA writes are visible before returning - fence(Ordering::SeqCst); - Poll::Ready(()) - } else { - Poll::Pending - } - } -} - -impl<'a> Drop for Transfer<'a> { - fn drop(&mut self) { - // Only abort if the transfer is still running - // If already complete, no need to abort - if self.is_running() { - self.abort(); - - // Wait for abort to complete - while self.is_running() { - core::hint::spin_loop(); - } - } - - fence(Ordering::SeqCst); - } -} - -// ============================================================================ -// Ring Buffer for Circular DMA -// ============================================================================ - -/// A ring buffer for continuous DMA reception. -/// -/// This structure manages a circular DMA transfer, allowing continuous -/// reception of data without losing bytes between reads. It uses both -/// half-transfer and complete-transfer interrupts to track available data. -/// -/// # Example -/// -/// ```no_run -/// use embassy_mcxa::dma::{DmaChannel, RingBuffer, TransferOptions}; -/// -/// static mut RX_BUF: [u8; 64] = [0; 64]; -/// -/// let dma_ch = DmaChannel::new(p.DMA_CH0); -/// let ring_buf = unsafe { -/// dma_ch.setup_circular_read( -/// uart_rx_addr, -/// &mut RX_BUF, -/// ) -/// }; -/// -/// // Read data as it arrives -/// let mut buf = [0u8; 16]; -/// let n = ring_buf.read(&mut buf).await?; -/// ``` -pub struct RingBuffer<'a, W: Word> { - channel: AnyChannel, - /// Buffer pointer. We use NonNull instead of &mut because DMA acts like - /// a separate thread writing to this buffer, and &mut claims exclusive - /// access which the compiler could optimize incorrectly. - buf: NonNull<[W]>, - /// Buffer length cached for convenience - buf_len: usize, - /// Read position in the buffer (consumer side) - read_pos: AtomicUsize, - /// Phantom data to tie the lifetime to the original buffer - _lt: PhantomData<&'a mut [W]>, -} - -impl<'a, W: Word> RingBuffer<'a, W> { - /// Create a new ring buffer for the given channel and buffer. - /// - /// # Safety - /// - /// The caller must ensure: - /// - The DMA channel has been configured for circular transfer - /// - The buffer remains valid for the lifetime of the ring buffer - /// - Only one RingBuffer exists per DMA channel at a time - pub(crate) unsafe fn new(channel: AnyChannel, buf: &'a mut [W]) -> Self { - let buf_len = buf.len(); - Self { - channel, - buf: NonNull::from(buf), - buf_len, - read_pos: AtomicUsize::new(0), - _lt: PhantomData, - } - } - - /// Get a slice reference to the buffer. - /// - /// # Safety - /// - /// The caller must ensure that DMA is not actively writing to the - /// portion of the buffer being accessed, or that the access is - /// appropriately synchronized. - #[inline] - unsafe fn buf_slice(&self) -> &[W] { - self.buf.as_ref() - } - - /// Get the current DMA write position in the buffer. - /// - /// This reads the current destination address from the DMA controller - /// and calculates the buffer offset. - fn dma_write_pos(&self) -> usize { - let t = self.channel.tcd(); - let daddr = t.tcd_daddr().read().daddr().bits() as usize; - let buf_start = self.buf.as_ptr() as *const W as usize; - - // Calculate offset from buffer start - let offset = daddr.wrapping_sub(buf_start) / core::mem::size_of::(); - - // Ensure we're within bounds (DMA wraps around) - offset % self.buf_len - } - - /// Returns the number of bytes available to read. - pub fn available(&self) -> usize { - let write_pos = self.dma_write_pos(); - let read_pos = self.read_pos.load(Ordering::Acquire); - - if write_pos >= read_pos { - write_pos - read_pos - } else { - self.buf_len - read_pos + write_pos - } - } - - /// Check if the buffer has overrun (data was lost). - /// - /// This happens when DMA writes faster than the application reads. - pub fn is_overrun(&self) -> bool { - // In a true overrun, the DMA would have wrapped around and caught up - // to our read position. We can detect this by checking if available() - // equals the full buffer size (minus 1 to distinguish from empty). - self.available() >= self.buf_len - 1 - } - - /// Read data from the ring buffer into the provided slice. - /// - /// Returns the number of elements read, which may be less than - /// `dst.len()` if not enough data is available. - /// - /// This method does not block; use `read_async()` for async waiting. - pub fn read_immediate(&self, dst: &mut [W]) -> usize { - let write_pos = self.dma_write_pos(); - let read_pos = self.read_pos.load(Ordering::Acquire); - - // Calculate available bytes - let available = if write_pos >= read_pos { - write_pos - read_pos - } else { - self.buf_len - read_pos + write_pos - }; - - let to_read = dst.len().min(available); - if to_read == 0 { - return 0; - } - - // Safety: We only read from portions of the buffer that DMA has - // already written to (between read_pos and write_pos). - let buf = unsafe { self.buf_slice() }; - - // Read data, handling wrap-around - let first_chunk = (self.buf_len - read_pos).min(to_read); - dst[..first_chunk].copy_from_slice(&buf[read_pos..read_pos + first_chunk]); - - if to_read > first_chunk { - let second_chunk = to_read - first_chunk; - dst[first_chunk..to_read].copy_from_slice(&buf[..second_chunk]); - } - - // Update read position - let new_read_pos = (read_pos + to_read) % self.buf_len; - self.read_pos.store(new_read_pos, Ordering::Release); - - to_read - } - - /// Read data from the ring buffer asynchronously. - /// - /// This waits until at least one byte is available, then reads as much - /// as possible into the destination buffer. - /// - /// Returns the number of elements read. - pub async fn read(&self, dst: &mut [W]) -> Result { - use core::future::poll_fn; - - if dst.is_empty() { - return Ok(0); - } - - poll_fn(|cx| { - // Check for overrun - if self.is_overrun() { - return Poll::Ready(Err(Error::Overrun)); - } - - // Try to read immediately - let n = self.read_immediate(dst); - if n > 0 { - return Poll::Ready(Ok(n)); - } - - // Register wakers for both half and complete interrupts - let state = &STATES[self.channel.index()]; - state.waker.register(cx.waker()); - state.half_waker.register(cx.waker()); - - // Check again after registering waker (avoid race) - let n = self.read_immediate(dst); - if n > 0 { - return Poll::Ready(Ok(n)); - } - - Poll::Pending - }) - .await - } - - /// Clear the ring buffer, discarding all unread data. - pub fn clear(&self) { - let write_pos = self.dma_write_pos(); - self.read_pos.store(write_pos, Ordering::Release); - } - - /// Stop the DMA transfer and consume the ring buffer. - /// - /// Returns any remaining unread data count. - pub fn stop(self) -> usize { - let available = self.available(); - - // Disable the channel - let t = self.channel.tcd(); - t.ch_csr().modify(|_, w| w.erq().disable()); - - // Clear flags - t.ch_int().write(|w| w.int().clear_bit_by_one()); - t.ch_csr().modify(|_, w| w.done().clear_bit_by_one()); - - fence(Ordering::SeqCst); - - available - } -} - -impl DmaChannel { - /// Set up a circular DMA transfer for continuous peripheral-to-memory reception. - /// - /// This configures the DMA channel for circular operation with both half-transfer - /// and complete-transfer interrupts enabled. The transfer runs continuously until - /// stopped via [`RingBuffer::stop()`]. - /// - /// # Arguments - /// - /// * `peri_addr` - Peripheral register address to read from - /// * `buf` - Destination buffer (should be power-of-2 size for best efficiency) - /// - /// # Returns - /// - /// A [`RingBuffer`] that can be used to read received data. - /// - /// # Safety - /// - /// - The buffer must remain valid for the lifetime of the returned RingBuffer. - /// - The peripheral address must be valid for reads. - /// - The peripheral's DMA request must be configured to trigger this channel. - pub unsafe fn setup_circular_read<'a, W: Word>(&self, peri_addr: *const W, buf: &'a mut [W]) -> RingBuffer<'a, W> { - assert!(!buf.is_empty()); - assert!(buf.len() <= 0x7fff); - // For circular mode, buffer size should ideally be power of 2 - // but we don't enforce it - - let size = W::size(); - let byte_size = size.bytes(); - - let t = self.tcd(); - - // Reset channel state - t.ch_csr().write(|w| { - w.erq() - .disable() - .earq() - .disable() - .eei() - .no_error() - .ebw() - .disable() - .done() - .clear_bit_by_one() - }); - t.ch_es().write(|w| w.bits(0)); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Source: peripheral register, fixed - t.tcd_saddr().write(|w| w.saddr().bits(peri_addr as u32)); - t.tcd_soff().write(|w| w.soff().bits(0)); // No increment - - // Destination: memory buffer, incrementing - t.tcd_daddr().write(|w| w.daddr().bits(buf.as_mut_ptr() as u32)); - t.tcd_doff().write(|w| w.doff().bits(byte_size as u16)); - - // Transfer attributes - let hw_size = size.to_hw_size(); - t.tcd_attr().write(|w| { - w.ssize() - .bits(hw_size) - .dsize() - .bits(hw_size) - .smod() - .disable() - .dmod() - .bits(0) - }); - - // Minor loop: transfer one word per request - t.tcd_nbytes_mloffno().write(|w| { - w.nbytes() - .bits(byte_size as u32) - .dmloe() - .offset_not_applied() - .smloe() - .offset_not_applied() - }); - - // Major loop count = buffer size - let count = buf.len() as u16; - t.tcd_citer_elinkno().write(|w| w.citer().bits(count).elink().disable()); - t.tcd_biter_elinkno().write(|w| w.biter().bits(count).elink().disable()); - - // After major loop: reset destination to buffer start (circular) - let buf_bytes = (buf.len() * byte_size) as i32; - t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); // Source doesn't change - t.tcd_dlast_sga().write(|w| w.dlast_sga().bits((-buf_bytes) as u32)); - - // Control/status: enable both half and complete interrupts, NO DREQ (continuous) - t.tcd_csr().write(|w| { - w.intmajor() - .enable() - .inthalf() - .enable() - .dreq() - .channel_not_affected() // Don't clear ERQ on complete (circular) - .esg() - .normal_format() - .majorelink() - .disable() - .eeop() - .disable() - .esda() - .disable() - .bwc() - .no_stall() - }); - - cortex_m::asm::dsb(); - - // Enable the channel request - t.ch_csr().modify(|_, w| w.erq().enable()); - - // Enable NVIC interrupt for this channel so async wakeups work - self.enable_interrupt(); - - RingBuffer::new(self.as_any(), buf) - } -} - -// ============================================================================ -// Scatter-Gather Builder -// ============================================================================ - -/// Maximum number of TCDs in a scatter-gather chain. -pub const MAX_SCATTER_GATHER_TCDS: usize = 16; - -/// A builder for constructing scatter-gather DMA transfer chains. -/// -/// This provides a type-safe way to build TCD chains for scatter-gather -/// transfers without manual TCD manipulation. -/// -/// # Example -/// -/// ```no_run -/// use embassy_mcxa::dma::{DmaChannel, ScatterGatherBuilder}; -/// -/// let mut builder = ScatterGatherBuilder::::new(); -/// -/// // Add transfer segments -/// builder.add_transfer(&src1, &mut dst1); -/// builder.add_transfer(&src2, &mut dst2); -/// builder.add_transfer(&src3, &mut dst3); -/// -/// // Build and execute -/// let transfer = unsafe { builder.build(&dma_ch).unwrap() }; -/// transfer.await; -/// ``` -pub struct ScatterGatherBuilder { - /// TCD pool (must be 32-byte aligned) - tcds: [Tcd; MAX_SCATTER_GATHER_TCDS], - /// Number of TCDs configured - count: usize, - /// Phantom marker for word type - _phantom: core::marker::PhantomData, -} - -impl ScatterGatherBuilder { - /// Create a new scatter-gather builder. - pub fn new() -> Self { - Self { - tcds: [Tcd::default(); MAX_SCATTER_GATHER_TCDS], - count: 0, - _phantom: core::marker::PhantomData, - } - } - - /// Add a memory-to-memory transfer segment to the chain. - /// - /// # Arguments - /// - /// * `src` - Source buffer for this segment - /// * `dst` - Destination buffer for this segment - /// - /// # Panics - /// - /// Panics if the maximum number of segments (16) is exceeded. - pub fn add_transfer(&mut self, src: &[W], dst: &mut [W]) -> &mut Self { - assert!(self.count < MAX_SCATTER_GATHER_TCDS, "Too many scatter-gather segments"); - assert!(!src.is_empty()); - assert!(dst.len() >= src.len()); - - let size = W::size(); - let byte_size = size.bytes(); - let hw_size = size.to_hw_size(); - let nbytes = (src.len() * byte_size) as u32; - - // Build the TCD for this segment - self.tcds[self.count] = Tcd { - saddr: src.as_ptr() as u32, - soff: byte_size as i16, - attr: ((hw_size as u16) << 8) | (hw_size as u16), // SSIZE | DSIZE - nbytes, - slast: 0, - daddr: dst.as_mut_ptr() as u32, - doff: byte_size as i16, - citer: 1, - dlast_sga: 0, // Will be filled in by build() - csr: 0x0002, // INTMAJOR only (ESG will be set for non-last TCDs) - biter: 1, - }; - - self.count += 1; - self - } - - /// Get the number of transfer segments added. - pub fn segment_count(&self) -> usize { - self.count - } - - /// Build the scatter-gather chain and start the transfer. - /// - /// # Arguments - /// - /// * `channel` - The DMA channel to use for the transfer - /// - /// # Returns - /// - /// A `Transfer` future that completes when the entire chain has executed. - /// - /// # Safety - /// - /// All source and destination buffers passed to `add_transfer()` must - /// remain valid for the duration of the transfer. - pub unsafe fn build(&mut self, channel: &DmaChannel) -> Result, Error> { - if self.count == 0 { - return Err(Error::Configuration); - } - - // Link TCDs together - // - // CSR bit definitions: - // - START = bit 0 = 0x0001 (triggers transfer when set) - // - INTMAJOR = bit 1 = 0x0002 (interrupt on major loop complete) - // - ESG = bit 4 = 0x0010 (enable scatter-gather, loads next TCD on complete) - // - // When hardware loads a TCD via scatter-gather (ESG), it copies the TCD's - // CSR directly into the hardware register. If START is not set in that CSR, - // the hardware will NOT auto-execute the loaded TCD. - // - // Strategy: - // - First TCD: ESG | INTMAJOR (no START - we add it manually after loading) - // - Middle TCDs: ESG | INTMAJOR | START (auto-execute when loaded via S/G) - // - Last TCD: INTMAJOR | START (auto-execute, no further linking) - for i in 0..self.count { - let is_first = i == 0; - let is_last = i == self.count - 1; - - if is_first { - if is_last { - // Only one TCD - no ESG, no START (we add START manually) - self.tcds[i].dlast_sga = 0; - self.tcds[i].csr = 0x0002; // INTMAJOR only - } else { - // First of multiple - ESG to link, no START (we add START manually) - self.tcds[i].dlast_sga = &self.tcds[i + 1] as *const Tcd as i32; - self.tcds[i].csr = 0x0012; // ESG | INTMAJOR - } - } else if is_last { - // Last TCD (not first) - no ESG, but START so it auto-executes - self.tcds[i].dlast_sga = 0; - self.tcds[i].csr = 0x0003; // INTMAJOR | START - } else { - // Middle TCD - ESG to link, and START so it auto-executes - self.tcds[i].dlast_sga = &self.tcds[i + 1] as *const Tcd as i32; - self.tcds[i].csr = 0x0013; // ESG | INTMAJOR | START - } - } - - let t = channel.tcd(); - - // Reset channel state - clear DONE, disable requests, clear errors - // This ensures the channel is in a clean state before loading the TCD - t.ch_csr().write(|w| { - w.erq() - .disable() - .earq() - .disable() - .eei() - .no_error() - .done() - .clear_bit_by_one() - }); - t.ch_es().write(|w| w.err().clear_bit_by_one()); - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // Memory barrier to ensure channel state is reset before loading TCD - cortex_m::asm::dsb(); - - // Load first TCD into hardware - channel.load_tcd(&self.tcds[0]); - - // Memory barrier before setting START - cortex_m::asm::dsb(); - - // Start the transfer - t.tcd_csr().modify(|_, w| w.start().channel_started()); - - Ok(Transfer::new(channel.as_any())) - } - - /// Reset the builder for reuse. - pub fn clear(&mut self) { - self.count = 0; - } -} - -impl Default for ScatterGatherBuilder { - fn default() -> Self { - Self::new() - } -} - -/// A completed scatter-gather transfer result. -/// -/// This type is returned after a scatter-gather transfer completes, -/// providing access to any error information. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ScatterGatherResult { - /// Number of segments successfully transferred - pub segments_completed: usize, - /// Error if any occurred - pub error: Option, -} - -// ============================================================================ -// Interrupt Handler -// ============================================================================ - -/// Interrupt handler helper. -/// -/// Call this from your interrupt handler to clear the interrupt flag and wake the waker. -/// This handles both half-transfer and complete-transfer interrupts. -/// -/// # Safety -/// Must be called from the correct DMA channel interrupt context. -pub unsafe fn on_interrupt(ch_index: usize) { - let p = pac::Peripherals::steal(); - let edma = &p.edma_0_tcd0; - let t = edma.tcd(ch_index); - - // Read TCD CSR to determine interrupt source - let csr = t.tcd_csr().read(); - - // Check if this is a half-transfer interrupt - // INTHALF is set and we're at or past the half-way point - if csr.inthalf().bit_is_set() { - let biter = t.tcd_biter_elinkno().read().biter().bits(); - let citer = t.tcd_citer_elinkno().read().citer().bits(); - let half_point = biter / 2; - - if citer <= half_point && citer > 0 { - // Half-transfer interrupt - wake half_waker - half_waker(ch_index).wake(); - } - } - - // Clear INT flag - t.ch_int().write(|w| w.int().clear_bit_by_one()); - - // If DONE is set, this is a complete-transfer interrupt - // Only wake the full-transfer waker when the transfer is actually complete - if t.ch_csr().read().done().bit_is_set() { - waker(ch_index).wake(); - } -} - -// ============================================================================ -// Type-level Interrupt Handlers for bind_interrupts! macro -// ============================================================================ - -/// Macro to generate DMA channel interrupt handlers. -/// -/// This generates handler structs that implement the `Handler` trait for use -/// with the `bind_interrupts!` macro. -macro_rules! impl_dma_interrupt_handler { - ($name:ident, $irq:ident, $ch:expr) => { - /// Interrupt handler for DMA channel. - /// - /// Use this with the `bind_interrupts!` macro: - /// ```ignore - /// bind_interrupts!(struct Irqs { - #[doc = concat!(" ", stringify!($irq), " => dma::", stringify!($name), ";")] - /// }); - /// ``` - pub struct $name; - - impl crate::interrupt::typelevel::Handler for $name { - unsafe fn on_interrupt() { - on_interrupt($ch); - } - } - }; -} - -impl_dma_interrupt_handler!(DmaCh0InterruptHandler, DMA_CH0, 0); -impl_dma_interrupt_handler!(DmaCh1InterruptHandler, DMA_CH1, 1); -impl_dma_interrupt_handler!(DmaCh2InterruptHandler, DMA_CH2, 2); -impl_dma_interrupt_handler!(DmaCh3InterruptHandler, DMA_CH3, 3); -impl_dma_interrupt_handler!(DmaCh4InterruptHandler, DMA_CH4, 4); -impl_dma_interrupt_handler!(DmaCh5InterruptHandler, DMA_CH5, 5); -impl_dma_interrupt_handler!(DmaCh6InterruptHandler, DMA_CH6, 6); -impl_dma_interrupt_handler!(DmaCh7InterruptHandler, DMA_CH7, 7); diff --git a/src/gpio.rs b/src/gpio.rs deleted file mode 100644 index 65f8df985..000000000 --- a/src/gpio.rs +++ /dev/null @@ -1,1062 +0,0 @@ -//! GPIO driver built around a type-erased `Flex` pin, similar to other Embassy HALs. -//! The exported `Output`/`Input` drivers own a `Flex` so they no longer depend on the -//! concrete pin type. - -use core::convert::Infallible; -use core::future::Future; -use core::marker::PhantomData; -use core::pin::pin; - -use embassy_hal_internal::{Peri, PeripheralType}; -use maitake_sync::WaitMap; -use paste::paste; - -use crate::pac::interrupt; -use crate::pac::port0::pcr0::{Dse, Inv, Mux, Pe, Ps, Sre}; - -struct BitIter(u32); - -impl Iterator for BitIter { - type Item = usize; - - fn next(&mut self) -> Option { - match self.0.trailing_zeros() { - 32 => None, - b => { - self.0 &= !(1 << b); - Some(b as usize) - } - } - } -} - -const PORT_COUNT: usize = 5; - -static PORT_WAIT_MAPS: [WaitMap; PORT_COUNT] = [ - WaitMap::new(), - WaitMap::new(), - WaitMap::new(), - WaitMap::new(), - WaitMap::new(), -]; - -fn irq_handler(port_index: usize, gpio_base: *const crate::pac::gpio0::RegisterBlock) { - let gpio = unsafe { &*gpio_base }; - let isfr = gpio.isfr0().read().bits(); - - for pin in BitIter(isfr) { - // Clear all pending interrupts - gpio.isfr0().write(|w| unsafe { w.bits(1 << pin) }); - gpio.icr(pin).modify(|_, w| w.irqc().irqc0()); // Disable interrupt - - // Wake the corresponding port waker - if let Some(w) = PORT_WAIT_MAPS.get(port_index) { - w.wake(&pin, ()); - } - } -} - -#[interrupt] -fn GPIO0() { - irq_handler(0, crate::pac::Gpio0::ptr()); -} - -#[interrupt] -fn GPIO1() { - irq_handler(1, crate::pac::Gpio1::ptr()); -} - -#[interrupt] -fn GPIO2() { - irq_handler(2, crate::pac::Gpio2::ptr()); -} - -#[interrupt] -fn GPIO3() { - irq_handler(3, crate::pac::Gpio3::ptr()); -} - -#[interrupt] -fn GPIO4() { - irq_handler(4, crate::pac::Gpio4::ptr()); -} - -pub(crate) unsafe fn init() { - use embassy_hal_internal::interrupt::InterruptExt; - - crate::pac::interrupt::GPIO0.enable(); - crate::pac::interrupt::GPIO1.enable(); - crate::pac::interrupt::GPIO2.enable(); - crate::pac::interrupt::GPIO3.enable(); - crate::pac::interrupt::GPIO4.enable(); - - cortex_m::interrupt::enable(); -} - -/// Logical level for GPIO pins. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Level { - Low, - High, -} - -impl From for Level { - fn from(val: bool) -> Self { - match val { - true => Self::High, - false => Self::Low, - } - } -} - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum Pull { - Disabled, - Up, - Down, -} - -impl From for (Pe, Ps) { - fn from(pull: Pull) -> Self { - match pull { - Pull::Disabled => (Pe::Pe0, Ps::Ps0), - Pull::Up => (Pe::Pe1, Ps::Ps1), - Pull::Down => (Pe::Pe1, Ps::Ps0), - } - } -} - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum SlewRate { - Fast, - Slow, -} - -impl From for Sre { - fn from(slew_rate: SlewRate) -> Self { - match slew_rate { - SlewRate::Fast => Sre::Sre0, - SlewRate::Slow => Sre::Sre1, - } - } -} - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum DriveStrength { - Normal, - Double, -} - -impl From for Dse { - fn from(strength: DriveStrength) -> Self { - match strength { - DriveStrength::Normal => Dse::Dse0, - DriveStrength::Double => Dse::Dse1, - } - } -} - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum Inverter { - Disabled, - Enabled, -} - -impl From for Inv { - fn from(strength: Inverter) -> Self { - match strength { - Inverter::Disabled => Inv::Inv0, - Inverter::Enabled => Inv::Inv1, - } - } -} - -pub type Gpio = crate::peripherals::GPIO0; - -/// Type-erased representation of a GPIO pin. -pub struct AnyPin { - port: usize, - pin: usize, - gpio: &'static crate::pac::gpio0::RegisterBlock, - port_reg: &'static crate::pac::port0::RegisterBlock, - pcr_reg: &'static crate::pac::port0::Pcr0, -} - -impl AnyPin { - /// Create an `AnyPin` from raw components. - fn new( - port: usize, - pin: usize, - gpio: &'static crate::pac::gpio0::RegisterBlock, - port_reg: &'static crate::pac::port0::RegisterBlock, - pcr_reg: &'static crate::pac::port0::Pcr0, - ) -> Self { - Self { - port, - pin, - gpio, - port_reg, - pcr_reg, - } - } - - #[inline(always)] - fn mask(&self) -> u32 { - 1 << self.pin - } - - #[inline(always)] - fn gpio(&self) -> &'static crate::pac::gpio0::RegisterBlock { - self.gpio - } - - #[inline(always)] - pub fn port_index(&self) -> usize { - self.port - } - - #[inline(always)] - pub fn pin_index(&self) -> usize { - self.pin - } - - #[inline(always)] - fn port_reg(&self) -> &'static crate::pac::port0::RegisterBlock { - self.port_reg - } - - #[inline(always)] - fn pcr_reg(&self) -> &'static crate::pac::port0::Pcr0 { - self.pcr_reg - } -} - -embassy_hal_internal::impl_peripheral!(AnyPin); - -pub(crate) trait SealedPin { - fn pin_port(&self) -> usize; - - fn port(&self) -> usize { - self.pin_port() / 32 - } - - fn pin(&self) -> usize { - self.pin_port() % 32 - } - - fn gpio(&self) -> &'static crate::pac::gpio0::RegisterBlock; - - fn port_reg(&self) -> &'static crate::pac::port0::RegisterBlock; - - fn pcr_reg(&self) -> &'static crate::pac::port0::Pcr0; - - fn set_function(&self, function: Mux); - - fn set_pull(&self, pull: Pull); - - fn set_drive_strength(&self, strength: Dse); - - fn set_slew_rate(&self, slew_rate: Sre); - - fn set_enable_input_buffer(&self); -} - -/// GPIO pin trait. -#[allow(private_bounds)] -pub trait GpioPin: SealedPin + Sized + PeripheralType + Into + 'static { - /// Type-erase the pin. - fn degrade(self) -> AnyPin { - // SAFETY: This is only called within the GpioPin trait, which is only - // implemented within this module on valid pin peripherals and thus - // has been verified to be correct. - AnyPin::new(self.port(), self.pin(), self.gpio(), self.port_reg(), self.pcr_reg()) - } -} - -impl SealedPin for AnyPin { - fn pin_port(&self) -> usize { - self.port * 32 + self.pin - } - - fn gpio(&self) -> &'static crate::pac::gpio0::RegisterBlock { - self.gpio() - } - - fn port_reg(&self) -> &'static crate::pac::port0::RegisterBlock { - self.port_reg() - } - - fn pcr_reg(&self) -> &'static crate::pac::port0::Pcr0 { - self.pcr_reg() - } - - fn set_function(&self, function: Mux) { - self.pcr_reg().modify(|_, w| w.mux().variant(function)); - } - - fn set_pull(&self, pull: Pull) { - let (pull_enable, pull_select) = pull.into(); - self.pcr_reg().modify(|_, w| { - w.pe().variant(pull_enable); - w.ps().variant(pull_select) - }); - } - - fn set_drive_strength(&self, strength: Dse) { - self.pcr_reg().modify(|_, w| w.dse().variant(strength)); - } - - fn set_slew_rate(&self, slew_rate: Sre) { - self.pcr_reg().modify(|_, w| w.sre().variant(slew_rate)); - } - - fn set_enable_input_buffer(&self) { - self.pcr_reg().modify(|_, w| w.ibe().ibe1()); - } -} - -impl GpioPin for AnyPin {} - -macro_rules! impl_pin { - ($peri:ident, $port:expr, $pin:expr, $block:ident) => { - paste! { - impl SealedPin for crate::peripherals::$peri { - fn pin_port(&self) -> usize { - $port * 32 + $pin - } - - fn gpio(&self) -> &'static crate::pac::gpio0::RegisterBlock { - unsafe { &*crate::pac::$block::ptr() } - } - - fn port_reg(&self) -> &'static crate::pac::port0::RegisterBlock { - unsafe { &*crate::pac::[]::ptr() } - } - - fn pcr_reg(&self) -> &'static crate::pac::port0::Pcr0 { - self.port_reg().[]() - } - - fn set_function(&self, function: Mux) { - unsafe { - let port_reg = &*crate::pac::[]::ptr(); - port_reg.[]().modify(|_, w| { - w.mux().variant(function) - }); - } - } - - fn set_pull(&self, pull: Pull) { - let port_reg = unsafe {&*crate::pac::[]::ptr()}; - let (pull_enable, pull_select) = pull.into(); - port_reg.[]().modify(|_, w| { - w.pe().variant(pull_enable); - w.ps().variant(pull_select) - }); - } - - fn set_drive_strength(&self, strength: Dse) { - let port_reg = unsafe {&*crate::pac::[]::ptr()}; - port_reg.[]().modify(|_, w| w.dse().variant(strength)); - } - - fn set_slew_rate(&self, slew_rate: Sre) { - let port_reg = unsafe {&*crate::pac::[]::ptr()}; - port_reg.[]().modify(|_, w| w.sre().variant(slew_rate)); - } - - fn set_enable_input_buffer(&self) { - let port_reg = unsafe {&*crate::pac::[]::ptr()}; - port_reg.[]().modify(|_, w| w.ibe().ibe1()); - } - } - - impl GpioPin for crate::peripherals::$peri {} - - impl From for AnyPin { - fn from(value: crate::peripherals::$peri) -> Self { - value.degrade() - } - } - - impl crate::peripherals::$peri { - /// Convenience helper to obtain a type-erased handle to this pin. - pub fn degrade(&self) -> AnyPin { - AnyPin::new(self.port(), self.pin(), self.gpio(), self.port_reg(), self.pcr_reg()) - } - } - } - }; -} - -impl_pin!(P0_0, 0, 0, Gpio0); -impl_pin!(P0_1, 0, 1, Gpio0); -impl_pin!(P0_2, 0, 2, Gpio0); -impl_pin!(P0_3, 0, 3, Gpio0); -impl_pin!(P0_4, 0, 4, Gpio0); -impl_pin!(P0_5, 0, 5, Gpio0); -impl_pin!(P0_6, 0, 6, Gpio0); -impl_pin!(P0_7, 0, 7, Gpio0); -impl_pin!(P0_8, 0, 8, Gpio0); -impl_pin!(P0_9, 0, 9, Gpio0); -impl_pin!(P0_10, 0, 10, Gpio0); -impl_pin!(P0_11, 0, 11, Gpio0); -impl_pin!(P0_12, 0, 12, Gpio0); -impl_pin!(P0_13, 0, 13, Gpio0); -impl_pin!(P0_14, 0, 14, Gpio0); -impl_pin!(P0_15, 0, 15, Gpio0); -impl_pin!(P0_16, 0, 16, Gpio0); -impl_pin!(P0_17, 0, 17, Gpio0); -impl_pin!(P0_18, 0, 18, Gpio0); -impl_pin!(P0_19, 0, 19, Gpio0); -impl_pin!(P0_20, 0, 20, Gpio0); -impl_pin!(P0_21, 0, 21, Gpio0); -impl_pin!(P0_22, 0, 22, Gpio0); -impl_pin!(P0_23, 0, 23, Gpio0); -impl_pin!(P0_24, 0, 24, Gpio0); -impl_pin!(P0_25, 0, 25, Gpio0); -impl_pin!(P0_26, 0, 26, Gpio0); -impl_pin!(P0_27, 0, 27, Gpio0); -impl_pin!(P0_28, 0, 28, Gpio0); -impl_pin!(P0_29, 0, 29, Gpio0); -impl_pin!(P0_30, 0, 30, Gpio0); -impl_pin!(P0_31, 0, 31, Gpio0); - -impl_pin!(P1_0, 1, 0, Gpio1); -impl_pin!(P1_1, 1, 1, Gpio1); -impl_pin!(P1_2, 1, 2, Gpio1); -impl_pin!(P1_3, 1, 3, Gpio1); -impl_pin!(P1_4, 1, 4, Gpio1); -impl_pin!(P1_5, 1, 5, Gpio1); -impl_pin!(P1_6, 1, 6, Gpio1); -impl_pin!(P1_7, 1, 7, Gpio1); -impl_pin!(P1_8, 1, 8, Gpio1); -impl_pin!(P1_9, 1, 9, Gpio1); -impl_pin!(P1_10, 1, 10, Gpio1); -impl_pin!(P1_11, 1, 11, Gpio1); -impl_pin!(P1_12, 1, 12, Gpio1); -impl_pin!(P1_13, 1, 13, Gpio1); -impl_pin!(P1_14, 1, 14, Gpio1); -impl_pin!(P1_15, 1, 15, Gpio1); -impl_pin!(P1_16, 1, 16, Gpio1); -impl_pin!(P1_17, 1, 17, Gpio1); -impl_pin!(P1_18, 1, 18, Gpio1); -impl_pin!(P1_19, 1, 19, Gpio1); -impl_pin!(P1_20, 1, 20, Gpio1); -impl_pin!(P1_21, 1, 21, Gpio1); -impl_pin!(P1_22, 1, 22, Gpio1); -impl_pin!(P1_23, 1, 23, Gpio1); -impl_pin!(P1_24, 1, 24, Gpio1); -impl_pin!(P1_25, 1, 25, Gpio1); -impl_pin!(P1_26, 1, 26, Gpio1); -impl_pin!(P1_27, 1, 27, Gpio1); -impl_pin!(P1_28, 1, 28, Gpio1); -impl_pin!(P1_29, 1, 29, Gpio1); -impl_pin!(P1_30, 1, 30, Gpio1); -impl_pin!(P1_31, 1, 31, Gpio1); - -impl_pin!(P2_0, 2, 0, Gpio2); -impl_pin!(P2_1, 2, 1, Gpio2); -impl_pin!(P2_2, 2, 2, Gpio2); -impl_pin!(P2_3, 2, 3, Gpio2); -impl_pin!(P2_4, 2, 4, Gpio2); -impl_pin!(P2_5, 2, 5, Gpio2); -impl_pin!(P2_6, 2, 6, Gpio2); -impl_pin!(P2_7, 2, 7, Gpio2); -impl_pin!(P2_8, 2, 8, Gpio2); -impl_pin!(P2_9, 2, 9, Gpio2); -impl_pin!(P2_10, 2, 10, Gpio2); -impl_pin!(P2_11, 2, 11, Gpio2); -impl_pin!(P2_12, 2, 12, Gpio2); -impl_pin!(P2_13, 2, 13, Gpio2); -impl_pin!(P2_14, 2, 14, Gpio2); -impl_pin!(P2_15, 2, 15, Gpio2); -impl_pin!(P2_16, 2, 16, Gpio2); -impl_pin!(P2_17, 2, 17, Gpio2); -impl_pin!(P2_18, 2, 18, Gpio2); -impl_pin!(P2_19, 2, 19, Gpio2); -impl_pin!(P2_20, 2, 20, Gpio2); -impl_pin!(P2_21, 2, 21, Gpio2); -impl_pin!(P2_22, 2, 22, Gpio2); -impl_pin!(P2_23, 2, 23, Gpio2); -impl_pin!(P2_24, 2, 24, Gpio2); -impl_pin!(P2_25, 2, 25, Gpio2); -impl_pin!(P2_26, 2, 26, Gpio2); -impl_pin!(P2_27, 2, 27, Gpio2); -impl_pin!(P2_28, 2, 28, Gpio2); -impl_pin!(P2_29, 2, 29, Gpio2); -impl_pin!(P2_30, 2, 30, Gpio2); -impl_pin!(P2_31, 2, 31, Gpio2); - -impl_pin!(P3_0, 3, 0, Gpio3); -impl_pin!(P3_1, 3, 1, Gpio3); -impl_pin!(P3_2, 3, 2, Gpio3); -impl_pin!(P3_3, 3, 3, Gpio3); -impl_pin!(P3_4, 3, 4, Gpio3); -impl_pin!(P3_5, 3, 5, Gpio3); -impl_pin!(P3_6, 3, 6, Gpio3); -impl_pin!(P3_7, 3, 7, Gpio3); -impl_pin!(P3_8, 3, 8, Gpio3); -impl_pin!(P3_9, 3, 9, Gpio3); -impl_pin!(P3_10, 3, 10, Gpio3); -impl_pin!(P3_11, 3, 11, Gpio3); -impl_pin!(P3_12, 3, 12, Gpio3); -impl_pin!(P3_13, 3, 13, Gpio3); -impl_pin!(P3_14, 3, 14, Gpio3); -impl_pin!(P3_15, 3, 15, Gpio3); -impl_pin!(P3_16, 3, 16, Gpio3); -impl_pin!(P3_17, 3, 17, Gpio3); -impl_pin!(P3_18, 3, 18, Gpio3); -impl_pin!(P3_19, 3, 19, Gpio3); -impl_pin!(P3_20, 3, 20, Gpio3); -impl_pin!(P3_21, 3, 21, Gpio3); -impl_pin!(P3_22, 3, 22, Gpio3); -impl_pin!(P3_23, 3, 23, Gpio3); -impl_pin!(P3_24, 3, 24, Gpio3); -impl_pin!(P3_25, 3, 25, Gpio3); -impl_pin!(P3_26, 3, 26, Gpio3); -impl_pin!(P3_27, 3, 27, Gpio3); -impl_pin!(P3_28, 3, 28, Gpio3); -impl_pin!(P3_29, 3, 29, Gpio3); -impl_pin!(P3_30, 3, 30, Gpio3); -impl_pin!(P3_31, 3, 31, Gpio3); - -impl_pin!(P4_0, 4, 0, Gpio4); -impl_pin!(P4_1, 4, 1, Gpio4); -impl_pin!(P4_2, 4, 2, Gpio4); -impl_pin!(P4_3, 4, 3, Gpio4); -impl_pin!(P4_4, 4, 4, Gpio4); -impl_pin!(P4_5, 4, 5, Gpio4); -impl_pin!(P4_6, 4, 6, Gpio4); -impl_pin!(P4_7, 4, 7, Gpio4); -impl_pin!(P4_8, 4, 8, Gpio4); -impl_pin!(P4_9, 4, 9, Gpio4); -impl_pin!(P4_10, 4, 10, Gpio4); -impl_pin!(P4_11, 4, 11, Gpio4); -impl_pin!(P4_12, 4, 12, Gpio4); -impl_pin!(P4_13, 4, 13, Gpio4); -impl_pin!(P4_14, 4, 14, Gpio4); -impl_pin!(P4_15, 4, 15, Gpio4); -impl_pin!(P4_16, 4, 16, Gpio4); -impl_pin!(P4_17, 4, 17, Gpio4); -impl_pin!(P4_18, 4, 18, Gpio4); -impl_pin!(P4_19, 4, 19, Gpio4); -impl_pin!(P4_20, 4, 20, Gpio4); -impl_pin!(P4_21, 4, 21, Gpio4); -impl_pin!(P4_22, 4, 22, Gpio4); -impl_pin!(P4_23, 4, 23, Gpio4); -impl_pin!(P4_24, 4, 24, Gpio4); -impl_pin!(P4_25, 4, 25, Gpio4); -impl_pin!(P4_26, 4, 26, Gpio4); -impl_pin!(P4_27, 4, 27, Gpio4); -impl_pin!(P4_28, 4, 28, Gpio4); -impl_pin!(P4_29, 4, 29, Gpio4); -impl_pin!(P4_30, 4, 30, Gpio4); -impl_pin!(P4_31, 4, 31, Gpio4); - -/// A flexible pin that can be configured as input or output. -pub struct Flex<'d> { - pin: Peri<'d, AnyPin>, - _marker: PhantomData<&'d mut ()>, -} - -impl<'d> Flex<'d> { - /// Wrap the pin in a `Flex`. - /// - /// The pin remains unmodified. The initial output level is unspecified, but - /// can be changed before the pin is put into output mode. - pub fn new(pin: Peri<'d, impl GpioPin>) -> Self { - pin.set_function(Mux::Mux0); - Self { - pin: pin.into(), - _marker: PhantomData, - } - } - - #[inline] - fn gpio(&self) -> &'static crate::pac::gpio0::RegisterBlock { - self.pin.gpio() - } - - #[inline] - fn mask(&self) -> u32 { - self.pin.mask() - } - - /// Put the pin into input mode. - pub fn set_as_input(&mut self) { - let mask = self.mask(); - let gpio = self.gpio(); - - self.set_enable_input_buffer(); - - gpio.pddr().modify(|r, w| unsafe { w.bits(r.bits() & !mask) }); - } - - /// Put the pin into output mode. - pub fn set_as_output(&mut self) { - let mask = self.mask(); - let gpio = self.gpio(); - - self.set_pull(Pull::Disabled); - - gpio.pddr().modify(|r, w| unsafe { w.bits(r.bits() | mask) }); - } - - /// Set output level to High. - #[inline] - pub fn set_high(&mut self) { - self.gpio().psor().write(|w| unsafe { w.bits(self.mask()) }); - } - - /// Set output level to Low. - #[inline] - pub fn set_low(&mut self) { - self.gpio().pcor().write(|w| unsafe { w.bits(self.mask()) }); - } - - /// Set output level to the given `Level`. - #[inline] - pub fn set_level(&mut self, level: Level) { - match level { - Level::High => self.set_high(), - Level::Low => self.set_low(), - } - } - - /// Toggle output level. - #[inline] - pub fn toggle(&mut self) { - self.gpio().ptor().write(|w| unsafe { w.bits(self.mask()) }); - } - - /// Get whether the pin input level is high. - #[inline] - pub fn is_high(&self) -> bool { - (self.gpio().pdir().read().bits() & self.mask()) != 0 - } - - /// Get whether the pin input level is low. - #[inline] - pub fn is_low(&self) -> bool { - !self.is_high() - } - - /// Is the output pin set as high? - #[inline] - pub fn is_set_high(&self) -> bool { - self.is_high() - } - - /// Is the output pin set as low? - #[inline] - pub fn is_set_low(&self) -> bool { - !self.is_set_high() - } - - /// Configure the pin pull up/down level. - pub fn set_pull(&mut self, pull_select: Pull) { - self.pin.set_pull(pull_select); - } - - /// Configure the pin drive strength. - pub fn set_drive_strength(&mut self, strength: DriveStrength) { - self.pin.set_drive_strength(strength.into()); - } - - /// Configure the pin slew rate. - pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { - self.pin.set_slew_rate(slew_rate.into()); - } - - /// Enable input buffer for the pin. - pub fn set_enable_input_buffer(&mut self) { - self.pin.set_enable_input_buffer(); - } - - /// Get pin level. - pub fn get_level(&self) -> Level { - self.is_high().into() - } -} - -/// Async methods -impl<'d> Flex<'d> { - /// Helper function that waits for a given interrupt trigger - async fn wait_for_inner(&mut self, level: crate::pac::gpio0::icr::Irqc) { - // First, ensure that we have a waker that is ready for this port+pin - let w = PORT_WAIT_MAPS[self.pin.port].wait(self.pin.pin); - let mut w = pin!(w); - // Wait for the subscription to occur, which requires polling at least once - // - // This function returns a result, but can only be an Err if: - // - // * We call `.close()` on a WaitMap, which we never do - // * We have a duplicate key, which can't happen because `wait_for_*` methods - // take an &mut ref of their unique port+pin combo - // - // So we wait for it to complete, but ignore the result. - _ = w.as_mut().subscribe().await; - - // Now that our waker is in the map, we can enable the appropriate interrupt - // - // Clear any existing pending interrupt on this pin - self.pin - .gpio() - .isfr0() - .write(|w| unsafe { w.bits(1 << self.pin.pin()) }); - self.pin.gpio().icr(self.pin.pin()).write(|w| w.isf().isf1()); - - // Pin interrupt configuration - self.pin - .gpio() - .icr(self.pin.pin()) - .modify(|_, w| w.irqc().variant(level)); - - // Finally, we can await the matching call to `.wake()` from the interrupt. - // - // Again, technically, this could return a result, but for the same reasons - // as above, this can't be an error in our case, so just wait for it to complete - _ = w.await; - } - - /// Wait until the pin is high. If it is already high, return immediately. - #[inline] - pub fn wait_for_high(&mut self) -> impl Future + use<'_, 'd> { - self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc12) - } - - /// Wait until the pin is low. If it is already low, return immediately. - #[inline] - pub fn wait_for_low(&mut self) -> impl Future + use<'_, 'd> { - self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc8) - } - - /// Wait for the pin to undergo a transition from low to high. - #[inline] - pub fn wait_for_rising_edge(&mut self) -> impl Future + use<'_, 'd> { - self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc9) - } - - /// Wait for the pin to undergo a transition from high to low. - #[inline] - pub fn wait_for_falling_edge(&mut self) -> impl Future + use<'_, 'd> { - self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc10) - } - - /// Wait for the pin to undergo any transition, i.e low to high OR high to low. - #[inline] - pub fn wait_for_any_edge(&mut self) -> impl Future + use<'_, 'd> { - self.wait_for_inner(crate::pac::gpio0::icr::Irqc::Irqc11) - } -} - -/// GPIO output driver that owns a `Flex` pin. -pub struct Output<'d> { - flex: Flex<'d>, -} - -impl<'d> Output<'d> { - /// Create a GPIO output driver for a [GpioPin] with the provided [Level]. - pub fn new(pin: Peri<'d, impl GpioPin>, initial: Level, strength: DriveStrength, slew_rate: SlewRate) -> Self { - let mut flex = Flex::new(pin); - flex.set_level(initial); - flex.set_as_output(); - flex.set_drive_strength(strength); - flex.set_slew_rate(slew_rate); - Self { flex } - } - - /// Set the output as high. - #[inline] - pub fn set_high(&mut self) { - self.flex.set_high(); - } - - /// Set the output as low. - #[inline] - pub fn set_low(&mut self) { - self.flex.set_low(); - } - - /// Set the output level. - #[inline] - pub fn set_level(&mut self, level: Level) { - self.flex.set_level(level); - } - - /// Toggle the output level. - #[inline] - pub fn toggle(&mut self) { - self.flex.toggle(); - } - - /// Is the output pin set as high? - #[inline] - pub fn is_set_high(&self) -> bool { - self.flex.is_high() - } - - /// Is the output pin set as low? - #[inline] - pub fn is_set_low(&self) -> bool { - !self.is_set_high() - } - - /// Expose the inner `Flex` if callers need to reconfigure the pin. - #[inline] - pub fn into_flex(self) -> Flex<'d> { - self.flex - } -} - -/// GPIO input driver that owns a `Flex` pin. -pub struct Input<'d> { - flex: Flex<'d>, -} - -impl<'d> Input<'d> { - /// Create a GPIO input driver for a [GpioPin]. - /// - pub fn new(pin: Peri<'d, impl GpioPin>, pull_select: Pull) -> Self { - let mut flex = Flex::new(pin); - flex.set_as_input(); - flex.set_pull(pull_select); - Self { flex } - } - - /// Get whether the pin input level is high. - #[inline] - pub fn is_high(&self) -> bool { - self.flex.is_high() - } - - /// Get whether the pin input level is low. - #[inline] - pub fn is_low(&self) -> bool { - self.flex.is_low() - } - - /// Expose the inner `Flex` if callers need to reconfigure the pin. - /// - /// Since Drive Strength and Slew Rate are not set when creating the Input - /// pin, they need to be set when converting - #[inline] - pub fn into_flex(mut self, strength: DriveStrength, slew_rate: SlewRate) -> Flex<'d> { - self.flex.set_drive_strength(strength); - self.flex.set_slew_rate(slew_rate); - self.flex - } - - /// Get the pin level. - pub fn get_level(&self) -> Level { - self.flex.get_level() - } -} - -/// Async methods -impl<'d> Input<'d> { - /// Wait until the pin is high. If it is already high, return immediately. - #[inline] - pub fn wait_for_high(&mut self) -> impl Future + use<'_, 'd> { - self.flex.wait_for_high() - } - - /// Wait until the pin is low. If it is already low, return immediately. - #[inline] - pub fn wait_for_low(&mut self) -> impl Future + use<'_, 'd> { - self.flex.wait_for_low() - } - - /// Wait for the pin to undergo a transition from low to high. - #[inline] - pub fn wait_for_rising_edge(&mut self) -> impl Future + use<'_, 'd> { - self.flex.wait_for_rising_edge() - } - - /// Wait for the pin to undergo a transition from high to low. - #[inline] - pub fn wait_for_falling_edge(&mut self) -> impl Future + use<'_, 'd> { - self.flex.wait_for_falling_edge() - } - - /// Wait for the pin to undergo any transition, i.e low to high OR high to low. - #[inline] - pub fn wait_for_any_edge(&mut self) -> impl Future + use<'_, 'd> { - self.flex.wait_for_any_edge() - } -} - -impl embedded_hal_async::digital::Wait for Input<'_> { - 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(()) - } -} - -impl embedded_hal_async::digital::Wait for Flex<'_> { - 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(()) - } -} - -// Both embedded_hal 0.2 and 1.0 must be supported by embassy HALs. -impl embedded_hal_02::digital::v2::InputPin for Flex<'_> { - // GPIO operations on this block cannot fail, therefor we set the error type - // to Infallible to guarantee that we can only produce Ok variants. - type Error = Infallible; - - #[inline] - fn is_high(&self) -> Result { - Ok(self.is_high()) - } - - #[inline] - fn is_low(&self) -> Result { - Ok(self.is_low()) - } -} - -impl embedded_hal_02::digital::v2::InputPin for Input<'_> { - type Error = Infallible; - - #[inline] - fn is_high(&self) -> Result { - Ok(self.is_high()) - } - - #[inline] - fn is_low(&self) -> Result { - Ok(self.is_low()) - } -} - -impl embedded_hal_02::digital::v2::OutputPin for Flex<'_> { - type Error = Infallible; - - #[inline] - fn set_high(&mut self) -> Result<(), Self::Error> { - self.set_high(); - Ok(()) - } - - #[inline] - fn set_low(&mut self) -> Result<(), Self::Error> { - self.set_low(); - Ok(()) - } -} - -impl embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'_> { - #[inline] - fn is_set_high(&self) -> Result { - Ok(self.is_set_high()) - } - - #[inline] - fn is_set_low(&self) -> Result { - Ok(self.is_set_low()) - } -} - -impl embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'_> { - type Error = Infallible; - - #[inline] - fn toggle(&mut self) -> Result<(), Self::Error> { - self.toggle(); - Ok(()) - } -} - -impl embedded_hal_1::digital::ErrorType for Flex<'_> { - type Error = Infallible; -} - -impl embedded_hal_1::digital::ErrorType for Input<'_> { - type Error = Infallible; -} - -impl embedded_hal_1::digital::ErrorType for Output<'_> { - type Error = Infallible; -} - -impl embedded_hal_1::digital::InputPin for Input<'_> { - #[inline] - fn is_high(&mut self) -> Result { - Ok((*self).is_high()) - } - - #[inline] - fn is_low(&mut self) -> Result { - Ok((*self).is_low()) - } -} - -impl embedded_hal_1::digital::OutputPin for Flex<'_> { - #[inline] - fn set_high(&mut self) -> Result<(), Self::Error> { - self.set_high(); - Ok(()) - } - - #[inline] - fn set_low(&mut self) -> Result<(), Self::Error> { - self.set_low(); - Ok(()) - } -} - -impl embedded_hal_1::digital::StatefulOutputPin for Flex<'_> { - #[inline] - fn is_set_high(&mut self) -> Result { - Ok((*self).is_set_high()) - } - - #[inline] - fn is_set_low(&mut self) -> Result { - Ok((*self).is_set_low()) - } -} diff --git a/src/i2c/controller.rs b/src/i2c/controller.rs deleted file mode 100644 index 41bbc821d..000000000 --- a/src/i2c/controller.rs +++ /dev/null @@ -1,455 +0,0 @@ -//! LPI2C controller driver - -use core::marker::PhantomData; - -use embassy_hal_internal::Peri; -use mcxa_pac::lpi2c0::mtdr::Cmd; - -use super::{Blocking, Error, Instance, Mode, Result, SclPin, SdaPin}; -use crate::clocks::periph_helpers::{Div4, Lpi2cClockSel, Lpi2cConfig}; -use crate::clocks::{enable_and_reset, PoweredClock}; -use crate::AnyPin; - -/// Bus speed (nominal SCL, no clock stretching) -#[derive(Clone, Copy, Default, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Speed { - #[default] - /// 100 kbit/sec - Standard, - /// 400 kbit/sec - Fast, - /// 1 Mbit/sec - FastPlus, - /// 3.4 Mbit/sec - UltraFast, -} - -impl From for (u8, u8, u8, u8) { - fn from(value: Speed) -> (u8, u8, u8, u8) { - match value { - Speed::Standard => (0x3d, 0x37, 0x3b, 0x1d), - Speed::Fast => (0x0e, 0x0c, 0x0d, 0x06), - Speed::FastPlus => (0x04, 0x03, 0x03, 0x02), - - // UltraFast is "special". Leaving it unimplemented until - // the driver and the clock API is further stabilized. - Speed::UltraFast => todo!(), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -enum SendStop { - No, - Yes, -} - -/// I2C controller configuration -#[derive(Clone, Copy, Default)] -#[non_exhaustive] -pub struct Config { - /// Bus speed - pub speed: Speed, -} - -/// I2C Controller Driver. -pub struct I2c<'d, T: Instance, M: Mode> { - _peri: Peri<'d, T>, - _scl: Peri<'d, AnyPin>, - _sda: Peri<'d, AnyPin>, - _phantom: PhantomData, - is_hs: bool, -} - -impl<'d, T: Instance> I2c<'d, T, Blocking> { - /// Create a new blocking instance of the I2C Controller bus driver. - pub fn new_blocking( - peri: Peri<'d, T>, - scl: Peri<'d, impl SclPin>, - sda: Peri<'d, impl SdaPin>, - config: Config, - ) -> Result { - Self::new_inner(peri, scl, sda, config) - } -} - -impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { - fn new_inner( - _peri: Peri<'d, T>, - scl: Peri<'d, impl SclPin>, - sda: Peri<'d, impl SdaPin>, - config: Config, - ) -> Result { - let (power, source, div) = Self::clock_config(config.speed); - - // Enable clocks - let conf = Lpi2cConfig { - power, - source, - div, - instance: T::CLOCK_INSTANCE, - }; - - _ = unsafe { enable_and_reset::(&conf).map_err(Error::ClockSetup)? }; - - scl.mux(); - sda.mux(); - - let _scl = scl.into(); - let _sda = sda.into(); - - Self::set_config(&config)?; - - Ok(Self { - _peri, - _scl, - _sda, - _phantom: PhantomData, - is_hs: config.speed == Speed::UltraFast, - }) - } - - fn set_config(config: &Config) -> Result<()> { - // Disable the controller. - critical_section::with(|_| T::regs().mcr().modify(|_, w| w.men().disabled())); - - // Soft-reset the controller, read and write FIFOs. - critical_section::with(|_| { - T::regs() - .mcr() - .modify(|_, w| w.rst().reset().rtf().reset().rrf().reset()); - // According to Reference Manual section 40.7.1.4, "There - // is no minimum delay required before clearing the - // software reset", therefore we clear it immediately. - T::regs().mcr().modify(|_, w| w.rst().not_reset()); - - T::regs().mcr().modify(|_, w| w.dozen().clear_bit().dbgen().clear_bit()); - }); - - let (clklo, clkhi, sethold, datavd) = config.speed.into(); - - critical_section::with(|_| { - T::regs().mccr0().modify(|_, w| unsafe { - w.clklo() - .bits(clklo) - .clkhi() - .bits(clkhi) - .sethold() - .bits(sethold) - .datavd() - .bits(datavd) - }) - }); - - // Enable the controller. - critical_section::with(|_| T::regs().mcr().modify(|_, w| w.men().enabled())); - - // Clear all flags - T::regs().msr().write(|w| { - w.epf() - .clear_bit_by_one() - .sdf() - .clear_bit_by_one() - .ndf() - .clear_bit_by_one() - .alf() - .clear_bit_by_one() - .fef() - .clear_bit_by_one() - .pltf() - .clear_bit_by_one() - .dmf() - .clear_bit_by_one() - .stf() - .clear_bit_by_one() - }); - - Ok(()) - } - - // REVISIT: turn this into a function of the speed parameter - fn clock_config(speed: Speed) -> (PoweredClock, Lpi2cClockSel, Div4) { - match speed { - Speed::Standard | Speed::Fast | Speed::FastPlus => ( - PoweredClock::NormalEnabledDeepSleepDisabled, - Lpi2cClockSel::FroLfDiv, - const { Div4::no_div() }, - ), - Speed::UltraFast => ( - PoweredClock::NormalEnabledDeepSleepDisabled, - Lpi2cClockSel::FroHfDiv, - const { Div4::no_div() }, - ), - } - } - - fn is_tx_fifo_full(&mut self) -> bool { - let txfifo_size = 1 << T::regs().param().read().mtxfifo().bits(); - T::regs().mfsr().read().txcount().bits() == txfifo_size - } - - fn is_tx_fifo_empty(&mut self) -> bool { - T::regs().mfsr().read().txcount() == 0 - } - - fn is_rx_fifo_empty(&mut self) -> bool { - T::regs().mfsr().read().rxcount() == 0 - } - - fn status(&mut self) -> Result<()> { - // Wait for TxFIFO to be drained - while !self.is_tx_fifo_empty() {} - - let msr = T::regs().msr().read(); - T::regs().msr().write(|w| { - w.epf() - .clear_bit_by_one() - .sdf() - .clear_bit_by_one() - .ndf() - .clear_bit_by_one() - .alf() - .clear_bit_by_one() - .fef() - .clear_bit_by_one() - .fef() - .clear_bit_by_one() - .pltf() - .clear_bit_by_one() - .dmf() - .clear_bit_by_one() - .stf() - .clear_bit_by_one() - }); - - if msr.ndf().bit_is_set() { - Err(Error::AddressNack) - } else if msr.alf().bit_is_set() { - Err(Error::ArbitrationLoss) - } else { - Ok(()) - } - } - - fn send_cmd(&mut self, cmd: Cmd, data: u8) { - #[cfg(feature = "defmt")] - defmt::trace!( - "Sending cmd '{}' ({}) with data '{:08x}' MSR: {:08x}", - cmd, - cmd as u8, - data, - T::regs().msr().read().bits() - ); - - T::regs() - .mtdr() - .write(|w| unsafe { w.data().bits(data) }.cmd().variant(cmd)); - } - - fn start(&mut self, address: u8, read: bool) -> Result<()> { - if address >= 0x80 { - return Err(Error::AddressOutOfRange(address)); - } - - // Wait until we have space in the TxFIFO - while self.is_tx_fifo_full() {} - - let addr_rw = address << 1 | if read { 1 } else { 0 }; - self.send_cmd(if self.is_hs { Cmd::StartHs } else { Cmd::Start }, addr_rw); - - // Check controller status - self.status() - } - - fn stop(&mut self) -> Result<()> { - // Wait until we have space in the TxFIFO - while self.is_tx_fifo_full() {} - - self.send_cmd(Cmd::Stop, 0); - self.status() - } - - fn blocking_read_internal(&mut self, address: u8, read: &mut [u8], send_stop: SendStop) -> Result<()> { - self.start(address, true)?; - - if read.is_empty() { - return Err(Error::InvalidReadBufferLength); - } - - for chunk in read.chunks_mut(256) { - // Wait until we have space in the TxFIFO - while self.is_tx_fifo_full() {} - - self.send_cmd(Cmd::Receive, (chunk.len() - 1) as u8); - - for byte in chunk.iter_mut() { - // Wait until there's data in the RxFIFO - while self.is_rx_fifo_empty() {} - - *byte = T::regs().mrdr().read().data().bits(); - } - - if send_stop == SendStop::Yes { - self.stop()?; - } - } - - Ok(()) - } - - fn blocking_write_internal(&mut self, address: u8, write: &[u8], send_stop: SendStop) -> Result<()> { - self.start(address, false)?; - - // Usually, embassy HALs error out with an empty write, - // however empty writes are useful for writing I2C scanning - // logic through write probing. That is, we send a start with - // R/w bit cleared, but instead of writing any data, just send - // the stop onto the bus. This has the effect of checking if - // the resulting address got an ACK but causing no - // side-effects to the device on the other end. - // - // Because of this, we are not going to error out in case of - // empty writes. - #[cfg(feature = "defmt")] - if write.is_empty() { - defmt::trace!("Empty write, write probing?"); - } - - for byte in write { - // Wait until we have space in the TxFIFO - while self.is_tx_fifo_full() {} - - self.send_cmd(Cmd::Transmit, *byte); - } - - if send_stop == SendStop::Yes { - self.stop()?; - } - - Ok(()) - } - - // Public API: Blocking - - /// Read from address into buffer blocking caller until done. - pub fn blocking_read(&mut self, address: u8, read: &mut [u8]) -> Result<()> { - self.blocking_read_internal(address, read, SendStop::Yes) - // Automatic Stop - } - - /// Write to address from buffer blocking caller until done. - pub fn blocking_write(&mut self, address: u8, write: &[u8]) -> Result<()> { - self.blocking_write_internal(address, write, SendStop::Yes) - } - - /// Write to address from bytes and read from address into buffer blocking caller until done. - pub fn blocking_write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<()> { - self.blocking_write_internal(address, write, SendStop::No)?; - self.blocking_read_internal(address, read, SendStop::Yes) - } -} - -impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Read for I2c<'d, T, M> { - type Error = Error; - - fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<()> { - self.blocking_read(address, buffer) - } -} - -impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Write for I2c<'d, T, M> { - type Error = Error; - - fn write(&mut self, address: u8, bytes: &[u8]) -> Result<()> { - self.blocking_write(address, bytes) - } -} - -impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T, M> { - type Error = Error; - - fn write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<()> { - self.blocking_write_read(address, bytes, buffer) - } -} - -impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Transactional for I2c<'d, T, M> { - type Error = Error; - - fn exec(&mut self, address: u8, operations: &mut [embedded_hal_02::blocking::i2c::Operation<'_>]) -> Result<()> { - if let Some((last, rest)) = operations.split_last_mut() { - for op in rest { - match op { - embedded_hal_02::blocking::i2c::Operation::Read(buf) => { - self.blocking_read_internal(address, buf, SendStop::No)? - } - embedded_hal_02::blocking::i2c::Operation::Write(buf) => { - self.blocking_write_internal(address, buf, SendStop::No)? - } - } - } - - match last { - embedded_hal_02::blocking::i2c::Operation::Read(buf) => { - self.blocking_read_internal(address, buf, SendStop::Yes) - } - embedded_hal_02::blocking::i2c::Operation::Write(buf) => { - self.blocking_write_internal(address, buf, SendStop::Yes) - } - } - } else { - Ok(()) - } - } -} - -impl embedded_hal_1::i2c::Error for Error { - fn kind(&self) -> embedded_hal_1::i2c::ErrorKind { - match *self { - Self::ArbitrationLoss => embedded_hal_1::i2c::ErrorKind::ArbitrationLoss, - Self::AddressNack => { - embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address) - } - _ => embedded_hal_1::i2c::ErrorKind::Other, - } - } -} - -impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::ErrorType for I2c<'d, T, M> { - type Error = Error; -} - -impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::I2c for I2c<'d, T, M> { - fn transaction(&mut self, address: u8, operations: &mut [embedded_hal_1::i2c::Operation<'_>]) -> Result<()> { - if let Some((last, rest)) = operations.split_last_mut() { - for op in rest { - match op { - embedded_hal_1::i2c::Operation::Read(buf) => { - self.blocking_read_internal(address, buf, SendStop::No)? - } - embedded_hal_1::i2c::Operation::Write(buf) => { - self.blocking_write_internal(address, buf, SendStop::No)? - } - } - } - - match last { - embedded_hal_1::i2c::Operation::Read(buf) => self.blocking_read_internal(address, buf, SendStop::Yes), - embedded_hal_1::i2c::Operation::Write(buf) => self.blocking_write_internal(address, buf, SendStop::Yes), - } - } else { - Ok(()) - } - } -} - -impl<'d, T: Instance, M: Mode> embassy_embedded_hal::SetConfig for I2c<'d, T, M> { - type Config = Config; - type ConfigError = Error; - - fn set_config(&mut self, config: &Self::Config) -> Result<()> { - Self::set_config(config) - } -} diff --git a/src/i2c/mod.rs b/src/i2c/mod.rs deleted file mode 100644 index a1f842029..000000000 --- a/src/i2c/mod.rs +++ /dev/null @@ -1,171 +0,0 @@ -//! I2C Support - -use core::marker::PhantomData; - -use embassy_hal_internal::PeripheralType; -use embassy_sync::waitqueue::AtomicWaker; -use paste::paste; - -use crate::clocks::periph_helpers::Lpi2cConfig; -use crate::clocks::{ClockError, Gate}; -use crate::gpio::{GpioPin, SealedPin}; -use crate::{interrupt, pac}; - -/// Shorthand for `Result`. -pub type Result = core::result::Result; - -pub mod controller; - -/// Error information type -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - /// Clock configuration error. - ClockSetup(ClockError), - /// Reading for I2C failed. - ReadFail, - /// Writing to I2C failed. - WriteFail, - /// I2C address NAK condition. - AddressNack, - /// Bus level arbitration loss. - ArbitrationLoss, - /// Address out of range. - AddressOutOfRange(u8), - /// Invalid write buffer length. - InvalidWriteBufferLength, - /// Invalid read buffer length. - InvalidReadBufferLength, - /// Other internal errors or unexpected state. - Other, -} - -/// I2C interrupt handler. -pub struct InterruptHandler { - _phantom: PhantomData, -} - -impl interrupt::typelevel::Handler for InterruptHandler { - unsafe fn on_interrupt() { - let waker = T::waker(); - - waker.wake(); - - todo!() - } -} - -mod sealed { - /// Seal a trait - pub trait Sealed {} -} - -impl sealed::Sealed for T {} - -trait SealedInstance { - fn regs() -> &'static pac::lpi2c0::RegisterBlock; - fn waker() -> &'static AtomicWaker; -} - -/// I2C Instance -#[allow(private_bounds)] -pub trait Instance: SealedInstance + PeripheralType + 'static + Send + Gate { - /// Interrupt for this I2C instance. - type Interrupt: interrupt::typelevel::Interrupt; - /// Clock instance - const CLOCK_INSTANCE: crate::clocks::periph_helpers::Lpi2cInstance; -} - -macro_rules! impl_instance { - ($($n:expr),*) => { - $( - paste!{ - impl SealedInstance for crate::peripherals::[] { - fn regs() -> &'static pac::lpi2c0::RegisterBlock { - unsafe { &*pac::[]::ptr() } - } - - fn waker() -> &'static AtomicWaker { - static WAKER: AtomicWaker = AtomicWaker::new(); - &WAKER - } - } - - impl Instance for crate::peripherals::[] { - type Interrupt = crate::interrupt::typelevel::[]; - const CLOCK_INSTANCE: crate::clocks::periph_helpers::Lpi2cInstance - = crate::clocks::periph_helpers::Lpi2cInstance::[]; - } - } - )* - }; -} - -impl_instance!(0, 1, 2, 3); - -/// SCL pin trait. -pub trait SclPin: GpioPin + sealed::Sealed + PeripheralType { - fn mux(&self); -} - -/// SDA pin trait. -pub trait SdaPin: GpioPin + sealed::Sealed + PeripheralType { - fn mux(&self); -} - -/// Driver mode. -#[allow(private_bounds)] -pub trait Mode: sealed::Sealed {} - -/// Blocking mode. -pub struct Blocking; -impl sealed::Sealed for Blocking {} -impl Mode for Blocking {} - -/// Async mode. -pub struct Async; -impl sealed::Sealed for Async {} -impl Mode for Async {} - -macro_rules! impl_pin { - ($pin:ident, $peri:ident, $fn:ident, $trait:ident) => { - impl $trait for crate::peripherals::$pin { - fn mux(&self) { - self.set_pull(crate::gpio::Pull::Disabled); - self.set_slew_rate(crate::gpio::SlewRate::Fast.into()); - self.set_drive_strength(crate::gpio::DriveStrength::Double.into()); - self.set_function(crate::pac::port0::pcr0::Mux::$fn); - self.set_enable_input_buffer(); - } - } - }; -} - -impl_pin!(P0_16, LPI2C0, Mux2, SdaPin); -impl_pin!(P0_17, LPI2C0, Mux2, SclPin); -impl_pin!(P0_18, LPI2C0, Mux2, SclPin); -impl_pin!(P0_19, LPI2C0, Mux2, SdaPin); -impl_pin!(P1_0, LPI2C1, Mux3, SdaPin); -impl_pin!(P1_1, LPI2C1, Mux3, SclPin); -impl_pin!(P1_2, LPI2C1, Mux3, SdaPin); -impl_pin!(P1_3, LPI2C1, Mux3, SclPin); -impl_pin!(P1_8, LPI2C2, Mux3, SdaPin); -impl_pin!(P1_9, LPI2C2, Mux3, SclPin); -impl_pin!(P1_10, LPI2C2, Mux3, SdaPin); -impl_pin!(P1_11, LPI2C2, Mux3, SclPin); -impl_pin!(P1_12, LPI2C1, Mux2, SdaPin); -impl_pin!(P1_13, LPI2C1, Mux2, SclPin); -impl_pin!(P1_14, LPI2C1, Mux2, SclPin); -impl_pin!(P1_15, LPI2C1, Mux2, SdaPin); -impl_pin!(P1_30, LPI2C0, Mux3, SdaPin); -impl_pin!(P1_31, LPI2C0, Mux3, SclPin); -impl_pin!(P3_27, LPI2C3, Mux2, SclPin); -impl_pin!(P3_28, LPI2C3, Mux2, SdaPin); -// impl_pin!(P3_29, LPI2C3, Mux2, HreqPin); What is this HREQ pin? -impl_pin!(P3_30, LPI2C3, Mux2, SclPin); -impl_pin!(P3_31, LPI2C3, Mux2, SdaPin); -impl_pin!(P4_2, LPI2C2, Mux2, SdaPin); -impl_pin!(P4_3, LPI2C0, Mux2, SclPin); -impl_pin!(P4_4, LPI2C2, Mux2, SdaPin); -impl_pin!(P4_5, LPI2C0, Mux2, SclPin); -// impl_pin!(P4_6, LPI2C0, Mux2, HreqPin); What is this HREQ pin? diff --git a/src/interrupt.rs b/src/interrupt.rs deleted file mode 100644 index 000b2f9cd..000000000 --- a/src/interrupt.rs +++ /dev/null @@ -1,546 +0,0 @@ -//! Minimal interrupt helpers mirroring embassy-imxrt style for OS_EVENT and LPUART2. -//! Type-level interrupt traits and bindings are provided by the -//! `embassy_hal_internal::interrupt_mod!` macro via the generated module below. - -// TODO(AJM): As of 2025-11-13, we need to do a pass to ensure safety docs -// are complete prior to release. -#![allow(clippy::missing_safety_doc)] - -mod generated { - embassy_hal_internal::interrupt_mod!( - OS_EVENT, RTC, ADC1, GPIO0, GPIO1, GPIO2, GPIO3, GPIO4, LPI2C0, LPI2C1, LPI2C2, LPI2C3, LPUART0, LPUART1, - LPUART2, LPUART3, LPUART4, LPUART5, DMA_CH0, DMA_CH1, DMA_CH2, DMA_CH3, DMA_CH4, DMA_CH5, DMA_CH6, DMA_CH7, - ); -} - -use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; - -pub use generated::interrupt::{typelevel, Priority}; - -use crate::pac::Interrupt; - -/// Trait for configuring and controlling interrupts. -/// -/// This trait provides a consistent interface for interrupt management across -/// different interrupt sources, similar to embassy-imxrt's InterruptExt. -pub trait InterruptExt { - /// Clear any pending interrupt in NVIC. - fn unpend(&self); - - /// Set NVIC priority for this interrupt. - fn set_priority(&self, priority: Priority); - - /// Enable this interrupt in NVIC. - /// - /// # Safety - /// This function is unsafe because it can enable interrupts that may not be - /// properly configured, potentially leading to undefined behavior. - unsafe fn enable(&self); - - /// Disable this interrupt in NVIC. - /// - /// # Safety - /// This function is unsafe because disabling interrupts may leave the system - /// in an inconsistent state if the interrupt was expected to fire. - unsafe fn disable(&self); - - /// Check if the interrupt is pending in NVIC. - fn is_pending(&self) -> bool; -} - -#[derive(Clone, Copy, Debug, Default)] -pub struct DefaultHandlerSnapshot { - pub vector: u16, - pub count: u32, - pub cfsr: u32, - pub hfsr: u32, - pub stacked_pc: u32, - pub stacked_lr: u32, - pub stacked_sp: u32, -} - -static LAST_DEFAULT_VECTOR: AtomicU16 = AtomicU16::new(0); -static LAST_DEFAULT_COUNT: AtomicU32 = AtomicU32::new(0); -static LAST_DEFAULT_CFSR: AtomicU32 = AtomicU32::new(0); -static LAST_DEFAULT_HFSR: AtomicU32 = AtomicU32::new(0); -static LAST_DEFAULT_PC: AtomicU32 = AtomicU32::new(0); -static LAST_DEFAULT_LR: AtomicU32 = AtomicU32::new(0); -static LAST_DEFAULT_SP: AtomicU32 = AtomicU32::new(0); - -#[inline] -pub fn default_handler_snapshot() -> DefaultHandlerSnapshot { - DefaultHandlerSnapshot { - vector: LAST_DEFAULT_VECTOR.load(Ordering::Relaxed), - count: LAST_DEFAULT_COUNT.load(Ordering::Relaxed), - cfsr: LAST_DEFAULT_CFSR.load(Ordering::Relaxed), - hfsr: LAST_DEFAULT_HFSR.load(Ordering::Relaxed), - stacked_pc: LAST_DEFAULT_PC.load(Ordering::Relaxed), - stacked_lr: LAST_DEFAULT_LR.load(Ordering::Relaxed), - stacked_sp: LAST_DEFAULT_SP.load(Ordering::Relaxed), - } -} - -#[inline] -pub fn clear_default_handler_snapshot() { - LAST_DEFAULT_VECTOR.store(0, Ordering::Relaxed); - LAST_DEFAULT_COUNT.store(0, Ordering::Relaxed); - LAST_DEFAULT_CFSR.store(0, Ordering::Relaxed); - LAST_DEFAULT_HFSR.store(0, Ordering::Relaxed); - LAST_DEFAULT_PC.store(0, Ordering::Relaxed); - LAST_DEFAULT_LR.store(0, Ordering::Relaxed); - LAST_DEFAULT_SP.store(0, Ordering::Relaxed); -} - -/// OS_EVENT interrupt helper with methods similar to embassy-imxrt's InterruptExt. -pub struct OsEvent; -pub const OS_EVENT: OsEvent = OsEvent; - -impl InterruptExt for OsEvent { - /// Clear any pending OS_EVENT in NVIC. - #[inline] - fn unpend(&self) { - cortex_m::peripheral::NVIC::unpend(Interrupt::OS_EVENT); - } - - /// Set NVIC priority for OS_EVENT. - #[inline] - fn set_priority(&self, priority: Priority) { - unsafe { - let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; - nvic.set_priority(Interrupt::OS_EVENT, u8::from(priority)); - } - } - - /// Enable OS_EVENT in NVIC. - #[inline] - unsafe fn enable(&self) { - cortex_m::peripheral::NVIC::unmask(Interrupt::OS_EVENT); - } - - /// Disable OS_EVENT in NVIC. - #[inline] - unsafe fn disable(&self) { - cortex_m::peripheral::NVIC::mask(Interrupt::OS_EVENT); - } - - /// Check if OS_EVENT is pending in NVIC. - #[inline] - fn is_pending(&self) -> bool { - cortex_m::peripheral::NVIC::is_pending(Interrupt::OS_EVENT) - } -} - -impl OsEvent { - /// Configure OS_EVENT interrupt for timer operation. - /// This sets up the NVIC priority, enables the interrupt, and ensures global interrupts are enabled. - /// Also performs a software event to wake any pending WFE. - pub fn configure_for_timer(&self, priority: Priority) { - // Configure NVIC - self.unpend(); - self.set_priority(priority); - unsafe { - self.enable(); - } - - // Ensure global interrupts are enabled in no-reset scenarios (e.g., cargo run) - // Debuggers typically perform a reset which leaves PRIMASK=0; cargo run may not. - unsafe { - cortex_m::interrupt::enable(); - } - - // Wake any executor WFE that might be sleeping when we armed the first deadline - cortex_m::asm::sev(); - } -} - -/// LPUART2 interrupt helper with methods similar to embassy-imxrt's InterruptExt. -pub struct Lpuart2; -pub const LPUART2: Lpuart2 = Lpuart2; - -impl InterruptExt for Lpuart2 { - /// Clear any pending LPUART2 in NVIC. - #[inline] - fn unpend(&self) { - cortex_m::peripheral::NVIC::unpend(Interrupt::LPUART2); - } - - /// Set NVIC priority for LPUART2. - #[inline] - fn set_priority(&self, priority: Priority) { - unsafe { - let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; - nvic.set_priority(Interrupt::LPUART2, u8::from(priority)); - } - } - - /// Enable LPUART2 in NVIC. - #[inline] - unsafe fn enable(&self) { - cortex_m::peripheral::NVIC::unmask(Interrupt::LPUART2); - } - - /// Disable LPUART2 in NVIC. - #[inline] - unsafe fn disable(&self) { - cortex_m::peripheral::NVIC::mask(Interrupt::LPUART2); - } - - /// Check if LPUART2 is pending in NVIC. - #[inline] - fn is_pending(&self) -> bool { - cortex_m::peripheral::NVIC::is_pending(Interrupt::LPUART2) - } -} - -impl Lpuart2 { - /// Configure LPUART2 interrupt for UART operation. - /// This sets up the NVIC priority, enables the interrupt, and ensures global interrupts are enabled. - pub fn configure_for_uart(&self, priority: Priority) { - // Configure NVIC - self.unpend(); - self.set_priority(priority); - unsafe { - self.enable(); - } - - // Ensure global interrupts are enabled in no-reset scenarios (e.g., cargo run) - // Debuggers typically perform a reset which leaves PRIMASK=0; cargo run may not. - unsafe { - cortex_m::interrupt::enable(); - } - } - - /// Install LPUART2 handler into the RAM vector table. - /// Safety: See `install_irq_handler`. - pub unsafe fn install_handler(&self, handler: unsafe extern "C" fn()) { - install_irq_handler(Interrupt::LPUART2, handler); - } -} - -pub struct Rtc; -pub const RTC: Rtc = Rtc; - -impl InterruptExt for Rtc { - /// Clear any pending RTC in NVIC. - #[inline] - fn unpend(&self) { - cortex_m::peripheral::NVIC::unpend(Interrupt::RTC); - } - - /// Set NVIC priority for RTC. - #[inline] - fn set_priority(&self, priority: Priority) { - unsafe { - let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; - nvic.set_priority(Interrupt::RTC, u8::from(priority)); - } - } - - /// Enable RTC in NVIC. - #[inline] - unsafe fn enable(&self) { - cortex_m::peripheral::NVIC::unmask(Interrupt::RTC); - } - - /// Disable RTC in NVIC. - #[inline] - unsafe fn disable(&self) { - cortex_m::peripheral::NVIC::mask(Interrupt::RTC); - } - - /// Check if RTC is pending in NVIC. - #[inline] - fn is_pending(&self) -> bool { - cortex_m::peripheral::NVIC::is_pending(Interrupt::RTC) - } -} - -pub struct Adc; -pub const ADC1: Adc = Adc; - -impl InterruptExt for Adc { - /// Clear any pending ADC1 in NVIC. - #[inline] - fn unpend(&self) { - cortex_m::peripheral::NVIC::unpend(Interrupt::ADC1); - } - - /// Set NVIC priority for ADC1. - #[inline] - fn set_priority(&self, priority: Priority) { - unsafe { - let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; - nvic.set_priority(Interrupt::ADC1, u8::from(priority)); - } - } - - /// Enable ADC1 in NVIC. - #[inline] - unsafe fn enable(&self) { - cortex_m::peripheral::NVIC::unmask(Interrupt::ADC1); - } - - /// Disable ADC1 in NVIC. - #[inline] - unsafe fn disable(&self) { - cortex_m::peripheral::NVIC::mask(Interrupt::ADC1); - } - - /// Check if ADC1 is pending in NVIC. - #[inline] - fn is_pending(&self) -> bool { - cortex_m::peripheral::NVIC::is_pending(Interrupt::ADC1) - } -} - -pub struct Gpio0; -pub const GPIO0: Gpio0 = Gpio0; - -impl InterruptExt for Gpio0 { - /// Clear any pending GPIO0 in NVIC. - #[inline] - fn unpend(&self) { - cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO0); - } - - /// Set NVIC priority for GPIO0. - #[inline] - fn set_priority(&self, priority: Priority) { - unsafe { - let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; - nvic.set_priority(Interrupt::GPIO0, u8::from(priority)); - } - } - - /// Enable GPIO0 in NVIC. - #[inline] - unsafe fn enable(&self) { - cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO0); - } - - /// Disable GPIO0 in NVIC. - #[inline] - unsafe fn disable(&self) { - cortex_m::peripheral::NVIC::mask(Interrupt::GPIO0); - } - - /// Check if GPIO0 is pending in NVIC. - #[inline] - fn is_pending(&self) -> bool { - cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO0) - } -} - -pub struct Gpio1; -pub const GPIO1: Gpio1 = Gpio1; - -impl InterruptExt for Gpio1 { - /// Clear any pending GPIO1 in NVIC. - #[inline] - fn unpend(&self) { - cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO1); - } - - /// Set NVIC priority for GPIO1. - #[inline] - fn set_priority(&self, priority: Priority) { - unsafe { - let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; - nvic.set_priority(Interrupt::GPIO1, u8::from(priority)); - } - } - - /// Enable GPIO1 in NVIC. - #[inline] - unsafe fn enable(&self) { - cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO1); - } - - /// Disable GPIO1 in NVIC. - #[inline] - unsafe fn disable(&self) { - cortex_m::peripheral::NVIC::mask(Interrupt::GPIO1); - } - - /// Check if GPIO1 is pending in NVIC. - #[inline] - fn is_pending(&self) -> bool { - cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO1) - } -} - -pub struct Gpio2; -pub const GPIO2: Gpio2 = Gpio2; - -impl InterruptExt for Gpio2 { - /// Clear any pending GPIO2 in NVIC. - #[inline] - fn unpend(&self) { - cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO2); - } - - /// Set NVIC priority for GPIO2. - #[inline] - fn set_priority(&self, priority: Priority) { - unsafe { - let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; - nvic.set_priority(Interrupt::GPIO2, u8::from(priority)); - } - } - - /// Enable GPIO2 in NVIC. - #[inline] - unsafe fn enable(&self) { - cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO2); - } - - /// Disable GPIO2 in NVIC. - #[inline] - unsafe fn disable(&self) { - cortex_m::peripheral::NVIC::mask(Interrupt::GPIO2); - } - - /// Check if GPIO2 is pending in NVIC. - #[inline] - fn is_pending(&self) -> bool { - cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO2) - } -} - -pub struct Gpio3; -pub const GPIO3: Gpio3 = Gpio3; - -impl InterruptExt for Gpio3 { - /// Clear any pending GPIO3 in NVIC. - #[inline] - fn unpend(&self) { - cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO3); - } - - /// Set NVIC priority for GPIO3. - #[inline] - fn set_priority(&self, priority: Priority) { - unsafe { - let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; - nvic.set_priority(Interrupt::GPIO3, u8::from(priority)); - } - } - - /// Enable GPIO3 in NVIC. - #[inline] - unsafe fn enable(&self) { - cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO3); - } - - /// Disable GPIO3 in NVIC. - #[inline] - unsafe fn disable(&self) { - cortex_m::peripheral::NVIC::mask(Interrupt::GPIO3); - } - - /// Check if GPIO3 is pending in NVIC. - #[inline] - fn is_pending(&self) -> bool { - cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO3) - } -} - -pub struct Gpio4; -pub const GPIO4: Gpio4 = Gpio4; - -impl InterruptExt for Gpio4 { - /// Clear any pending GPIO4 in NVIC. - #[inline] - fn unpend(&self) { - cortex_m::peripheral::NVIC::unpend(Interrupt::GPIO4); - } - - /// Set NVIC priority for GPIO4. - #[inline] - fn set_priority(&self, priority: Priority) { - unsafe { - let mut nvic = cortex_m::peripheral::Peripherals::steal().NVIC; - nvic.set_priority(Interrupt::GPIO4, u8::from(priority)); - } - } - - /// Enable GPIO4 in NVIC. - #[inline] - unsafe fn enable(&self) { - cortex_m::peripheral::NVIC::unmask(Interrupt::GPIO4); - } - - /// Disable GPIO4 in NVIC. - #[inline] - unsafe fn disable(&self) { - cortex_m::peripheral::NVIC::mask(Interrupt::GPIO4); - } - - /// Check if GPIO4 is pending in NVIC. - #[inline] - fn is_pending(&self) -> bool { - cortex_m::peripheral::NVIC::is_pending(Interrupt::GPIO4) - } -} - -/// Set VTOR (Vector Table Offset) to a RAM-based vector table. -/// Pass a pointer to the first word in the RAM table (stack pointer slot 0). -/// Safety: Caller must ensure the RAM table is valid and aligned as required by the core. -pub unsafe fn vtor_set_ram_vector_base(base: *const u32) { - core::ptr::write_volatile(0xE000_ED08 as *mut u32, base as u32); -} - -/// Install an interrupt handler into the current VTOR-based vector table. -/// This writes the function pointer at index 16 + irq number. -/// Safety: Caller must ensure VTOR points at a writable RAM table and that `handler` -/// has the correct ABI and lifetime. -pub unsafe fn install_irq_handler(irq: Interrupt, handler: unsafe extern "C" fn()) { - let vtor_base = core::ptr::read_volatile(0xE000_ED08 as *const u32) as *mut u32; - let idx = 16 + (irq as isize as usize); - core::ptr::write_volatile(vtor_base.add(idx), handler as usize as u32); -} - -impl OsEvent { - /// Convenience to install the OS_EVENT handler into the RAM vector table. - /// Safety: See `install_irq_handler`. - pub unsafe fn install_handler(&self, handler: extern "C" fn()) { - install_irq_handler(Interrupt::OS_EVENT, handler); - } -} - -/// Install OS_EVENT handler by raw address. Useful to avoid fn pointer type mismatches. -/// Safety: Caller must ensure the address is a valid `extern "C" fn()` handler. -pub unsafe fn os_event_install_handler_raw(handler_addr: usize) { - let vtor_base = core::ptr::read_volatile(0xE000_ED08 as *const u32) as *mut u32; - let idx = 16 + (Interrupt::OS_EVENT as isize as usize); - core::ptr::write_volatile(vtor_base.add(idx), handler_addr as u32); -} - -/// Provide a conservative default IRQ handler that avoids wedging the system. -/// It clears all NVIC pending bits and returns, so spurious or reserved IRQs -/// don’t trap the core in an infinite WFI loop during bring-up. -#[no_mangle] -pub unsafe extern "C" fn DefaultHandler() { - let active = core::ptr::read_volatile(0xE000_ED04 as *const u32) & 0x1FF; - let cfsr = core::ptr::read_volatile(0xE000_ED28 as *const u32); - let hfsr = core::ptr::read_volatile(0xE000_ED2C as *const u32); - - let sp = cortex_m::register::msp::read(); - let stacked = sp as *const u32; - // Stacked registers follow ARMv8-M procedure call standard order - let stacked_pc = unsafe { stacked.add(6).read() }; - let stacked_lr = unsafe { stacked.add(5).read() }; - - LAST_DEFAULT_VECTOR.store(active as u16, Ordering::Relaxed); - LAST_DEFAULT_CFSR.store(cfsr, Ordering::Relaxed); - LAST_DEFAULT_HFSR.store(hfsr, Ordering::Relaxed); - LAST_DEFAULT_COUNT.fetch_add(1, Ordering::Relaxed); - LAST_DEFAULT_PC.store(stacked_pc, Ordering::Relaxed); - LAST_DEFAULT_LR.store(stacked_lr, Ordering::Relaxed); - LAST_DEFAULT_SP.store(sp, Ordering::Relaxed); - - // Do nothing here: on some MCUs/TrustZone setups, writing NVIC from a spurious - // handler can fault if targeting the Secure bank. Just return. - cortex_m::asm::dsb(); - cortex_m::asm::isb(); -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index d721f53e6..000000000 --- a/src/lib.rs +++ /dev/null @@ -1,484 +0,0 @@ -#![no_std] -#![allow(async_fn_in_trait)] -#![doc = include_str!("../README.md")] - -// //! ## Feature flags -// #![doc = document_features::document_features!(feature_label = r#"{feature}"#)] - -pub mod clocks; // still provide clock helpers -pub mod dma; -pub mod gpio; -pub mod pins; // pin mux helpers - -pub mod adc; -pub mod clkout; -pub mod config; -pub mod i2c; -pub mod interrupt; -pub mod lpuart; -pub mod ostimer; -pub mod rtc; - -pub use crate::pac::NVIC_PRIO_BITS; - -#[rustfmt::skip] -embassy_hal_internal::peripherals!( - ADC0, - ADC1, - - AOI0, - AOI1, - - CAN0, - CAN1, - - CDOG0, - CDOG1, - - // CLKOUT is not specifically a peripheral (it's part of SYSCON), - // but we still want it to be a singleton. - CLKOUT, - - CMC, - CMP0, - CMP1, - CRC0, - - CTIMER0, - CTIMER1, - CTIMER2, - CTIMER3, - CTIMER4, - - DBGMAILBOX, - DMA0, - DMA_CH0, - DMA_CH1, - DMA_CH2, - DMA_CH3, - DMA_CH4, - DMA_CH5, - DMA_CH6, - DMA_CH7, - EDMA0_TCD0, - EIM0, - EQDC0, - EQDC1, - ERM0, - FLEXIO0, - FLEXPWM0, - FLEXPWM1, - FMC0, - FMU0, - FREQME0, - GLIKEY0, - - GPIO0, - GPIO1, - GPIO2, - GPIO3, - GPIO4, - - I3C0, - INPUTMUX0, - - LPI2C0, - LPI2C1, - LPI2C2, - LPI2C3, - - LPSPI0, - LPSPI1, - - LPTMR0, - - LPUART0, - LPUART1, - LPUART2, - LPUART3, - LPUART4, - LPUART5, - - MAU0, - MBC0, - MRCC0, - OPAMP0, - - #[cfg(not(feature = "time"))] - OSTIMER0, - - P0_0, - P0_1, - P0_2, - P0_3, - P0_4, - P0_5, - P0_6, - P0_7, - P0_8, - P0_9, - P0_10, - P0_11, - P0_12, - P0_13, - P0_14, - P0_15, - P0_16, - P0_17, - P0_18, - P0_19, - P0_20, - P0_21, - P0_22, - P0_23, - P0_24, - P0_25, - P0_26, - P0_27, - P0_28, - P0_29, - P0_30, - P0_31, - - P1_0, - P1_1, - P1_2, - P1_3, - P1_4, - P1_5, - P1_6, - P1_7, - P1_8, - P1_9, - P1_10, - P1_11, - P1_12, - P1_13, - P1_14, - P1_15, - P1_16, - P1_17, - P1_18, - P1_19, - P1_20, - P1_21, - P1_22, - P1_23, - P1_24, - P1_25, - P1_26, - P1_27, - P1_28, - P1_29, - P1_30, - P1_31, - - P2_0, - P2_1, - P2_2, - P2_3, - P2_4, - P2_5, - P2_6, - P2_7, - P2_8, - P2_9, - P2_10, - P2_11, - P2_12, - P2_13, - P2_14, - P2_15, - P2_16, - P2_17, - P2_18, - P2_19, - P2_20, - P2_21, - P2_22, - P2_23, - P2_24, - P2_25, - P2_26, - P2_27, - P2_28, - P2_29, - P2_30, - P2_31, - - P3_0, - P3_1, - P3_2, - P3_3, - P3_4, - P3_5, - P3_6, - P3_7, - P3_8, - P3_9, - P3_10, - P3_11, - P3_12, - P3_13, - P3_14, - P3_15, - P3_16, - P3_17, - P3_18, - P3_19, - P3_20, - P3_21, - P3_22, - P3_23, - P3_24, - P3_25, - P3_26, - P3_27, - P3_28, - P3_29, - P3_30, - P3_31, - - P4_0, - P4_1, - P4_2, - P4_3, - P4_4, - P4_5, - P4_6, - P4_7, - P4_8, - P4_9, - P4_10, - P4_11, - P4_12, - P4_13, - P4_14, - P4_15, - P4_16, - P4_17, - P4_18, - P4_19, - P4_20, - P4_21, - P4_22, - P4_23, - P4_24, - P4_25, - P4_26, - P4_27, - P4_28, - P4_29, - P4_30, - P4_31, - - P5_0, - P5_1, - P5_2, - P5_3, - P5_4, - P5_5, - P5_6, - P5_7, - P5_8, - P5_9, - P5_10, - P5_11, - P5_12, - P5_13, - P5_14, - P5_15, - P5_16, - P5_17, - P5_18, - P5_19, - P5_20, - P5_21, - P5_22, - P5_23, - P5_24, - P5_25, - P5_26, - P5_27, - P5_28, - P5_29, - P5_30, - P5_31, - - PKC0, - - PORT0, - PORT1, - PORT2, - PORT3, - PORT4, - - RTC0, - SAU, - SCG0, - SCN_SCB, - SGI0, - SMARTDMA0, - SPC0, - SYSCON, - TDET0, - TRNG0, - UDF0, - USB0, - UTICK0, - VBAT0, - WAKETIMER0, - WUU0, - WWDT0, -); - -// Use cortex-m-rt's #[interrupt] attribute directly; PAC does not re-export it. - -// Re-export interrupt traits and types -pub use adc::Adc1 as Adc1Token; -pub use gpio::{AnyPin, Flex, Gpio as GpioToken, Input, Level, Output}; -pub use interrupt::InterruptExt; -#[cfg(feature = "unstable-pac")] -pub use mcxa_pac as pac; -#[cfg(not(feature = "unstable-pac"))] -pub(crate) use mcxa_pac as pac; -pub use rtc::Rtc0 as Rtc0Token; - -/// Initialize HAL with configuration (mirrors embassy-imxrt style). Minimal: just take peripherals. -/// Also applies configurable NVIC priority for the OSTIMER OS_EVENT interrupt (no enabling). -pub fn init(cfg: crate::config::Config) -> Peripherals { - let peripherals = Peripherals::take(); - // Apply user-configured priority early; enabling is left to examples/apps - #[cfg(feature = "time")] - crate::interrupt::OS_EVENT.set_priority(cfg.time_interrupt_priority); - // Apply user-configured priority early; enabling is left to examples/apps - crate::interrupt::RTC.set_priority(cfg.rtc_interrupt_priority); - // Apply user-configured priority early; enabling is left to examples/apps - crate::interrupt::ADC1.set_priority(cfg.adc_interrupt_priority); - // Apply user-configured priority early; enabling is left to examples/apps - crate::interrupt::GPIO0.set_priority(cfg.gpio_interrupt_priority); - // Apply user-configured priority early; enabling is left to examples/apps - crate::interrupt::GPIO1.set_priority(cfg.gpio_interrupt_priority); - // Apply user-configured priority early; enabling is left to examples/apps - crate::interrupt::GPIO2.set_priority(cfg.gpio_interrupt_priority); - // Apply user-configured priority early; enabling is left to examples/apps - crate::interrupt::GPIO3.set_priority(cfg.gpio_interrupt_priority); - // Apply user-configured priority early; enabling is left to examples/apps - crate::interrupt::GPIO4.set_priority(cfg.gpio_interrupt_priority); - - // Configure clocks - crate::clocks::init(cfg.clock_cfg).unwrap(); - - unsafe { - crate::gpio::init(); - } - - // Initialize DMA controller (clock, reset, configuration) - crate::dma::init(); - - // Initialize embassy-time global driver backed by OSTIMER0 - #[cfg(feature = "time")] - crate::ostimer::time_driver::init(crate::config::Config::default().time_interrupt_priority, 1_000_000); - - // Enable GPIO clocks - unsafe { - _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); - _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); - - _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); - _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); - - _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); - _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); - - _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); - _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); - - _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); - _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); - } - - peripherals -} - -// /// Optional hook called by cortex-m-rt before RAM init. -// /// We proactively mask and clear all NVIC IRQs to avoid wedges from stale state -// /// left by soft resets/debug sessions. -// /// -// /// NOTE: Manual VTOR setup is required for RAM execution. The cortex-m-rt 'set-vtor' -// /// feature is incompatible with our setup because it expects __vector_table to be -// /// defined differently than how our RAM-based linker script arranges it. -// #[no_mangle] -// pub unsafe extern "C" fn __pre_init() { -// // Set the VTOR to point to the interrupt vector table in RAM -// // This is required since code runs from RAM on this MCU -// crate::interrupt::vtor_set_ram_vector_base(0x2000_0000 as *const u32); - -// // Mask and clear pending for all NVIC lines (0..127) to avoid stale state across runs. -// let nvic = &*cortex_m::peripheral::NVIC::PTR; -// for i in 0..4 { -// // 4 words x 32 = 128 IRQs -// nvic.icer[i].write(0xFFFF_FFFF); -// nvic.icpr[i].write(0xFFFF_FFFF); -// } -// // Do NOT touch peripheral registers here: clocks may be off and accesses can fault. -// crate::interrupt::clear_default_handler_snapshot(); -// } - -/// Internal helper to dispatch a type-level interrupt handler. -#[inline(always)] -#[doc(hidden)] -pub unsafe fn __handle_interrupt() -where - T: crate::interrupt::typelevel::Interrupt, - H: crate::interrupt::typelevel::Handler, -{ - H::on_interrupt(); -} - -/// Macro to bind interrupts to handlers, similar to embassy-imxrt. -/// -/// Example: -/// - Bind OS_EVENT to the OSTIMER time-driver handler -/// bind_interrupts!(struct Irqs { OS_EVENT => crate::ostimer::time_driver::OsEventHandler; }); -#[macro_export] -macro_rules! bind_interrupts { - ($(#[$attr:meta])* $vis:vis struct $name:ident { - $( - $(#[cfg($cond_irq:meta)])? - $irq:ident => $( - $(#[cfg($cond_handler:meta)])? - $handler:ty - ),*; - )* - }) => { - #[derive(Copy, Clone)] - $(#[$attr])* - $vis struct $name; - - $( - #[allow(non_snake_case)] - #[no_mangle] - $(#[cfg($cond_irq)])? - unsafe extern "C" fn $irq() { - unsafe { - $( - $(#[cfg($cond_handler)])? - <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); - )* - } - } - - $(#[cfg($cond_irq)])? - $crate::bind_interrupts!(@inner - $( - $(#[cfg($cond_handler)])? - unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} - )* - ); - )* - }; - (@inner $($t:tt)*) => { - $($t)* - } -} diff --git a/src/lpuart/buffered.rs b/src/lpuart/buffered.rs deleted file mode 100644 index 8eb443ca7..000000000 --- a/src/lpuart/buffered.rs +++ /dev/null @@ -1,780 +0,0 @@ -use core::future::poll_fn; -use core::marker::PhantomData; -use core::sync::atomic::{AtomicBool, Ordering}; -use core::task::Poll; - -use embassy_hal_internal::atomic_ring_buffer::RingBuffer; -use embassy_hal_internal::Peri; -use embassy_sync::waitqueue::AtomicWaker; - -use super::*; -use crate::interrupt; - -// ============================================================================ -// STATIC STATE MANAGEMENT -// ============================================================================ - -/// State for buffered LPUART operations -pub struct State { - tx_waker: AtomicWaker, - tx_buf: RingBuffer, - tx_done: AtomicBool, - rx_waker: AtomicWaker, - rx_buf: RingBuffer, - initialized: AtomicBool, -} - -impl Default for State { - fn default() -> Self { - Self::new() - } -} - -impl State { - /// Create a new state instance - pub const fn new() -> Self { - Self { - tx_waker: AtomicWaker::new(), - tx_buf: RingBuffer::new(), - tx_done: AtomicBool::new(true), - rx_waker: AtomicWaker::new(), - rx_buf: RingBuffer::new(), - initialized: AtomicBool::new(false), - } - } -} -// ============================================================================ -// BUFFERED DRIVER STRUCTURES -// ============================================================================ - -/// Buffered LPUART driver -pub struct BufferedLpuart<'a> { - tx: BufferedLpuartTx<'a>, - rx: BufferedLpuartRx<'a>, -} - -/// Buffered LPUART TX driver -pub struct BufferedLpuartTx<'a> { - info: Info, - state: &'static State, - _tx_pin: Peri<'a, AnyPin>, - _cts_pin: Option>, -} - -/// Buffered LPUART RX driver -pub struct BufferedLpuartRx<'a> { - info: Info, - state: &'static State, - _rx_pin: Peri<'a, AnyPin>, - _rts_pin: Option>, -} - -// ============================================================================ -// BUFFERED LPUART IMPLEMENTATION -// ============================================================================ - -impl<'a> BufferedLpuart<'a> { - /// Common initialization logic - fn init_common( - _inner: &Peri<'a, T>, - tx_buffer: Option<&'a mut [u8]>, - rx_buffer: Option<&'a mut [u8]>, - config: &Config, - enable_tx: bool, - enable_rx: bool, - enable_rts: bool, - enable_cts: bool, - ) -> Result<&'static State> { - let state = T::buffered_state(); - - if state.initialized.load(Ordering::Relaxed) { - return Err(Error::InvalidArgument); - } - - // Initialize buffers - if let Some(tx_buffer) = tx_buffer { - if tx_buffer.is_empty() { - return Err(Error::InvalidArgument); - } - unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), tx_buffer.len()) }; - } - - if let Some(rx_buffer) = rx_buffer { - if rx_buffer.is_empty() { - return Err(Error::InvalidArgument); - } - unsafe { state.rx_buf.init(rx_buffer.as_mut_ptr(), rx_buffer.len()) }; - } - - state.initialized.store(true, Ordering::Relaxed); - - // Enable clocks and initialize hardware - let conf = LpuartConfig { - power: config.power, - source: config.source, - div: config.div, - instance: T::CLOCK_INSTANCE, - }; - let clock_freq = unsafe { enable_and_reset::(&conf).map_err(Error::ClockSetup)? }; - - Self::init_hardware( - T::info().regs, - *config, - clock_freq, - enable_tx, - enable_rx, - enable_rts, - enable_cts, - )?; - - Ok(state) - } - - /// Helper for full-duplex initialization - fn new_inner( - inner: Peri<'a, T>, - tx_pin: Peri<'a, AnyPin>, - rx_pin: Peri<'a, AnyPin>, - rts_pin: Option>, - cts_pin: Option>, - tx_buffer: &'a mut [u8], - rx_buffer: &'a mut [u8], - config: Config, - ) -> Result<(BufferedLpuartTx<'a>, BufferedLpuartRx<'a>)> { - let state = Self::init_common::( - &inner, - Some(tx_buffer), - Some(rx_buffer), - &config, - true, - true, - rts_pin.is_some(), - cts_pin.is_some(), - )?; - - let tx = BufferedLpuartTx { - info: T::info(), - state, - _tx_pin: tx_pin, - _cts_pin: cts_pin, - }; - - let rx = BufferedLpuartRx { - info: T::info(), - state, - _rx_pin: rx_pin, - _rts_pin: rts_pin, - }; - - Ok((tx, rx)) - } - - /// Common hardware initialization logic - fn init_hardware( - regs: &'static mcxa_pac::lpuart0::RegisterBlock, - config: Config, - clock_freq: u32, - enable_tx: bool, - enable_rx: bool, - enable_rts: bool, - enable_cts: bool, - ) -> Result<()> { - // Perform standard initialization - perform_software_reset(regs); - disable_transceiver(regs); - configure_baudrate(regs, config.baudrate_bps, clock_freq)?; - configure_frame_format(regs, &config); - configure_control_settings(regs, &config); - configure_fifo(regs, &config); - clear_all_status_flags(regs); - configure_flow_control(regs, enable_rts, enable_cts, &config); - configure_bit_order(regs, config.msb_first); - - // Enable interrupts for buffered operation - cortex_m::interrupt::free(|_| { - regs.ctrl().modify(|_, w| { - w.rie() - .enabled() // RX interrupt - .orie() - .enabled() // Overrun interrupt - .peie() - .enabled() // Parity error interrupt - .feie() - .enabled() // Framing error interrupt - .neie() - .enabled() // Noise error interrupt - }); - }); - - // Enable the transceiver - enable_transceiver(regs, enable_rx, enable_tx); - - Ok(()) - } - - /// Create a new full duplex buffered LPUART - pub fn new( - inner: Peri<'a, T>, - tx_pin: Peri<'a, impl TxPin>, - rx_pin: Peri<'a, impl RxPin>, - _irq: impl interrupt::typelevel::Binding> + 'a, - tx_buffer: &'a mut [u8], - rx_buffer: &'a mut [u8], - config: Config, - ) -> Result { - tx_pin.as_tx(); - rx_pin.as_rx(); - - let (tx, rx) = Self::new_inner::( - inner, - tx_pin.into(), - rx_pin.into(), - None, - None, - tx_buffer, - rx_buffer, - config, - )?; - - Ok(Self { tx, rx }) - } - - /// Create a new buffered LPUART instance with RTS/CTS flow control - pub fn new_with_rtscts( - inner: Peri<'a, T>, - tx_pin: Peri<'a, impl TxPin>, - rx_pin: Peri<'a, impl RxPin>, - rts_pin: Peri<'a, impl RtsPin>, - cts_pin: Peri<'a, impl CtsPin>, - _irq: impl interrupt::typelevel::Binding> + 'a, - tx_buffer: &'a mut [u8], - rx_buffer: &'a mut [u8], - config: Config, - ) -> Result { - tx_pin.as_tx(); - rx_pin.as_rx(); - rts_pin.as_rts(); - cts_pin.as_cts(); - - let (tx, rx) = Self::new_inner::( - inner, - tx_pin.into(), - rx_pin.into(), - Some(rts_pin.into()), - Some(cts_pin.into()), - tx_buffer, - rx_buffer, - config, - )?; - - Ok(Self { tx, rx }) - } - - /// Create a new buffered LPUART with only RTS flow control (RX flow control) - pub fn new_with_rts( - inner: Peri<'a, T>, - tx_pin: Peri<'a, impl TxPin>, - rx_pin: Peri<'a, impl RxPin>, - rts_pin: Peri<'a, impl RtsPin>, - _irq: impl interrupt::typelevel::Binding> + 'a, - tx_buffer: &'a mut [u8], - rx_buffer: &'a mut [u8], - config: Config, - ) -> Result { - tx_pin.as_tx(); - rx_pin.as_rx(); - rts_pin.as_rts(); - - let (tx, rx) = Self::new_inner::( - inner, - tx_pin.into(), - rx_pin.into(), - Some(rts_pin.into()), - None, - tx_buffer, - rx_buffer, - config, - )?; - - Ok(Self { tx, rx }) - } - - /// Create a new buffered LPUART with only CTS flow control (TX flow control) - pub fn new_with_cts( - inner: Peri<'a, T>, - tx_pin: Peri<'a, impl TxPin>, - rx_pin: Peri<'a, impl RxPin>, - cts_pin: Peri<'a, impl CtsPin>, - _irq: impl interrupt::typelevel::Binding> + 'a, - tx_buffer: &'a mut [u8], - rx_buffer: &'a mut [u8], - config: Config, - ) -> Result { - tx_pin.as_tx(); - rx_pin.as_rx(); - cts_pin.as_cts(); - - let (tx, rx) = Self::new_inner::( - inner, - tx_pin.into(), - rx_pin.into(), - None, - Some(cts_pin.into()), - tx_buffer, - rx_buffer, - config, - )?; - - Ok(Self { tx, rx }) - } - - /// Split the buffered LPUART into separate TX and RX parts - pub fn split(self) -> (BufferedLpuartTx<'a>, BufferedLpuartRx<'a>) { - (self.tx, self.rx) - } - - /// Get mutable references to TX and RX parts - pub fn split_ref(&mut self) -> (&mut BufferedLpuartTx<'a>, &mut BufferedLpuartRx<'a>) { - (&mut self.tx, &mut self.rx) - } -} - -// ============================================================================ -// BUFFERED TX IMPLEMENTATION -// ============================================================================ - -impl<'a> BufferedLpuartTx<'a> { - /// Helper for TX-only initialization - fn new_inner( - inner: Peri<'a, T>, - tx_pin: Peri<'a, AnyPin>, - cts_pin: Option>, - tx_buffer: &'a mut [u8], - config: Config, - ) -> Result> { - let state = BufferedLpuart::init_common::( - &inner, - Some(tx_buffer), - None, - &config, - true, - false, - false, - cts_pin.is_some(), - )?; - - Ok(BufferedLpuartTx { - info: T::info(), - state, - _tx_pin: tx_pin, - _cts_pin: cts_pin, - }) - } - - pub fn new( - inner: Peri<'a, T>, - tx_pin: Peri<'a, impl TxPin>, - _irq: impl interrupt::typelevel::Binding> + 'a, - tx_buffer: &'a mut [u8], - config: Config, - ) -> Result { - tx_pin.as_tx(); - - Self::new_inner::(inner, tx_pin.into(), None, tx_buffer, config) - } - - /// Create a new TX-only buffered LPUART with CTS flow control - pub fn new_with_cts( - inner: Peri<'a, T>, - tx_pin: Peri<'a, impl TxPin>, - cts_pin: Peri<'a, impl CtsPin>, - _irq: impl interrupt::typelevel::Binding> + 'a, - tx_buffer: &'a mut [u8], - config: Config, - ) -> Result { - tx_pin.as_tx(); - cts_pin.as_cts(); - - Self::new_inner::(inner, tx_pin.into(), Some(cts_pin.into()), tx_buffer, config) - } -} - -impl<'a> BufferedLpuartTx<'a> { - /// Write data asynchronously - pub async fn write(&mut self, buf: &[u8]) -> Result { - let mut written = 0; - - for &byte in buf { - // Wait for space in the buffer - poll_fn(|cx| { - self.state.tx_waker.register(cx.waker()); - - let mut writer = unsafe { self.state.tx_buf.writer() }; - if writer.push_one(byte) { - // Enable TX interrupt to start transmission - cortex_m::interrupt::free(|_| { - self.info.regs.ctrl().modify(|_, w| w.tie().enabled()); - }); - Poll::Ready(Ok(())) - } else { - Poll::Pending - } - }) - .await?; - - written += 1; - } - - Ok(written) - } - - /// Flush the TX buffer and wait for transmission to complete - pub async fn flush(&mut self) -> Result<()> { - // Wait for TX buffer to empty and transmission to complete - poll_fn(|cx| { - self.state.tx_waker.register(cx.waker()); - - let tx_empty = self.state.tx_buf.is_empty(); - let fifo_empty = self.info.regs.water().read().txcount().bits() == 0; - let tc_complete = self.info.regs.stat().read().tc().is_complete(); - - if tx_empty && fifo_empty && tc_complete { - Poll::Ready(Ok(())) - } else { - // Enable appropriate interrupt - cortex_m::interrupt::free(|_| { - if !tx_empty { - self.info.regs.ctrl().modify(|_, w| w.tie().enabled()); - } else { - self.info.regs.ctrl().modify(|_, w| w.tcie().enabled()); - } - }); - Poll::Pending - } - }) - .await - } - - /// Try to write without blocking - pub fn try_write(&mut self, buf: &[u8]) -> Result { - let mut writer = unsafe { self.state.tx_buf.writer() }; - let mut written = 0; - - for &byte in buf { - if writer.push_one(byte) { - written += 1; - } else { - break; - } - } - - if written > 0 { - // Enable TX interrupt to start transmission - cortex_m::interrupt::free(|_| { - self.info.regs.ctrl().modify(|_, w| w.tie().enabled()); - }); - } - - Ok(written) - } -} - -// ============================================================================ -// BUFFERED RX IMPLEMENTATION -// ============================================================================ - -impl<'a> BufferedLpuartRx<'a> { - /// Helper for RX-only initialization - fn new_inner( - inner: Peri<'a, T>, - rx_pin: Peri<'a, AnyPin>, - rts_pin: Option>, - rx_buffer: &'a mut [u8], - config: Config, - ) -> Result> { - let state = BufferedLpuart::init_common::( - &inner, - None, - Some(rx_buffer), - &config, - false, - true, - rts_pin.is_some(), - false, - )?; - - Ok(BufferedLpuartRx { - info: T::info(), - state, - _rx_pin: rx_pin, - _rts_pin: rts_pin, - }) - } - - /// Create a new RX-only buffered LPUART - pub fn new( - inner: Peri<'a, T>, - rx_pin: Peri<'a, impl RxPin>, - _irq: impl interrupt::typelevel::Binding> + 'a, - rx_buffer: &'a mut [u8], - config: Config, - ) -> Result { - rx_pin.as_rx(); - - Self::new_inner::(inner, rx_pin.into(), None, rx_buffer, config) - } - - /// Create a new RX-only buffered LPUART with RTS flow control - pub fn new_with_rts( - inner: Peri<'a, T>, - rx_pin: Peri<'a, impl RxPin>, - rts_pin: Peri<'a, impl RtsPin>, - _irq: impl interrupt::typelevel::Binding> + 'a, - rx_buffer: &'a mut [u8], - config: Config, - ) -> Result { - rx_pin.as_rx(); - rts_pin.as_rts(); - - Self::new_inner::(inner, rx_pin.into(), Some(rts_pin.into()), rx_buffer, config) - } -} - -impl<'a> BufferedLpuartRx<'a> { - /// Read data asynchronously - pub async fn read(&mut self, buf: &mut [u8]) -> Result { - if buf.is_empty() { - return Ok(0); - } - - let mut read = 0; - - // Try to read available data - poll_fn(|cx| { - self.state.rx_waker.register(cx.waker()); - - // Disable RX interrupt while reading from buffer - cortex_m::interrupt::free(|_| { - self.info.regs.ctrl().modify(|_, w| w.rie().disabled()); - }); - - let mut reader = unsafe { self.state.rx_buf.reader() }; - let available = reader.pop(|data| { - let to_copy = core::cmp::min(data.len(), buf.len() - read); - if to_copy > 0 { - buf[read..read + to_copy].copy_from_slice(&data[..to_copy]); - read += to_copy; - } - to_copy - }); - - // Re-enable RX interrupt - cortex_m::interrupt::free(|_| { - self.info.regs.ctrl().modify(|_, w| w.rie().enabled()); - }); - - if read > 0 { - Poll::Ready(Ok(read)) - } else if available == 0 { - Poll::Pending - } else { - Poll::Ready(Ok(0)) - } - }) - .await - } - - /// Try to read without blocking - pub fn try_read(&mut self, buf: &mut [u8]) -> Result { - if buf.is_empty() { - return Ok(0); - } - - // Disable RX interrupt while reading from buffer - cortex_m::interrupt::free(|_| { - self.info.regs.ctrl().modify(|_, w| w.rie().disabled()); - }); - - let mut reader = unsafe { self.state.rx_buf.reader() }; - let read = reader.pop(|data| { - let to_copy = core::cmp::min(data.len(), buf.len()); - if to_copy > 0 { - buf[..to_copy].copy_from_slice(&data[..to_copy]); - } - to_copy - }); - - // Re-enable RX interrupt - cortex_m::interrupt::free(|_| { - self.info.regs.ctrl().modify(|_, w| w.rie().enabled()); - }); - - Ok(read) - } -} - -// ============================================================================ -// INTERRUPT HANDLER -// ============================================================================ - -/// Buffered UART interrupt handler -pub struct BufferedInterruptHandler { - _phantom: PhantomData, -} - -impl crate::interrupt::typelevel::Handler for BufferedInterruptHandler { - unsafe fn on_interrupt() { - let regs = T::info().regs; - let state = T::buffered_state(); - - // Check if this instance is initialized - if !state.initialized.load(Ordering::Relaxed) { - return; - } - - let ctrl = regs.ctrl().read(); - let stat = regs.stat().read(); - let has_fifo = regs.param().read().rxfifo().bits() > 0; - - // Handle overrun error - if stat.or().is_overrun() { - regs.stat().write(|w| w.or().clear_bit_by_one()); - state.rx_waker.wake(); - return; - } - - // Clear other error flags - if stat.pf().is_parity() { - regs.stat().write(|w| w.pf().clear_bit_by_one()); - } - if stat.fe().is_error() { - regs.stat().write(|w| w.fe().clear_bit_by_one()); - } - if stat.nf().is_noise() { - regs.stat().write(|w| w.nf().clear_bit_by_one()); - } - - // Handle RX data - if ctrl.rie().is_enabled() && (has_data(regs) || stat.idle().is_idle()) { - let mut pushed_any = false; - let mut writer = state.rx_buf.writer(); - - if has_fifo { - // Read from FIFO - while regs.water().read().rxcount().bits() > 0 { - let byte = (regs.data().read().bits() & 0xFF) as u8; - if writer.push_one(byte) { - pushed_any = true; - } else { - // Buffer full, stop reading - break; - } - } - } else { - // Read single byte - if regs.stat().read().rdrf().is_rxdata() { - let byte = (regs.data().read().bits() & 0xFF) as u8; - if writer.push_one(byte) { - pushed_any = true; - } - } - } - - if pushed_any { - state.rx_waker.wake(); - } - - // Clear idle flag if set - if stat.idle().is_idle() { - regs.stat().write(|w| w.idle().clear_bit_by_one()); - } - } - - // Handle TX data - if ctrl.tie().is_enabled() { - let mut sent_any = false; - let mut reader = state.tx_buf.reader(); - - // Send data while TX buffer is ready and we have data - while regs.stat().read().tdre().is_no_txdata() { - if let Some(byte) = reader.pop_one() { - regs.data().write(|w| w.bits(u32::from(byte))); - sent_any = true; - } else { - // No more data to send - break; - } - } - - if sent_any { - state.tx_waker.wake(); - } - - // If buffer is empty, switch to TC interrupt or disable - if state.tx_buf.is_empty() { - cortex_m::interrupt::free(|_| { - regs.ctrl().modify(|_, w| w.tie().disabled().tcie().enabled()); - }); - } - } - - // Handle transmission complete - if ctrl.tcie().is_enabled() && regs.stat().read().tc().is_complete() { - state.tx_done.store(true, Ordering::Release); - state.tx_waker.wake(); - - // Disable TC interrupt - cortex_m::interrupt::free(|_| { - regs.ctrl().modify(|_, w| w.tcie().disabled()); - }); - } - } -} - -// ============================================================================ -// EMBEDDED-IO ASYNC TRAIT IMPLEMENTATIONS -// ============================================================================ - -impl embedded_io_async::ErrorType for BufferedLpuartTx<'_> { - type Error = Error; -} - -impl embedded_io_async::ErrorType for BufferedLpuartRx<'_> { - type Error = Error; -} - -impl embedded_io_async::ErrorType for BufferedLpuart<'_> { - type Error = Error; -} - -impl embedded_io_async::Write for BufferedLpuartTx<'_> { - async fn write(&mut self, buf: &[u8]) -> core::result::Result { - self.write(buf).await - } - - async fn flush(&mut self) -> core::result::Result<(), Self::Error> { - self.flush().await - } -} - -impl embedded_io_async::Read for BufferedLpuartRx<'_> { - async fn read(&mut self, buf: &mut [u8]) -> core::result::Result { - self.read(buf).await - } -} - -impl embedded_io_async::Write for BufferedLpuart<'_> { - async fn write(&mut self, buf: &[u8]) -> core::result::Result { - self.tx.write(buf).await - } - - async fn flush(&mut self) -> core::result::Result<(), Self::Error> { - self.tx.flush().await - } -} - -impl embedded_io_async::Read for BufferedLpuart<'_> { - async fn read(&mut self, buf: &mut [u8]) -> core::result::Result { - self.rx.read(buf).await - } -} diff --git a/src/lpuart/mod.rs b/src/lpuart/mod.rs deleted file mode 100644 index 5722b759c..000000000 --- a/src/lpuart/mod.rs +++ /dev/null @@ -1,1733 +0,0 @@ -use core::marker::PhantomData; - -use embassy_hal_internal::{Peri, PeripheralType}; -use paste::paste; - -use crate::clocks::periph_helpers::{Div4, LpuartClockSel, LpuartConfig}; -use crate::clocks::{enable_and_reset, ClockError, Gate, PoweredClock}; -use crate::gpio::SealedPin; -use crate::pac::lpuart0::baud::Sbns as StopBits; -use crate::pac::lpuart0::ctrl::{Idlecfg as IdleConfig, Ilt as IdleType, Pt as Parity, M as DataBits}; -use crate::pac::lpuart0::modir::{Txctsc as TxCtsConfig, Txctssrc as TxCtsSource}; -use crate::pac::lpuart0::stat::Msbf as MsbFirst; -use crate::{interrupt, pac, AnyPin}; - -pub mod buffered; - -// ============================================================================ -// DMA INTEGRATION -// ============================================================================ - -use crate::dma::{ - Channel as DmaChannelTrait, DmaChannel, DmaRequest, EnableInterrupt, RingBuffer, DMA_MAX_TRANSFER_SIZE, -}; - -// ============================================================================ -// MISC -// ============================================================================ - -mod sealed { - /// Simply seal a trait to prevent external implementations - pub trait Sealed {} -} - -// ============================================================================ -// INSTANCE TRAIT -// ============================================================================ - -pub type Regs = &'static crate::pac::lpuart0::RegisterBlock; - -pub trait SealedInstance { - fn info() -> Info; - fn index() -> usize; - fn buffered_state() -> &'static buffered::State; -} - -pub struct Info { - pub regs: Regs, -} - -/// Trait for LPUART peripheral instances -#[allow(private_bounds)] -pub trait Instance: SealedInstance + PeripheralType + 'static + Send + Gate { - const CLOCK_INSTANCE: crate::clocks::periph_helpers::LpuartInstance; - type Interrupt: interrupt::typelevel::Interrupt; - /// Type-safe DMA request source for TX - type TxDmaRequest: DmaRequest; - /// Type-safe DMA request source for RX - type RxDmaRequest: DmaRequest; -} - -macro_rules! impl_instance { - ($($n:expr);* $(;)?) => { - $( - paste!{ - impl SealedInstance for crate::peripherals::[] { - fn info() -> Info { - Info { - regs: unsafe { &*pac::[]::ptr() }, - } - } - - #[inline] - fn index() -> usize { - $n - } - - fn buffered_state() -> &'static buffered::State { - static BUFFERED_STATE: buffered::State = buffered::State::new(); - &BUFFERED_STATE - } - } - - impl Instance for crate::peripherals::[] { - const CLOCK_INSTANCE: crate::clocks::periph_helpers::LpuartInstance - = crate::clocks::periph_helpers::LpuartInstance::[]; - type Interrupt = crate::interrupt::typelevel::[]; - type TxDmaRequest = crate::dma::[]; - type RxDmaRequest = crate::dma::[]; - } - } - )* - }; -} - -// DMA request sources are now type-safe via associated types. -// The request source numbers are defined in src/dma.rs: -// LPUART0: RX=21, TX=22 -> Lpuart0RxRequest, Lpuart0TxRequest -// LPUART1: RX=23, TX=24 -> Lpuart1RxRequest, Lpuart1TxRequest -// LPUART2: RX=25, TX=26 -> Lpuart2RxRequest, Lpuart2TxRequest -// LPUART3: RX=27, TX=28 -> Lpuart3RxRequest, Lpuart3TxRequest -// LPUART4: RX=29, TX=30 -> Lpuart4RxRequest, Lpuart4TxRequest -// LPUART5: RX=31, TX=32 -> Lpuart5RxRequest, Lpuart5TxRequest -impl_instance!(0; 1; 2; 3; 4; 5); - -// ============================================================================ -// INSTANCE HELPER FUNCTIONS -// ============================================================================ - -/// Perform software reset on the LPUART peripheral -pub fn perform_software_reset(regs: Regs) { - // Software reset - set and clear RST bit (Global register) - regs.global().write(|w| w.rst().reset()); - regs.global().write(|w| w.rst().no_effect()); -} - -/// Disable both transmitter and receiver -pub fn disable_transceiver(regs: Regs) { - regs.ctrl().modify(|_, w| w.te().disabled().re().disabled()); -} - -/// Calculate and configure baudrate settings -pub fn configure_baudrate(regs: Regs, baudrate_bps: u32, clock_freq: u32) -> Result<()> { - let (osr, sbr) = calculate_baudrate(baudrate_bps, clock_freq)?; - - // Configure BAUD register - regs.baud().modify(|_, w| unsafe { - // Clear and set OSR - w.osr().bits(osr - 1); - // Clear and set SBR - w.sbr().bits(sbr); - // Set BOTHEDGE if OSR is between 4 and 7 - if osr > 3 && osr < 8 { - w.bothedge().enabled() - } else { - w.bothedge().disabled() - } - }); - - Ok(()) -} - -/// Configure frame format (stop bits, data bits) -pub fn configure_frame_format(regs: Regs, config: &Config) { - // Configure stop bits - regs.baud().modify(|_, w| w.sbns().variant(config.stop_bits_count)); - - // Clear M10 for now (10-bit mode) - regs.baud().modify(|_, w| w.m10().disabled()); -} - -/// Configure control settings (parity, data bits, idle config, pin swap) -pub fn configure_control_settings(regs: Regs, config: &Config) { - regs.ctrl().modify(|_, w| { - // Parity configuration - let mut w = if let Some(parity) = config.parity_mode { - w.pe().enabled().pt().variant(parity) - } else { - w.pe().disabled() - }; - - // Data bits configuration - w = match config.data_bits_count { - DataBits::Data8 => { - if config.parity_mode.is_some() { - w.m().data9() // 8 data + 1 parity = 9 bits - } else { - w.m().data8() // 8 data bits only - } - } - DataBits::Data9 => w.m().data9(), - }; - - // Idle configuration - w = w.idlecfg().variant(config.rx_idle_config); - w = w.ilt().variant(config.rx_idle_type); - - // Swap TXD/RXD if configured - if config.swap_txd_rxd { - w.swap().swap() - } else { - w.swap().standard() - } - }); -} - -/// Configure FIFO settings and watermarks -pub fn configure_fifo(regs: Regs, config: &Config) { - // Configure WATER register for FIFO watermarks - regs.water().write(|w| unsafe { - w.rxwater() - .bits(config.rx_fifo_watermark) - .txwater() - .bits(config.tx_fifo_watermark) - }); - - // Enable TX/RX FIFOs - regs.fifo().modify(|_, w| w.txfe().enabled().rxfe().enabled()); - - // Flush FIFOs - regs.fifo() - .modify(|_, w| w.txflush().txfifo_rst().rxflush().rxfifo_rst()); -} - -/// Clear all status flags -pub fn clear_all_status_flags(regs: Regs) { - regs.stat().reset(); -} - -/// Configure hardware flow control if enabled -pub fn configure_flow_control(regs: Regs, enable_tx_cts: bool, enable_rx_rts: bool, config: &Config) { - if enable_rx_rts || enable_tx_cts { - regs.modir().modify(|_, w| { - let mut w = w; - - // Configure TX CTS - w = w.txctsc().variant(config.tx_cts_config); - w = w.txctssrc().variant(config.tx_cts_source); - - if enable_rx_rts { - w = w.rxrtse().enabled(); - } else { - w = w.rxrtse().disabled(); - } - - if enable_tx_cts { - w = w.txctse().enabled(); - } else { - w = w.txctse().disabled(); - } - - w - }); - } -} - -/// Configure bit order (MSB first or LSB first) -pub fn configure_bit_order(regs: Regs, msb_first: MsbFirst) { - regs.stat().modify(|_, w| w.msbf().variant(msb_first)); -} - -/// Enable transmitter and/or receiver based on configuration -pub fn enable_transceiver(regs: Regs, enable_tx: bool, enable_rx: bool) { - regs.ctrl().modify(|_, w| { - let mut w = w; - if enable_tx { - w = w.te().enabled(); - } - if enable_rx { - w = w.re().enabled(); - } - w - }); -} - -pub fn calculate_baudrate(baudrate: u32, src_clock_hz: u32) -> Result<(u8, u16)> { - let mut baud_diff = baudrate; - let mut osr = 0u8; - let mut sbr = 0u16; - - // Try OSR values from 4 to 32 - for osr_temp in 4u8..=32u8 { - // Calculate SBR: (srcClock_Hz * 2 / (baudRate * osr) + 1) / 2 - let sbr_calc = ((src_clock_hz * 2) / (baudrate * osr_temp as u32)).div_ceil(2); - - let sbr_temp = if sbr_calc == 0 { - 1 - } else if sbr_calc > 0x1FFF { - 0x1FFF - } else { - sbr_calc as u16 - }; - - // Calculate actual baud rate - let calculated_baud = src_clock_hz / (osr_temp as u32 * sbr_temp as u32); - - let temp_diff = calculated_baud.abs_diff(baudrate); - - if temp_diff <= baud_diff { - baud_diff = temp_diff; - osr = osr_temp; - sbr = sbr_temp; - } - } - - // Check if baud rate difference is within 3% - if baud_diff > (baudrate / 100) * 3 { - return Err(Error::UnsupportedBaudrate); - } - - Ok((osr, sbr)) -} - -/// Wait for all transmit operations to complete -pub fn wait_for_tx_complete(regs: Regs) { - // Wait for TX FIFO to empty - while regs.water().read().txcount().bits() != 0 { - // Wait for TX FIFO to drain - } - - // Wait for last character to shift out (TC = Transmission Complete) - while regs.stat().read().tc().is_active() { - // Wait for transmission to complete - } -} - -pub fn check_and_clear_rx_errors(regs: Regs) -> Result<()> { - let stat = regs.stat().read(); - let mut status = Ok(()); - - // Check for overrun first - other error flags are prevented when OR is set - if stat.or().is_overrun() { - regs.stat().write(|w| w.or().clear_bit_by_one()); - - return Err(Error::Overrun); - } - - if stat.pf().is_parity() { - regs.stat().write(|w| w.pf().clear_bit_by_one()); - status = Err(Error::Parity); - } - - if stat.fe().is_error() { - regs.stat().write(|w| w.fe().clear_bit_by_one()); - status = Err(Error::Framing); - } - - if stat.nf().is_noise() { - regs.stat().write(|w| w.nf().clear_bit_by_one()); - status = Err(Error::Noise); - } - - status -} - -pub fn has_data(regs: Regs) -> bool { - if regs.param().read().rxfifo().bits() > 0 { - // FIFO is available - check RXCOUNT in WATER register - regs.water().read().rxcount().bits() > 0 - } else { - // No FIFO - check RDRF flag in STAT register - regs.stat().read().rdrf().is_rxdata() - } -} - -// ============================================================================ -// PIN TRAITS FOR LPUART FUNCTIONALITY -// ============================================================================ - -impl sealed::Sealed for T {} - -/// io configuration trait for Lpuart Tx configuration -pub trait TxPin: Into + sealed::Sealed + PeripheralType { - /// convert the pin to appropriate function for Lpuart Tx usage - fn as_tx(&self); -} - -/// io configuration trait for Lpuart Rx configuration -pub trait RxPin: Into + sealed::Sealed + PeripheralType { - /// convert the pin to appropriate function for Lpuart Rx usage - fn as_rx(&self); -} - -/// io configuration trait for Lpuart Cts -pub trait CtsPin: Into + sealed::Sealed + PeripheralType { - /// convert the pin to appropriate function for Lpuart Cts usage - fn as_cts(&self); -} - -/// io configuration trait for Lpuart Rts -pub trait RtsPin: Into + sealed::Sealed + PeripheralType { - /// convert the pin to appropriate function for Lpuart Rts usage - fn as_rts(&self); -} - -macro_rules! impl_tx_pin { - ($inst:ident, $pin:ident, $alt:ident) => { - impl TxPin for crate::peripherals::$pin { - fn as_tx(&self) { - // TODO: Check these are right - self.set_pull(crate::gpio::Pull::Up); - self.set_slew_rate(crate::gpio::SlewRate::Fast.into()); - self.set_drive_strength(crate::gpio::DriveStrength::Double.into()); - self.set_function(crate::pac::port0::pcr0::Mux::$alt); - self.set_enable_input_buffer(); - } - } - }; -} - -macro_rules! impl_rx_pin { - ($inst:ident, $pin:ident, $alt:ident) => { - impl RxPin for crate::peripherals::$pin { - fn as_rx(&self) { - // TODO: Check these are right - self.set_pull(crate::gpio::Pull::Up); - self.set_slew_rate(crate::gpio::SlewRate::Fast.into()); - self.set_drive_strength(crate::gpio::DriveStrength::Double.into()); - self.set_function(crate::pac::port0::pcr0::Mux::$alt); - self.set_enable_input_buffer(); - } - } - }; -} - -// TODO: Macro and impls for CTS/RTS pins -macro_rules! impl_cts_pin { - ($inst:ident, $pin:ident, $alt:ident) => { - impl CtsPin for crate::peripherals::$pin { - fn as_cts(&self) { - todo!() - } - } - }; -} - -macro_rules! impl_rts_pin { - ($inst:ident, $pin:ident, $alt:ident) => { - impl RtsPin for crate::peripherals::$pin { - fn as_rts(&self) { - todo!() - } - } - }; -} - -// LPUART 0 -impl_tx_pin!(LPUART0, P0_3, Mux2); -impl_tx_pin!(LPUART0, P0_21, Mux3); -impl_tx_pin!(LPUART0, P2_1, Mux2); - -impl_rx_pin!(LPUART0, P0_2, Mux2); -impl_rx_pin!(LPUART0, P0_20, Mux3); -impl_rx_pin!(LPUART0, P2_0, Mux2); - -impl_cts_pin!(LPUART0, P0_1, Mux2); -impl_cts_pin!(LPUART0, P0_23, Mux3); -impl_cts_pin!(LPUART0, P2_3, Mux2); - -impl_rts_pin!(LPUART0, P0_0, Mux2); -impl_rts_pin!(LPUART0, P0_22, Mux3); -impl_rts_pin!(LPUART0, P2_2, Mux2); - -// LPUART 1 -impl_tx_pin!(LPUART1, P1_9, Mux2); -impl_tx_pin!(LPUART1, P2_13, Mux3); -impl_tx_pin!(LPUART1, P3_9, Mux3); -impl_tx_pin!(LPUART1, P3_21, Mux3); - -impl_rx_pin!(LPUART1, P1_8, Mux2); -impl_rx_pin!(LPUART1, P2_12, Mux3); -impl_rx_pin!(LPUART1, P3_8, Mux3); -impl_rx_pin!(LPUART1, P3_20, Mux3); - -impl_cts_pin!(LPUART1, P1_11, Mux2); -impl_cts_pin!(LPUART1, P2_17, Mux3); -impl_cts_pin!(LPUART1, P3_11, Mux3); -impl_cts_pin!(LPUART1, P3_23, Mux3); - -impl_rts_pin!(LPUART1, P1_10, Mux2); -impl_rts_pin!(LPUART1, P2_15, Mux3); -impl_rts_pin!(LPUART1, P2_16, Mux3); -impl_rts_pin!(LPUART1, P3_10, Mux3); - -// LPUART 2 -impl_tx_pin!(LPUART2, P1_5, Mux3); -impl_tx_pin!(LPUART2, P1_13, Mux3); -impl_tx_pin!(LPUART2, P2_2, Mux3); -impl_tx_pin!(LPUART2, P2_10, Mux3); -impl_tx_pin!(LPUART2, P3_15, Mux2); - -impl_rx_pin!(LPUART2, P1_4, Mux3); -impl_rx_pin!(LPUART2, P1_12, Mux3); -impl_rx_pin!(LPUART2, P2_3, Mux3); -impl_rx_pin!(LPUART2, P2_11, Mux3); -impl_rx_pin!(LPUART2, P3_14, Mux2); - -impl_cts_pin!(LPUART2, P1_7, Mux3); -impl_cts_pin!(LPUART2, P1_15, Mux3); -impl_cts_pin!(LPUART2, P2_4, Mux3); -impl_cts_pin!(LPUART2, P3_13, Mux2); - -impl_rts_pin!(LPUART2, P1_6, Mux3); -impl_rts_pin!(LPUART2, P1_14, Mux3); -impl_rts_pin!(LPUART2, P2_5, Mux3); -impl_rts_pin!(LPUART2, P3_12, Mux2); - -// LPUART 3 -impl_tx_pin!(LPUART3, P3_1, Mux3); -impl_tx_pin!(LPUART3, P3_12, Mux3); -impl_tx_pin!(LPUART3, P4_5, Mux3); - -impl_rx_pin!(LPUART3, P3_0, Mux3); -impl_rx_pin!(LPUART3, P3_13, Mux3); -impl_rx_pin!(LPUART3, P4_2, Mux3); - -impl_cts_pin!(LPUART3, P3_7, Mux3); -impl_cts_pin!(LPUART3, P3_14, Mux3); -impl_cts_pin!(LPUART3, P4_6, Mux3); - -impl_rts_pin!(LPUART3, P3_6, Mux3); -impl_rts_pin!(LPUART3, P3_15, Mux3); -impl_rts_pin!(LPUART3, P4_7, Mux3); - -// LPUART 4 -impl_tx_pin!(LPUART4, P2_7, Mux3); -impl_tx_pin!(LPUART4, P3_19, Mux2); -impl_tx_pin!(LPUART4, P3_27, Mux3); -impl_tx_pin!(LPUART4, P4_3, Mux3); - -impl_rx_pin!(LPUART4, P2_6, Mux3); -impl_rx_pin!(LPUART4, P3_18, Mux2); -impl_rx_pin!(LPUART4, P3_28, Mux3); -impl_rx_pin!(LPUART4, P4_4, Mux3); - -impl_cts_pin!(LPUART4, P2_0, Mux3); -impl_cts_pin!(LPUART4, P3_17, Mux2); -impl_cts_pin!(LPUART4, P3_31, Mux3); - -impl_rts_pin!(LPUART4, P2_1, Mux3); -impl_rts_pin!(LPUART4, P3_16, Mux2); -impl_rts_pin!(LPUART4, P3_30, Mux3); - -// LPUART 5 -impl_tx_pin!(LPUART5, P1_10, Mux8); -impl_tx_pin!(LPUART5, P1_17, Mux8); - -impl_rx_pin!(LPUART5, P1_11, Mux8); -impl_rx_pin!(LPUART5, P1_16, Mux8); - -impl_cts_pin!(LPUART5, P1_12, Mux8); -impl_cts_pin!(LPUART5, P1_19, Mux8); - -impl_rts_pin!(LPUART5, P1_13, Mux8); -impl_rts_pin!(LPUART5, P1_18, Mux8); - -// ============================================================================ -// ERROR TYPES AND RESULTS -// ============================================================================ - -/// LPUART error types -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - /// Read error - Read, - /// Buffer overflow - Overrun, - /// Noise error - Noise, - /// Framing error - Framing, - /// Parity error - Parity, - /// Failure - Fail, - /// Invalid argument - InvalidArgument, - /// Lpuart baud rate cannot be supported with the given clock - UnsupportedBaudrate, - /// RX FIFO Empty - RxFifoEmpty, - /// TX FIFO Full - TxFifoFull, - /// TX Busy - TxBusy, - /// Clock Error - ClockSetup(ClockError), -} - -/// A specialized Result type for LPUART operations -pub type Result = core::result::Result; - -// ============================================================================ -// CONFIGURATION STRUCTURES -// ============================================================================ - -/// Lpuart config -#[derive(Debug, Clone, Copy)] -pub struct Config { - /// Power state required for this peripheral - pub power: PoweredClock, - /// Clock source - pub source: LpuartClockSel, - /// Clock divisor - pub div: Div4, - /// Baud rate in bits per second - pub baudrate_bps: u32, - /// Parity configuration - pub parity_mode: Option, - /// Number of data bits - pub data_bits_count: DataBits, - /// MSB First or LSB First configuration - pub msb_first: MsbFirst, - /// Number of stop bits - pub stop_bits_count: StopBits, - /// TX FIFO watermark - pub tx_fifo_watermark: u8, - /// RX FIFO watermark - pub rx_fifo_watermark: u8, - /// TX CTS source - pub tx_cts_source: TxCtsSource, - /// TX CTS configure - pub tx_cts_config: TxCtsConfig, - /// RX IDLE type - pub rx_idle_type: IdleType, - /// RX IDLE configuration - pub rx_idle_config: IdleConfig, - /// Swap TXD and RXD pins - pub swap_txd_rxd: bool, -} - -impl Default for Config { - fn default() -> Self { - Self { - baudrate_bps: 115_200u32, - parity_mode: None, - data_bits_count: DataBits::Data8, - msb_first: MsbFirst::LsbFirst, - stop_bits_count: StopBits::One, - tx_fifo_watermark: 0, - rx_fifo_watermark: 1, - tx_cts_source: TxCtsSource::Cts, - tx_cts_config: TxCtsConfig::Start, - rx_idle_type: IdleType::FromStart, - rx_idle_config: IdleConfig::Idle1, - swap_txd_rxd: false, - power: PoweredClock::NormalEnabledDeepSleepDisabled, - source: LpuartClockSel::FroLfDiv, - div: Div4::no_div(), - } - } -} - -/// LPUART status flags -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Status { - /// Transmit data register empty - pub tx_empty: bool, - /// Transmission complete - pub tx_complete: bool, - /// Receive data register full - pub rx_full: bool, - /// Idle line detected - pub idle: bool, - /// Receiver overrun - pub overrun: bool, - /// Noise error - pub noise: bool, - /// Framing error - pub framing: bool, - /// Parity error - pub parity: bool, -} - -// ============================================================================ -// MODE TRAITS (BLOCKING/ASYNC) -// ============================================================================ - -/// Driver move trait. -#[allow(private_bounds)] -pub trait Mode: sealed::Sealed {} - -/// Blocking mode. -pub struct Blocking; -impl sealed::Sealed for Blocking {} -impl Mode for Blocking {} - -/// Async mode. -pub struct Async; -impl sealed::Sealed for Async {} -impl Mode for Async {} - -// ============================================================================ -// CORE DRIVER STRUCTURES -// ============================================================================ - -/// Lpuart driver. -pub struct Lpuart<'a, M: Mode> { - info: Info, - tx: LpuartTx<'a, M>, - rx: LpuartRx<'a, M>, -} - -/// Lpuart TX driver. -pub struct LpuartTx<'a, M: Mode> { - info: Info, - _tx_pin: Peri<'a, AnyPin>, - _cts_pin: Option>, - mode: PhantomData<(&'a (), M)>, -} - -/// Lpuart Rx driver. -pub struct LpuartRx<'a, M: Mode> { - info: Info, - _rx_pin: Peri<'a, AnyPin>, - _rts_pin: Option>, - mode: PhantomData<(&'a (), M)>, -} - -/// Lpuart TX driver with DMA support. -pub struct LpuartTxDma<'a, T: Instance, C: DmaChannelTrait> { - info: Info, - _tx_pin: Peri<'a, AnyPin>, - tx_dma: DmaChannel, - _instance: core::marker::PhantomData, -} - -/// Lpuart RX driver with DMA support. -pub struct LpuartRxDma<'a, T: Instance, C: DmaChannelTrait> { - info: Info, - _rx_pin: Peri<'a, AnyPin>, - rx_dma: DmaChannel, - _instance: core::marker::PhantomData, -} - -/// Lpuart driver with DMA support for both TX and RX. -pub struct LpuartDma<'a, T: Instance, TxC: DmaChannelTrait, RxC: DmaChannelTrait> { - tx: LpuartTxDma<'a, T, TxC>, - rx: LpuartRxDma<'a, T, RxC>, -} - -// ============================================================================ -// LPUART CORE IMPLEMENTATION -// ============================================================================ - -impl<'a, M: Mode> Lpuart<'a, M> { - fn init( - enable_tx: bool, - enable_rx: bool, - enable_tx_cts: bool, - enable_rx_rts: bool, - config: Config, - ) -> Result<()> { - let regs = T::info().regs; - - // Enable clocks - let conf = LpuartConfig { - power: config.power, - source: config.source, - div: config.div, - instance: T::CLOCK_INSTANCE, - }; - let clock_freq = unsafe { enable_and_reset::(&conf).map_err(Error::ClockSetup)? }; - - // Perform initialization sequence - perform_software_reset(regs); - disable_transceiver(regs); - configure_baudrate(regs, config.baudrate_bps, clock_freq)?; - configure_frame_format(regs, &config); - configure_control_settings(regs, &config); - configure_fifo(regs, &config); - clear_all_status_flags(regs); - configure_flow_control(regs, enable_tx_cts, enable_rx_rts, &config); - configure_bit_order(regs, config.msb_first); - enable_transceiver(regs, enable_rx, enable_tx); - - Ok(()) - } - - /// Deinitialize the LPUART peripheral - pub fn deinit(&self) -> Result<()> { - let regs = self.info.regs; - - // Wait for TX operations to complete - wait_for_tx_complete(regs); - - // Clear all status flags - clear_all_status_flags(regs); - - // Disable the module - clear all CTRL register bits - regs.ctrl().reset(); - - Ok(()) - } - - /// Split the Lpuart into a transmitter and receiver - pub fn split(self) -> (LpuartTx<'a, M>, LpuartRx<'a, M>) { - (self.tx, self.rx) - } - - /// Split the Lpuart into a transmitter and receiver by mutable reference - pub fn split_ref(&mut self) -> (&mut LpuartTx<'a, M>, &mut LpuartRx<'a, M>) { - (&mut self.tx, &mut self.rx) - } -} - -// ============================================================================ -// BLOCKING MODE IMPLEMENTATIONS -// ============================================================================ - -impl<'a> Lpuart<'a, Blocking> { - /// Create a new blocking LPUART instance with RX/TX pins. - pub fn new_blocking( - _inner: Peri<'a, T>, - tx_pin: Peri<'a, impl TxPin>, - rx_pin: Peri<'a, impl RxPin>, - config: Config, - ) -> Result { - // Configure the pins for LPUART usage - tx_pin.as_tx(); - rx_pin.as_rx(); - - // Initialize the peripheral - Self::init::(true, true, false, false, config)?; - - Ok(Self { - info: T::info(), - tx: LpuartTx::new_inner(T::info(), tx_pin.into(), None), - rx: LpuartRx::new_inner(T::info(), rx_pin.into(), None), - }) - } - - /// Create a new blocking LPUART instance with RX, TX and RTS/CTS flow control pins - pub fn new_blocking_with_rtscts( - _inner: Peri<'a, T>, - tx_pin: Peri<'a, impl TxPin>, - rx_pin: Peri<'a, impl RxPin>, - cts_pin: Peri<'a, impl CtsPin>, - rts_pin: Peri<'a, impl RtsPin>, - config: Config, - ) -> Result { - // Configure the pins for LPUART usage - rx_pin.as_rx(); - tx_pin.as_tx(); - rts_pin.as_rts(); - cts_pin.as_cts(); - - // Initialize the peripheral with flow control - Self::init::(true, true, true, true, config)?; - - Ok(Self { - info: T::info(), - rx: LpuartRx::new_inner(T::info(), rx_pin.into(), Some(rts_pin.into())), - tx: LpuartTx::new_inner(T::info(), tx_pin.into(), Some(cts_pin.into())), - }) - } -} - -// ---------------------------------------------------------------------------- -// Blocking TX Implementation -// ---------------------------------------------------------------------------- - -impl<'a, M: Mode> LpuartTx<'a, M> { - fn new_inner(info: Info, tx_pin: Peri<'a, AnyPin>, cts_pin: Option>) -> Self { - Self { - info, - _tx_pin: tx_pin, - _cts_pin: cts_pin, - mode: PhantomData, - } - } -} - -impl<'a> LpuartTx<'a, Blocking> { - /// Create a new blocking LPUART transmitter instance - pub fn new_blocking( - _inner: Peri<'a, T>, - tx_pin: Peri<'a, impl TxPin>, - config: Config, - ) -> Result { - // Configure the pins for LPUART usage - tx_pin.as_tx(); - - // Initialize the peripheral - Lpuart::::init::(true, false, false, false, config)?; - - Ok(Self::new_inner(T::info(), tx_pin.into(), None)) - } - - /// Create a new blocking LPUART transmitter instance with CTS flow control - pub fn new_blocking_with_cts( - _inner: Peri<'a, T>, - tx_pin: Peri<'a, impl TxPin>, - cts_pin: Peri<'a, impl CtsPin>, - config: Config, - ) -> Result { - tx_pin.as_tx(); - cts_pin.as_cts(); - - Lpuart::::init::(true, false, true, false, config)?; - - Ok(Self::new_inner(T::info(), tx_pin.into(), Some(cts_pin.into()))) - } - - fn write_byte_internal(&mut self, byte: u8) -> Result<()> { - self.info.regs.data().modify(|_, w| unsafe { w.bits(u32::from(byte)) }); - - Ok(()) - } - - fn blocking_write_byte(&mut self, byte: u8) -> Result<()> { - while self.info.regs.stat().read().tdre().is_txdata() {} - self.write_byte_internal(byte) - } - - fn write_byte(&mut self, byte: u8) -> Result<()> { - if self.info.regs.stat().read().tdre().is_txdata() { - Err(Error::TxFifoFull) - } else { - self.write_byte_internal(byte) - } - } - - /// Write data to LPUART TX blocking execution until all data is sent. - pub fn blocking_write(&mut self, buf: &[u8]) -> Result<()> { - for x in buf { - self.blocking_write_byte(*x)?; - } - - Ok(()) - } - - pub fn write_str_blocking(&mut self, buf: &str) { - let _ = self.blocking_write(buf.as_bytes()); - } - - /// Write data to LPUART TX without blocking. - pub fn write(&mut self, buf: &[u8]) -> Result<()> { - for x in buf { - self.write_byte(*x)?; - } - - Ok(()) - } - - /// Flush LPUART TX blocking execution until all data has been transmitted. - pub fn blocking_flush(&mut self) -> Result<()> { - while self.info.regs.water().read().txcount().bits() != 0 { - // Wait for TX FIFO to drain - } - - // Wait for last character to shift out - while self.info.regs.stat().read().tc().is_active() { - // Wait for transmission to complete - } - - Ok(()) - } - - /// Flush LPUART TX. - pub fn flush(&mut self) -> Result<()> { - // Check if TX FIFO is empty - if self.info.regs.water().read().txcount().bits() != 0 { - return Err(Error::TxBusy); - } - - // Check if transmission is complete - if self.info.regs.stat().read().tc().is_active() { - return Err(Error::TxBusy); - } - - Ok(()) - } -} - -// ---------------------------------------------------------------------------- -// Blocking RX Implementation -// ---------------------------------------------------------------------------- - -impl<'a, M: Mode> LpuartRx<'a, M> { - fn new_inner(info: Info, rx_pin: Peri<'a, AnyPin>, rts_pin: Option>) -> Self { - Self { - info, - _rx_pin: rx_pin, - _rts_pin: rts_pin, - mode: PhantomData, - } - } -} - -impl<'a> LpuartRx<'a, Blocking> { - /// Create a new blocking LPUART Receiver instance - pub fn new_blocking( - _inner: Peri<'a, T>, - rx_pin: Peri<'a, impl RxPin>, - config: Config, - ) -> Result { - rx_pin.as_rx(); - - Lpuart::::init::(false, true, false, false, config)?; - - Ok(Self::new_inner(T::info(), rx_pin.into(), None)) - } - - /// Create a new blocking LPUART Receiver instance with RTS flow control - pub fn new_blocking_with_rts( - _inner: Peri<'a, T>, - rx_pin: Peri<'a, impl RxPin>, - rts_pin: Peri<'a, impl RtsPin>, - config: Config, - ) -> Result { - rx_pin.as_rx(); - rts_pin.as_rts(); - - Lpuart::::init::(false, true, false, true, config)?; - - Ok(Self::new_inner(T::info(), rx_pin.into(), Some(rts_pin.into()))) - } - - fn read_byte_internal(&mut self) -> Result { - let data = self.info.regs.data().read(); - - Ok((data.bits() & 0xFF) as u8) - } - - fn read_byte(&mut self) -> Result { - check_and_clear_rx_errors(self.info.regs)?; - - if !has_data(self.info.regs) { - return Err(Error::RxFifoEmpty); - } - - self.read_byte_internal() - } - - fn blocking_read_byte(&mut self) -> Result { - loop { - if has_data(self.info.regs) { - return self.read_byte_internal(); - } - - check_and_clear_rx_errors(self.info.regs)?; - } - } - - /// Read data from LPUART RX without blocking. - pub fn read(&mut self, buf: &mut [u8]) -> Result<()> { - for byte in buf.iter_mut() { - *byte = self.read_byte()?; - } - Ok(()) - } - - /// Read data from LPUART RX blocking execution until the buffer is filled. - pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<()> { - for byte in buf.iter_mut() { - *byte = self.blocking_read_byte()?; - } - Ok(()) - } -} - -impl<'a> Lpuart<'a, Blocking> { - /// Read data from LPUART RX blocking execution until the buffer is filled - pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<()> { - self.rx.blocking_read(buf) - } - - /// Read data from LPUART RX without blocking - pub fn read(&mut self, buf: &mut [u8]) -> Result<()> { - self.rx.read(buf) - } - - /// Write data to LPUART TX blocking execution until all data is sent - pub fn blocking_write(&mut self, buf: &[u8]) -> Result<()> { - self.tx.blocking_write(buf) - } - - pub fn write_byte(&mut self, byte: u8) -> Result<()> { - self.tx.write_byte(byte) - } - - pub fn read_byte_blocking(&mut self) -> u8 { - loop { - if let Ok(b) = self.rx.read_byte() { - return b; - } - } - } - - pub fn write_str_blocking(&mut self, buf: &str) { - self.tx.write_str_blocking(buf); - } - - /// Write data to LPUART TX without blocking - pub fn write(&mut self, buf: &[u8]) -> Result<()> { - self.tx.write(buf) - } - - /// Flush LPUART TX blocking execution until all data has been transmitted - pub fn blocking_flush(&mut self) -> Result<()> { - self.tx.blocking_flush() - } - - /// Flush LPUART TX without blocking - pub fn flush(&mut self) -> Result<()> { - self.tx.flush() - } -} - -// ============================================================================ -// ASYNC MODE IMPLEMENTATIONS (DMA-based) -// ============================================================================ - -/// Guard struct that ensures DMA is stopped if the async future is cancelled. -/// -/// This implements the RAII pattern: if the future is dropped before completion -/// (e.g., due to a timeout), the DMA transfer is automatically aborted to prevent -/// use-after-free when the buffer goes out of scope. -struct TxDmaGuard<'a, C: DmaChannelTrait> { - dma: &'a DmaChannel, - regs: Regs, -} - -impl<'a, C: DmaChannelTrait> TxDmaGuard<'a, C> { - fn new(dma: &'a DmaChannel, regs: Regs) -> Self { - Self { dma, regs } - } - - /// Complete the transfer normally (don't abort on drop). - fn complete(self) { - // Cleanup - self.regs.baud().modify(|_, w| w.tdmae().disabled()); - unsafe { - self.dma.disable_request(); - self.dma.clear_done(); - } - // Don't run drop since we've cleaned up - core::mem::forget(self); - } -} - -impl Drop for TxDmaGuard<'_, C> { - fn drop(&mut self) { - // Abort the DMA transfer if still running - unsafe { - self.dma.disable_request(); - self.dma.clear_done(); - self.dma.clear_interrupt(); - } - // Disable UART TX DMA request - self.regs.baud().modify(|_, w| w.tdmae().disabled()); - } -} - -/// Guard struct for RX DMA transfers. -struct RxDmaGuard<'a, C: DmaChannelTrait> { - dma: &'a DmaChannel, - regs: Regs, -} - -impl<'a, C: DmaChannelTrait> RxDmaGuard<'a, C> { - fn new(dma: &'a DmaChannel, regs: Regs) -> Self { - Self { dma, regs } - } - - /// Complete the transfer normally (don't abort on drop). - fn complete(self) { - // Ensure DMA writes are visible to CPU - cortex_m::asm::dsb(); - // Cleanup - self.regs.baud().modify(|_, w| w.rdmae().disabled()); - unsafe { - self.dma.disable_request(); - self.dma.clear_done(); - } - // Don't run drop since we've cleaned up - core::mem::forget(self); - } -} - -impl Drop for RxDmaGuard<'_, C> { - fn drop(&mut self) { - // Abort the DMA transfer if still running - unsafe { - self.dma.disable_request(); - self.dma.clear_done(); - self.dma.clear_interrupt(); - } - // Disable UART RX DMA request - self.regs.baud().modify(|_, w| w.rdmae().disabled()); - } -} - -impl<'a, T: Instance, C: DmaChannelTrait> LpuartTxDma<'a, T, C> { - /// Create a new LPUART TX driver with DMA support. - pub fn new( - _inner: Peri<'a, T>, - tx_pin: Peri<'a, impl TxPin>, - tx_dma_ch: Peri<'a, C>, - config: Config, - ) -> Result { - tx_pin.as_tx(); - let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); - - // Initialize LPUART with TX enabled, RX disabled, no flow control - Lpuart::::init::(true, false, false, false, config)?; - - Ok(Self { - info: T::info(), - _tx_pin: tx_pin, - tx_dma: DmaChannel::new(tx_dma_ch), - _instance: core::marker::PhantomData, - }) - } - - /// Write data using DMA. - /// - /// This configures the DMA channel for a memory-to-peripheral transfer - /// and waits for completion asynchronously. Large buffers are automatically - /// split into chunks that fit within the DMA transfer limit. - /// - /// The DMA request source is automatically derived from the LPUART instance type. - /// - /// # Safety - /// - /// If the returned future is dropped before completion (e.g., due to a timeout), - /// the DMA transfer is automatically aborted to prevent use-after-free. - /// - /// # Arguments - /// * `buf` - Data buffer to transmit - pub async fn write_dma(&mut self, buf: &[u8]) -> Result { - if buf.is_empty() { - return Ok(0); - } - - let mut total = 0; - for chunk in buf.chunks(DMA_MAX_TRANSFER_SIZE) { - total += self.write_dma_inner(chunk).await?; - } - - Ok(total) - } - - /// Internal helper to write a single chunk (max 0x7FFF bytes) using DMA. - async fn write_dma_inner(&mut self, buf: &[u8]) -> Result { - let len = buf.len(); - let peri_addr = self.info.regs.data().as_ptr() as *mut u8; - - unsafe { - // Clean up channel state - self.tx_dma.disable_request(); - self.tx_dma.clear_done(); - self.tx_dma.clear_interrupt(); - - // Set DMA request source from instance type (type-safe) - self.tx_dma.set_request_source::(); - - // Configure TCD for memory-to-peripheral transfer - self.tx_dma - .setup_write_to_peripheral(buf, peri_addr, EnableInterrupt::Yes); - - // Enable UART TX DMA request - self.info.regs.baud().modify(|_, w| w.tdmae().enabled()); - - // Enable DMA channel request - self.tx_dma.enable_request(); - } - - // Create guard that will abort DMA if this future is dropped - let guard = TxDmaGuard::new(&self.tx_dma, self.info.regs); - - // Wait for completion asynchronously - core::future::poll_fn(|cx| { - self.tx_dma.waker().register(cx.waker()); - if self.tx_dma.is_done() { - core::task::Poll::Ready(()) - } else { - core::task::Poll::Pending - } - }) - .await; - - // Transfer completed successfully - clean up without aborting - guard.complete(); - - Ok(len) - } - - /// Blocking write (fallback when DMA is not needed) - pub fn blocking_write(&mut self, buf: &[u8]) -> Result<()> { - for &byte in buf { - while self.info.regs.stat().read().tdre().is_txdata() {} - self.info.regs.data().modify(|_, w| unsafe { w.bits(u32::from(byte)) }); - } - Ok(()) - } - - /// Flush TX blocking - pub fn blocking_flush(&mut self) -> Result<()> { - while self.info.regs.water().read().txcount().bits() != 0 {} - while self.info.regs.stat().read().tc().is_active() {} - Ok(()) - } -} - -impl<'a, T: Instance, C: DmaChannelTrait> LpuartRxDma<'a, T, C> { - /// Create a new LPUART RX driver with DMA support. - pub fn new( - _inner: Peri<'a, T>, - rx_pin: Peri<'a, impl RxPin>, - rx_dma_ch: Peri<'a, C>, - config: Config, - ) -> Result { - rx_pin.as_rx(); - let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); - - // Initialize LPUART with TX disabled, RX enabled, no flow control - Lpuart::::init::(false, true, false, false, config)?; - - Ok(Self { - info: T::info(), - _rx_pin: rx_pin, - rx_dma: DmaChannel::new(rx_dma_ch), - _instance: core::marker::PhantomData, - }) - } - - /// Read data using DMA. - /// - /// This configures the DMA channel for a peripheral-to-memory transfer - /// and waits for completion asynchronously. Large buffers are automatically - /// split into chunks that fit within the DMA transfer limit. - /// - /// The DMA request source is automatically derived from the LPUART instance type. - /// - /// # Safety - /// - /// If the returned future is dropped before completion (e.g., due to a timeout), - /// the DMA transfer is automatically aborted to prevent use-after-free. - /// - /// # Arguments - /// * `buf` - Buffer to receive data into - pub async fn read_dma(&mut self, buf: &mut [u8]) -> Result { - if buf.is_empty() { - return Ok(0); - } - - let mut total = 0; - for chunk in buf.chunks_mut(DMA_MAX_TRANSFER_SIZE) { - total += self.read_dma_inner(chunk).await?; - } - - Ok(total) - } - - /// Internal helper to read a single chunk (max 0x7FFF bytes) using DMA. - async fn read_dma_inner(&mut self, buf: &mut [u8]) -> Result { - let len = buf.len(); - let peri_addr = self.info.regs.data().as_ptr() as *const u8; - - unsafe { - // Clean up channel state - self.rx_dma.disable_request(); - self.rx_dma.clear_done(); - self.rx_dma.clear_interrupt(); - - // Set DMA request source from instance type (type-safe) - self.rx_dma.set_request_source::(); - - // Configure TCD for peripheral-to-memory transfer - self.rx_dma - .setup_read_from_peripheral(peri_addr, buf, EnableInterrupt::Yes); - - // Enable UART RX DMA request - self.info.regs.baud().modify(|_, w| w.rdmae().enabled()); - - // Enable DMA channel request - self.rx_dma.enable_request(); - } - - // Create guard that will abort DMA if this future is dropped - let guard = RxDmaGuard::new(&self.rx_dma, self.info.regs); - - // Wait for completion asynchronously - core::future::poll_fn(|cx| { - self.rx_dma.waker().register(cx.waker()); - if self.rx_dma.is_done() { - core::task::Poll::Ready(()) - } else { - core::task::Poll::Pending - } - }) - .await; - - // Transfer completed successfully - clean up without aborting - guard.complete(); - - Ok(len) - } - - /// Blocking read (fallback when DMA is not needed) - pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<()> { - for byte in buf.iter_mut() { - loop { - if has_data(self.info.regs) { - *byte = (self.info.regs.data().read().bits() & 0xFF) as u8; - break; - } - check_and_clear_rx_errors(self.info.regs)?; - } - } - Ok(()) - } - - /// Set up a ring buffer for continuous DMA reception. - /// - /// This configures the DMA channel for circular operation, enabling continuous - /// reception of data without gaps. The DMA will continuously write received - /// bytes into the buffer, wrapping around when it reaches the end. - /// - /// This method encapsulates all the low-level setup: - /// - Configures the DMA request source for this LPUART instance - /// - Enables the RX DMA request in the LPUART peripheral - /// - Sets up the circular DMA transfer - /// - Enables the NVIC interrupt for async wakeups - /// - /// # Arguments - /// - /// * `buf` - Destination buffer for received data (power-of-2 size is ideal for efficiency) - /// - /// # Returns - /// - /// A [`RingBuffer`] that can be used to asynchronously read received data. - /// - /// # Example - /// - /// ```no_run - /// static mut RX_BUF: [u8; 64] = [0; 64]; - /// - /// let rx = LpuartRxDma::new(p.LPUART2, p.P2_3, p.DMA_CH0, config).unwrap(); - /// let ring_buf = unsafe { rx.setup_ring_buffer(&mut RX_BUF) }; - /// - /// // Read data as it arrives - /// let mut buf = [0u8; 16]; - /// let n = ring_buf.read(&mut buf).await.unwrap(); - /// ``` - /// - /// # Safety - /// - /// - The buffer must remain valid for the lifetime of the returned RingBuffer. - /// - Only one RingBuffer should exist per LPUART RX channel at a time. - /// - The caller must ensure the static buffer is not accessed elsewhere while - /// the ring buffer is active. - pub unsafe fn setup_ring_buffer<'b>(&self, buf: &'b mut [u8]) -> RingBuffer<'b, u8> { - // Get the peripheral data register address - let peri_addr = self.info.regs.data().as_ptr() as *const u8; - - // Configure DMA request source for this LPUART instance (type-safe) - self.rx_dma.set_request_source::(); - - // Enable RX DMA request in the LPUART peripheral - self.info.regs.baud().modify(|_, w| w.rdmae().enabled()); - - // Set up circular DMA transfer (this also enables NVIC interrupt) - self.rx_dma.setup_circular_read(peri_addr, buf) - } - - /// Enable the DMA channel request. - /// - /// Call this after `setup_ring_buffer()` to start continuous reception. - /// This is separated from setup to allow for any additional configuration - /// before starting the transfer. - pub unsafe fn enable_dma_request(&self) { - self.rx_dma.enable_request(); - } -} - -impl<'a, T: Instance, TxC: DmaChannelTrait, RxC: DmaChannelTrait> LpuartDma<'a, T, TxC, RxC> { - /// Create a new LPUART driver with DMA support for both TX and RX. - pub fn new( - _inner: Peri<'a, T>, - tx_pin: Peri<'a, impl TxPin>, - rx_pin: Peri<'a, impl RxPin>, - tx_dma_ch: Peri<'a, TxC>, - rx_dma_ch: Peri<'a, RxC>, - config: Config, - ) -> Result { - tx_pin.as_tx(); - rx_pin.as_rx(); - - let tx_pin: Peri<'a, AnyPin> = tx_pin.into(); - let rx_pin: Peri<'a, AnyPin> = rx_pin.into(); - - // Initialize LPUART with both TX and RX enabled, no flow control - Lpuart::::init::(true, true, false, false, config)?; - - Ok(Self { - tx: LpuartTxDma { - info: T::info(), - _tx_pin: tx_pin, - tx_dma: DmaChannel::new(tx_dma_ch), - _instance: core::marker::PhantomData, - }, - rx: LpuartRxDma { - info: T::info(), - _rx_pin: rx_pin, - rx_dma: DmaChannel::new(rx_dma_ch), - _instance: core::marker::PhantomData, - }, - }) - } - - /// Split into separate TX and RX drivers - pub fn split(self) -> (LpuartTxDma<'a, T, TxC>, LpuartRxDma<'a, T, RxC>) { - (self.tx, self.rx) - } - - /// Write data using DMA - pub async fn write_dma(&mut self, buf: &[u8]) -> Result { - self.tx.write_dma(buf).await - } - - /// Read data using DMA - pub async fn read_dma(&mut self, buf: &mut [u8]) -> Result { - self.rx.read_dma(buf).await - } -} - -// ============================================================================ -// EMBEDDED-IO-ASYNC TRAIT IMPLEMENTATIONS -// ============================================================================ - -impl embedded_io::ErrorType for LpuartTxDma<'_, T, C> { - type Error = Error; -} - -impl embedded_io::ErrorType for LpuartRxDma<'_, T, C> { - type Error = Error; -} - -impl embedded_io::ErrorType for LpuartDma<'_, T, TxC, RxC> { - type Error = Error; -} - -// ============================================================================ -// EMBEDDED-HAL 0.2 TRAIT IMPLEMENTATIONS -// ============================================================================ - -impl embedded_hal_02::serial::Read for LpuartRx<'_, Blocking> { - type Error = Error; - - fn read(&mut self) -> core::result::Result> { - let mut buf = [0; 1]; - match self.read(&mut buf) { - Ok(_) => Ok(buf[0]), - Err(Error::RxFifoEmpty) => Err(nb::Error::WouldBlock), - Err(e) => Err(nb::Error::Other(e)), - } - } -} - -impl embedded_hal_02::serial::Write for LpuartTx<'_, Blocking> { - type Error = Error; - - fn write(&mut self, word: u8) -> core::result::Result<(), nb::Error> { - match self.write(&[word]) { - Ok(_) => Ok(()), - Err(Error::TxFifoFull) => Err(nb::Error::WouldBlock), - Err(e) => Err(nb::Error::Other(e)), - } - } - - fn flush(&mut self) -> core::result::Result<(), nb::Error> { - match self.flush() { - Ok(_) => Ok(()), - Err(Error::TxBusy) => Err(nb::Error::WouldBlock), - Err(e) => Err(nb::Error::Other(e)), - } - } -} - -impl embedded_hal_02::blocking::serial::Write for LpuartTx<'_, Blocking> { - type Error = Error; - - fn bwrite_all(&mut self, buffer: &[u8]) -> core::result::Result<(), Self::Error> { - self.blocking_write(buffer) - } - - fn bflush(&mut self) -> core::result::Result<(), Self::Error> { - self.blocking_flush() - } -} - -impl embedded_hal_02::serial::Read for Lpuart<'_, Blocking> { - type Error = Error; - - fn read(&mut self) -> core::result::Result> { - embedded_hal_02::serial::Read::read(&mut self.rx) - } -} - -impl embedded_hal_02::serial::Write for Lpuart<'_, Blocking> { - type Error = Error; - - fn write(&mut self, word: u8) -> core::result::Result<(), nb::Error> { - embedded_hal_02::serial::Write::write(&mut self.tx, word) - } - - fn flush(&mut self) -> core::result::Result<(), nb::Error> { - embedded_hal_02::serial::Write::flush(&mut self.tx) - } -} - -impl embedded_hal_02::blocking::serial::Write for Lpuart<'_, Blocking> { - type Error = Error; - - fn bwrite_all(&mut self, buffer: &[u8]) -> core::result::Result<(), Self::Error> { - self.blocking_write(buffer) - } - - fn bflush(&mut self) -> core::result::Result<(), Self::Error> { - self.blocking_flush() - } -} - -// ============================================================================ -// EMBEDDED-HAL-NB TRAIT IMPLEMENTATIONS -// ============================================================================ - -impl embedded_hal_nb::serial::Error for Error { - fn kind(&self) -> embedded_hal_nb::serial::ErrorKind { - match *self { - Self::Framing => embedded_hal_nb::serial::ErrorKind::FrameFormat, - Self::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun, - Self::Parity => embedded_hal_nb::serial::ErrorKind::Parity, - Self::Noise => embedded_hal_nb::serial::ErrorKind::Noise, - _ => embedded_hal_nb::serial::ErrorKind::Other, - } - } -} - -impl embedded_hal_nb::serial::ErrorType for LpuartRx<'_, Blocking> { - type Error = Error; -} - -impl embedded_hal_nb::serial::ErrorType for LpuartTx<'_, Blocking> { - type Error = Error; -} - -impl embedded_hal_nb::serial::ErrorType for Lpuart<'_, Blocking> { - type Error = Error; -} - -impl embedded_hal_nb::serial::Read for LpuartRx<'_, Blocking> { - fn read(&mut self) -> nb::Result { - let mut buf = [0; 1]; - match self.read(&mut buf) { - Ok(_) => Ok(buf[0]), - Err(Error::RxFifoEmpty) => Err(nb::Error::WouldBlock), - Err(e) => Err(nb::Error::Other(e)), - } - } -} - -impl embedded_hal_nb::serial::Write for LpuartTx<'_, Blocking> { - fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { - match self.write(&[word]) { - Ok(_) => Ok(()), - Err(Error::TxFifoFull) => Err(nb::Error::WouldBlock), - Err(e) => Err(nb::Error::Other(e)), - } - } - - fn flush(&mut self) -> nb::Result<(), Self::Error> { - match self.flush() { - Ok(_) => Ok(()), - Err(Error::TxBusy) => Err(nb::Error::WouldBlock), - Err(e) => Err(nb::Error::Other(e)), - } - } -} - -impl embedded_hal_nb::serial::Read for Lpuart<'_, Blocking> { - fn read(&mut self) -> nb::Result { - embedded_hal_nb::serial::Read::read(&mut self.rx) - } -} - -impl embedded_hal_nb::serial::Write for Lpuart<'_, Blocking> { - fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { - embedded_hal_nb::serial::Write::write(&mut self.tx, char) - } - - fn flush(&mut self) -> nb::Result<(), Self::Error> { - embedded_hal_nb::serial::Write::flush(&mut self.tx) - } -} - -// ============================================================================ -// EMBEDDED-IO TRAIT IMPLEMENTATIONS -// ============================================================================ - -impl embedded_io::Error for Error { - fn kind(&self) -> embedded_io::ErrorKind { - embedded_io::ErrorKind::Other - } -} - -impl embedded_io::ErrorType for LpuartRx<'_, Blocking> { - type Error = Error; -} - -impl embedded_io::ErrorType for LpuartTx<'_, Blocking> { - type Error = Error; -} - -impl embedded_io::ErrorType for Lpuart<'_, Blocking> { - type Error = Error; -} - -impl embedded_io::Read for LpuartRx<'_, Blocking> { - fn read(&mut self, buf: &mut [u8]) -> core::result::Result { - self.blocking_read(buf).map(|_| buf.len()) - } -} - -impl embedded_io::Write for LpuartTx<'_, Blocking> { - fn write(&mut self, buf: &[u8]) -> core::result::Result { - self.blocking_write(buf).map(|_| buf.len()) - } - - fn flush(&mut self) -> core::result::Result<(), Self::Error> { - self.blocking_flush() - } -} - -impl embedded_io::Read for Lpuart<'_, Blocking> { - fn read(&mut self, buf: &mut [u8]) -> core::result::Result { - embedded_io::Read::read(&mut self.rx, buf) - } -} - -impl embedded_io::Write for Lpuart<'_, Blocking> { - fn write(&mut self, buf: &[u8]) -> core::result::Result { - embedded_io::Write::write(&mut self.tx, buf) - } - - fn flush(&mut self) -> core::result::Result<(), Self::Error> { - embedded_io::Write::flush(&mut self.tx) - } -} diff --git a/src/ostimer.rs b/src/ostimer.rs deleted file mode 100644 index c51812e3d..000000000 --- a/src/ostimer.rs +++ /dev/null @@ -1,745 +0,0 @@ -//! # OSTIMER Driver with Robustness Features -//! -//! This module provides an async timer driver for the NXP MCXA276 OSTIMER peripheral -//! with protection against race conditions and timer rollover issues. -//! -//! ## Features -//! -//! - Async timing with embassy-time integration -//! - Gray code counter handling (42-bit counter) -//! - Interrupt-driven wakeups -//! - Configurable interrupt priority -//! - **Race condition protection**: Critical sections and atomic operations -//! - **Timer rollover handling**: Bounds checking and rollover prevention -//! -//! ## Clock Frequency Configuration -//! -//! The OSTIMER frequency depends on your system's clock configuration. You must provide -//! the actual frequency when calling `time_driver::init()`. -//! -//! ## Race Condition Protection -//! - Critical sections in interrupt handlers prevent concurrent access -//! - Atomic register operations with memory barriers -//! - Proper interrupt flag clearing and validation -//! -//! ## Timer Rollover Handling -//! - Bounds checking prevents scheduling beyond timer capacity -//! - Immediate wake for timestamps that would cause rollover issues -#![allow(dead_code)] - -use core::sync::atomic::{AtomicBool, Ordering}; - -use embassy_hal_internal::{Peri, PeripheralType}; - -use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; -use crate::clocks::{assert_reset, enable_and_reset, is_reset_released, release_reset, Gate, PoweredClock}; -use crate::interrupt::InterruptExt; -use crate::pac; - -// PAC defines the shared RegisterBlock under `ostimer0`. -type Regs = pac::ostimer0::RegisterBlock; - -// OSTIMER EVTIMER register layout constants -/// Total width of the EVTIMER counter in bits (42 bits total) -const EVTIMER_TOTAL_BITS: u32 = 42; -/// Width of the low part of EVTIMER (bits 31:0) -const EVTIMER_LO_BITS: u32 = 32; -/// Width of the high part of EVTIMER (bits 41:32) -const EVTIMER_HI_BITS: u32 = 10; -/// Bit position where high part starts in the combined 64-bit value -const EVTIMER_HI_SHIFT: u32 = 32; - -/// Bit mask for the high part of EVTIMER -const EVTIMER_HI_MASK: u16 = (1 << EVTIMER_HI_BITS) - 1; - -/// Maximum value for MATCH_L register (32-bit) -const MATCH_L_MAX: u32 = u32::MAX; -/// Maximum value for MATCH_H register (10-bit) -const MATCH_H_MAX: u16 = EVTIMER_HI_MASK; - -/// Bit mask for extracting the low 32 bits from a 64-bit value -const LOW_32_BIT_MASK: u64 = u32::MAX as u64; - -/// Gray code conversion bit shifts (most significant to least) -const GRAY_CONVERSION_SHIFTS: [u32; 6] = [32, 16, 8, 4, 2, 1]; - -/// Maximum timer value before rollover (2^42 - 1 ticks) -/// Actual rollover time depends on the configured clock frequency -const TIMER_MAX_VALUE: u64 = (1u64 << EVTIMER_TOTAL_BITS) - 1; - -/// Threshold for detecting timer rollover in comparisons (1 second at 1MHz) -const TIMER_ROLLOVER_THRESHOLD: u64 = 1_000_000; - -/// Common default interrupt priority for OSTIMER -const DEFAULT_INTERRUPT_PRIORITY: u8 = 3; - -// Global alarm state for interrupt handling -static ALARM_ACTIVE: AtomicBool = AtomicBool::new(false); -static mut ALARM_CALLBACK: Option = None; -static mut ALARM_FLAG: Option<*const AtomicBool> = None; -static mut ALARM_TARGET_TIME: u64 = 0; - -/// Number of tight spin iterations between elapsed time checks while waiting for MATCH writes to return to the idle (0) state. -const MATCH_WRITE_READY_SPINS: usize = 512; -/// Maximum time (in OSTIMER ticks) to wait for MATCH registers to become writable (~5 ms at 1 MHz). -const MATCH_WRITE_READY_TIMEOUT_TICKS: u64 = 5_000; -/// Short stabilization delay executed after toggling the MRCC reset line to let the OSTIMER bus interface settle. -const RESET_STABILIZE_SPINS: usize = 512; - -pub(super) fn wait_for_match_write_ready(r: &Regs) -> bool { - let start = now_ticks_read(); - let mut spin_budget = 0usize; - - loop { - if r.osevent_ctrl().read().match_wr_rdy().bit_is_clear() { - return true; - } - - cortex_m::asm::nop(); - spin_budget += 1; - - if spin_budget >= MATCH_WRITE_READY_SPINS { - spin_budget = 0; - - let elapsed = now_ticks_read().wrapping_sub(start); - if elapsed >= MATCH_WRITE_READY_TIMEOUT_TICKS { - return false; - } - } - } -} - -pub(super) fn wait_for_match_write_complete(r: &Regs) -> bool { - let start = now_ticks_read(); - let mut spin_budget = 0usize; - - loop { - if r.osevent_ctrl().read().match_wr_rdy().bit_is_clear() { - return true; - } - - cortex_m::asm::nop(); - spin_budget += 1; - - if spin_budget >= MATCH_WRITE_READY_SPINS { - spin_budget = 0; - - let elapsed = now_ticks_read().wrapping_sub(start); - if elapsed >= MATCH_WRITE_READY_TIMEOUT_TICKS { - return false; - } - } - } -} - -fn prime_match_registers(r: &Regs) { - // Disable the interrupt, clear any pending flag, then wait until the MATCH registers are writable. - r.osevent_ctrl() - .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); - - if wait_for_match_write_ready(r) { - r.match_l().write(|w| unsafe { w.match_value().bits(MATCH_L_MAX) }); - r.match_h().write(|w| unsafe { w.match_value().bits(MATCH_H_MAX) }); - let _ = wait_for_match_write_complete(r); - } -} - -/// Single-shot alarm functionality for OSTIMER -pub struct Alarm<'d> { - /// Whether the alarm is currently active - active: AtomicBool, - /// Callback to execute when alarm expires (optional) - callback: Option, - /// Flag that gets set when alarm expires (optional) - flag: Option<&'d AtomicBool>, - _phantom: core::marker::PhantomData<&'d mut ()>, -} - -impl<'d> Default for Alarm<'d> { - fn default() -> Self { - Self::new() - } -} - -impl<'d> Alarm<'d> { - /// Create a new alarm instance - pub fn new() -> Self { - Self { - active: AtomicBool::new(false), - callback: None, - flag: None, - _phantom: core::marker::PhantomData, - } - } - - /// Set a callback that will be executed when the alarm expires - /// Note: Due to interrupt handler constraints, callbacks must be static function pointers - pub fn with_callback(mut self, callback: fn()) -> Self { - self.callback = Some(callback); - self - } - - /// Set a flag that will be set to true when the alarm expires - pub fn with_flag(mut self, flag: &'d AtomicBool) -> Self { - self.flag = Some(flag); - self - } - - /// Check if the alarm is currently active - pub fn is_active(&self) -> bool { - self.active.load(Ordering::Acquire) - } - - /// Cancel the alarm if it's active - pub fn cancel(&self) { - self.active.store(false, Ordering::Release); - } -} - -/// Configuration for Ostimer::new() -#[derive(Copy, Clone)] -pub struct Config { - /// Initialize MATCH registers to their max values and mask/clear the interrupt flag. - pub init_match_max: bool, - pub power: PoweredClock, - pub source: OstimerClockSel, -} - -impl Default for Config { - fn default() -> Self { - Self { - init_match_max: true, - power: PoweredClock::NormalEnabledDeepSleepDisabled, - source: OstimerClockSel::Clk1M, - } - } -} - -/// OSTIMER peripheral instance -pub struct Ostimer<'d, I: Instance> { - _inst: core::marker::PhantomData, - clock_frequency_hz: u64, - _phantom: core::marker::PhantomData<&'d mut ()>, -} - -impl<'d, I: Instance> Ostimer<'d, I> { - /// Construct OSTIMER handle. - /// Requires clocks for the instance to be enabled by the board before calling. - /// Does not enable NVIC or INTENA; use time_driver::init() for async operation. - pub fn new(_inst: Peri<'d, I>, cfg: Config) -> Self { - let clock_freq = unsafe { - enable_and_reset::(&OsTimerConfig { - power: cfg.power, - source: cfg.source, - }) - .expect("Enabling OsTimer clock should not fail") - }; - - assert!(clock_freq > 0, "OSTIMER frequency must be greater than 0"); - - if cfg.init_match_max { - let r: &Regs = unsafe { &*I::ptr() }; - // Mask INTENA, clear pending flag, and set MATCH to max so no spurious IRQ fires. - prime_match_registers(r); - } - - Self { - _inst: core::marker::PhantomData, - clock_frequency_hz: clock_freq as u64, - _phantom: core::marker::PhantomData, - } - } - - /// Get the configured clock frequency in Hz - pub fn clock_frequency_hz(&self) -> u64 { - self.clock_frequency_hz - } - - /// Read the current timer counter value in timer ticks - /// - /// # Returns - /// Current timer counter value as a 64-bit unsigned integer - pub fn now(&self) -> u64 { - now_ticks_read() - } - - /// Reset the timer counter to zero - /// - /// This performs a hardware reset of the OSTIMER peripheral, which will reset - /// the counter to zero and clear any pending interrupts. Note that this will - /// affect all timer operations including embassy-time. - /// - /// # Safety - /// This operation will reset the entire OSTIMER peripheral. Any active alarms - /// or time_driver operations will be disrupted. Use with caution. - pub fn reset(&self, _peripherals: &crate::pac::Peripherals) { - critical_section::with(|_| { - let r: &Regs = unsafe { &*I::ptr() }; - - // Mask the peripheral interrupt flag before we toggle the reset line so that - // no new NVIC activity races with the reset sequence. - r.osevent_ctrl() - .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); - - unsafe { - assert_reset::(); - - for _ in 0..RESET_STABILIZE_SPINS { - cortex_m::asm::nop(); - } - - release_reset::(); - - while !is_reset_released::() { - cortex_m::asm::nop(); - } - } - - for _ in 0..RESET_STABILIZE_SPINS { - cortex_m::asm::nop(); - } - - // Clear alarm bookkeeping before re-arming MATCH registers. - ALARM_ACTIVE.store(false, Ordering::Release); - unsafe { - ALARM_TARGET_TIME = 0; - ALARM_CALLBACK = None; - ALARM_FLAG = None; - } - - prime_match_registers(r); - }); - - // Ensure no stale OS_EVENT request remains pending after the reset sequence. - crate::interrupt::OS_EVENT.unpend(); - } - - /// Schedule a single-shot alarm to expire after the specified delay in microseconds - /// - /// # Parameters - /// * `alarm` - The alarm instance to schedule - /// * `delay_us` - Delay in microseconds from now - /// - /// # Returns - /// `true` if the alarm was scheduled successfully, `false` if it would exceed timer capacity - pub fn schedule_alarm_delay(&self, alarm: &Alarm, delay_us: u64) -> bool { - let delay_ticks = (delay_us * self.clock_frequency_hz) / 1_000_000; - let target_time = now_ticks_read() + delay_ticks; - self.schedule_alarm_at(alarm, target_time) - } - - /// Schedule a single-shot alarm to expire at the specified absolute time in timer ticks - /// - /// # Parameters - /// * `alarm` - The alarm instance to schedule - /// * `target_ticks` - Absolute time in timer ticks when the alarm should expire - /// - /// # Returns - /// `true` if the alarm was scheduled successfully, `false` if it would exceed timer capacity - pub fn schedule_alarm_at(&self, alarm: &Alarm, target_ticks: u64) -> bool { - let now = now_ticks_read(); - - // Check if target time is in the past - if target_ticks <= now { - // Execute callback immediately if alarm was supposed to be active - if alarm.active.load(Ordering::Acquire) { - alarm.active.store(false, Ordering::Release); - if let Some(callback) = alarm.callback { - callback(); - } - if let Some(flag) = &alarm.flag { - flag.store(true, Ordering::Release); - } - } - return true; - } - - // Check for timer rollover - let max_future = now + TIMER_MAX_VALUE; - if target_ticks > max_future { - return false; // Would exceed timer capacity - } - - // Program the timer - let r: &Regs = unsafe { &*I::ptr() }; - - critical_section::with(|_| { - // Disable interrupt and clear flag - r.osevent_ctrl() - .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); - - if !wait_for_match_write_ready(r) { - prime_match_registers(r); - - if !wait_for_match_write_ready(r) { - alarm.active.store(false, Ordering::Release); - ALARM_ACTIVE.store(false, Ordering::Release); - unsafe { - ALARM_TARGET_TIME = 0; - ALARM_CALLBACK = None; - ALARM_FLAG = None; - } - return false; - } - } - - // Mark alarm as active now that we know the MATCH registers are writable - alarm.active.store(true, Ordering::Release); - - // Set global alarm state for interrupt handler - ALARM_ACTIVE.store(true, Ordering::Release); - unsafe { - ALARM_TARGET_TIME = target_ticks; - ALARM_CALLBACK = alarm.callback; - ALARM_FLAG = alarm.flag.map(|f| f as *const AtomicBool); - } - - // Program MATCH registers (Gray-coded) - let gray = bin_to_gray(target_ticks); - let l = (gray & LOW_32_BIT_MASK) as u32; - let h = (((gray >> EVTIMER_HI_SHIFT) as u16) & EVTIMER_HI_MASK) as u16; - - r.match_l().write(|w| unsafe { w.match_value().bits(l) }); - r.match_h().write(|w| unsafe { w.match_value().bits(h) }); - - if !wait_for_match_write_complete(r) { - alarm.active.store(false, Ordering::Release); - ALARM_ACTIVE.store(false, Ordering::Release); - unsafe { - ALARM_TARGET_TIME = 0; - ALARM_CALLBACK = None; - ALARM_FLAG = None; - } - return false; - } - - let now_after_program = now_ticks_read(); - let intrflag_set = r.osevent_ctrl().read().ostimer_intrflag().bit_is_set(); - if now_after_program >= target_ticks && !intrflag_set { - alarm.active.store(false, Ordering::Release); - ALARM_ACTIVE.store(false, Ordering::Release); - unsafe { - ALARM_TARGET_TIME = 0; - ALARM_CALLBACK = None; - ALARM_FLAG = None; - } - return false; - } - - // Enable interrupt - r.osevent_ctrl().write(|w| w.ostimer_intena().set_bit()); - - true - }) - } - - /// Cancel any active alarm - pub fn cancel_alarm(&self, alarm: &Alarm) { - critical_section::with(|_| { - alarm.cancel(); - - // Clear global alarm state - ALARM_ACTIVE.store(false, Ordering::Release); - unsafe { ALARM_TARGET_TIME = 0 }; - - // Reset MATCH registers to maximum values to prevent spurious interrupts - let r: &Regs = unsafe { &*I::ptr() }; - prime_match_registers(r); - }); - } - - /// Check if an alarm has expired (call this from your interrupt handler) - /// Returns true if the alarm was active and has now expired - pub fn check_alarm_expired(&self, alarm: &Alarm) -> bool { - if alarm.active.load(Ordering::Acquire) { - alarm.active.store(false, Ordering::Release); - - // Execute callback - if let Some(callback) = alarm.callback { - callback(); - } - - // Set flag - if let Some(flag) = &alarm.flag { - flag.store(true, Ordering::Release); - } - - true - } else { - false - } - } -} - -/// Read current EVTIMER (Gray-coded) and convert to binary ticks. -#[inline(always)] -fn now_ticks_read() -> u64 { - let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; - - // Read high then low to minimize incoherent snapshots - let hi = (r.evtimerh().read().evtimer_count_value().bits() as u64) & (EVTIMER_HI_MASK as u64); - let lo = r.evtimerl().read().evtimer_count_value().bits() as u64; - // Combine and convert from Gray code to binary - let gray = lo | (hi << EVTIMER_HI_SHIFT); - gray_to_bin(gray) -} - -// Instance trait like other drivers, providing a PAC pointer for this OSTIMER instance -pub trait Instance: Gate + PeripheralType { - fn ptr() -> *const Regs; -} - -#[cfg(not(feature = "time"))] -impl Instance for crate::peripherals::OSTIMER0 { - #[inline(always)] - fn ptr() -> *const Regs { - pac::Ostimer0::ptr() - } -} - -#[inline(always)] -fn bin_to_gray(x: u64) -> u64 { - x ^ (x >> 1) -} - -#[inline(always)] -fn gray_to_bin(gray: u64) -> u64 { - // More efficient iterative conversion using predefined shifts - let mut bin = gray; - for &shift in &GRAY_CONVERSION_SHIFTS { - bin ^= bin >> shift; - } - bin -} - -#[cfg(feature = "time")] -pub mod time_driver { - use core::sync::atomic::Ordering; - use core::task::Waker; - - use embassy_sync::waitqueue::AtomicWaker; - use embassy_time_driver as etd; - - use super::{ - bin_to_gray, now_ticks_read, Regs, ALARM_ACTIVE, ALARM_CALLBACK, ALARM_FLAG, ALARM_TARGET_TIME, - EVTIMER_HI_MASK, EVTIMER_HI_SHIFT, LOW_32_BIT_MASK, - }; - use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; - use crate::clocks::{enable_and_reset, PoweredClock}; - use crate::pac; - - #[allow(non_camel_case_types)] - pub(crate) struct _OSTIMER0_TIME_DRIVER { - _x: (), - } - - // #[cfg(feature = "time")] - // impl_cc_gate!(_OSTIMER0_TIME_DRIVER, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig); - - impl crate::clocks::Gate for _OSTIMER0_TIME_DRIVER { - type MrccPeriphConfig = crate::clocks::periph_helpers::OsTimerConfig; - - #[inline] - unsafe fn enable_clock() { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.mrcc_glb_cc1().modify(|_, w| w.ostimer0().enabled()); - } - - #[inline] - unsafe fn disable_clock() { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.mrcc_glb_cc1().modify(|_r, w| w.ostimer0().disabled()); - } - - #[inline] - fn is_clock_enabled() -> bool { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.mrcc_glb_cc1().read().ostimer0().is_enabled() - } - - #[inline] - unsafe fn release_reset() { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.mrcc_glb_rst1().modify(|_, w| w.ostimer0().enabled()); - } - - #[inline] - unsafe fn assert_reset() { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.mrcc_glb_rst1().modify(|_, w| w.ostimer0().disabled()); - } - - #[inline] - fn is_reset_released() -> bool { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.mrcc_glb_rst1().read().ostimer0().is_enabled() - } - } - - pub struct Driver; - static TIMER_WAKER: AtomicWaker = AtomicWaker::new(); - - impl etd::Driver for Driver { - fn now(&self) -> u64 { - // Use the hardware counter (frequency configured in init) - super::now_ticks_read() - } - - fn schedule_wake(&self, timestamp: u64, waker: &Waker) { - let now = self.now(); - - // If timestamp is in the past or very close to now, wake immediately - if timestamp <= now { - waker.wake_by_ref(); - return; - } - - // Prevent scheduling too far in the future (beyond timer rollover) - // This prevents wraparound issues - let max_future = now + super::TIMER_MAX_VALUE; - if timestamp > max_future { - // For very long timeouts, wake immediately to avoid rollover issues - waker.wake_by_ref(); - return; - } - - // Register the waker first so any immediate wake below is observed by the executor. - TIMER_WAKER.register(waker); - - let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; - - critical_section::with(|_| { - // Mask INTENA and clear flag - r.osevent_ctrl() - .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); - - // Read back to ensure W1C took effect on hardware - let _ = r.osevent_ctrl().read().ostimer_intrflag().bit(); - - if !super::wait_for_match_write_ready(r) { - super::prime_match_registers(r); - - if !super::wait_for_match_write_ready(r) { - // If we can't safely program MATCH, wake immediately and leave INTENA masked. - waker.wake_by_ref(); - return; - } - } - - // Program MATCH (Gray-coded). Write low then high, then fence. - let gray = bin_to_gray(timestamp); - let l = (gray & LOW_32_BIT_MASK) as u32; - - let h = (((gray >> EVTIMER_HI_SHIFT) as u16) & EVTIMER_HI_MASK) as u16; - - r.match_l().write(|w| unsafe { w.match_value().bits(l) }); - r.match_h().write(|w| unsafe { w.match_value().bits(h) }); - - if !super::wait_for_match_write_complete(r) { - waker.wake_by_ref(); - return; - } - - let now_after_program = super::now_ticks_read(); - let intrflag_set = r.osevent_ctrl().read().ostimer_intrflag().bit_is_set(); - if now_after_program >= timestamp && !intrflag_set { - waker.wake_by_ref(); - return; - } - - // Enable peripheral interrupt - r.osevent_ctrl().write(|w| w.ostimer_intena().set_bit()); - }); - } - } - - /// Install the global embassy-time driver and configure NVIC priority for OS_EVENT. - /// - /// # Parameters - /// * `priority` - Interrupt priority for the OSTIMER interrupt - /// * `frequency_hz` - Actual OSTIMER clock frequency in Hz (stored for future use) - /// - /// Note: The frequency parameter is currently accepted for API compatibility. - /// The embassy_time_driver macro handles driver registration automatically. - pub fn init(priority: crate::interrupt::Priority, frequency_hz: u64) { - let _clock_freq = unsafe { - enable_and_reset::<_OSTIMER0_TIME_DRIVER>(&OsTimerConfig { - power: PoweredClock::AlwaysEnabled, - source: OstimerClockSel::Clk1M, - }) - .expect("Enabling OsTimer clock should not fail") - }; - - // Mask/clear at peripheral and set default MATCH - let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; - super::prime_match_registers(r); - - // Configure NVIC for timer operation - crate::interrupt::OS_EVENT.configure_for_timer(priority); - - // Note: The embassy_time_driver macro automatically registers the driver - // The frequency parameter is accepted for future compatibility - let _ = frequency_hz; // Suppress unused parameter warning - } - - // Export the global time driver expected by embassy-time - embassy_time_driver::time_driver_impl!(static DRIVER: Driver = Driver); - - /// To be called from the OS_EVENT IRQ. - pub fn on_interrupt() { - let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; - - // Critical section to prevent races with schedule_wake - critical_section::with(|_| { - // Check if interrupt is actually pending and handle it atomically - if r.osevent_ctrl().read().ostimer_intrflag().bit_is_set() { - // Clear flag and disable interrupt atomically - r.osevent_ctrl().write(|w| { - w.ostimer_intrflag() - .clear_bit_by_one() // Write-1-to-clear using safe helper - .ostimer_intena() - .clear_bit() - }); - - // Wake the waiting task - TIMER_WAKER.wake(); - - // Handle alarm callback if active and this interrupt is for the alarm - if ALARM_ACTIVE.load(Ordering::SeqCst) { - let current_time = now_ticks_read(); - let target_time = unsafe { ALARM_TARGET_TIME }; - - // Check if current time is close to alarm target time (within 1000 ticks for timing variations) - if current_time >= target_time && current_time <= target_time + 1000 { - ALARM_ACTIVE.store(false, Ordering::SeqCst); - unsafe { ALARM_TARGET_TIME = 0 }; - - // Execute callback if set - unsafe { - if let Some(callback) = ALARM_CALLBACK { - callback(); - } - } - - // Set flag if provided - unsafe { - if let Some(flag) = ALARM_FLAG { - (*flag).store(true, Ordering::SeqCst); - } - } - } - } - } - }); - } -} - -#[cfg(feature = "time")] -use crate::pac::interrupt; - -#[cfg(feature = "time")] -#[allow(non_snake_case)] -#[interrupt] -fn OS_EVENT() { - time_driver::on_interrupt() -} diff --git a/src/pins.rs b/src/pins.rs deleted file mode 100644 index 9adbe64c8..000000000 --- a/src/pins.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Pin configuration helpers (separate from peripheral drivers). -use crate::pac; - -/// Configure pins for ADC usage. -/// -/// # Safety -/// -/// Must be called after PORT clocks are enabled. -pub unsafe fn configure_adc_pins() { - // P1_10 = ADC1_A8 - let port1 = &*pac::Port1::ptr(); - port1.pcr10().write(|w| { - w.ps() - .ps0() - .pe() - .pe0() - .sre() - .sre0() - .ode() - .ode0() - .dse() - .dse0() - .mux() - .mux0() - .ibe() - .ibe0() - .inv() - .inv0() - .lk() - .lk0() - }); - core::arch::asm!("dsb sy; isb sy"); -} diff --git a/src/rtc.rs b/src/rtc.rs deleted file mode 100644 index b750a97ea..000000000 --- a/src/rtc.rs +++ /dev/null @@ -1,299 +0,0 @@ -//! RTC DateTime driver. -use core::sync::atomic::{AtomicBool, Ordering}; - -use embassy_hal_internal::{Peri, PeripheralType}; - -use crate::clocks::with_clocks; -use crate::pac; -use crate::pac::rtc0::cr::Um; - -type Regs = pac::rtc0::RegisterBlock; - -static ALARM_TRIGGERED: AtomicBool = AtomicBool::new(false); - -// Token-based instance pattern like embassy-imxrt -pub trait Instance: PeripheralType { - fn ptr() -> *const Regs; -} - -/// Token for RTC0 -pub type Rtc0 = crate::peripherals::RTC0; -impl Instance for crate::peripherals::RTC0 { - #[inline(always)] - fn ptr() -> *const Regs { - pac::Rtc0::ptr() - } -} - -const DAYS_IN_A_YEAR: u32 = 365; -const SECONDS_IN_A_DAY: u32 = 86400; -const SECONDS_IN_A_HOUR: u32 = 3600; -const SECONDS_IN_A_MINUTE: u32 = 60; -const YEAR_RANGE_START: u16 = 1970; - -#[derive(Debug, Clone, Copy)] -pub struct RtcDateTime { - pub year: u16, - pub month: u8, - pub day: u8, - pub hour: u8, - pub minute: u8, - pub second: u8, -} -#[derive(Copy, Clone)] -pub struct RtcConfig { - #[allow(dead_code)] - wakeup_select: bool, - update_mode: Um, - #[allow(dead_code)] - supervisor_access: bool, - compensation_interval: u8, - compensation_time: u8, -} - -#[derive(Copy, Clone)] -pub struct RtcInterruptEnable; -impl RtcInterruptEnable { - pub const RTC_TIME_INVALID_INTERRUPT_ENABLE: u32 = 1 << 0; - pub const RTC_TIME_OVERFLOW_INTERRUPT_ENABLE: u32 = 1 << 1; - pub const RTC_ALARM_INTERRUPT_ENABLE: u32 = 1 << 2; - pub const RTC_SECONDS_INTERRUPT_ENABLE: u32 = 1 << 4; -} - -pub fn convert_datetime_to_seconds(datetime: &RtcDateTime) -> u32 { - let month_days: [u16; 13] = [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; - - let mut seconds = (datetime.year as u32 - 1970) * DAYS_IN_A_YEAR; - seconds += (datetime.year as u32 / 4) - (1970 / 4); - seconds += month_days[datetime.month as usize] as u32; - seconds += datetime.day as u32 - 1; - - if (datetime.year & 3 == 0) && (datetime.month <= 2) { - seconds -= 1; - } - - seconds = seconds * SECONDS_IN_A_DAY - + (datetime.hour as u32 * SECONDS_IN_A_HOUR) - + (datetime.minute as u32 * SECONDS_IN_A_MINUTE) - + datetime.second as u32; - - seconds -} - -pub fn convert_seconds_to_datetime(seconds: u32) -> RtcDateTime { - let mut seconds_remaining = seconds; - let mut days = seconds_remaining / SECONDS_IN_A_DAY + 1; - seconds_remaining %= SECONDS_IN_A_DAY; - - let hour = (seconds_remaining / SECONDS_IN_A_HOUR) as u8; - seconds_remaining %= SECONDS_IN_A_HOUR; - let minute = (seconds_remaining / SECONDS_IN_A_MINUTE) as u8; - let second = (seconds_remaining % SECONDS_IN_A_MINUTE) as u8; - - let mut year = YEAR_RANGE_START; - let mut days_in_year = DAYS_IN_A_YEAR; - - while days > days_in_year { - days -= days_in_year; - year += 1; - - days_in_year = if year.is_multiple_of(4) { - DAYS_IN_A_YEAR + 1 - } else { - DAYS_IN_A_YEAR - }; - } - - let days_per_month = [ - 31, - if year.is_multiple_of(4) { 29 } else { 28 }, - 31, - 30, - 31, - 30, - 31, - 31, - 30, - 31, - 30, - 31, - ]; - - let mut month = 1; - for (m, month_days) in days_per_month.iter().enumerate() { - let m = m + 1; - if days <= *month_days as u32 { - month = m; - break; - } else { - days -= *month_days as u32; - } - } - - let day = days as u8; - - RtcDateTime { - year, - month: month as u8, - day, - hour, - minute, - second, - } -} - -pub fn get_default_config() -> RtcConfig { - RtcConfig { - wakeup_select: false, - update_mode: Um::Um0, - supervisor_access: false, - compensation_interval: 0, - compensation_time: 0, - } -} -/// Minimal RTC handle for a specific instance I (store the zero-sized token like embassy) -pub struct Rtc<'a, I: Instance> { - _inst: core::marker::PhantomData<&'a mut I>, -} - -impl<'a, I: Instance> Rtc<'a, I> { - /// initialize RTC - pub fn new(_inst: Peri<'a, I>, config: RtcConfig) -> Self { - let rtc = unsafe { &*I::ptr() }; - - // The RTC is NOT gated by the MRCC, but we DO need to make sure the 16k clock - // on the vsys domain is active - let clocks = with_clocks(|c| c.clk_16k_vsys.clone()); - match clocks { - None => panic!("Clocks have not been initialized"), - Some(None) => panic!("Clocks initialized, but clk_16k_vsys not active"), - Some(Some(_)) => {} - } - - /* RTC reset */ - rtc.cr().modify(|_, w| w.swr().set_bit()); - rtc.cr().modify(|_, w| w.swr().clear_bit()); - rtc.tsr().write(|w| unsafe { w.bits(1) }); - - rtc.cr().modify(|_, w| w.um().variant(config.update_mode)); - - rtc.tcr().modify(|_, w| unsafe { - w.cir() - .bits(config.compensation_interval) - .tcr() - .bits(config.compensation_time) - }); - - Self { - _inst: core::marker::PhantomData, - } - } - - pub fn set_datetime(&self, datetime: RtcDateTime) { - let rtc = unsafe { &*I::ptr() }; - let seconds = convert_datetime_to_seconds(&datetime); - rtc.tsr().write(|w| unsafe { w.bits(seconds) }); - } - - pub fn get_datetime(&self) -> RtcDateTime { - let rtc = unsafe { &*I::ptr() }; - let seconds = rtc.tsr().read().bits(); - convert_seconds_to_datetime(seconds) - } - - pub fn set_alarm(&self, alarm: RtcDateTime) { - let rtc = unsafe { &*I::ptr() }; - let seconds = convert_datetime_to_seconds(&alarm); - - rtc.tar().write(|w| unsafe { w.bits(0) }); - let mut timeout = 10000; - while rtc.tar().read().bits() != 0 && timeout > 0 { - timeout -= 1; - } - - rtc.tar().write(|w| unsafe { w.bits(seconds) }); - - let mut timeout = 10000; - while rtc.tar().read().bits() != seconds && timeout > 0 { - timeout -= 1; - } - } - - pub fn get_alarm(&self) -> RtcDateTime { - let rtc = unsafe { &*I::ptr() }; - let alarm_seconds = rtc.tar().read().bits(); - convert_seconds_to_datetime(alarm_seconds) - } - - pub fn start(&self) { - let rtc = unsafe { &*I::ptr() }; - rtc.sr().modify(|_, w| w.tce().set_bit()); - } - - pub fn stop(&self) { - let rtc = unsafe { &*I::ptr() }; - rtc.sr().modify(|_, w| w.tce().clear_bit()); - } - - pub fn set_interrupt(&self, mask: u32) { - let rtc = unsafe { &*I::ptr() }; - - if (RtcInterruptEnable::RTC_TIME_INVALID_INTERRUPT_ENABLE & mask) != 0 { - rtc.ier().modify(|_, w| w.tiie().tiie_1()); - } - if (RtcInterruptEnable::RTC_TIME_OVERFLOW_INTERRUPT_ENABLE & mask) != 0 { - rtc.ier().modify(|_, w| w.toie().toie_1()); - } - if (RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE & mask) != 0 { - rtc.ier().modify(|_, w| w.taie().taie_1()); - } - if (RtcInterruptEnable::RTC_SECONDS_INTERRUPT_ENABLE & mask) != 0 { - rtc.ier().modify(|_, w| w.tsie().tsie_1()); - } - - ALARM_TRIGGERED.store(false, Ordering::SeqCst); - } - - pub fn disable_interrupt(&self, mask: u32) { - let rtc = unsafe { &*I::ptr() }; - - if (RtcInterruptEnable::RTC_TIME_INVALID_INTERRUPT_ENABLE & mask) != 0 { - rtc.ier().modify(|_, w| w.tiie().tiie_0()); - } - if (RtcInterruptEnable::RTC_TIME_OVERFLOW_INTERRUPT_ENABLE & mask) != 0 { - rtc.ier().modify(|_, w| w.toie().toie_0()); - } - if (RtcInterruptEnable::RTC_ALARM_INTERRUPT_ENABLE & mask) != 0 { - rtc.ier().modify(|_, w| w.taie().taie_0()); - } - if (RtcInterruptEnable::RTC_SECONDS_INTERRUPT_ENABLE & mask) != 0 { - rtc.ier().modify(|_, w| w.tsie().tsie_0()); - } - } - - pub fn clear_alarm_flag(&self) { - let rtc = unsafe { &*I::ptr() }; - rtc.ier().modify(|_, w| w.taie().clear_bit()); - } - - pub fn is_alarm_triggered(&self) -> bool { - ALARM_TRIGGERED.load(Ordering::Relaxed) - } -} - -pub fn on_interrupt() { - let rtc = unsafe { &*pac::Rtc0::ptr() }; - // Check if this is actually a time alarm interrupt - let sr = rtc.sr().read(); - if sr.taf().bit_is_set() { - rtc.ier().modify(|_, w| w.taie().clear_bit()); - ALARM_TRIGGERED.store(true, Ordering::SeqCst); - } -} - -pub struct RtcHandler; -impl crate::interrupt::typelevel::Handler for RtcHandler { - unsafe fn on_interrupt() { - on_interrupt(); - } -} diff --git a/supply-chain/README.md b/supply-chain/README.md deleted file mode 100644 index 12f8777b0..000000000 --- a/supply-chain/README.md +++ /dev/null @@ -1,149 +0,0 @@ -# Working with cargo vet - -## Introduction - -`cargo vet` is a tool to help ensure that third-party Rust dependencies have been audited by a trusted entity. -It matches all dependencies against a set of audits conducted by the authors of the project or entities they trust. -To learn more, visit [mozilla/cargo-vet](https://github.com/mozilla/cargo-vet) - ---- - -## Adding a new dependency - -When updating or adding a new dependency, we need to ensure it's audited before being merged into main. -For our repositories, we have designated experts who are responsible for vetting any new dependencies being added to their repository. -_It is the shared responsibility of the developer creating the PR and the auditors to conduct a successful audit._ -Follow the process below to ensure compliance: - -### For Developers -1. **Respond to `cargo vet` failures**: - - If your PR fails the `cargo vet` step, the cargo-vet workflow will add a comment to the PR with a template questionnaire - - Copy the questionnaire, fill it out and paste it as a new comment on the PR. This greatly helps the auditors get some context of the changes requiring the new dependencies - -2. **Engage with auditors**: - - Respond to any questions that the auditors might have regarding the need of any new dependencies - -3. **Rebase and verify**: - - At their discretion, auditors will check in their audits into either [rust-crate-audits](https://github.com/OpenDevicePartnership/rust-crate-audits) or into the same repository - - Once the new audits have been merged, rebase your branch on main and verify it passes `cargo vet` - ```bash - git fetch upstream - git rebase upstream/main - cargo vet - ``` - -4. **Update PR**: - - If the audits were checked into rust-crate-audits, they will show up in _imports.lock_ on running `cargo vet`. In this case add the updated _imports.lock_ to your PR - - If the audits were checked into the same repository, they will be present in _audits.toml_ after rebase and you can simply force push to your PR after rebase - ```bash - git push -f - ``` - -5. **Check PR status**: - - The existing PR comment from the previous failure will be updated with a success message once the check passes - -### For Auditors - -1. **Review the questionnaire**: - - Check the filled questionnaire on the PR once the developer responds to the `cargo vet` failure - - Respond to the developer comment in case more information is needed - -2. **Audit new dependencies**: - - Inspect the `cargo vet` failures using your preferred method - - Use [gh pr checkout](https://cli.github.com/manual/gh_pr_checkout) to checkout the PR and run `cargo vet --locked` - - Use [Github Pull Requests for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github) to checkout the PR and run `cargo vet --locked` - - For more suggestions: [Checking out pull requests locally](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally) - -3. **Follow `cargo vet` recommendations**: - - Follow the recommendations of the `cargo vet` command output, either `cargo vet diff` for version update or `cargo vet inspect` for new dependencies - -4. **Record audits**: - - Use `cargo vet certify` to add new audits to _audits.toml_ - - Verify all dependencies pass using `cargo vet` - -5. **Decide audit location**: - - **Shared audits**: New audits should ideally be shared across ODP repositories to reduce the overhead of multiple audits for the same dependencies. To facilitate this, it's recommended to cut and paste the new audits and submit as a separate PR to the _audits.toml_ in [rust-crate-audits](https://github.com/OpenDevicePartnership/rust-crate-audits) - - If due to business reasons, the audits are not to be shared across repositories, copy the updated _audits.toml_ to a new branch off main in the same repository and submit the PR to update the audits - -6. **Communicate successful audit**: - - Communicate to the PR developer via a PR comment so they can update the PR and get `cargo vet` to pass - ---- - -## Audit criteria -`cargo vet` comes pre-equipped with two built-in criteria but supports adding new criteria to suit our needs. -As defined [here](https://mozilla.github.io/cargo-vet/built-in-criteria.html), the default criteria are: - -- **safe-to-run** - This crate can be compiled, run, and tested on a local workstation or in - controlled automation without surprising consequences, such as: - * Reading or writing data from sensitive or unrelated parts of the filesystem. - * Installing software or reconfiguring the device. - * Connecting to untrusted network endpoints. - * Misuse of system resources (e.g. cryptocurrency mining). - -- **safe-to-deploy** - This crate will not introduce a serious security vulnerability to production - software exposed to untrusted input. - - Auditors are not required to perform a full logic review of the entire crate. - Rather, they must review enough to fully reason about the behavior of all unsafe - blocks and usage of powerful imports. For any reasonable usage of the crate in - real-world software, an attacker must not be able to manipulate the runtime - behavior of these sections in an exploitable or surprising way. - - Ideally, all unsafe code is fully sound, and ambient capabilities (e.g. - filesystem access) are hardened against manipulation and consistent with the - advertised behavior of the crate. However, some discretion is permitted. In such - cases, the nature of the discretion should be recorded in the `notes` field of - the audit record. - - For crates which generate deployed code (e.g. build dependencies or procedural - macros), reasonable usage of the crate should output code which meets the above - criteria. - - **Note: `safe-to-deploy` implies `safe-to-run`** - ---- - -## Conducting an audit - -When performing an audit for a new or updated dependency, auditors may consider the following criteria to ensure the safety, reliability, and suitability of the crate for use in our projects: - -- **Security**: - - Review the crate for known vulnerabilities or security advisories. - - Check for unsafe code usage and ensure it is justified and well-documented. - - Evaluate the crate’s history of security issues and responsiveness to reported problems. - -- **Maintenance and Activity**: - - Assess the frequency of updates and the responsiveness of maintainers to issues and pull requests. - - Prefer crates that are actively maintained and have a healthy contributor base. - -- **License Compliance**: - - Verify that the crate’s license is compatible with our project’s licensing requirements. - -- **Community Trust and Adoption**: - - Consider the crate’s adoption in the wider Rust ecosystem. - - Prefer crates that are widely used and trusted by the community. - -- **Functionality and Suitability**: - - Confirm that the crate provides the required functionality without unnecessary features or bloat. - - Evaluate whether the crate’s API is stable and unlikely to introduce breaking changes unexpectedly. - -- **Audit Trail**: - - Record the audit decision, including any concerns, mitigations, or recommendations for future updates. - - If exemptions are granted, document the justification and any follow-up actions required. - ---- - -## Tips for using `cargo vet`: - -- **Update _imports.lock_**: - - Import trusted third party audits to reduce the number of new audits to be performed. Running `cargo vet` without `--locked` fetches new imports and updates _imports.lock_ with any audits that are helpful for our project. - -- **Add exemptions**: - - If an audit cannot be performed for some dependency due to time sensitivity or business justified reasons, use `cargo vet add-exemption ` to add the dependency to exemptions in _config.toml_ - - To add all remaining audits to exemptions at once, use `cargo vet regenerate exemptions` - -- **Prune unnecessary entries**: - - Remove unnecessary exemptions and imports using `cargo vet prune` \ No newline at end of file diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml deleted file mode 100644 index 871109648..000000000 --- a/supply-chain/audits.toml +++ /dev/null @@ -1,349 +0,0 @@ - -# cargo-vet audits file - -[[audits.autocfg]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "1.5.0" - -[[audits.cc]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "1.2.47" - -[[audits.cfg-if]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "1.0.4" - -[[audits.cordyceps]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "0.3.4" - -[[audits.darling]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "0.20.11" - -[[audits.darling_core]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "0.20.11" - -[[audits.darling_macro]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "0.20.11" - -[[audits.defmt-rtt]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "1.0.0" -notes = "defmt-rtt is used for all our logging purposes. Version 1.0.0 merely stabilizes what was already available previously." - -[[audits.defmt-rtt]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -delta = "1.0.0 -> 1.1.0" - -[[audits.document-features]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "0.2.12" - -[[audits.document-features]] -who = "Felipe Balbi " -criteria = "safe-to-run" -version = "0.2.12" - -[[audits.embassy-executor]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "0.9.1" - -[[audits.embassy-executor-macros]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "0.7.0" - -[[audits.embassy-executor-timer-queue]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "0.1.0" - -[[audits.embassy-executor-timer-queue]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "0.1.0" - -[[audits.embassy-time-queue-utils]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "0.3.0" - -[[audits.find-msvc-tools]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "0.1.5" - -[[audits.generator]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "0.8.7" - -[[audits.ident_case]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "1.0.1" - -[[audits.litrs]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "1.0.0" - -[[audits.maitake-sync]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "0.2.2" - -[[audits.mutex-traits]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "1.0.1" - -[[audits.mycelium-bitfield]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "0.1.5" - -[[audits.once_cell]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "1.20.1" - -[[audits.panic-probe]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "1.0.0" - -[[audits.pin-project]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "1.1.10" - -[[audits.pin-project-internal]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "1.1.10" - -[[audits.portable-atomic]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "1.11.1" - -[[audits.proc-macro2]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "1.0.103" - -[[audits.quote]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "1.0.42" - -[[audits.stable_deref_trait]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "1.2.1" - -[[audits.static_cell]] -who = "jerrysxie " -criteria = "safe-to-run" -delta = "2.1.0 -> 2.1.1" - -[[audits.syn]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "2.0.110" - -[[audits.syn]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -delta = "2.0.100 -> 2.0.109" - -[[audits.thiserror]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "2.0.17" - -[[audits.thiserror-impl]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "2.0.17" - -[[audits.unicode-ident]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "1.0.22" - -[[audits.valuable]] -who = "Felipe Balbi " -criteria = "safe-to-deploy" -version = "0.1.1" - -[[trusted.aho-corasick]] -criteria = "safe-to-deploy" -user-id = 189 # Andrew Gallant (BurntSushi) -start = "2019-03-28" -end = "2026-11-26" - -[[trusted.cc]] -criteria = "safe-to-deploy" -user-id = 55123 # rust-lang-owner -start = "2022-10-29" -end = "2026-11-26" - -[[trusted.find-msvc-tools]] -criteria = "safe-to-deploy" -user-id = 539 -start = "2025-08-29" -end = "2026-11-26" - -[[trusted.libc]] -criteria = "safe-to-deploy" -user-id = 55123 # rust-lang-owner -start = "2024-08-15" -end = "2026-11-26" - -[[trusted.loom]] -criteria = "safe-to-deploy" -user-id = 6741 # Alice Ryhl (Darksonn) -start = "2021-04-12" -end = "2026-11-26" - -[[trusted.memchr]] -criteria = "safe-to-deploy" -user-id = 189 # Andrew Gallant (BurntSushi) -start = "2019-07-07" -end = "2026-11-26" - -[[trusted.paste]] -criteria = "safe-to-deploy" -user-id = 3618 # David Tolnay (dtolnay) -start = "2019-03-19" -end = "2026-11-26" - -[[trusted.regex-automata]] -criteria = "safe-to-deploy" -user-id = 189 # Andrew Gallant (BurntSushi) -start = "2019-02-25" -end = "2026-11-26" - -[[trusted.regex-syntax]] -criteria = "safe-to-deploy" -user-id = 189 # Andrew Gallant (BurntSushi) -start = "2019-03-30" -end = "2026-11-26" - -[[trusted.rustversion]] -criteria = "safe-to-deploy" -user-id = 3618 # David Tolnay (dtolnay) -start = "2019-07-08" -end = "2026-11-26" - -[[trusted.scoped-tls]] -criteria = "safe-to-deploy" -user-id = 1 # Alex Crichton (alexcrichton) -start = "2019-02-26" -end = "2026-11-26" - -[[trusted.thread_local]] -criteria = "safe-to-deploy" -user-id = 2915 # Amanieu d'Antras (Amanieu) -start = "2019-09-07" -end = "2026-11-26" - -[[trusted.tracing-subscriber]] -criteria = "safe-to-deploy" -user-id = 10 # Carl Lerche (carllerche) -start = "2025-08-29" -end = "2026-11-26" - -[[trusted.valuable]] -criteria = "safe-to-deploy" -user-id = 10 # Carl Lerche (carllerche) -start = "2022-01-03" -end = "2026-11-26" - -[[trusted.windows]] -criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) -start = "2021-01-15" -end = "2026-11-26" - -[[trusted.windows-collections]] -criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) -start = "2025-02-06" -end = "2026-11-26" - -[[trusted.windows-core]] -criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) -start = "2021-11-15" -end = "2026-11-26" - -[[trusted.windows-future]] -criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) -start = "2025-02-10" -end = "2026-11-26" - -[[trusted.windows-implement]] -criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) -start = "2022-01-27" -end = "2026-11-26" - -[[trusted.windows-interface]] -criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) -start = "2022-02-18" -end = "2026-11-26" - -[[trusted.windows-link]] -criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) -start = "2024-07-17" -end = "2026-11-26" - -[[trusted.windows-numerics]] -criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) -start = "2023-05-15" -end = "2026-11-26" - -[[trusted.windows-result]] -criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) -start = "2024-02-02" -end = "2026-11-26" - -[[trusted.windows-strings]] -criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) -start = "2024-02-02" -end = "2026-11-26" - -[[trusted.windows-sys]] -criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) -start = "2021-11-15" -end = "2026-11-26" - -[[trusted.windows-threading]] -criteria = "safe-to-deploy" -user-id = 64539 # Kenny Kerr (kennykerr) -start = "2025-04-29" -end = "2026-11-26" diff --git a/supply-chain/config.toml b/supply-chain/config.toml deleted file mode 100644 index 5682db9ea..000000000 --- a/supply-chain/config.toml +++ /dev/null @@ -1,141 +0,0 @@ - -# cargo-vet config file - -[cargo-vet] -version = "0.10" - -[imports.OpenDevicePartnership] -url = "https://raw.githubusercontent.com/OpenDevicePartnership/rust-crate-audits/main/audits.toml" - -[imports.bytecode-alliance] -url = "https://raw.githubusercontent.com/bytecodealliance/wasmtime/main/supply-chain/audits.toml" - -[imports.google] -url = "https://raw.githubusercontent.com/google/rust-crate-audits/main/audits.toml" - -[imports.mozilla] -url = "https://raw.githubusercontent.com/mozilla/supply-chain/main/audits.toml" - -[[exemptions.bare-metal]] -version = "0.2.5" -criteria = "safe-to-deploy" - -[[exemptions.bitfield]] -version = "0.13.2" -criteria = "safe-to-deploy" - -[[exemptions.cortex-m]] -version = "0.7.7" -criteria = "safe-to-deploy" - -[[exemptions.cortex-m-rt]] -version = "0.7.5" -criteria = "safe-to-deploy" - -[[exemptions.cortex-m-rt-macros]] -version = "0.7.5" -criteria = "safe-to-deploy" - -[[exemptions.critical-section]] -version = "1.2.0" -criteria = "safe-to-deploy" - -[[exemptions.defmt]] -version = "1.0.1" -criteria = "safe-to-deploy" - -[[exemptions.defmt-macros]] -version = "1.0.1" -criteria = "safe-to-deploy" - -[[exemptions.defmt-parser]] -version = "1.0.0" -criteria = "safe-to-deploy" - -[[exemptions.embassy-embedded-hal]] -version = "0.5.0" -criteria = "safe-to-deploy" - -[[exemptions.embassy-futures]] -version = "0.1.2" -criteria = "safe-to-deploy" - -[[exemptions.embassy-hal-internal]] -version = "0.3.0" -criteria = "safe-to-deploy" - -[[exemptions.embassy-sync]] -version = "0.7.2" -criteria = "safe-to-deploy" - -[[exemptions.embassy-time]] -version = "0.5.0" -criteria = "safe-to-deploy" - -[[exemptions.embassy-time-driver]] -version = "0.2.1" -criteria = "safe-to-deploy" - -[[exemptions.embedded-hal]] -version = "0.2.7" -criteria = "safe-to-deploy" - -[[exemptions.embedded-hal]] -version = "1.0.0" -criteria = "safe-to-deploy" - -[[exemptions.embedded-hal-async]] -version = "1.0.0" -criteria = "safe-to-deploy" - -[[exemptions.embedded-hal-nb]] -version = "1.0.0" -criteria = "safe-to-deploy" - -[[exemptions.embedded-io-async]] -version = "0.6.1" -criteria = "safe-to-deploy" - -[[exemptions.embedded-storage]] -version = "0.3.1" -criteria = "safe-to-deploy" - -[[exemptions.embedded-storage-async]] -version = "0.4.1" -criteria = "safe-to-deploy" - -[[exemptions.hash32]] -version = "0.3.1" -criteria = "safe-to-deploy" - -[[exemptions.heapless]] -version = "0.8.0" -criteria = "safe-to-deploy" - -[[exemptions.proc-macro-error-attr2]] -version = "2.0.0" -criteria = "safe-to-deploy" - -[[exemptions.proc-macro-error2]] -version = "2.0.1" -criteria = "safe-to-deploy" - -[[exemptions.rustc_version]] -version = "0.2.3" -criteria = "safe-to-deploy" - -[[exemptions.semver]] -version = "0.9.0" -criteria = "safe-to-deploy" - -[[exemptions.semver-parser]] -version = "0.7.0" -criteria = "safe-to-deploy" - -[[exemptions.vcell]] -version = "0.1.3" -criteria = "safe-to-deploy" - -[[exemptions.volatile-register]] -version = "0.2.2" -criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock deleted file mode 100644 index dbd1235b0..000000000 --- a/supply-chain/imports.lock +++ /dev/null @@ -1,523 +0,0 @@ - -# cargo-vet imports lock - -[[publisher.aho-corasick]] -version = "1.1.4" -when = "2025-10-28" -user-id = 189 -user-login = "BurntSushi" -user-name = "Andrew Gallant" - -[[publisher.libc]] -version = "0.2.177" -when = "2025-10-09" -user-id = 55123 -user-login = "rust-lang-owner" - -[[publisher.loom]] -version = "0.7.2" -when = "2024-04-23" -user-id = 6741 -user-login = "Darksonn" -user-name = "Alice Ryhl" - -[[publisher.memchr]] -version = "2.7.6" -when = "2025-09-25" -user-id = 189 -user-login = "BurntSushi" -user-name = "Andrew Gallant" - -[[publisher.paste]] -version = "1.0.15" -when = "2024-05-07" -user-id = 3618 -user-login = "dtolnay" -user-name = "David Tolnay" - -[[publisher.regex-automata]] -version = "0.4.13" -when = "2025-10-13" -user-id = 189 -user-login = "BurntSushi" -user-name = "Andrew Gallant" - -[[publisher.regex-syntax]] -version = "0.8.8" -when = "2025-10-13" -user-id = 189 -user-login = "BurntSushi" -user-name = "Andrew Gallant" - -[[publisher.rustversion]] -version = "1.0.22" -when = "2025-08-08" -user-id = 3618 -user-login = "dtolnay" -user-name = "David Tolnay" - -[[publisher.scoped-tls]] -version = "1.0.1" -when = "2022-10-31" -user-id = 1 -user-login = "alexcrichton" -user-name = "Alex Crichton" - -[[publisher.thread_local]] -version = "1.1.9" -when = "2025-06-12" -user-id = 2915 -user-login = "Amanieu" -user-name = "Amanieu d'Antras" - -[[publisher.tracing-subscriber]] -version = "0.3.20" -when = "2025-08-29" -user-id = 10 -user-login = "carllerche" -user-name = "Carl Lerche" - -[[publisher.windows]] -version = "0.61.3" -when = "2025-06-12" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows-collections]] -version = "0.2.0" -when = "2025-03-18" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows-core]] -version = "0.61.2" -when = "2025-05-19" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows-future]] -version = "0.2.1" -when = "2025-05-15" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows-implement]] -version = "0.60.2" -when = "2025-10-06" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows-interface]] -version = "0.59.3" -when = "2025-10-06" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows-link]] -version = "0.1.3" -when = "2025-06-12" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows-link]] -version = "0.2.1" -when = "2025-10-06" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows-numerics]] -version = "0.2.0" -when = "2025-03-18" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows-result]] -version = "0.3.4" -when = "2025-05-19" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows-strings]] -version = "0.4.2" -when = "2025-05-19" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows-sys]] -version = "0.61.2" -when = "2025-10-06" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[[publisher.windows-threading]] -version = "0.1.0" -when = "2025-05-15" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - -[audits.OpenDevicePartnership.audits] - -[[audits.bytecode-alliance.audits.embedded-io]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -version = "0.4.0" -notes = "No `unsafe` code and only uses `std` in ways one would expect the crate to do so." - -[[audits.bytecode-alliance.audits.embedded-io]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.4.0 -> 0.6.1" -notes = "Major updates, but almost all safe code. Lots of pruning/deletions, nothing out of the ordrinary." - -[[audits.bytecode-alliance.audits.futures-core]] -who = "Pat Hickey " -criteria = "safe-to-deploy" -version = "0.3.27" -notes = "Unsafe used to implement a concurrency primitive AtomicWaker. Well-commented and not obviously incorrect. Like my other audits of these concurrency primitives inside the futures family, I couldn't certify that it is correct without formal methods, but that is out of scope for this vetting." - -[[audits.bytecode-alliance.audits.futures-core]] -who = "Pat Hickey " -criteria = "safe-to-deploy" -delta = "0.3.28 -> 0.3.31" - -[[audits.bytecode-alliance.audits.futures-sink]] -who = "Pat Hickey " -criteria = "safe-to-deploy" -version = "0.3.27" - -[[audits.bytecode-alliance.audits.futures-sink]] -who = "Pat Hickey " -criteria = "safe-to-deploy" -delta = "0.3.28 -> 0.3.31" - -[[audits.bytecode-alliance.audits.log]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.4.22 -> 0.4.27" -notes = "Lots of minor updates to macros and such, nothing touching `unsafe`" - -[[audits.bytecode-alliance.audits.log]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.4.27 -> 0.4.28" -notes = "Minor doc updates and lots new tests, nothing out of the ordinary." - -[[audits.bytecode-alliance.audits.matchers]] -who = "Pat Hickey " -criteria = "safe-to-deploy" -version = "0.1.0" - -[[audits.bytecode-alliance.audits.matchers]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.1.0 -> 0.2.0" -notes = "Some unsafe code, but not more than before. Nothing awry." - -[[audits.bytecode-alliance.audits.nu-ansi-term]] -who = "Pat Hickey " -criteria = "safe-to-deploy" -version = "0.46.0" -notes = "one use of unsafe to call windows specific api to get console handle." - -[[audits.bytecode-alliance.audits.nu-ansi-term]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.46.0 -> 0.50.1" -notes = "Lots of stylistic/rust-related chanegs, plus new features, but nothing out of the ordrinary." - -[[audits.bytecode-alliance.audits.nu-ansi-term]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.50.1 -> 0.50.3" -notes = "CI changes, Rust changes, nothing out of the ordinary." - -[[audits.bytecode-alliance.audits.sharded-slab]] -who = "Pat Hickey " -criteria = "safe-to-deploy" -version = "0.1.4" -notes = "I always really enjoy reading eliza's code, she left perfect comments at every use of unsafe." - -[[audits.bytecode-alliance.audits.shlex]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -version = "1.1.0" -notes = "Only minor `unsafe` code blocks which look valid and otherwise does what it says on the tin." - -[[audits.bytecode-alliance.audits.tracing-attributes]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.1.28 -> 0.1.30" -notes = "Few code changes, a pretty minor update." - -[[audits.bytecode-alliance.audits.tracing-core]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.1.33 -> 0.1.34" -notes = "Mostly just an update with Rust stylistic conventions changing. Nothing awry." - -[[audits.bytecode-alliance.audits.tracing-log]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -version = "0.1.3" -notes = """ -This is a standard adapter between the `log` ecosystem and the `tracing` -ecosystem. There's one `unsafe` block in this crate and it's well-scoped. -""" - -[[audits.bytecode-alliance.audits.tracing-log]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.1.3 -> 0.2.0" -notes = "Nothing out of the ordinary, a typical major version update and nothing awry." - -[[audits.google.audits.bitflags]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -version = "1.3.2" -notes = """ -Security review of earlier versions of the crate can be found at -(Google-internal, sorry): go/image-crate-chromium-security-review - -The crate exposes a function marked as `unsafe`, but doesn't use any -`unsafe` blocks (except for tests of the single `unsafe` function). I -think this justifies marking this crate as `ub-risk-1`. - -Additional review comments can be found at https://crrev.com/c/4723145/31 -""" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.byteorder]] -who = "danakj " -criteria = "safe-to-deploy" -version = "1.5.0" -notes = "Unsafe review in https://crrev.com/c/5838022" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.lazy_static]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -version = "1.4.0" -notes = ''' -I grepped for \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits. - -There are two places where `unsafe` is used. Unsafe review notes can be found -in https://crrev.com/c/5347418. - -This crate has been added to Chromium in https://crrev.com/c/3321895. -''' -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.lazy_static]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -delta = "1.4.0 -> 1.5.0" -notes = "Unsafe review notes: https://crrev.com/c/5650836" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.log]] -who = "danakj " -criteria = "safe-to-deploy" -version = "0.4.22" -notes = """ -Unsafe review in https://docs.google.com/document/d/1IXQbD1GhTRqNHIGxq6yy7qHqxeO4CwN5noMFXnqyDIM/edit?usp=sharing - -Unsafety is generally very well-documented, with one exception, which we -describe in the review doc. -""" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.nb]] -who = "George Burgess IV " -criteria = "safe-to-deploy" -version = "1.0.0" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" - -[[audits.google.audits.nb]] -who = "George Burgess IV " -criteria = "safe-to-deploy" -delta = "1.0.0 -> 0.1.3" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" - -[[audits.google.audits.nb]] -who = "George Burgess IV " -criteria = "safe-to-deploy" -delta = "1.0.0 -> 1.1.0" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" - -[[audits.google.audits.num-traits]] -who = "Manish Goregaokar " -criteria = "safe-to-deploy" -version = "0.2.19" -notes = "Contains a single line of float-to-int unsafe with decent safety comments" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.pin-project-lite]] -who = "David Koloski " -criteria = "safe-to-deploy" -version = "0.2.9" -notes = "Reviewed on https://fxrev.dev/824504" -aggregated-from = "https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/third_party/rust_crates/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.pin-project-lite]] -who = "David Koloski " -criteria = "safe-to-deploy" -delta = "0.2.9 -> 0.2.13" -notes = "Audited at https://fxrev.dev/946396" -aggregated-from = "https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/third_party/rust_crates/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.smallvec]] -who = "Manish Goregaokar " -criteria = "safe-to-deploy" -version = "1.13.2" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.smallvec]] -who = "Jonathan Hao " -criteria = "safe-to-deploy" -delta = "1.13.2 -> 1.14.0" -notes = """ -WARNING: This certification is a result of a **partial** audit. The -`malloc_size_of` feature has **not** been audited. This feature does -not explicitly document its safety requirements. -See also https://chromium-review.googlesource.com/c/chromium/src/+/6275133/comment/ea0d7a93_98051a2e/ -and https://github.com/servo/malloc_size_of/issues/8. -This feature is banned in gnrt_config.toml. -""" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.void]] -who = "George Burgess IV " -criteria = "safe-to-deploy" -version = "1.0.2" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" - -[[audits.mozilla.audits.futures-core]] -who = "Mike Hommey " -criteria = "safe-to-deploy" -delta = "0.3.27 -> 0.3.28" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.futures-sink]] -who = "Mike Hommey " -criteria = "safe-to-deploy" -delta = "0.3.27 -> 0.3.28" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.once_cell]] -who = "Erich Gubler " -criteria = "safe-to-deploy" -delta = "1.20.1 -> 1.20.2" -notes = "This update works around a Cargo bug that forces the addition of `portable-atomic` into a lockfile, which we have never needed to use." -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.once_cell]] -who = "Erich Gubler " -criteria = "safe-to-deploy" -delta = "1.20.2 -> 1.20.3" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.once_cell]] -who = "Erich Gubler " -criteria = "safe-to-deploy" -delta = "1.20.3 -> 1.21.1" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.once_cell]] -who = "Erich Gubler " -criteria = "safe-to-deploy" -delta = "1.21.1 -> 1.21.3" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.pin-project-lite]] -who = "Mike Hommey " -criteria = "safe-to-deploy" -delta = "0.2.13 -> 0.2.14" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.pin-project-lite]] -who = "Nika Layzell " -criteria = "safe-to-deploy" -delta = "0.2.14 -> 0.2.16" -notes = """ -Only functional change is to work around a bug in the negative_impls feature -(https://github.com/taiki-e/pin-project/issues/340#issuecomment-2432146009) -""" -aggregated-from = "https://raw.githubusercontent.com/mozilla/cargo-vet/main/supply-chain/audits.toml" - -[[audits.mozilla.audits.sharded-slab]] -who = "Mark Hammond " -criteria = "safe-to-deploy" -delta = "0.1.4 -> 0.1.7" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.shlex]] -who = "Max Inden " -criteria = "safe-to-deploy" -delta = "1.1.0 -> 1.3.0" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.smallvec]] -who = "Erich Gubler " -criteria = "safe-to-deploy" -delta = "1.14.0 -> 1.15.1" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.tracing]] -who = "Alex Franchuk " -criteria = "safe-to-deploy" -version = "0.1.37" -notes = """ -There's only one unsafe impl, and its purpose is to ensure correct behavior by -creating a non-Send marker type (it has nothing to do with soundness). All -dependencies make sense, and no side-effectful std functions are used. -""" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.tracing]] -who = "Mark Hammond " -criteria = "safe-to-deploy" -delta = "0.1.37 -> 0.1.41" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.tracing-attributes]] -who = "Alex Franchuk " -criteria = "safe-to-deploy" -version = "0.1.24" -notes = "No unsafe code, macros extensively tested and produce reasonable code." -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.tracing-attributes]] -who = "Mark Hammond " -criteria = "safe-to-deploy" -delta = "0.1.24 -> 0.1.28" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.tracing-core]] -who = "Alex Franchuk " -criteria = "safe-to-deploy" -version = "0.1.30" -notes = """ -Most unsafe code is in implementing non-std sync primitives. Unsafe impls are -logically correct and justified in comments, and unsafe code is sound and -justified in comments. -""" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" - -[[audits.mozilla.audits.tracing-core]] -who = "Mark Hammond " -criteria = "safe-to-deploy" -delta = "0.1.30 -> 0.1.33" -aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" diff --git a/tools/run_and_attach_rtt.sh b/tools/run_and_attach_rtt.sh deleted file mode 100644 index 13041d06b..000000000 --- a/tools/run_and_attach_rtt.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ELF="${1:-target/thumbv8m.main-none-eabihf/debug/examples/hello}" -PROBE_ID="${2:-1fc9:0143:H3AYDQVQMTROB}" -CHIP="${3:-MCXA276}" -SPEED="${4:-1000}" - -# 1) Flash & run using the existing run.sh (probe is in use only during this step) -./run.sh "$ELF" - -# 2) Give target a short moment to boot and set up RTT CB in RAM -sleep 0.5 - -# 3) Attach RTT/defmt using probe-rs (no flashing) -exec probe-rs attach \ - --chip "$CHIP" \ - --probe "$PROBE_ID" \ - --protocol swd \ - --speed "$SPEED" \ - "$ELF" \ - --rtt-scan-memory \ - --log-format oneline - diff --git a/tools/run_jlink_noblock.sh b/tools/run_jlink_noblock.sh deleted file mode 100644 index 3ea1f2b4b..000000000 --- a/tools/run_jlink_noblock.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ELF="${1:-}" -PROBE_ID="${2:-1366:0101:000600110607}" # default to your J-Link -CHIP="${3:-MCXA276}" -SPEED="${4:-1000}" -PORT="${PROBE_RS_GDB_PORT:-1337}" - -if [[ -z "${ELF}" || ! -f "${ELF}" ]]; then - echo "Usage: $0 [probe-id] [chip] [speed-khz]" >&2 - exit 1 -fi - -if ! command -v probe-rs >/dev/null 2>&1; then - echo "probe-rs not found (cargo install probe-rs --features cli)" >&2 - exit 1 -fi -if ! command -v gdb-multiarch >/dev/null 2>&1; then - echo "gdb-multiarch not found; install it (e.g., sudo apt install gdb-multiarch)." >&2 - exit 1 -fi - -# Start probe-rs GDB server -SERVER_LOG=$(mktemp) -probe-rs gdb --chip "${CHIP}" --protocol swd --speed "${SPEED}" --non-interactive "${ELF}" --probe "${PROBE_ID}" \ - >"${SERVER_LOG}" 2>&1 & -GDB_SERVER_PID=$! - -# Wait for readiness -for _ in {1..50}; do - if grep -q "Firing up GDB stub" "${SERVER_LOG}"; then break; fi - if grep -q "Connecting to the chip was unsuccessful" "${SERVER_LOG}"; then - echo "probe-rs gdb server failed. Log:" >&2 - sed -e 's/^/ /' "${SERVER_LOG}" >&2 || true - kill "${GDB_SERVER_PID}" 2>/dev/null || true - exit 1 - fi - sleep 0.1 -done - -# GDB script: load, resume, detach -GDB_SCRIPT=$(mktemp) -cat >"${GDB_SCRIPT}" <&2 - sed -e 's/^/ /' "${SERVER_LOG}" >&2 || true - kill "${GDB_SERVER_PID}" 2>/dev/null || true - exit 1 -fi - -# Stop server now that we've detached -kill "${GDB_SERVER_PID}" 2>/dev/null || true -rm -f "${GDB_SCRIPT}" "${SERVER_LOG}" || true - -echo "Flashed, resumed, and detached (probe free)." - -- cgit