Embassy for MSPM0

This adds an embassy hal for the Texas Instruments MSPM0 microcontroller series.

So far the GPIO and time drivers have been implemented. I have tested these drivers on the following parts:
- C1104
- L1306
- L2228
- G3507
- G3519

The PAC is generated at https://github.com/mspm0-rs
This commit is contained in:
i509VCB
2025-03-13 22:10:45 -05:00
parent 38f26137fc
commit e0cdc356cc
51 changed files with 4476 additions and 0 deletions

125
embassy-mspm0/Cargo.toml Normal file
View File

@@ -0,0 +1,125 @@
[package]
name = "embassy-mspm0"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Embassy Hardware Abstraction Layer (HAL) for Texas Instruments MSPM0 series microcontrollers"
keywords = ["embedded", "async", "mspm0", "hal", "embedded-hal"]
categories = ["embedded", "hardware-support", "no-std", "asynchronous"]
repository = "https://github.com/embassy-rs/embassy"
documentation = "https://docs.embassy.dev/embassy-mspm0"
[package.metadata.docs.rs]
features = ["defmt", "unstable-pac", "time-driver-any", "time", "mspm0g3507"]
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
embassy-sync = { version = "0.6.2", path = "../embassy-sync" }
embassy-time = { version = "0.4.0", path = "../embassy-time", optional = true }
# TODO: Support other tick rates
embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true, features = ["tick-hz-32_768"] }
embassy-time-queue-utils = { version = "0.1", path = "../embassy-time-queue-utils", optional = true }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] }
embassy-embedded-hal = { version = "0.3.0", path = "../embassy-embedded-hal", default-features = false }
embassy-executor = { version = "0.7.0", path = "../embassy-executor", optional = true }
embedded-hal = { version = "1.0" }
embedded-hal-async = { version = "1.0" }
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
cortex-m-rt = ">=0.6.15,<0.8"
cortex-m = "0.7.6"
critical-section = "1.2.0"
# mspm0-metapac = { version = "" }
mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-9faa5239a8eab04946086158f2a7fdff5a6a179d" }
[build-dependencies]
proc-macro2 = "1.0.94"
quote = "1.0.40"
# mspm0-metapac = { version = "", default-features = false, features = ["metadata"] }
mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-9faa5239a8eab04946086158f2a7fdff5a6a179d", default-features = false, features = ["metadata"] }
[features]
default = ["rt"]
## Enable `mspm0-metapac`'s `rt` feature
rt = ["mspm0-metapac/rt"]
## Use [`defmt`](https://docs.rs/defmt/latest/defmt/) for logging
defmt = [
"dep:defmt",
"embassy-sync/defmt",
"embassy-embedded-hal/defmt",
"embassy-hal-internal/defmt",
"embassy-time?/defmt",
]
## Re-export mspm0-metapac at `mspm0::pac`.
## This is unstable because semver-minor (non-breaking) releases of embassy-mspm0 may major-bump (breaking) the mspm0-metapac version.
## If this is an issue for you, you're encouraged to directly depend on a fixed version of the PAC.
## There are no plans to make this stable.
unstable-pac = []
#! ## Time
# Features starting with `_` are for internal use only. They're not intended
# to be enabled by other crates, and are not covered by semver guarantees.
_time-driver = ["dep:embassy-time-driver", "dep:embassy-time-queue-utils"]
# Use any time driver
time-driver-any = ["_time-driver"]
## Use TIMG0 as time driver
time-driver-timg0 = ["_time-driver"]
## Use TIMG1 as time driver
time-driver-timg1 = ["_time-driver"]
## Use TIMG2 as time driver
time-driver-timg2 = ["_time-driver"]
## Use TIMG3 as time driver
time-driver-timg3 = ["_time-driver"]
## Use TIMG4 as time driver
time-driver-timg4 = ["_time-driver"]
## Use TIMG5 as time driver
time-driver-timg5 = ["_time-driver"]
## Use TIMG6 as time driver
time-driver-timg6 = ["_time-driver"]
## Use TIMG7 as time driver
time-driver-timg7 = ["_time-driver"]
## Use TIMG8 as time driver
time-driver-timg8 = ["_time-driver"]
## Use TIMG9 as time driver
time-driver-timg9 = ["_time-driver"]
## Use TIMG10 as time driver
time-driver-timg10 = ["_time-driver"]
## Use TIMG11 as time driver
time-driver-timg11 = ["_time-driver"]
# TODO: Support TIMG12 and TIMG13
## Use TIMG14 as time driver
time-driver-timg14 = ["_time-driver"]
## Use TIMA0 as time driver
time-driver-tima0 = ["_time-driver"]
## Use TIMA1 as time driver
time-driver-tima1 = ["_time-driver"]
#! ## Chip-selection features
#! Select your chip by specifying the model as a feature, e.g. `mspm0g3507`.
#! Check the `Cargo.toml` for the latest list of supported chips.
#!
#! **Important:** Do not forget to adapt the target chip in your toolchain,
#! e.g. in `.cargo/config.toml`.
mspm0c110x = [ "mspm0-metapac/mspm0c110x" ]
mspm0g110x = [ "mspm0-metapac/mspm0g110x" ]
mspm0g150x = [ "mspm0-metapac/mspm0g150x" ]
mspm0g151x = [ "mspm0-metapac/mspm0g151x" ]
mspm0g310x = [ "mspm0-metapac/mspm0g310x" ]
mspm0g350x = [ "mspm0-metapac/mspm0g350x" ]
mspm0g351x = [ "mspm0-metapac/mspm0g351x" ]
mspm0l110x = [ "mspm0-metapac/mspm0l110x" ]
mspm0l122x = [ "mspm0-metapac/mspm0l122x" ]
mspm0l130x = [ "mspm0-metapac/mspm0l130x" ]
mspm0l134x = [ "mspm0-metapac/mspm0l134x" ]
mspm0l222x = [ "mspm0-metapac/mspm0l222x" ]

616
embassy-mspm0/build.rs Normal file
View File

@@ -0,0 +1,616 @@
use std::collections::HashMap;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::LazyLock;
use std::{env, fs};
use common::CfgSet;
use mspm0_metapac::metadata::METADATA;
use proc_macro2::{Ident, Literal, Span, TokenStream};
use quote::{format_ident, quote};
#[path = "./build_common.rs"]
mod common;
fn main() {
generate_code();
}
fn generate_code() {
let mut cfgs = common::CfgSet::new();
common::set_target_cfgs(&mut cfgs);
cfgs.declare_all(&["gpio_pb", "gpio_pc", "int_group1"]);
let mut singletons = Vec::new();
// Generate singletons for GPIO pins. To only consider pins available on a family, use the name of
// the pins from the pincm mappings.
for pincm_mapping in METADATA.pincm_mappings.iter() {
singletons.push(pincm_mapping.pin.to_string());
}
for peri in METADATA.peripherals {
match peri.kind {
// Specially generated.
"gpio" => match peri.name {
"GPIOB" => cfgs.enable("gpio_pb"),
"GPIOC" => cfgs.enable("gpio_pc"),
_ => (),
},
// These peripherals are managed internally by the hal.
"iomux" | "cpuss" => {}
_ => singletons.push(peri.name.to_string()),
}
}
time_driver(&singletons, &mut cfgs);
// ========
// Write singletons
let mut g = TokenStream::new();
let singleton_tokens: Vec<_> = singletons.iter().map(|s| format_ident!("{}", s)).collect();
g.extend(quote! {
embassy_hal_internal::peripherals_definition!(#(#singleton_tokens),*);
});
g.extend(quote! {
embassy_hal_internal::peripherals_struct!(#(#singleton_tokens),*);
});
// ========
// Generate GPIO pincm lookup tables.
let pincms = METADATA.pincm_mappings.iter().map(|mapping| {
let port_letter = mapping.pin.strip_prefix("P").unwrap();
let port_base = (port_letter.chars().next().unwrap() as u8 - b'A') * 32;
// This assumes all ports are single letter length.
// This is fine unless TI releases a part with 833+ GPIO pins.
let pin_number = mapping.pin[2..].parse::<u8>().unwrap();
let num = port_base + pin_number;
// But subtract 1 since pincm indices start from 0, not 1.
let pincm = Literal::u8_unsuffixed(mapping.pincm - 1);
quote! {
#num => #pincm
}
});
g.extend(quote! {
#[doc = "Get the mapping from GPIO pin port to IOMUX PINCM index. This is required since the mapping from IO to PINCM index is not consistent across parts."]
pub(crate) fn gpio_pincm(pin_port: u8) -> u8 {
match pin_port {
#(#pincms),*,
_ => unreachable!(),
}
}
});
for pincm_mapping in METADATA.pincm_mappings.iter() {
let name = Ident::new(&pincm_mapping.pin, Span::call_site());
let port_letter = pincm_mapping.pin.strip_prefix("P").unwrap();
let port_letter = port_letter.chars().next().unwrap();
let pin_number = Literal::u8_unsuffixed(pincm_mapping.pin[2..].parse::<u8>().unwrap());
let port = Ident::new(&format!("Port{}", port_letter), Span::call_site());
// TODO: Feature gate pins that can be used as NRST
g.extend(quote! {
impl_pin!(#name, crate::gpio::Port::#port, #pin_number);
});
}
// Generate timers
for peripheral in METADATA
.peripherals
.iter()
.filter(|p| p.name.starts_with("TIM"))
{
let name = Ident::new(&peripheral.name, Span::call_site());
let timers = &*TIMERS;
let timer = timers.get(peripheral.name).expect("Timer does not exist");
assert!(timer.bits == 16 || timer.bits == 32);
let bits = if timer.bits == 16 {
quote! { Bits16 }
} else {
quote! { Bits32 }
};
g.extend(quote! {
impl_timer!(#name, #bits);
});
}
// Generate interrupt module
let interrupts: Vec<Ident> = METADATA
.interrupts
.iter()
.map(|interrupt| Ident::new(interrupt.name, Span::call_site()))
.collect();
g.extend(quote! {
embassy_hal_internal::interrupt_mod! {
#(#interrupts),*
}
});
let group_interrupt_enables = METADATA
.interrupts
.iter()
.filter(|interrupt| interrupt.name.contains("GROUP"))
.map(|interrupt| {
let name = Ident::new(interrupt.name, Span::call_site());
quote! {
crate::interrupt::typelevel::#name::enable();
}
});
// Generate interrupt enables for groups
g.extend(quote! {
pub fn enable_group_interrupts(_cs: critical_section::CriticalSection) {
use crate::interrupt::typelevel::Interrupt;
unsafe {
#(#group_interrupt_enables)*
}
}
});
let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
let out_file = out_dir.join("_generated.rs").to_string_lossy().to_string();
fs::write(&out_file, g.to_string()).unwrap();
rustfmt(&out_file);
}
fn time_driver(singletons: &[String], cfgs: &mut CfgSet) {
// Timer features
for (timer, desc) in TIMERS.iter() {
if desc.bits != 16 {
continue;
}
let name = timer.to_lowercase();
cfgs.declare(&format!("time_driver_{}", name));
}
let time_driver = match env::vars()
.map(|(a, _)| a)
.filter(|x| x.starts_with("CARGO_FEATURE_TIME_DRIVER_"))
.get_one()
{
Ok(x) => Some(
x.strip_prefix("CARGO_FEATURE_TIME_DRIVER_")
.unwrap()
.to_ascii_lowercase(),
),
Err(GetOneError::None) => None,
Err(GetOneError::Multiple) => panic!("Multiple time-driver-xxx Cargo features enabled"),
};
// Verify the selected timer is available
let singleton = match time_driver.as_ref().map(|x| x.as_ref()) {
None => "",
Some("timg0") => "TIMG0",
Some("timg1") => "TIMG1",
Some("timg2") => "TIMG2",
Some("timg3") => "TIMG3",
Some("timg4") => "TIMG4",
Some("timg5") => "TIMG5",
Some("timg6") => "TIMG6",
Some("timg7") => "TIMG7",
Some("timg8") => "TIMG8",
Some("timg9") => "TIMG9",
Some("timg10") => "TIMG10",
Some("timg11") => "TIMG11",
Some("timg14") => "TIMG14",
Some("tima0") => "TIMA0",
Some("tima1") => "TIMA1",
Some("any") => {
// Order of timer candidates:
// 1. 16-bit, 2 channel
// 2. 16-bit, 2 channel with shadow registers
// 3. 16-bit, 4 channel
// 4. 16-bit with QEI
// 5. Advanced timers
//
// TODO: Select RTC first if available
// TODO: 32-bit timers are not considered yet
[
// 16-bit, 2 channel
"TIMG0", "TIMG1", "TIMG2", "TIMG3",
// 16-bit, 2 channel with shadow registers
"TIMG4", "TIMG5", "TIMG6", "TIMG7", // 16-bit, 4 channel
"TIMG14", // 16-bit with QEI
"TIMG8", "TIMG9", "TIMG10", "TIMG11", // Advanced timers
"TIMA0", "TIMA1",
]
.iter()
.find(|tim| singletons.contains(&tim.to_string()))
.expect("Could not find any timer")
}
_ => panic!("unknown time_driver {:?}", time_driver),
};
if !singleton.is_empty() {
cfgs.enable(format!("time_driver_{}", singleton.to_lowercase()));
}
}
/// rustfmt a given path.
/// Failures are logged to stderr and ignored.
fn rustfmt(path: impl AsRef<Path>) {
let path = path.as_ref();
match Command::new("rustfmt").args([path]).output() {
Err(e) => {
eprintln!("failed to exec rustfmt {:?}: {:?}", path, e);
}
Ok(out) => {
if !out.status.success() {
eprintln!("rustfmt {:?} failed:", path);
eprintln!("=== STDOUT:");
std::io::stderr().write_all(&out.stdout).unwrap();
eprintln!("=== STDERR:");
std::io::stderr().write_all(&out.stderr).unwrap();
}
}
}
}
#[allow(dead_code)]
struct TimerDesc {
bits: u8,
/// Is there an 8-bit prescaler
prescaler: bool,
/// Is there a repeat counter
repeat_counter: bool,
ccp_channels_internal: u8,
ccp_channels_external: u8,
external_pwm_channels: u8,
phase_load: bool,
shadow_load: bool,
shadow_ccs: bool,
deadband: bool,
fault_handler: bool,
qei_hall: bool,
}
/// Description of all timer instances.
const TIMERS: LazyLock<HashMap<String, TimerDesc>> = LazyLock::new(|| {
let mut map = HashMap::new();
map.insert(
"TIMG0".into(),
TimerDesc {
bits: 16,
prescaler: true,
repeat_counter: false,
ccp_channels_internal: 2,
ccp_channels_external: 2,
external_pwm_channels: 2,
phase_load: false,
shadow_load: false,
shadow_ccs: false,
deadband: false,
fault_handler: false,
qei_hall: false,
},
);
map.insert(
"TIMG1".into(),
TimerDesc {
bits: 16,
prescaler: true,
repeat_counter: false,
ccp_channels_internal: 2,
ccp_channels_external: 2,
external_pwm_channels: 2,
phase_load: false,
shadow_load: false,
shadow_ccs: false,
deadband: false,
fault_handler: false,
qei_hall: false,
},
);
map.insert(
"TIMG2".into(),
TimerDesc {
bits: 16,
prescaler: true,
repeat_counter: false,
ccp_channels_internal: 2,
ccp_channels_external: 2,
external_pwm_channels: 2,
phase_load: false,
shadow_load: false,
shadow_ccs: false,
deadband: false,
fault_handler: false,
qei_hall: false,
},
);
map.insert(
"TIMG3".into(),
TimerDesc {
bits: 16,
prescaler: true,
repeat_counter: false,
ccp_channels_internal: 2,
ccp_channels_external: 2,
external_pwm_channels: 2,
phase_load: false,
shadow_load: false,
shadow_ccs: false,
deadband: false,
fault_handler: false,
qei_hall: false,
},
);
map.insert(
"TIMG4".into(),
TimerDesc {
bits: 16,
prescaler: true,
repeat_counter: false,
ccp_channels_internal: 2,
ccp_channels_external: 2,
external_pwm_channels: 2,
phase_load: false,
shadow_load: true,
shadow_ccs: true,
deadband: false,
fault_handler: false,
qei_hall: false,
},
);
map.insert(
"TIMG5".into(),
TimerDesc {
bits: 16,
prescaler: true,
repeat_counter: false,
ccp_channels_internal: 2,
ccp_channels_external: 2,
external_pwm_channels: 2,
phase_load: false,
shadow_load: true,
shadow_ccs: true,
deadband: false,
fault_handler: false,
qei_hall: false,
},
);
map.insert(
"TIMG6".into(),
TimerDesc {
bits: 16,
prescaler: true,
repeat_counter: false,
ccp_channels_internal: 2,
ccp_channels_external: 2,
external_pwm_channels: 2,
phase_load: false,
shadow_load: true,
shadow_ccs: true,
deadband: false,
fault_handler: false,
qei_hall: false,
},
);
map.insert(
"TIMG7".into(),
TimerDesc {
bits: 16,
prescaler: true,
repeat_counter: false,
ccp_channels_internal: 2,
ccp_channels_external: 2,
external_pwm_channels: 2,
phase_load: false,
shadow_load: true,
shadow_ccs: true,
deadband: false,
fault_handler: false,
qei_hall: false,
},
);
map.insert(
"TIMG8".into(),
TimerDesc {
bits: 16,
prescaler: true,
repeat_counter: false,
ccp_channels_internal: 2,
ccp_channels_external: 2,
external_pwm_channels: 2,
phase_load: false,
shadow_load: false,
shadow_ccs: false,
deadband: false,
fault_handler: false,
qei_hall: true,
},
);
map.insert(
"TIMG9".into(),
TimerDesc {
bits: 16,
prescaler: true,
repeat_counter: false,
ccp_channels_internal: 2,
ccp_channels_external: 2,
external_pwm_channels: 2,
phase_load: false,
shadow_load: false,
shadow_ccs: false,
deadband: false,
fault_handler: false,
qei_hall: true,
},
);
map.insert(
"TIMG10".into(),
TimerDesc {
bits: 16,
prescaler: true,
repeat_counter: false,
ccp_channels_internal: 2,
ccp_channels_external: 2,
external_pwm_channels: 2,
phase_load: false,
shadow_load: false,
shadow_ccs: false,
deadband: false,
fault_handler: false,
qei_hall: true,
},
);
map.insert(
"TIMG11".into(),
TimerDesc {
bits: 16,
prescaler: true,
repeat_counter: false,
ccp_channels_internal: 2,
ccp_channels_external: 2,
external_pwm_channels: 2,
phase_load: false,
shadow_load: false,
shadow_ccs: false,
deadband: false,
fault_handler: false,
qei_hall: true,
},
);
map.insert(
"TIMG12".into(),
TimerDesc {
bits: 32,
prescaler: false,
repeat_counter: false,
ccp_channels_internal: 2,
ccp_channels_external: 2,
external_pwm_channels: 2,
phase_load: false,
shadow_load: false,
shadow_ccs: true,
deadband: false,
fault_handler: false,
qei_hall: false,
},
);
map.insert(
"TIMG13".into(),
TimerDesc {
bits: 32,
prescaler: false,
repeat_counter: false,
ccp_channels_internal: 2,
ccp_channels_external: 2,
external_pwm_channels: 2,
phase_load: false,
shadow_load: false,
shadow_ccs: true,
deadband: false,
fault_handler: false,
qei_hall: false,
},
);
map.insert(
"TIMG14".into(),
TimerDesc {
bits: 16,
prescaler: true,
repeat_counter: false,
ccp_channels_internal: 4,
ccp_channels_external: 4,
external_pwm_channels: 4,
phase_load: false,
shadow_load: false,
shadow_ccs: false,
deadband: false,
fault_handler: false,
qei_hall: false,
},
);
map.insert(
"TIMA0".into(),
TimerDesc {
bits: 16,
prescaler: true,
repeat_counter: true,
ccp_channels_internal: 4,
ccp_channels_external: 2,
external_pwm_channels: 8,
phase_load: true,
shadow_load: true,
shadow_ccs: true,
deadband: true,
fault_handler: true,
qei_hall: false,
},
);
map.insert(
"TIMA1".into(),
TimerDesc {
bits: 16,
prescaler: true,
repeat_counter: true,
ccp_channels_internal: 2,
ccp_channels_external: 2,
external_pwm_channels: 4,
phase_load: true,
shadow_load: true,
shadow_ccs: true,
deadband: true,
fault_handler: true,
qei_hall: false,
},
);
map
});
enum GetOneError {
None,
Multiple,
}
trait IteratorExt: Iterator {
fn get_one(self) -> Result<Self::Item, GetOneError>;
}
impl<T: Iterator> IteratorExt for T {
fn get_one(mut self) -> Result<Self::Item, GetOneError> {
match self.next() {
None => Err(GetOneError::None),
Some(res) => match self.next() {
Some(_) => Err(GetOneError::Multiple),
None => Ok(res),
},
}
}
}

View File

@@ -0,0 +1,94 @@
// NOTE: this file is copy-pasted between several Embassy crates, because there is no
// straightforward way to share this code:
// - it cannot be placed into the root of the repo and linked from each build.rs using `#[path =
// "../build_common.rs"]`, because `cargo publish` requires that all files published with a crate
// reside in the crate's directory,
// - it cannot be symlinked from `embassy-xxx/build_common.rs` to `../build_common.rs`, because
// symlinks don't work on Windows.
use std::collections::HashSet;
use std::env;
/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring
/// them (`cargo:rust-check-cfg=cfg(X)`).
#[derive(Debug)]
pub struct CfgSet {
enabled: HashSet<String>,
declared: HashSet<String>,
}
impl CfgSet {
pub fn new() -> Self {
Self {
enabled: HashSet::new(),
declared: HashSet::new(),
}
}
/// Enable a config, which can then be used in `#[cfg(...)]` for conditional compilation.
///
/// All configs that can potentially be enabled should be unconditionally declared using
/// [`Self::declare()`].
pub fn enable(&mut self, cfg: impl AsRef<str>) {
if self.enabled.insert(cfg.as_ref().to_owned()) {
println!("cargo:rustc-cfg={}", cfg.as_ref());
}
}
pub fn enable_all(&mut self, cfgs: &[impl AsRef<str>]) {
for cfg in cfgs.iter() {
self.enable(cfg.as_ref());
}
}
/// Declare a valid config for conditional compilation, without enabling it.
///
/// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid.
pub fn declare(&mut self, cfg: impl AsRef<str>) {
if self.declared.insert(cfg.as_ref().to_owned()) {
println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref());
}
}
pub fn declare_all(&mut self, cfgs: &[impl AsRef<str>]) {
for cfg in cfgs.iter() {
self.declare(cfg.as_ref());
}
}
pub fn set(&mut self, cfg: impl Into<String>, enable: bool) {
let cfg = cfg.into();
if enable {
self.enable(cfg.clone());
}
self.declare(cfg);
}
}
/// Sets configs that describe the target platform.
pub fn set_target_cfgs(cfgs: &mut CfgSet) {
let target = env::var("TARGET").unwrap();
if target.starts_with("thumbv6m-") {
cfgs.enable_all(&["cortex_m", "armv6m"]);
} else if target.starts_with("thumbv7m-") {
cfgs.enable_all(&["cortex_m", "armv7m"]);
} else if target.starts_with("thumbv7em-") {
cfgs.enable_all(&["cortex_m", "armv7m", "armv7em"]);
} else if target.starts_with("thumbv8m.base") {
cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_base"]);
} else if target.starts_with("thumbv8m.main") {
cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_main"]);
}
cfgs.declare_all(&[
"cortex_m",
"armv6m",
"armv7m",
"armv7em",
"armv8m",
"armv8m_base",
"armv8m_main",
]);
cfgs.set("has_fpu", target.ends_with("-eabihf"));
}

270
embassy-mspm0/src/fmt.rs Normal file
View File

@@ -0,0 +1,270 @@
#![macro_use]
#![allow(unused)]
use core::fmt::{Debug, Display, LowerHex};
#[cfg(all(feature = "defmt", feature = "log"))]
compile_error!("You may not enable both `defmt` and `log` features.");
#[collapse_debuginfo(yes)]
macro_rules! assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_eq!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_ne!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug_assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug_assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_eq!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug_assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_ne!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! todo {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::todo!($($x)*);
#[cfg(feature = "defmt")]
::defmt::todo!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! panic {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::panic!($($x)*);
#[cfg(feature = "defmt")]
::defmt::panic!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! trace {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::trace!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::trace!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::debug!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::debug!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! info {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::info!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::info!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! warn {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::warn!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::warn!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! error {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::error!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::error!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[cfg(feature = "defmt")]
#[collapse_debuginfo(yes)]
macro_rules! unwrap {
($($x:tt)*) => {
::defmt::unwrap!($($x)*)
};
}
#[cfg(not(feature = "defmt"))]
#[collapse_debuginfo(yes)]
macro_rules! unwrap {
($arg:expr) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
}
}
};
($arg:expr, $($msg:expr),+ $(,)? ) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NoneError;
pub trait Try {
type Ok;
type Error;
fn into_result(self) -> Result<Self::Ok, Self::Error>;
}
impl<T> Try for Option<T> {
type Ok = T;
type Error = NoneError;
#[inline]
fn into_result(self) -> Result<T, NoneError> {
self.ok_or(NoneError)
}
}
impl<T, E> Try for Result<T, E> {
type Ok = T;
type Error = E;
#[inline]
fn into_result(self) -> Self {
self
}
}
pub(crate) struct Bytes<'a>(pub &'a [u8]);
impl<'a> Debug for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
impl<'a> Display for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
impl<'a> LowerHex for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
#[cfg(feature = "defmt")]
impl<'a> defmt::Format for Bytes<'a> {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(fmt, "{:02x}", self.0)
}
}

1070
embassy-mspm0/src/gpio.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
use crate::pac;
use crate::pac::interrupt;
#[cfg(feature = "rt")]
#[interrupt]
fn GROUP0() {
use mspm0_metapac::Group0;
let group = pac::CPUSS.int_group(1);
// TODO: Decompose to direct u8
let iidx = group.iidx().read().stat().to_bits();
let Ok(group) = pac::Group0::try_from(iidx as u8) else {
debug!("Invalid IIDX for group 0: {}", iidx);
return;
};
match group {
Group0::WWDT0 => todo!("implement WWDT0"),
Group0::DEBUGSS => todo!("implement DEBUGSS"),
Group0::FLASHCTL => todo!("implement FLASHCTL"),
Group0::SYSCTL => todo!("implement SYSCTL"),
}
}

View File

@@ -0,0 +1,51 @@
use crate::pac;
use crate::pac::interrupt;
#[cfg(feature = "rt")]
#[interrupt]
fn GROUP0() {
use mspm0_metapac::Group0;
let group = pac::CPUSS.int_group(1);
// Must subtract by 1 since NO_INTR is value 0
let iidx = group.iidx().read().stat().to_bits() - 1;
let Ok(group) = pac::Group0::try_from(iidx as u8) else {
debug!("Invalid IIDX for group 0: {}", iidx);
return;
};
match group {
Group0::WWDT0 => todo!("implement WWDT0"),
Group0::WWDT1 => todo!("implement WWDT1"),
Group0::DEBUGSS => todo!("implement DEBUGSS"),
Group0::FLASHCTL => todo!("implement FLASHCTL"),
Group0::SYSCTL => todo!("implement SYSCTL"),
}
}
#[cfg(feature = "rt")]
#[interrupt]
fn GROUP1() {
use mspm0_metapac::Group1;
let group = pac::CPUSS.int_group(1);
// Must subtract by 1 since NO_INTR is value 0
let iidx = group.iidx().read().stat().to_bits() - 1;
let Ok(group) = pac::Group1::try_from(iidx as u8) else {
debug!("Invalid IIDX for group 1: {}", iidx);
return;
};
match group {
Group1::GPIOA => crate::gpio::gpioa_interrupt(),
Group1::GPIOB => crate::gpio::gpiob_interrupt(),
Group1::COMP0 => todo!("implement COMP0"),
Group1::COMP1 => todo!("implement COMP1"),
Group1::COMP2 => todo!("implement COMP2"),
Group1::TRNG => todo!("implement TRNG"),
}
}

View File

@@ -0,0 +1,52 @@
use crate::pac;
use crate::pac::interrupt;
#[cfg(feature = "rt")]
#[interrupt]
fn GROUP0() {
use mspm0_metapac::Group0;
let group = pac::CPUSS.int_group(1);
// Must subtract by 1 since NO_INTR is value 0
let iidx = group.iidx().read().stat().to_bits() - 1;
let Ok(group) = pac::Group0::try_from(iidx as u8) else {
debug!("Invalid IIDX for group 0: {}", iidx);
return;
};
match group {
Group0::WWDT0 => todo!("implement WWDT0"),
Group0::WWDT1 => todo!("implement WWDT1"),
Group0::DEBUGSS => todo!("implement DEBUGSS"),
Group0::FLASHCTL => todo!("implement FLASHCTL"),
Group0::SYSCTL => todo!("implement SYSCTL"),
}
}
#[cfg(feature = "rt")]
#[interrupt]
fn GROUP1() {
use mspm0_metapac::Group1;
let group = pac::CPUSS.int_group(1);
// Must subtract by 1 since NO_INTR is value 0
let iidx = group.iidx().read().stat().to_bits() - 1;
let Ok(group) = pac::Group1::try_from(iidx as u8) else {
debug!("Invalid IIDX for group 1: {}", iidx);
return;
};
match group {
Group1::GPIOA => crate::gpio::gpioa_interrupt(),
Group1::GPIOB => crate::gpio::gpiob_interrupt(),
Group1::COMP0 => todo!("implement COMP0"),
Group1::COMP1 => todo!("implement COMP1"),
Group1::COMP2 => todo!("implement COMP2"),
Group1::TRNG => todo!("implement TRNG"),
Group1::GPIOC => crate::gpio::gpioc_interrupt(),
}
}

View File

@@ -0,0 +1,46 @@
use crate::pac;
use crate::pac::interrupt;
#[cfg(feature = "rt")]
#[interrupt]
fn GROUP0() {
use mspm0_metapac::Group0;
let group = pac::CPUSS.int_group(1);
// Must subtract by 1 since NO_INTR is value 0
let iidx = group.iidx().read().stat().to_bits() - 1;
let Ok(group) = pac::Group0::try_from(iidx as u8) else {
debug!("Invalid IIDX for group 0: {}", iidx);
return;
};
match group {
Group0::WWDT0 => todo!("implement WWDT0"),
Group0::DEBUGSS => todo!("implement DEBUGSS"),
Group0::FLASHCTL => todo!("implement FLASHCTL"),
Group0::SYSCTL => todo!("implement SYSCTL"),
}
}
#[cfg(feature = "rt")]
#[interrupt]
fn GROUP1() {
use mspm0_metapac::Group1;
let group = pac::CPUSS.int_group(1);
// Must subtract by 1 since NO_INTR is value 0
let iidx = group.iidx().read().stat().to_bits() - 1;
let Ok(group) = pac::Group1::try_from(iidx as u8) else {
debug!("Invalid IIDX for group 1: {}", iidx);
return;
};
match group {
Group1::GPIOA => crate::gpio::gpioa_interrupt(),
Group1::COMP0 => todo!("implement COMP0"),
}
}

View File

@@ -0,0 +1,49 @@
use crate::pac;
use crate::pac::interrupt;
#[cfg(feature = "rt")]
#[interrupt]
fn GROUP0() {
use mspm0_metapac::Group0;
let group = pac::CPUSS.int_group(1);
// Must subtract by 1 since NO_INTR is value 0
let iidx = group.iidx().read().stat().to_bits() - 1;
let Ok(group) = pac::Group0::try_from(iidx as u8) else {
debug!("Invalid IIDX for group 0: {}", iidx);
return;
};
match group {
Group0::WWDT0 => todo!("implement WWDT0"),
Group0::DEBUGSS => todo!("implement DEBUGSS"),
Group0::FLASHCTL => todo!("implement FLASHCTL"),
Group0::SYSCTL => todo!("implement SYSCTL"),
}
}
#[cfg(feature = "rt")]
#[interrupt]
fn GROUP1() {
use mspm0_metapac::Group1;
let group = pac::CPUSS.int_group(1);
// Must subtract by 1 since NO_INTR is value 0
let iidx = group.iidx().read().stat().to_bits() - 1;
let Ok(group) = pac::Group1::try_from(iidx as u8) else {
debug!("Invalid IIDX for group 1: {}", iidx);
return;
};
match group {
Group1::GPIOA => crate::gpio::gpioa_interrupt(),
Group1::GPIOB => crate::gpio::gpiob_interrupt(),
Group1::COMP0 => todo!("implement COMP0"),
Group1::TRNG => todo!("implement TRNG"),
Group1::GPIOC => crate::gpio::gpioc_interrupt(),
}
}

112
embassy-mspm0/src/lib.rs Normal file
View File

@@ -0,0 +1,112 @@
#![no_std]
// Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc
#![cfg_attr(
docsrs,
feature(doc_auto_cfg, doc_cfg_hide),
doc(cfg_hide(doc, docsrs))
)]
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
pub mod gpio;
pub mod timer;
#[cfg(feature = "_time-driver")]
mod time_driver;
// Interrupt group handlers.
#[cfg_attr(feature = "mspm0c110x", path = "int_group/c110x.rs")]
#[cfg_attr(feature = "mspm0g110x", path = "int_group/g110x.rs")]
#[cfg_attr(feature = "mspm0g150x", path = "int_group/g150x.rs")]
#[cfg_attr(feature = "mspm0g151x", path = "int_group/g151x.rs")]
#[cfg_attr(feature = "mspm0g310x", path = "int_group/g310x.rs")]
#[cfg_attr(feature = "mspm0g350x", path = "int_group/g350x.rs")]
#[cfg_attr(feature = "mspm0g351x", path = "int_group/g351x.rs")]
#[cfg_attr(feature = "mspm0l110x", path = "int_group/l110x.rs")]
#[cfg_attr(feature = "mspm0l122x", path = "int_group/l122x.rs")]
#[cfg_attr(feature = "mspm0l130x", path = "int_group/l130x.rs")]
#[cfg_attr(feature = "mspm0l134x", path = "int_group/l134x.rs")]
#[cfg_attr(feature = "mspm0l222x", path = "int_group/l222x.rs")]
mod int_group;
pub(crate) mod _generated {
#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(non_snake_case)]
#![allow(missing_docs)]
include!(concat!(env!("OUT_DIR"), "/_generated.rs"));
}
// Reexports
pub use _generated::{peripherals, Peripherals};
pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
#[cfg(feature = "unstable-pac")]
pub use mspm0_metapac as pac;
#[cfg(not(feature = "unstable-pac"))]
pub(crate) use mspm0_metapac as pac;
pub use crate::_generated::interrupt;
pub(crate) use _generated::gpio_pincm;
/// `embassy-mspm0` global configuration.
#[non_exhaustive]
#[derive(Clone, Copy)]
pub struct Config {
// TODO
}
impl Default for Config {
fn default() -> Self {
Self {
// TODO
}
}
}
pub fn init(_config: Config) -> Peripherals {
critical_section::with(|cs| {
let peripherals = Peripherals::take_with_cs(cs);
// TODO: Further clock configuration
pac::SYSCTL.mclkcfg().modify(|w| {
// Enable MFCLK
w.set_usemftick(true);
// MDIV must be disabled if MFCLK is enabled.
w.set_mdiv(0);
});
// Enable MFCLK for peripheral use
//
// TODO: Optional?
pac::SYSCTL.genclken().modify(|w| {
w.set_mfpclken(true);
});
pac::SYSCTL.borthreshold().modify(|w| {
w.set_level(0);
});
gpio::init(pac::GPIOA);
#[cfg(gpio_pb)]
gpio::init(pac::GPIOB);
#[cfg(gpio_pc)]
gpio::init(pac::GPIOC);
_generated::enable_group_interrupts(cs);
#[cfg(feature = "mspm0c110x")]
unsafe {
use crate::_generated::interrupt::typelevel::Interrupt;
crate::interrupt::typelevel::GPIOA::enable();
}
#[cfg(feature = "_time-driver")]
time_driver::init(cs);
peripherals
})
}

View File

@@ -0,0 +1,437 @@
use core::{
cell::{Cell, RefCell},
sync::atomic::{compiler_fence, AtomicU32, Ordering},
task::Waker,
};
use critical_section::{CriticalSection, Mutex};
use embassy_time_driver::Driver;
use embassy_time_queue_utils::Queue;
use mspm0_metapac::{
interrupt,
tim::{
vals::{Cm, Cvae, CxC, EvtCfg, PwrenKey, Ratio, Repeat, ResetKey},
Counterregs16, Tim,
},
};
use crate::peripherals;
use crate::timer::SealedTimer;
// Currently TIMG12 and TIMG13 are excluded because those are 32-bit timers.
#[cfg(time_driver_timg0)]
type T = peripherals::TIMG0;
#[cfg(time_driver_timg1)]
type T = peripherals::TIMG1;
#[cfg(time_driver_timg2)]
type T = peripherals::TIMG2;
#[cfg(time_driver_timg3)]
type T = peripherals::TIMG3;
#[cfg(time_driver_timg4)]
type T = peripherals::TIMG4;
#[cfg(time_driver_timg5)]
type T = peripherals::TIMG5;
#[cfg(time_driver_timg6)]
type T = peripherals::TIMG6;
#[cfg(time_driver_timg7)]
type T = peripherals::TIMG7;
#[cfg(time_driver_timg8)]
type T = peripherals::TIMG8;
#[cfg(time_driver_timg9)]
type T = peripherals::TIMG9;
#[cfg(time_driver_timg10)]
type T = peripherals::TIMG10;
#[cfg(time_driver_timg11)]
type T = peripherals::TIMG11;
#[cfg(time_driver_timg14)]
type T = peripherals::TIMG14;
#[cfg(time_driver_tima0)]
type T = peripherals::TIMA0;
#[cfg(time_driver_tima1)]
type T = peripherals::TIMA1;
// TODO: RTC
fn regs() -> Tim {
unsafe { Tim::from_ptr(T::regs()) }
}
fn regs_counter(tim: Tim) -> Counterregs16 {
unsafe { Counterregs16::from_ptr(tim.counterregs(0).as_ptr()) }
}
/// Clock timekeeping works with something we call "periods", which are time intervals
/// of 2^15 ticks. The Clock counter value is 16 bits, so one "overflow cycle" is 2 periods.
fn calc_now(period: u32, counter: u16) -> u64 {
((period as u64) << 15) + ((counter as u32 ^ ((period & 1) << 15)) as u64)
}
/// The TIMx driver uses one of the `TIMG` or `TIMA` timer instances to implement a timer with a 32.768 kHz
/// tick rate. (TODO: Allow setting the tick rate)
///
/// This driver defines a period to be 2^15 ticks. 16-bit timers of course count to 2^16 ticks.
///
/// To generate a period every 2^15 ticks, the CC0 value is set to 2^15 and the load value set to 2^16.
/// Incrementing the period on a CCU0 and load results in the a period of 2^15 ticks.
///
/// For a specific timestamp, load the lower 16 bits into the CC1 value. When the period where the timestamp
/// should be enabled is reached, then the CCU1 (CC1 up) interrupt runs to actually wake the timer.
///
/// TODO: Compensate for per part variance. This can supposedly be done with the FCC system.
/// TODO: Allow using 32-bit timers (TIMG12 and TIMG13).
struct TimxDriver {
/// Number of 2^15 periods elapsed since boot.
period: AtomicU32,
/// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
alarm: Mutex<Cell<u64>>,
queue: Mutex<RefCell<Queue>>,
}
impl TimxDriver {
#[inline(never)]
fn init(&'static self, _cs: CriticalSection) {
// Clock config
// TODO: Configurable tick rate up to 4 MHz (32 kHz for now)
let regs = regs();
// Reset timer
regs.gprcm(0).rstctl().write(|w| {
w.set_resetassert(true);
w.set_key(ResetKey::KEY);
w.set_resetstkyclr(true);
});
// Power up timer
regs.gprcm(0).pwren().write(|w| {
w.set_enable(true);
w.set_key(PwrenKey::KEY);
});
// Following the instructions according to SLAU847D 23.2.1: TIMCLK Configuration
// 1. Select TIMCLK source
regs.clksel().modify(|w| {
// Use LFCLK for a 32.768kHz tick rate
w.set_lfclk_sel(true);
// TODO: Allow MFCLK for configurable tick rate up to 4 MHz
// w.set_mfclk_sel(ClkSel::ENABLE);
});
// 2. Divide by TIMCLK, we don't need to divide further for the 32kHz tick rate
regs.clkdiv().modify(|w| {
w.set_ratio(Ratio::DIV_BY_1);
});
// 3. To be generic across timer instances, we do not use the prescaler.
// TODO: mspm0-sdk always sets this, regardless of timer width?
regs.commonregs(0).cps().modify(|w| {
w.set_pcnt(0);
});
regs.pdbgctl().modify(|w| {
w.set_free(true);
});
// 4. Enable the TIMCLK.
regs.commonregs(0).cclkctl().modify(|w| {
w.set_clken(true);
});
regs.counterregs(0).ctrctl().modify(|w| {
// allow counting during debug
w.set_repeat(Repeat::REPEAT_3);
w.set_cvae(Cvae::ZEROVAL);
w.set_cm(Cm::UP);
// Must explicitly set CZC, CAC and CLC to 0 in order for all the timers to count.
//
// The reset value of these registers is 0x07, which is a reserved value.
//
// Looking at a bit representation of the reset value, this appears to be an AND
// of 2-input QEI mode and CCCTL_3 ACOND. Given that TIMG14 and TIMA0 have no QEI
// and 4 capture and compare channels, this works by accident for those timer units.
w.set_czc(CxC::CCTL0);
w.set_cac(CxC::CCTL0);
w.set_clc(CxC::CCTL0);
});
// Setup the period
let ctr = regs_counter(regs);
// Middle
ctr.cc(0).modify(|w| {
w.set_ccval(0x7FFF);
});
ctr.load().modify(|w| {
w.set_ld(u16::MAX);
});
// Enable the period interrupts
//
// This does not appear to ever be set for CPU_INT in the TI SDK and is not technically needed.
regs.evt_mode().modify(|w| {
w.set_evt_cfg(0, EvtCfg::SOFTWARE);
});
regs.int_event(0).imask().modify(|w| {
w.set_l(true);
w.set_ccu0(true);
});
unsafe { T::enable_interrupt() };
// Allow the counter to start counting.
regs.counterregs(0).ctrctl().modify(|w| {
w.set_en(true);
});
}
#[inline(never)]
fn next_period(&self) {
let r = regs();
// We only modify the period from the timer interrupt, so we know this can't race.
let period = self.period.load(Ordering::Relaxed) + 1;
self.period.store(period, Ordering::Relaxed);
let t = (period as u64) << 15;
critical_section::with(move |cs| {
r.int_event(0).imask().modify(move |w| {
let alarm = self.alarm.borrow(cs);
let at = alarm.get();
if at < t + 0xC000 {
// just enable it. `set_alarm` has already set the correct CC1 val.
w.set_ccu1(true);
}
})
});
}
#[inline(never)]
fn on_interrupt(&self) {
let r = regs();
critical_section::with(|cs| {
let mis = r.int_event(0).mis().read();
// Advance to next period if overflowed
if mis.l() {
self.next_period();
r.int_event(0).iclr().write(|w| {
w.set_l(true);
});
}
if mis.ccu0() {
self.next_period();
r.int_event(0).iclr().write(|w| {
w.set_ccu0(true);
});
}
if mis.ccu1() {
r.int_event(0).iclr().write(|w| {
w.set_ccu1(true);
});
self.trigger_alarm(cs);
}
});
}
fn trigger_alarm(&self, cs: CriticalSection) {
let mut next = self
.queue
.borrow(cs)
.borrow_mut()
.next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = self
.queue
.borrow(cs)
.borrow_mut()
.next_expiration(self.now());
}
}
fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
let r = regs();
let ctr = regs_counter(r);
self.alarm.borrow(cs).set(timestamp);
let t = self.now();
if timestamp <= t {
// If alarm timestamp has passed the alarm will not fire.
// Disarm the alarm and return `false` to indicate that.
r.int_event(0).imask().modify(|w| w.set_ccu1(false));
self.alarm.borrow(cs).set(u64::MAX);
return false;
}
// Write the CC1 value regardless of whether we're going to enable it now or not.
// This way, when we enable it later, the right value is already set.
ctr.cc(1).write(|w| {
w.set_ccval(timestamp as u16);
});
// Enable it if it'll happen soon. Otherwise, `next_period` will enable it.
let diff = timestamp - t;
r.int_event(0).imask().modify(|w| w.set_ccu1(diff < 0xC000));
// Reevaluate if the alarm timestamp is still in the future
let t = self.now();
if timestamp <= t {
// If alarm timestamp has passed since we set it, we have a race condition and
// the alarm may or may not have fired.
// Disarm the alarm and return `false` to indicate that.
// It is the caller's responsibility to handle this ambiguity.
r.int_event(0).imask().modify(|w| w.set_ccu1(false));
self.alarm.borrow(cs).set(u64::MAX);
return false;
}
// We're confident the alarm will ring in the future.
true
}
}
impl Driver for TimxDriver {
fn now(&self) -> u64 {
let regs = regs();
let period = self.period.load(Ordering::Relaxed);
// Ensure the compiler does not read the counter before the period.
compiler_fence(Ordering::Acquire);
let counter = regs_counter(regs).ctr().read().cctr() as u16;
calc_now(period, counter)
}
fn schedule_wake(&self, at: u64, waker: &Waker) {
critical_section::with(|cs| {
let mut queue = self.queue.borrow(cs).borrow_mut();
if queue.schedule_wake(at, waker) {
let mut next = queue.next_expiration(self.now());
while !self.set_alarm(cs, next) {
next = queue.next_expiration(self.now());
}
}
});
}
}
embassy_time_driver::time_driver_impl!(static DRIVER: TimxDriver = TimxDriver {
period: AtomicU32::new(0),
alarm: Mutex::new(Cell::new(u64::MAX)),
queue: Mutex::new(RefCell::new(Queue::new()))
});
pub(crate) fn init(cs: CriticalSection) {
DRIVER.init(cs);
}
#[cfg(time_driver_timg0)]
#[interrupt]
fn TIMG0() {
DRIVER.on_interrupt();
}
#[cfg(time_driver_timg1)]
#[interrupt]
fn TIMG1() {
DRIVER.on_interrupt();
}
#[cfg(time_driver_timg2)]
#[interrupt]
fn TIMG2() {
DRIVER.on_interrupt();
}
#[cfg(time_driver_timg3)]
#[interrupt]
fn TIMG3() {
DRIVER.on_interrupt();
}
#[cfg(time_driver_timg4)]
#[interrupt]
fn TIMG4() {
DRIVER.on_interrupt();
}
#[cfg(time_driver_timg5)]
#[interrupt]
fn TIMG5() {
DRIVER.on_interrupt();
}
#[cfg(time_driver_timg6)]
#[interrupt]
fn TIMG6() {
DRIVER.on_interrupt();
}
#[cfg(time_driver_timg7)]
#[interrupt]
fn TIMG7() {
DRIVER.on_interrupt();
}
#[cfg(time_driver_timg8)]
#[interrupt]
fn TIMG8() {
DRIVER.on_interrupt();
}
#[cfg(time_driver_timg9)]
#[interrupt]
fn TIMG9() {
DRIVER.on_interrupt();
}
#[cfg(time_driver_timg10)]
#[interrupt]
fn TIMG10() {
DRIVER.on_interrupt();
}
#[cfg(time_driver_timg11)]
#[interrupt]
fn TIMG11() {
DRIVER.on_interrupt();
}
// TODO: TIMG12 and TIMG13
#[cfg(time_driver_timg14)]
#[interrupt]
fn TIMG14() {
DRIVER.on_interrupt();
}
#[cfg(time_driver_tima0)]
#[interrupt]
fn TIMA0() {
DRIVER.on_interrupt();
}
#[cfg(time_driver_tima1)]
#[interrupt]
fn TIMA1() {
DRIVER.on_interrupt();
}

View File

@@ -0,0 +1,48 @@
#![macro_use]
/// Amount of bits of a timer.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TimerBits {
/// 16 bits.
Bits16,
/// 32 bits.
Bits32,
}
#[allow(private_bounds)]
pub trait Timer: SealedTimer + 'static {
/// Amount of bits this timer has.
const BITS: TimerBits;
}
pub(crate) trait SealedTimer {
/// Registers for this timer.
///
/// This is a raw pointer to the register block. The actual register block layout varies depending on the
/// timer type.
fn regs() -> *mut ();
/// Enable the interrupt corresponding to this timer.
unsafe fn enable_interrupt();
}
macro_rules! impl_timer {
($name: ident, $bits: ident) => {
impl crate::timer::SealedTimer for crate::peripherals::$name {
fn regs() -> *mut () {
crate::pac::$name.as_ptr()
}
unsafe fn enable_interrupt() {
use embassy_hal_internal::interrupt::InterruptExt;
crate::interrupt::$name.unpend();
crate::interrupt::$name.enable();
}
}
impl crate::timer::Timer for crate::peripherals::$name {
const BITS: crate::timer::TimerBits = crate::timer::TimerBits::$bits;
}
};
}