Skip to content

nrf-nrf54l: add basic nrf54l support #4393

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

dmitry-tarnyagin
Copy link

Here we have implemented basic support for the following peripheral blocks of the nRF54L:

  • uarte, buffered_uarte
  • twim, twis
  • spim, spis
  • saadc
  • pwm
  • dppic, instantiated for dppic 00, 20, 30
  • gpiote, instantiated for gpiote 20, 30
  • timer

@dmitry-tarnyagin dmitry-tarnyagin marked this pull request as draft July 11, 2025 10:42
@dmitry-tarnyagin dmitry-tarnyagin marked this pull request as ready for review July 11, 2025 12:09
@dmitry-tarnyagin dmitry-tarnyagin force-pushed the dev/nrf54l branch 3 times, most recently from 703977a to 74d937f Compare July 11, 2025 12:49
@dmitry-tarnyagin
Copy link
Author

Checks passed, ready for review.

Copy link
Member

@lulf lulf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! A lot of stuff, so this will take some time to review in detail. After a first look I have several questions in the review comments. Overall, there's more code duplication than I expected, and I'm not sure about using the extend crate, which makes the nrf54 very 'bolted on' instead of doing the necessary rewrite to make the HAL flexible to support nrf54. The other thing is the use of enums for instance types, where the usual pattern has been to have an Instance trait that gets implemented for FooX, and potentially type-reased when converting to AnyFoo.


#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct SpiFrequencyVals(pub u32);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why define the values here instead of using them from nrf-pac for nrf54l? Is that because they're missing from the pac? If so, maybe it can be fixed in the pac.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are quite incompatible, unfortunately. The nRF54L has a prescaler register, and the integer value is interpreted as the core-clock-to-SCK divisor. Other nRFs use magic constants (reflected in the PAC) and a frequency register instead of a prescaler. The SpiFrequencyVals here is an attempt to maintain a common API for all the chips.

}

/// Buffered UARTE driver.
pub struct BufferedUarte<'d, U: UarteInstance> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for adding a separate BufferedUarte implementation for nrf54l?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UARTE blocks are implemented quite differently in nRF54L. In other nRFs, the number of transferred bytes can be counted as the number of RXRDY events. It’s a bit racy, since RXRDY means “received,” not “written to memory,” but it usually works—the inconsistency window is only a few 16 MHz cycles.

In nRF54, the UARTE block (or rather the EasyDMA block) implements buffering. Bytes are collected into u32 words before being sent to memory. The number of RXRDY events no longer matches the number of transferred bytes, and bytes in the internal buffer aren’t transferred to memory until the DMA END event, which can never happen if a packed size is not multiple-of-four.

The only way to flush the RX buffer is to trigger a STOP task and wait for DMA END. Fortunately, Nordic has implemented an RX timeout as a separate event, so the strategy is: wait for timeout → force stop → restart RX.

The common buffered_uarte implementation uses a PPI network to count transferred bytes via RXRDY events. nRF54L uses the timeout‐stop‐restart strategy and doesn’t need external PPI machinery at all.

An attempt to keep both implementations in the same code failed under a tangle of #[cfg]s and parallel code, so there are now two separate files.

@@ -39,6 +39,7 @@ pub(crate) struct State {
rx_started_count: AtomicU8,
rx_ended_count: AtomicU8,
rx_ppi_ch: AtomicU8,
rx_ppi_regs: AtomicPtr<()>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be some kind of Ppi instance type (or AnyPpi if you want to erase the type) instead of a raw pointer?

Copy link
Author

@dmitry-tarnyagin dmitry-tarnyagin Jul 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be completely removed as it’s not relevant to the nRF54. It was part of a naive attempt to just tune the buffered_uarte for the nRF54.

Comment on lines +10 to +17
pub enum DppiInstance {
/// DPPI_00 instance
Dppi00,
/// DPPI_20 instance
Dppi20,
/// DPPI_30 instance
Dppi30,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this instead be built into some type erased AnyPpi type (similar to AnyPin)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can try..

Copy link
Author

@dmitry-tarnyagin dmitry-tarnyagin Jul 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

..Update: The PPI implementation closely resembles the existing implementation of GPIO. Here, the DPPI instance is a direct analogue of a GPIO port. I think it’s quite in-style :).
Existing AnyChannel and AnyGroup are already extended with controller instances.

@dmitry-tarnyagin
Copy link
Author

dmitry-tarnyagin commented Jul 16, 2025

@lulf
Thanks for the comments!

I’ll clean up the original buffered_uarte.rs, removing rx_ppi_regs and/if any other leftover development code.

I think it’s fine to bolt on nRF54L for now. The massive change in the nRF54 register binary interface is probably due to the new DMA.RX.MATCH functionality, which is useless because of errata 44. Right now, I don’t want to ruin the existing embassy-nrf code with tons of

if #[cfg(feature = "_nrf54l")] {
r.dma().rx()…
} else {
r.txd()…
},
it's too ugly for no benefit.

If a dependency on the extend crate is problematic, the compatibility layer can be implemented manually using traits / impl traits for.

Again, thanks for the feedback! Please share any further comments and suggestions. Much appreciated!

Regards, Dmitry

Here we have implemented basic support for the following
peripheral blocks of the nRF54L:

 * uarte, buffered_uarte
 * twim, twis
 * spim, spis
 * saadc
 * pwm
 * dppic, instantiated for dppic 00, 20, 30
 * gpiote, instantiated for gpiote 20, 30
 * timer

Signed-off-by: Dmitry Tarnyagin <[email protected]>
Signed-off-by: Vegard Eriksen <[email protected]>
@dmitry-tarnyagin
Copy link
Author

non-54l buffered_uarte.rs is reverted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants