From 9391e05d124842f243279d228ee3eefd051f7e1d Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Wed, 29 Jul 2026 00:22:43 +0100 Subject: [PATCH 001/134] Derive Copy on the metadata types Signed-off-by: CordlessCoder --- mspm0-metapac-gen/res/metadata.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 28ddd60..83037e2 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -13,7 +13,7 @@ pub struct Metadata { pub adc_vrsel: u8, } -#[derive(Debug, Eq, PartialEq, Clone)] +#[derive(Debug, Eq, PartialEq, Clone, Copy)] pub struct Peripheral { pub name: &'static str, pub kind: &'static str, @@ -23,20 +23,20 @@ pub struct Peripheral { pub sys_fentries: Option, } -#[derive(Debug, Eq, PartialEq, Clone)] +#[derive(Debug, Eq, PartialEq, Clone, Copy)] pub struct Pin { pub pin: &'static str, pub pincm: u8, } -#[derive(Debug, Eq, PartialEq, Clone)] +#[derive(Debug, Eq, PartialEq, Clone, Copy)] pub struct PeripheralPin { pub pin: &'static str, pub signal: &'static str, pub pf: Option, } -#[derive(Debug, Eq, PartialEq, Clone)] +#[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum PowerDomain { /// "low speed" power domain. This power domain is powered in RUN, SLEEP, STOP and STANDBY modes. Pd0, @@ -50,26 +50,26 @@ pub enum PowerDomain { Backup, } -#[derive(Debug, Eq, PartialEq, Clone)] +#[derive(Debug, Eq, PartialEq, Clone, Copy)] pub struct Interrupt { pub name: &'static str, pub number: u32, } -#[derive(Debug, Eq, PartialEq, Clone)] +#[derive(Debug, Eq, PartialEq, Clone, Copy)] pub struct InterruptGroup { pub name: &'static str, pub number: u32, pub interrupts: &'static [GroupInterrupt], } -#[derive(Debug, Eq, PartialEq, Clone)] +#[derive(Debug, Eq, PartialEq, Clone, Copy)] pub struct GroupInterrupt { pub name: &'static str, pub number: u32, } -#[derive(Debug, Eq, PartialEq, Clone)] +#[derive(Debug, Eq, PartialEq, Clone, Copy)] pub struct DmaChannel { /// The number of the dma channel. pub number: u8, From c481f3814b471e5fef58d9cdf93d634b8ff75419 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Wed, 29 Jul 2026 00:25:49 +0100 Subject: [PATCH 002/134] Expose memory regions and NVIC priority bits Signed-off-by: CordlessCoder --- mspm0-data-gen/src/generate.rs | 24 ++++++++++++++++++------ mspm0-data-gen/src/header.rs | 29 +++++++++++++++++++++++++++++ mspm0-data-types/src/lib.rs | 12 ++++++++++++ mspm0-metapac-gen/res/metadata.rs | 20 ++++++++++++++++++-- mspm0-metapac-gen/src/main.rs | 8 ++++++++ mspm0-metapac-gen/src/metadata.rs | 26 +++++++++++++++++++++++++- 6 files changed, 110 insertions(+), 9 deletions(-) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index c7c078f..57ded8e 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -8,8 +8,8 @@ use std::{ use anyhow::{anyhow, bail, ensure, Context}; use mspm0_data_types::{ - Chip, DmaChannel, Interrupt, Memory, Package, PackagePin, Peripheral, PeripheralPin, - PeripheralType, PowerDomain, + Chip, DmaChannel, Interrupt, Memory, MemoryKind, Package, PackagePin, Peripheral, + PeripheralPin, PeripheralType, PowerDomain, }; use regex::Regex; @@ -98,7 +98,11 @@ fn generate_family( datasheet_url: family.datasheet_url.clone(), reference_manual_url: family.reference_manual_url.clone(), errata_url: family.errata_url.clone(), - memory: part_number.memory.iter().map(convert_memory).collect(), + memory: part_number + .memory + .iter() + .map(convert_memory) + .collect::>()?, packages: packages.collect(), iomux: iomux.clone(), peripherals: peripherals.clone(), @@ -106,6 +110,7 @@ fn generate_family( dma_channels: dma_channels.clone(), adc_memctl, adc_vrsel: adc_vrsel_mapping(&family.adc_vrsel)?, + nvic_priority_bits: header.nvic_priority_bits, }; if let Err(err) = verify::verify(&chip, &part_number.name) { @@ -890,10 +895,17 @@ fn skip_peripheral_pin(pin_name: &String, chip_name: &str) -> bool { false } -fn convert_memory(memory: &PartMemory) -> Memory { - Memory { +fn convert_memory(memory: &PartMemory) -> anyhow::Result { + let kind = match memory.name.as_str() { + "FLASH" => MemoryKind::Flash, + "RAM" | "RAM_BANK" => MemoryKind::Ram, + name => bail!("Unknown memory partition `{name}`, cannot tell what kind of memory it is"), + }; + + Ok(Memory { name: memory.name.clone(), + kind, length: memory.length, address: memory.address, - } + }) } diff --git a/mspm0-data-gen/src/header.rs b/mspm0-data-gen/src/header.rs index a6bc947..d23d9a1 100644 --- a/mspm0-data-gen/src/header.rs +++ b/mspm0-data-gen/src/header.rs @@ -39,6 +39,9 @@ pub struct Header { pub peripheral_addresses: BTreeMap, pub irq_numbers: BTreeMap>, + + /// Number of bits the NVIC uses for interrupt priority levels. + pub nvic_priority_bits: u8, // TODO: flash info // TODO: Available IOMUX indices // TODO: PF values (for non-analog) @@ -54,13 +57,39 @@ impl Header { // The Cortex-M0 only has 32 IRQs. This means that "interrupt groups" need to be resolved // for truly handling IRQs. let irq_numbers = Self::get_irq_numbers(chip_name, &content)?; + let nvic_priority_bits = Self::get_nvic_priority_bits(chip_name, &content)?; Ok(Self { peripheral_addresses, irq_numbers, + nvic_priority_bits, }) } + /// Read the NVIC priority bit count from the CMSIS device header. + /// + /// Deliberately *not* taken from the SVD, which also carries this as ``: the SVDs + /// say 3 for every MSPM0, which is wrong. The CMSIS header says 2, and the datasheets agree + /// ("Nested vectored interrupt controller (NVIC) with four programmable priority levels" โ€” four + /// levels is two bits). Taking the SVD's value would give consumers twice the priority levels + /// the hardware has. + fn get_nvic_priority_bits(chip_name: &str, content: &str) -> anyhow::Result { + /// Example: + /// ```c,no_run + /// #define __NVIC_PRIO_BITS 0x0002U /* Number of bits used for Priority Levels */ + /// ``` + static NVIC_PRIO_BITS: LazyLock = LazyLock::new(|| { + Regex::new(r"(?m)#define\s+__NVIC_PRIO_BITS\s+0x(?\w+)U").unwrap() + }); + + let capture = NVIC_PRIO_BITS + .captures(content) + .context(format!("{chip_name}: no __NVIC_PRIO_BITS in header"))?; + + u8::from_str_radix(&capture["bits"], 16) + .context(format!("{chip_name}: __NVIC_PRIO_BITS is not a valid u8")) + } + fn get_peripheral_addresses( chip_name: &str, content: &str, diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 1ee9a02..5379eb6 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -47,6 +47,9 @@ pub struct Chip { /// /// This is requried because we use a single adc_v1 pac for all chips. pub adc_vrsel: u8, + + /// Number of bits used by the NVIC for interrupt priority levels. + pub nvic_priority_bits: u8, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -283,9 +286,18 @@ pub struct Memory { /// The memory partition. pub name: String, + /// What kind of memory this partition is. + pub kind: MemoryKind, + /// Amount of memory in KB. pub length: u32, /// Address of the memory. pub address: u32, } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum MemoryKind { + Flash, + Ram, +} diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 83037e2..b417d13 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -2,10 +2,10 @@ pub struct Metadata { pub name: &'static str, pub family: &'static str, - // pub memory: &'static [MemoryRegion], + pub memory: &'static [MemoryRegion], pub peripherals: &'static [Peripheral], pub pins: &'static [Pin], - // pub nvic_priority_bits: Option, + pub nvic_priority_bits: u8, pub interrupts: &'static [Interrupt], pub interrupt_groups: &'static [InterruptGroup], pub dma_channels: &'static [DmaChannel], @@ -23,6 +23,22 @@ pub struct Peripheral { pub sys_fentries: Option, } +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct MemoryRegion { + pub name: &'static str, + pub kind: MemoryKind, + pub address: u32, + + /// Size of the region in bytes. + pub size: u32, +} + +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum MemoryKind { + Flash, + Ram, +} + #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub struct Pin { pub pin: &'static str, diff --git a/mspm0-metapac-gen/src/main.rs b/mspm0-metapac-gen/src/main.rs index 4cf936d..caa16bb 100644 --- a/mspm0-metapac-gen/src/main.rs +++ b/mspm0-metapac-gen/src/main.rs @@ -150,6 +150,11 @@ fn generate_chip_metadata( let family = &chip.family; let adc_memctl = &chip.adc_memctl; let adc_vrsel = &chip.adc_vrsel; + let nvic_priority_bits = &chip.nvic_priority_bits; + + // Memory is the one part of the metadata which varies by part number rather than by family, so + // it is emitted here instead of in the deduplicated body above. + let memory = metadata::memory(chip); write!( &mut file, @@ -157,11 +162,14 @@ fn generate_chip_metadata( {AUTOGENERATED_TEXT} include!(\"../{deduped_file}\"); + static MEMORY: &[MemoryRegion] = {memory}; pub static METADATA: Metadata = Metadata {{ name: \"{name}\", family: \"{family}\", + memory: MEMORY, peripherals: PERIPHERALS, pins: PINS, + nvic_priority_bits: {nvic_priority_bits}, interrupts: INTERRUPTS, interrupt_groups: INTERRUPT_GROUPS, dma_channels: DMA_CHANNELS, diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 3b405d6..23b0664 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -1,6 +1,6 @@ use std::{collections::HashSet, sync::LazyLock}; -use mspm0_data_types::{Chip, Package, Peripheral, PeripheralType, PowerDomain}; +use mspm0_data_types::{Chip, MemoryKind, Package, Peripheral, PeripheralType, PowerDomain}; use proc_macro2::{Literal, TokenStream}; use quote::quote; use regex::Regex; @@ -32,6 +32,30 @@ pub fn pins(chip: &Chip, package: &Package) -> TokenStream { quote! { &[#(#pins),*] } } +pub fn memory(chip: &Chip) -> TokenStream { + let regions = chip.memory.iter().map(|region| { + let name = ®ion.name; + let kind = match region.kind { + MemoryKind::Flash => quote! { MemoryKind::Flash }, + MemoryKind::Ram => quote! { MemoryKind::Ram }, + }; + let address = Literal::u32_unsuffixed(region.address); + // Sizes are in KB in the chip data, but bytes are what consumers need. + let size = Literal::u32_unsuffixed(region.length * 1024); + + quote! { + MemoryRegion { + name: #name, + kind: #kind, + address: #address, + size: #size, + } + } + }); + + quote! { &[#(#regions),*] } +} + pub fn peripherals(chip: &Chip, package: &Package) -> TokenStream { // Peripheral pins should only be marked as available if the package contains the pin. // From 9e15e5b6bb71d933fdc6377f8e0ad214b74bf9bd Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Wed, 29 Jul 2026 00:28:43 +0100 Subject: [PATCH 003/134] Link peripherals to their interrupts Signed-off-by: CordlessCoder --- mspm0-data-gen/src/generate.rs | 43 +++++++++++++++++++++++++++++-- mspm0-data-types/src/lib.rs | 25 ++++++++++++++++++ mspm0-metapac-gen/res/metadata.rs | 21 +++++++++++++++ mspm0-metapac-gen/src/metadata.rs | 24 +++++++++++++++++ 4 files changed, 111 insertions(+), 2 deletions(-) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 57ded8e..79c0188 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -9,7 +9,7 @@ use std::{ use anyhow::{anyhow, bail, ensure, Context}; use mspm0_data_types::{ Chip, DmaChannel, Interrupt, Memory, MemoryKind, Package, PackagePin, Peripheral, - PeripheralPin, PeripheralType, PowerDomain, + PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, }; use regex::Regex; @@ -64,11 +64,14 @@ fn generate_family( // Data shared across all chips in a family. let packages = get_packages(&family.family, sysconfig)?; let iomux = generate_pincm(&family.family, sysconfig)?; - let peripherals = generate_peripherals2(&family.family, header, sysconfig)?; + let mut peripherals = generate_peripherals2(&family.family, header, sysconfig)?; let interrupts = generate_irqs(&family.family, header, int_groups)?; let dma_channels = generate_dma_channels(&family.family, sysconfig)?; let adc_memctl = generate_adc_memctl_dim(&family.family, sysconfig)?; + // Facts which are easier to attach once every peripheral is known. + apply_peripheral_interrupts(&mut peripherals, &interrupts); + for part_number in family.part_numbers.iter() { // Filter for package types available on the part number. let packages = packages @@ -284,6 +287,7 @@ fn generate_peripherals2( power_domain, pins: vec![], sys_fentries, + interrupt: None, }; // Lookup the pins @@ -532,6 +536,7 @@ fn generate_missing( power_domain: PowerDomain::Pd1, pins: vec![], sys_fentries: None, + interrupt: None, }, ); @@ -563,6 +568,7 @@ fn generate_missing( power_domain: PowerDomain::Pd0, pins: vec![], sys_fentries: None, + interrupt: None, }); let pin = device_pin @@ -895,6 +901,39 @@ fn skip_peripheral_pin(pin_name: &String, chip_name: &str) -> bool { false } +/// Attach each peripheral to the interrupt it raises. +/// +/// A peripheral either owns an NVIC interrupt of its own or sits inside an `INT_GROUP` and shares +/// the group's interrupt, distinguished by an `IIDX` value. Both are matched by name, which is the +/// only thing tying them together in the vendor data. +fn apply_peripheral_interrupts( + peripherals: &mut BTreeMap, + interrupts: &BTreeMap, +) { + for (name, peripheral) in peripherals.iter_mut() { + peripheral.interrupt = interrupts + .values() + .find_map(|interrupt| { + (&interrupt.name == name).then(|| PeripheralInterrupt { + name: interrupt.name.clone(), + num: interrupt.num, + group_iidx: None, + }) + }) + .or_else(|| { + interrupts.values().find_map(|interrupt| { + let (&iidx, _) = interrupt.group.iter().find(|(_, member)| *member == name)?; + + Some(PeripheralInterrupt { + name: interrupt.name.clone(), + num: interrupt.num, + group_iidx: Some(iidx), + }) + }) + }); + } +} + fn convert_memory(memory: &PartMemory) -> anyhow::Result { let kind = match memory.name.as_str() { "FLASH" => MemoryKind::Flash, diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 5379eb6..4d70ac5 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -249,6 +249,31 @@ pub struct Peripheral { #[serde(skip_serializing_if = "Option::is_none")] pub sys_fentries: Option, + + /// The interrupt raised by this peripheral. + /// + /// `None` if the peripheral has no interrupt of its own. + #[serde(skip_serializing_if = "Option::is_none")] + pub interrupt: Option, +} + +/// The interrupt raised by a peripheral. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PeripheralInterrupt { + /// Name of the NVIC interrupt. + /// + /// For a peripheral inside an `INT_GROUP` this is the name of the group (e.g. `GROUP1`), not + /// the name of the peripheral. + pub name: String, + + /// Number of the NVIC interrupt. + pub num: i32, + + /// The peripheral's `IIDX` value within its `INT_GROUP`. + /// + /// `None` if the peripheral has an NVIC interrupt of its own rather than sharing a group. + #[serde(skip_serializing_if = "Option::is_none")] + pub group_iidx: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index b417d13..c9701d7 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -21,6 +21,27 @@ pub struct Peripheral { pub pins: &'static [PeripheralPin], pub power_domain: PowerDomain, pub sys_fentries: Option, + + /// The interrupt raised by this peripheral, if it has one. + pub interrupt: Option, +} + +/// The interrupt raised by a peripheral. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct PeripheralInterrupt { + /// Name of the NVIC interrupt. + /// + /// For a peripheral inside an `INT_GROUP` this is the name of the group (e.g. `GROUP1`), not + /// the name of the peripheral. + pub name: &'static str, + + /// Number of the NVIC interrupt. + pub number: u32, + + /// The peripheral's `IIDX` value within its `INT_GROUP`. + /// + /// `None` if the peripheral has an NVIC interrupt of its own rather than sharing a group. + pub group_iidx: Option, } #[derive(Debug, Eq, PartialEq, Clone, Copy)] diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 23b0664..12680b5 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -235,6 +235,29 @@ fn generate_peripheral( None => quote! { None }, }; + let interrupt = match &peripheral.interrupt { + Some(interrupt) => { + let name = &interrupt.name; + let number = Literal::u32_unsuffixed(interrupt.num as u32); + let group_iidx = match interrupt.group_iidx { + Some(iidx) => { + let iidx = Literal::u32_unsuffixed(iidx); + quote! { Some(#iidx) } + } + None => quote! { None }, + }; + + quote! { + Some(PeripheralInterrupt { + name: #name, + number: #number, + group_iidx: #group_iidx, + }) + } + } + None => quote! { None }, + }; + Some(quote! { Peripheral { name: #name, @@ -243,6 +266,7 @@ fn generate_peripheral( pins: &[#(#pins),*], power_domain: #power_domain, sys_fentries: #sys_fentries, + interrupt: #interrupt, } }) } From a5568ba5f6dfd3c044633ef12efb29400f3f3c5c Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Wed, 29 Jul 2026 00:31:11 +0100 Subject: [PATCH 004/134] Add wake-capable IO metadata Signed-off-by: CordlessCoder --- mspm0-data-gen/src/generate.rs | 14 ++++++++++++++ mspm0-data-gen/src/sysconfig.rs | 6 ++++++ mspm0-data-types/src/lib.rs | 11 ++++++++++- mspm0-metapac-gen/res/metadata.rs | 6 ++++++ mspm0-metapac-gen/src/metadata.rs | 3 ++- 5 files changed, 38 insertions(+), 2 deletions(-) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 79c0188..f156af9 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -64,6 +64,7 @@ fn generate_family( // Data shared across all chips in a family. let packages = get_packages(&family.family, sysconfig)?; let iomux = generate_pincm(&family.family, sysconfig)?; + let wakeup_pins = generate_wakeup_pins(sysconfig); let mut peripherals = generate_peripherals2(&family.family, header, sysconfig)?; let interrupts = generate_irqs(&family.family, header, int_groups)?; let dma_channels = generate_dma_channels(&family.family, sysconfig)?; @@ -108,6 +109,7 @@ fn generate_family( .collect::>()?, packages: packages.collect(), iomux: iomux.clone(), + wakeup_pins: wakeup_pins.clone(), peripherals: peripherals.clone(), interrupts: interrupts.clone(), dma_channels: dma_channels.clone(), @@ -901,6 +903,18 @@ fn skip_peripheral_pin(pin_name: &String, chip_name: &str) -> bool { false } +/// Device pins which have wakeup logic, and can therefore wake the device from SHUTDOWN. +fn generate_wakeup_pins(sysconfig: &SysconfigFile) -> BTreeSet { + sysconfig + .device_pins + .values() + .filter(|pin| pin.attributes.io_wakeup.unwrap_or(false)) + // Multi-bonded pins are excluded everywhere else too, see `generate_pincm`. + .filter(|pin| !pin.name.contains('/')) + .map(|pin| pin.name.clone()) + .collect() +} + /// Attach each peripheral to the interrupt it raises. /// /// A peripheral either owns an NVIC interrupt of its own or sits inside an `INT_GROUP` and shares diff --git a/mspm0-data-gen/src/sysconfig.rs b/mspm0-data-gen/src/sysconfig.rs index 0d87c70..3fa128d 100644 --- a/mspm0-data-gen/src/sysconfig.rs +++ b/mspm0-data-gen/src/sysconfig.rs @@ -115,6 +115,12 @@ pub struct PinAttributes { pub pad_name: String, pub io_type: Option, pub iomux_pincm: String, + + /// Whether the pin has wakeup logic, and can therefore wake the device from SHUTDOWN. + /// + /// Absent for pins which are not usable as I/O. Note that this is not the same as the pin being + /// able to wake the device at all: `FASTWAKE` works on any GPIO, but only down to STANDBY. + pub io_wakeup: Option, } #[allow(dead_code)] diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 4d70ac5..d38a48f 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -1,5 +1,8 @@ use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt, +}; #[derive(Debug, Serialize, Deserialize)] pub struct Chip { @@ -31,6 +34,12 @@ pub struct Chip { /// Mapping from device pin to IOMUX register index. pub iomux: BTreeMap, + /// Device pins which have wakeup logic and can therefore wake the device from SHUTDOWN. + /// + /// The `FASTWAKE` mechanism, which wakes the device from STOP and STANDBY, works on any GPIO + /// pin and is therefore not described here. + pub wakeup_pins: BTreeSet, + /// The peripherals available on the chip. pub peripherals: BTreeMap, diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index c9701d7..697ed0c 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -64,6 +64,12 @@ pub enum MemoryKind { pub struct Pin { pub pin: &'static str, pub pincm: u8, + + /// Whether the pin has wakeup logic, and can therefore wake the device from SHUTDOWN. + /// + /// This is not the same as the pin being able to wake the device at all: `FASTWAKE` works on + /// any GPIO pin, but only down to STANDBY. + pub wakeup: bool, } #[derive(Debug, Eq, PartialEq, Clone, Copy)] diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 12680b5..b31eebb 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -25,8 +25,9 @@ pub fn pins(chip: &Chip, package: &Package) -> TokenStream { .get(signal) .expect("Signal did not have an iomux pincm entry"); let pincm = Literal::u8_suffixed(*pincm as u8); + let wakeup = chip.wakeup_pins.contains(signal); - Some(quote! { Pin { pin: #signal, pincm: #pincm } }) + Some(quote! { Pin { pin: #signal, pincm: #pincm, wakeup: #wakeup } }) }); quote! { &[#(#pins),*] } From 56bc8172927bf5e07a27a09dd18298f13de8e7a7 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Wed, 29 Jul 2026 00:33:38 +0100 Subject: [PATCH 005/134] Add backup-domain flag and RTC variant Signed-off-by: CordlessCoder --- mspm0-data-gen/src/generate.rs | 33 +++++++++++++++++++++++++++++++ mspm0-data-gen/src/perimap.rs | 6 ++++++ mspm0-data-types/src/lib.rs | 6 ++++++ mspm0-metapac-gen/res/metadata.rs | 6 ++++++ mspm0-metapac-gen/src/main.rs | 2 ++ 5 files changed, 53 insertions(+) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index f156af9..1f6df78 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -69,6 +69,7 @@ fn generate_family( let interrupts = generate_irqs(&family.family, header, int_groups)?; let dma_channels = generate_dma_channels(&family.family, sysconfig)?; let adc_memctl = generate_adc_memctl_dim(&family.family, sysconfig)?; + let backup_domain = has_backup_domain(&family.family, sysconfig, &peripherals)?; // Facts which are easier to attach once every peripheral is known. apply_peripheral_interrupts(&mut peripherals, &interrupts); @@ -116,6 +117,7 @@ fn generate_family( adc_memctl, adc_vrsel: adc_vrsel_mapping(&family.adc_vrsel)?, nvic_priority_bits: header.nvic_priority_bits, + backup_domain, }; if let Err(err) = verify::verify(&chip, &part_number.name) { @@ -903,6 +905,37 @@ fn skip_peripheral_pin(pin_name: &String, chip_name: &str) -> bool { false } +/// Whether the chip has an independent `VBAT` supply, and therefore a real backup power domain. +/// +/// The presence of a `VBAT` device pin is the authoritative answer: TRM ยง30 distinguishes the RTC +/// variants by exactly this ("In devices for which the LFSS is powered by an independent VBAT supply +/// pin to support the backup-battery power domain (PDB), the RTC variant has extended features and +/// is referred to as RTC_A"). +fn has_backup_domain( + chip_name: &str, + sysconfig: &SysconfigFile, + peripherals: &BTreeMap, +) -> anyhow::Result { + let vbat = sysconfig + .device_pins + .values() + .any(|pin| pin.name.split('/').any(|signal| signal == "VBAT")); + + // Cross-check against the power domain sysconfig assigns to the peripherals. A chip with a VBAT + // pin must place something in the backup domain and vice versa; if the two disagree then one of + // the two sources has changed meaning and the flag cannot be trusted. + let backup_peripherals = peripherals + .values() + .any(|peripheral| peripheral.power_domain == PowerDomain::Backup); + + ensure!( + vbat == backup_peripherals, + "{chip_name}: VBAT pin present is {vbat} but a peripheral in the backup power domain present is {backup_peripherals}" + ); + + Ok(vbat) +} + /// Device pins which have wakeup logic, and can therefore wake the device from SHUTDOWN. fn generate_wakeup_pins(sysconfig: &SysconfigFile) -> BTreeSet { sysconfig diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index 59407de..c9b5804 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -16,6 +16,12 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ (".*:flashctl", "v1"), (".*:trng", "v1"), (".*:canfd", "v1"), + // The RTC comes in variants which are not distinguished by a version number (TRM ยง30). + // SysConfig calls the standalone pre-LFSS peripheral the "legacy" RTC; the variants inside the + // LFSS are RTC_A when the LFSS has an independent VBAT supply and RTC_B when it is powered from + // VDD. Versioning them here means consumers do not have to tell them apart by instance name. + ("mspm0g..0x:rtc", "legacy"), + (".*:rtc", "b"), ("mspm0c110x:sysctl", "c110x"), ("mspm0c1105_c1106:sysctl", "c110x"), ("msps003fx:sysctl", "c110x"), diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index d38a48f..3a378e5 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -59,6 +59,12 @@ pub struct Chip { /// Number of bits used by the NVIC for interrupt priority levels. pub nvic_priority_bits: u8, + + /// Whether the chip has an independent `VBAT` supply and therefore a real backup power domain + /// (PDB). + /// + /// Peripherals in the backup power domain are the only wake sources which survive SHUTDOWN. + pub backup_domain: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 697ed0c..c9090da 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -11,6 +11,12 @@ pub struct Metadata { pub dma_channels: &'static [DmaChannel], pub adc_memctl: u8, pub adc_vrsel: u8, + + /// Whether the chip has an independent `VBAT` supply and therefore a real backup power domain + /// (PDB). + /// + /// Peripherals in the backup power domain are the only wake sources which survive SHUTDOWN. + pub backup_domain: bool, } #[derive(Debug, Eq, PartialEq, Clone, Copy)] diff --git a/mspm0-metapac-gen/src/main.rs b/mspm0-metapac-gen/src/main.rs index caa16bb..f0af85a 100644 --- a/mspm0-metapac-gen/src/main.rs +++ b/mspm0-metapac-gen/src/main.rs @@ -151,6 +151,7 @@ fn generate_chip_metadata( let adc_memctl = &chip.adc_memctl; let adc_vrsel = &chip.adc_vrsel; let nvic_priority_bits = &chip.nvic_priority_bits; + let backup_domain = &chip.backup_domain; // Memory is the one part of the metadata which varies by part number rather than by family, so // it is emitted here instead of in the deduplicated body above. @@ -175,6 +176,7 @@ fn generate_chip_metadata( dma_channels: DMA_CHANNELS, adc_memctl: {adc_memctl}, adc_vrsel: {adc_vrsel}, + backup_domain: {backup_domain}, }}; ", ) From 3112d5a7fe4dfdde1bb2415bde028329c0261eb4 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Wed, 29 Jul 2026 00:36:16 +0100 Subject: [PATCH 006/134] Add chip-level clock ceilings Signed-off-by: CordlessCoder --- data/parts.yaml | 54 +++++++++++++++++++++++++++++++ mspm0-data-gen/src/generate.rs | 2 ++ mspm0-data-gen/src/parts.rs | 6 ++++ mspm0-data-types/src/lib.rs | 11 +++++++ mspm0-metapac-gen/res/metadata.rs | 11 +++++++ mspm0-metapac-gen/src/main.rs | 4 +++ 6 files changed, 88 insertions(+) diff --git a/data/parts.yaml b/data/parts.yaml index da2615d..90ce05e 100644 --- a/data/parts.yaml +++ b/data/parts.yaml @@ -16,6 +16,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau893 errata_url: https://www.ti.com/lit/pdf/slaz753 adc_vrsel: VDD_INTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0C1104 datasheet. + max_mclk_hz: 24000000 + max_ulpclk_hz: 24000000 part_numbers: - name: msps003f3 # Part number memory: @@ -45,6 +48,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau893 errata_url: https://www.ti.com/lit/pdf/slaz753 adc_vrsel: VDD_INTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0C1104 datasheet. + max_mclk_hz: 24000000 + max_ulpclk_hz: 24000000 part_numbers: - name: mspm0c1103 memory: @@ -80,6 +86,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau893 errata_url: https://www.ti.com/lit/pdf/slaz761 adc_vrsel: VDD_INTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0C1106 datasheet. + max_mclk_hz: 32000000 + max_ulpclk_hz: 32000000 part_numbers: - name: mspm0c1105 memory: @@ -127,6 +136,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau846 errata_url: https://www.ti.com/lit/pdf/slaz742 adc_vrsel: VDD_INTREF_EXTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0G1107 datasheet. + max_mclk_hz: 80000000 + max_ulpclk_hz: 40000000 part_numbers: - name: mspm0g1105 memory: @@ -182,6 +194,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau846 errata_url: https://www.ti.com/lit/pdf/slaz742 adc_vrsel: VDD_INTREF_EXTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0G1507 datasheet. + max_mclk_hz: 80000000 + max_ulpclk_hz: 40000000 part_numbers: - name: mspm0g1505 memory: @@ -237,6 +252,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau846 errata_url: https://www.ti.com/lit/pdf/slaz758 adc_vrsel: VDD_INTREF_EXTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0G1519 datasheet. + max_mclk_hz: 80000000 + max_ulpclk_hz: 40000000 part_numbers: - name: mspm0g1518 memory: @@ -293,6 +311,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau846 errata_url: https://www.ti.com/lit/pdf/slaz742 adc_vrsel: VDD_INTREF_EXTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0G3107 datasheet. + max_mclk_hz: 80000000 + max_ulpclk_hz: 40000000 part_numbers: - name: mspm0g3105 memory: @@ -338,6 +359,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau846 errata_url: https://www.ti.com/lit/pdf/slaz742 adc_vrsel: VDD_INTREF_EXTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0G3507 datasheet. + max_mclk_hz: 80000000 + max_ulpclk_hz: 40000000 part_numbers: - name: mspm0g3505 memory: @@ -389,6 +413,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau846 errata_url: https://www.ti.com/lit/pdf/slaz758 adc_vrsel: VDD_INTREF_EXTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0G3519 datasheet. + max_mclk_hz: 80000000 + max_ulpclk_hz: 40000000 part_numbers: - name: mspm0g3518 memory: @@ -445,6 +472,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau846 errata_url: https://www.ti.com/lit/pdf/slvsjm6 adc_vrsel: VDD_INTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0G5187 datasheet. + max_mclk_hz: 80000000 + max_ulpclk_hz: 40000000 part_numbers: - name: mspm0g5187 memory: @@ -470,6 +500,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau923 errata_url: https://www.ti.com/lit/pdf/slaz763 adc_vrsel: VDD_INTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0H3216 datasheet. + max_mclk_hz: 32000000 + max_ulpclk_hz: 32000000 part_numbers: - name: mspm0h3215 memory: @@ -499,6 +532,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau847 errata_url: https://www.ti.com/lit/pdf/slaz741 adc_vrsel: VDD_INTREF_EXTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0L1106 datasheet. + max_mclk_hz: 32000000 + max_ulpclk_hz: 32000000 part_numbers: - name: mspm0l1105 memory: @@ -537,6 +573,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau847 errata_url: https://www.ti.com/lit/pdf/slaz766 adc_vrsel: VDD_INTREF_EXTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0L2117 datasheet. + max_mclk_hz: 32000000 + max_ulpclk_hz: 32000000 part_numbers: - name: mspm0l1126 memory: @@ -583,6 +622,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau847 errata_url: https://www.ti.com/lit/pdf/slaz755 adc_vrsel: VDD_INTREF_EXTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0L1228 datasheet. + max_mclk_hz: 32000000 + max_ulpclk_hz: 32000000 part_numbers: - name: mspm0l1227 memory: @@ -621,6 +663,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau847 errata_url: https://www.ti.com/lit/pdf/slaz741 adc_vrsel: VDD_INTREF_EXTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0L1306 datasheet. + max_mclk_hz: 32000000 + max_ulpclk_hz: 32000000 part_numbers: - name: mspm0l1303 memory: @@ -685,6 +730,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau847 errata_url: https://www.ti.com/lit/pdf/slaz741 adc_vrsel: VDD_INTREF_EXTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0L1346 datasheet. + max_mclk_hz: 32000000 + max_ulpclk_hz: 32000000 part_numbers: - name: mspm0l1343 memory: @@ -735,6 +783,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau847 errata_url: https://www.ti.com/lit/pdf/slaz766 adc_vrsel: VDD_INTREF_EXTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0L2117 datasheet. + max_mclk_hz: 32000000 + max_ulpclk_hz: 32000000 part_numbers: - name: mspm0l2116 memory: @@ -781,6 +832,9 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau847 errata_url: https://www.ti.com/lit/pdf/slaz755 adc_vrsel: VDD_INTREF_EXTREF + # Clock ceilings and STANDBY1 timers come from the MSPM0L2228 datasheet. + max_mclk_hz: 32000000 + max_ulpclk_hz: 32000000 part_numbers: - name: mspm0l2227 memory: diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 1f6df78..7f4b29e 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -117,6 +117,8 @@ fn generate_family( adc_memctl, adc_vrsel: adc_vrsel_mapping(&family.adc_vrsel)?, nvic_priority_bits: header.nvic_priority_bits, + max_mclk_hz: family.max_mclk_hz, + max_ulpclk_hz: family.max_ulpclk_hz, backup_domain, }; diff --git a/mspm0-data-gen/src/parts.rs b/mspm0-data-gen/src/parts.rs index 61330ab..9b1c172 100644 --- a/mspm0-data-gen/src/parts.rs +++ b/mspm0-data-gen/src/parts.rs @@ -33,6 +33,12 @@ pub struct PartFamily { /// The number of options for VRSEL of the ADC peripheral. pub adc_vrsel: String, + /// Maximum frequency of MCLK, in Hz. + pub max_mclk_hz: u32, + + /// Maximum frequency of ULPCLK, in Hz. + pub max_ulpclk_hz: u32, + /// Part numbers in this family. pub part_numbers: Vec, } diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 3a378e5..785dbdf 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -60,6 +60,17 @@ pub struct Chip { /// Number of bits used by the NVIC for interrupt priority levels. pub nvic_priority_bits: u8, + /// Maximum frequency of MCLK, in Hz. + /// + /// MCLK sources the CPU and the PD1 peripherals. + pub max_mclk_hz: u32, + + /// Maximum frequency of ULPCLK, in Hz. + /// + /// ULPCLK sources the PD0 peripherals. Note that this is the ceiling in RUN and SLEEP modes; + /// entering STOP throttles ULPCLK to 4MHz and STANDBY to 32kHz on every device. + pub max_ulpclk_hz: u32, + /// Whether the chip has an independent `VBAT` supply and therefore a real backup power domain /// (PDB). /// diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index c9090da..19c4543 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -12,6 +12,17 @@ pub struct Metadata { pub adc_memctl: u8, pub adc_vrsel: u8, + /// Maximum frequency of MCLK, in Hz. + /// + /// MCLK sources the CPU and the PD1 peripherals. + pub max_mclk_hz: u32, + + /// Maximum frequency of ULPCLK, in Hz. + /// + /// ULPCLK sources the PD0 peripherals. This is the ceiling in RUN and SLEEP; entering STOP + /// throttles ULPCLK to 4MHz and STANDBY to 32kHz on every device. + pub max_ulpclk_hz: u32, + /// Whether the chip has an independent `VBAT` supply and therefore a real backup power domain /// (PDB). /// diff --git a/mspm0-metapac-gen/src/main.rs b/mspm0-metapac-gen/src/main.rs index f0af85a..073cffb 100644 --- a/mspm0-metapac-gen/src/main.rs +++ b/mspm0-metapac-gen/src/main.rs @@ -151,6 +151,8 @@ fn generate_chip_metadata( let adc_memctl = &chip.adc_memctl; let adc_vrsel = &chip.adc_vrsel; let nvic_priority_bits = &chip.nvic_priority_bits; + let max_mclk_hz = &chip.max_mclk_hz; + let max_ulpclk_hz = &chip.max_ulpclk_hz; let backup_domain = &chip.backup_domain; // Memory is the one part of the metadata which varies by part number rather than by family, so @@ -176,6 +178,8 @@ fn generate_chip_metadata( dma_channels: DMA_CHANNELS, adc_memctl: {adc_memctl}, adc_vrsel: {adc_vrsel}, + max_mclk_hz: {max_mclk_hz}, + max_ulpclk_hz: {max_ulpclk_hz}, backup_domain: {backup_domain}, }}; ", From 7fc32b1244a645fb0355da7f741fc3489a09aa92 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Wed, 29 Jul 2026 00:40:09 +0100 Subject: [PATCH 007/134] Read CLKCFG.BLOCKASYNC from the SVDs Signed-off-by: CordlessCoder --- mspm0-data-gen/src/generate.rs | 27 +++++++- mspm0-data-gen/src/main.rs | 7 +- mspm0-data-gen/src/svd.rs | 111 ++++++++++++++++++++++++++++++ mspm0-data-gen/src/verify.rs | 24 +++++++ mspm0-data-types/src/lib.rs | 15 ++++ mspm0-metapac-gen/res/metadata.rs | 13 ++++ mspm0-metapac-gen/src/metadata.rs | 6 ++ 7 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 mspm0-data-gen/src/svd.rs diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 7f4b29e..54484a7 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -18,6 +18,7 @@ use crate::{ int_group::Groups, parts::{PartFamily, PartMemory, PartsFile}, perimap::PERIMAP, + svd::{Svd, Svds}, sysconfig::{self, PartPeripheralWrapper, Sysconfig, SysconfigFile}, verify, }; @@ -26,6 +27,7 @@ pub fn generate( parts: &PartsFile, headers: &Headers, sysconfig: &Sysconfig, + svds: &Svds, int_groups: &BTreeMap, ) -> anyhow::Result<()> { fs::create_dir_all("./build/data/").unwrap(); @@ -48,7 +50,12 @@ pub fn generate( .get(&header_name.to_lowercase()) .context(format!("Could not lookup header for {}", header_name))?; - generate_family(family, header, sysconfig, int_groups) + // SVDs are named after the family, but TI does not publish one for every family: as of + // the pinned data sources mspm0c1105_c1106, mspm0l112x and mspm0l211x have none. The + // facts taken from the SVD are optional for that reason, and `verify` reports the gap. + let svd = svds.files.get(&family.family); + + generate_family(family, header, sysconfig, svd, int_groups) .context(format!("Error when generating family: {}", family.family))?; } @@ -59,6 +66,7 @@ fn generate_family( family: &PartFamily, header: &Header, sysconfig: &SysconfigFile, + svd: Option<&Svd>, int_groups: &BTreeMap, ) -> anyhow::Result<()> { // Data shared across all chips in a family. @@ -73,6 +81,7 @@ fn generate_family( // Facts which are easier to attach once every peripheral is known. apply_peripheral_interrupts(&mut peripherals, &interrupts); + apply_block_async(&mut peripherals, svd); for part_number in family.part_numbers.iter() { // Filter for package types available on the part number. @@ -294,6 +303,7 @@ fn generate_peripherals2( pins: vec![], sys_fentries, interrupt: None, + block_async: None, }; // Lookup the pins @@ -543,6 +553,7 @@ fn generate_missing( pins: vec![], sys_fentries: None, interrupt: None, + block_async: None, }, ); @@ -575,6 +586,7 @@ fn generate_missing( pins: vec![], sys_fentries: None, interrupt: None, + block_async: None, }); let pin = device_pin @@ -983,6 +995,19 @@ fn apply_peripheral_interrupts( } } +/// Mark the peripheral instances which have their own `CLKCFG.BLOCKASYNC` bit. +fn apply_block_async(peripherals: &mut BTreeMap, svd: Option<&Svd>) { + let Some(svd) = svd else { + // No SVD for this family, so leave every instance unknown rather than claiming that none of + // them can be masked. `verify` reports this. + return; + }; + + for (name, peripheral) in peripherals.iter_mut() { + peripheral.block_async = Some(svd.block_async.contains(name)); + } +} + fn convert_memory(memory: &PartMemory) -> anyhow::Result { let kind = match memory.name.as_str() { "FLASH" => MemoryKind::Flash, diff --git a/mspm0-data-gen/src/main.rs b/mspm0-data-gen/src/main.rs index fde8a01..1aa6ba7 100644 --- a/mspm0-data-gen/src/main.rs +++ b/mspm0-data-gen/src/main.rs @@ -4,6 +4,7 @@ mod header; mod int_group; mod parts; mod perimap; +mod svd; mod sysconfig; mod util; mod verify; @@ -60,6 +61,10 @@ fn main() -> anyhow::Result<()> { let sysconfig = sysconfig::Sysconfig::parse(&data_sources)?; let _clock_trees = clock_tree::ClockTree::read_clock_trees(&data_sources)?; + stopwatch.section("Parsing SVDs"); + + let svds = svd::Svds::parse(&data_sources)?; + stopwatch.section("Read interrupt group mappings"); let int_groups = int_group::Groups::parse()?; @@ -68,7 +73,7 @@ fn main() -> anyhow::Result<()> { // TODO: Expanded family names (ex. C110X -> C1103 & C1104) stopwatch.section("Generate data"); - generate::generate(&parts, &headers, &sysconfig, &int_groups)?; + generate::generate(&parts, &headers, &sysconfig, &svds, &int_groups)?; stopwatch.stop(); diff --git a/mspm0-data-gen/src/svd.rs b/mspm0-data-gen/src/svd.rs new file mode 100644 index 0000000..ac0a3d5 --- /dev/null +++ b/mspm0-data-gen/src/svd.rs @@ -0,0 +1,111 @@ +//! Facts which only the SVDs carry. +//! +//! The SVDs are normally consumed offline by chiptool to produce `data/registers/*.yaml`, and the +//! register YAMLs are shared between chips of the same peripheral version. That makes them useless +//! for facts which vary between *instances* of the same peripheral version, which is why this reads +//! the SVDs directly. +//! +//! Only one fact is read, and it is a presence test on a single field name, so the SVDs are scanned +//! textually rather than parsed. Parsing them properly would mean depending on `svd-parser` +//! directly: chiptool does not re-export it, and it is a pinned fork rather than a release, so it +//! would have to track chiptool's exact revision. + +use std::{ + collections::{BTreeMap, BTreeSet}, + fs, + path::Path, + sync::LazyLock, +}; + +use anyhow::Context; +use regex::Regex; + +/// The field which gates a peripheral's asynchronous fast clock request. +/// +/// Only peripherals which can raise such a request have it. Note that this is distinct from +/// `SYSCTL.SYSOSCCFG.BLOCKASYNCALL`, which masks the requests of every peripheral at once. +const BLOCK_ASYNC: &str = "BLOCKASYNC"; + +/// One `` element, capturing its `derivedFrom` if it has one. +static PERIPHERAL: LazyLock = LazyLock::new(|| { + Regex::new( + r#"(?s)[^"]*)")?\s*>(?.*?)"#, + ) + .unwrap() +}); + +/// The first `` of a peripheral, which is its instance name. +static NAME: LazyLock = LazyLock::new(|| Regex::new(r"([^<]+)").unwrap()); + +#[derive(Debug)] +pub struct Svds { + /// Keyed by family name, lowercase (e.g. `mspm0g350x`). + pub files: BTreeMap, +} + +impl Svds { + pub fn parse(data_sources: &Path) -> anyhow::Result { + let mut files = BTreeMap::new(); + + for path in glob::glob(&format!("{}/svd/*.svd", data_sources.display())) + .unwrap() + .flatten() + { + let name = path.file_stem().unwrap().to_string_lossy().to_lowercase(); + let svd = Svd::read(&path).context(format!("Error reading SVD for {name}"))?; + + files.insert(name, svd); + } + + Ok(Self { files }) + } +} + +#[derive(Debug)] +pub struct Svd { + /// Peripheral instances which have their own `CLKCFG.BLOCKASYNC` bit, masking the asynchronous + /// fast clock request they raise. + /// + /// This varies per instance rather than per peripheral type: on mspm0g120x only `UC0`, `UC2`, + /// `UC4`, `UC5` and `UC9` have the bit, despite every `UC` instance being the same IP. + pub block_async: BTreeSet, +} + +impl Svd { + fn read(path: &Path) -> anyhow::Result { + let content = fs::read_to_string(path)?; + + let mut own = BTreeMap::new(); + let mut derived_from = BTreeMap::new(); + + for peripheral in PERIPHERAL.captures_iter(&content) { + let body = &peripheral["body"]; + let name = NAME + .captures(body) + .context(format!("{path:?}: peripheral without a name"))?[1] + .to_string(); + + // The field name only ever appears as a ``, so a substring test over the + // peripheral's body is enough to tell whether one of its registers has the field. + let tag = format!("{BLOCK_ASYNC}"); + own.insert(name.clone(), body.contains(&tag)); + + // Peripherals in these SVDs are always fully expanded, but resolve `derivedFrom` + // anyway so a source bump which starts using it does not silently drop instances. + if let Some(base) = peripheral.name("derived") { + derived_from.insert(name, base.as_str().to_string()); + } + } + + let block_async = own + .keys() + .filter(|name| match derived_from.get(*name) { + Some(base) => own.get(base).copied().unwrap_or(false), + None => own[*name], + }) + .cloned() + .collect::>(); + + Ok(Self { block_async }) + } +} diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index ac4e288..c625e65 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -14,6 +14,9 @@ pub fn verify(chip: &Chip, name: &str) -> anyhow::Result<()> { // Peripherals which don't actually exist no_gpamp_c110x_l151x(chip, name)?; + // Low power data which is only as complete as the data sources + block_async_known(chip, name)?; + // Power domains verify_aesadv_power_domain(chip, name)?; verify_cpuss_power_domain(chip, name)?; @@ -69,6 +72,27 @@ fn core_peripherals(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// Report families for which no SVD is published, and whose `BLOCKASYNC` bits are therefore +/// unknown. +/// +/// This is not an error: TI publishes SVDs later than the rest of the metadata, so a newly added +/// family legitimately has none. It is reported so the gap is visible and gets closed on the next +/// data source bump rather than silently persisting. +fn block_async_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + if chip + .peripherals + .values() + .all(|peripheral| peripheral.block_async.is_none()) + { + bail!( + "{name}: no SVD for family {}, so CLKCFG.BLOCKASYNC is unknown for every peripheral", + chip.family + ); + } + + Ok(()) +} + fn pin_names(chip: &Chip, name: &str) -> anyhow::Result<()> { for peripheral in chip.peripherals.values() { for pin in peripheral.pins.iter() { diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 785dbdf..5100cb6 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -281,6 +281,21 @@ pub struct Peripheral { /// `None` if the peripheral has no interrupt of its own. #[serde(skip_serializing_if = "Option::is_none")] pub interrupt: Option, + + /// Whether this peripheral instance has its own `CLKCFG.BLOCKASYNC` bit. + /// + /// An asynchronous fast clock request temporarily suspends a low-power mode and brings MCLK and + /// ULPCLK back to full rate, which is how a PD0 peripheral wakes the system on an external + /// event while not being clocked. `BLOCKASYNC` masks the request for one instance and must be + /// clear to arm such a wake; `SYSCTL.SYSOSCCFG.BLOCKASYNCALL` masks every request at once. + /// + /// `false` does not mean the peripheral cannot raise a request: GPIO, the general purpose + /// timers and the ADC all can, but have no per-instance mask and are gated only by + /// `BLOCKASYNCALL`. `None` means no SVD is published for this family yet, so the answer is + /// unknown rather than negative. + /// This is deliberately per-instance: on mspm0g120x only `UC0`, `UC2`, `UC4`, `UC5` and + /// `UC9` have the bit despite every `UC` being the same IP. + pub block_async: Option, } /// The interrupt raised by a peripheral. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 19c4543..32413e2 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -41,6 +41,19 @@ pub struct Peripheral { /// The interrupt raised by this peripheral, if it has one. pub interrupt: Option, + + /// Whether this peripheral instance has its own `CLKCFG.BLOCKASYNC` bit. + /// + /// An asynchronous fast clock request temporarily suspends a low-power mode and brings MCLK and + /// ULPCLK back to full rate, which is how a PD0 peripheral wakes the system on an external + /// event while not being clocked. `BLOCKASYNC` masks the request for one instance and must be + /// clear to arm such a wake; `SYSCTL.SYSOSCCFG.BLOCKASYNCALL` masks every request at once. + /// + /// `false` does not mean the peripheral cannot raise a request: GPIO, the general purpose + /// timers and the ADC all can, but have no per-instance mask and are gated only by + /// `BLOCKASYNCALL`. `None` means no SVD is published for this family yet, so the answer is + /// unknown rather than negative. + pub block_async: Option, } /// The interrupt raised by a peripheral. diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index b31eebb..9dd9593 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -259,6 +259,11 @@ fn generate_peripheral( None => quote! { None }, }; + let block_async = match peripheral.block_async { + Some(block_async) => quote! { Some(#block_async) }, + None => quote! { None }, + }; + Some(quote! { Peripheral { name: #name, @@ -268,6 +273,7 @@ fn generate_peripheral( power_domain: #power_domain, sys_fentries: #sys_fentries, interrupt: #interrupt, + block_async: #block_async, } }) } From fe8d79a14a97d1ff6477ac12decfabd4d509e249 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Wed, 29 Jul 2026 00:43:03 +0100 Subject: [PATCH 008/134] Add operating-mode data from the datasheet tables Signed-off-by: CordlessCoder --- README.md | 26 +- data/operating_mode_overrides.yaml | 24 ++ data/operating_modes/mspm0c1105_c1106.yaml | 43 +++ data/operating_modes/mspm0c110x.yaml | 35 ++ data/operating_modes/mspm0g110x.yaml | 57 ++++ data/operating_modes/mspm0g150x.yaml | 67 ++++ data/operating_modes/mspm0g151x.yaml | 78 +++++ data/operating_modes/mspm0g310x.yaml | 62 ++++ data/operating_modes/mspm0g350x.yaml | 68 ++++ data/operating_modes/mspm0g351x.yaml | 80 +++++ data/operating_modes/mspm0g518x.yaml | 53 +++ data/operating_modes/mspm0h321x.yaml | 42 +++ data/operating_modes/mspm0l110x.yaml | 38 +++ data/operating_modes/mspm0l112x.yaml | 43 +++ data/operating_modes/mspm0l122x.yaml | 55 ++++ data/operating_modes/mspm0l130x.yaml | 40 +++ data/operating_modes/mspm0l134x.yaml | 40 +++ data/operating_modes/mspm0l211x.yaml | 44 +++ data/operating_modes/mspm0l222x.yaml | 56 ++++ data/operating_modes/msps003fx.yaml | 35 ++ data/parts.yaml | 93 ++++++ mspm0-data-gen/src/generate.rs | 148 +++++++-- mspm0-data-gen/src/main.rs | 11 +- mspm0-data-gen/src/operating_modes.rs | 68 ++++ mspm0-data-gen/src/parts.rs | 15 + mspm0-data-gen/src/verify.rs | 63 +++- mspm0-data-types/src/lib.rs | 65 +++- mspm0-metapac-gen/res/metadata.rs | 52 +++ mspm0-metapac-gen/src/metadata.rs | 40 ++- tools/operating_modes.py | 358 +++++++++++++++++++++ 30 files changed, 1860 insertions(+), 39 deletions(-) create mode 100644 data/operating_mode_overrides.yaml create mode 100644 data/operating_modes/mspm0c1105_c1106.yaml create mode 100644 data/operating_modes/mspm0c110x.yaml create mode 100644 data/operating_modes/mspm0g110x.yaml create mode 100644 data/operating_modes/mspm0g150x.yaml create mode 100644 data/operating_modes/mspm0g151x.yaml create mode 100644 data/operating_modes/mspm0g310x.yaml create mode 100644 data/operating_modes/mspm0g350x.yaml create mode 100644 data/operating_modes/mspm0g351x.yaml create mode 100644 data/operating_modes/mspm0g518x.yaml create mode 100644 data/operating_modes/mspm0h321x.yaml create mode 100644 data/operating_modes/mspm0l110x.yaml create mode 100644 data/operating_modes/mspm0l112x.yaml create mode 100644 data/operating_modes/mspm0l122x.yaml create mode 100644 data/operating_modes/mspm0l130x.yaml create mode 100644 data/operating_modes/mspm0l134x.yaml create mode 100644 data/operating_modes/mspm0l211x.yaml create mode 100644 data/operating_modes/mspm0l222x.yaml create mode 100644 data/operating_modes/msps003fx.yaml create mode 100644 mspm0-data-gen/src/operating_modes.rs create mode 100644 tools/operating_modes.py diff --git a/README.md b/README.md index ffd4f2d..93513e6 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,14 @@ - ๐Ÿšง DMA mappings - โœ”๏ธ Per package pinouts - ๐Ÿšง Links to applicable technical reference manual and datasheet PDFs. +- โœ”๏ธ Low power data + - Power domain per peripheral + - How deep a sleep each peripheral and memory region is retained through + - How deep a sleep each peripheral stays usable in (the datasheet `EN`/`DIS`/`OPT`/`NS`/`OFF` table) + - Which timers stay clocked in STANDBY1 + - Which peripheral instances have a `CLKCFG.BLOCKASYNC` bit + - Which pins can wake the device from SHUTDOWN + - MCLK and ULPCLK ceilings, backup power domain presence โœ”๏ธ = done, ๐Ÿšง = work in progress, โŒ = to do @@ -30,19 +38,33 @@ These are the data sources currently used. * Mapping from GPIO pin to IOMUX::PINCM register. * Peripheral PF (pin function) mappings. * Peripheral pin names. + * Which pins have wakeup logic (`io_wakeup`). * mspm0-sdk header files * Interrupt number, name * Peripheral addresses -* mspm0 SVDs: register blocks + * NVIC interrupt priority bits +* mspm0 SVDs + * Register blocks + * Which peripheral instances have a `CLKCFG.BLOCKASYNC` bit. TI does not publish an SVD for every + family, so this is optional per family. +* Device datasheets, from the "Supported Functionality by Operating Mode" table and its footnotes + * How deep a sleep each PD1 peripheral is retained through, and how deep each peripheral + stays usable ([`data/operating_modes/`](./data/operating_modes)) + * Which timers stay clocked in STANDBY1 (`standby1_timers` in [`parts.yaml`](./data/parts.yaml)) + * MCLK and ULPCLK ceilings * Manually entered * IIDX values for interrupts within a `INT_GROUP` # Adding a new chip 1. Update the data sources to include the new chip. You will need to get the SVD and sysconfig metadata. -2. Add the new chip family and part numbers to [`parts.yaml`](./data/parts.yaml) +2. Add the new chip family and part numbers to [`parts.yaml`](./data/parts.yaml). Besides the part + numbers and memory this needs the clock ceilings and `standby1_timers` from the datasheet. 3. If needed, add any chip specific register blocks like `sysctl`. 4. Check the peripheral mapping in [`perimap.rs`](./mspm0-data-gen/src/perimap.rs) to use the correct peripherals. +5. Generate `data/operating_modes/.yaml` with + [`tools/operating_modes.py`](./tools/operating_modes.py), which reads the datasheet's per-mode + table. # Adding support for a new peripheral diff --git a/data/operating_mode_overrides.yaml b/data/operating_mode_overrides.yaml new file mode 100644 index 0000000..72f1d8f --- /dev/null +++ b/data/operating_mode_overrides.yaml @@ -0,0 +1,24 @@ +# Values where a better source contradicts the datasheet's per-mode table. +# +# `tools/operating_modes.py` applies these last, after reading Table 8-1, and notes them in the +# generated `data/operating_modes/.yaml`. Every entry needs its evidence stated here, because +# each one overrides the otherwise authoritative source. +# +# This file lives outside `data/operating_modes/` because everything in that directory is generated +# and is parsed per family by `operating_modes.rs`. + +mspm0l122x: + retained_through: + # The MSPM0L1228 table marks TRNG `OPT` in every mode, which says it stays available but not what + # becomes of its configuration -- the same shape as the other Lx22x PD1 rows, which sit oddly + # with those peripherals being in PD1 at all. The TRM is unambiguous and unconditional where the + # table is not: 13.2.3 "Behavior in Low Power Modes" states "all TRNG configuration settings and + # data are lost when the device transitions to any operating mode below SLEEP", and that it + # "must be re-configured for use as needed when waking the device from STOP, STANDBY, or + # SHUTDOWN mode". Every other datasheet which has a TRNG row marks it `OFF`. + TRNG: Sleep + +mspm0l222x: + retained_through: + # As above; the MSPM0L2228 datasheet shares this table with the MSPM0L1228. + TRNG: Sleep diff --git a/data/operating_modes/mspm0c1105_c1106.yaml b/data/operating_modes/mspm0c1105_c1106.yaml new file mode 100644 index 0000000..e0c98ca --- /dev/null +++ b/data/operating_modes/mspm0c1105_c1106.yaml @@ -0,0 +1,43 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0C1106 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + CPUSS: Standby + CRC: Standby + DMA: Standby + SPI0: Standby + +usable_through: + ADC0: Sleep + COMP0: Standby + CPUSS: Run + CRC: Sleep + DMA: Sleep + GPIOA: Standby + GPIOB: Standby + I2C0: Stop + I2C1: Stop + SPI0: Sleep + TIMA0: Stop + TIMG1: Stop + TIMG14: Standby + TIMG2: Stop + TIMG8: Standby + UART0: Stop + UART1: Stop + UART2: Stop + VREF: Sleep + WWDT0: Standby diff --git a/data/operating_modes/mspm0c110x.yaml b/data/operating_modes/mspm0c110x.yaml new file mode 100644 index 0000000..36001a2 --- /dev/null +++ b/data/operating_modes/mspm0c110x.yaml @@ -0,0 +1,35 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0C1104 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + CPUSS: Standby + CRC: Standby + DMA: Standby + SPI0: Standby + +usable_through: + ADC0: Sleep + CPUSS: Run + CRC: Sleep + DMA: Sleep + GPIOA: Standby + SPI0: Sleep + TIMA0: Standby + TIMG14: Standby + TIMG8: Standby + UART0: Standby + VREF: Sleep + WWDT0: Stop diff --git a/data/operating_modes/mspm0g110x.yaml b/data/operating_modes/mspm0g110x.yaml new file mode 100644 index 0000000..2d5e637 --- /dev/null +++ b/data/operating_modes/mspm0g110x.yaml @@ -0,0 +1,57 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0G1107 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + CPUSS: Standby + CRC: Standby + DMA: Standby + SPI0: Standby + SPI1: Standby + TIMA0: Sleep + TIMA1: Sleep + TIMG12: Sleep + TIMG6: Sleep + TIMG7: Sleep + UART3: Standby + +usable_through: + ADC0: Sleep + ADC1: Sleep + CPUSS: Run + CRC: Sleep + DMA: Sleep + GPAMP: Sleep + GPIOA: Standby + GPIOB: Standby + I2C0: Standby + I2C1: Standby + RTC: Standby + SPI0: Sleep + SPI1: Sleep + TIMA0: Sleep + TIMA1: Sleep + TIMG0: Standby + TIMG12: Sleep + TIMG6: Sleep + TIMG7: Sleep + TIMG8: Standby + UART0: Standby + UART1: Standby + UART2: Standby + UART3: Sleep + VREF: Standby + WWDT0: Stop + WWDT1: Stop diff --git a/data/operating_modes/mspm0g150x.yaml b/data/operating_modes/mspm0g150x.yaml new file mode 100644 index 0000000..cec3422 --- /dev/null +++ b/data/operating_modes/mspm0g150x.yaml @@ -0,0 +1,67 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0G1507 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + AES: Sleep + CPUSS: Standby + CRC: Standby + DMA: Standby + MATHACL: Sleep + SPI0: Standby + SPI1: Standby + TIMA0: Sleep + TIMA1: Sleep + TIMG12: Sleep + TIMG6: Sleep + TIMG7: Sleep + TRNG: Sleep + UART3: Standby + +usable_through: + ADC0: Sleep + ADC1: Sleep + AES: Sleep + COMP0: Standby + COMP1: Standby + COMP2: Standby + CPUSS: Run + CRC: Sleep + DAC0: Sleep + DMA: Sleep + GPAMP: Sleep + GPIOA: Standby + GPIOB: Standby + I2C0: Standby + I2C1: Standby + MATHACL: Sleep + RTC: Standby + SPI0: Sleep + SPI1: Sleep + TIMA0: Sleep + TIMA1: Sleep + TIMG0: Standby + TIMG12: Sleep + TIMG6: Sleep + TIMG7: Sleep + TIMG8: Standby + TRNG: Sleep + UART0: Standby + UART1: Standby + UART2: Standby + UART3: Sleep + VREF: Standby + WWDT0: Stop + WWDT1: Stop diff --git a/data/operating_modes/mspm0g151x.yaml b/data/operating_modes/mspm0g151x.yaml new file mode 100644 index 0000000..b246d0f --- /dev/null +++ b/data/operating_modes/mspm0g151x.yaml @@ -0,0 +1,78 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0G1519 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + AESADV: Sleep + CPUSS: Standby + CRCP0: Standby + DMA: Standby + MATHACL: Sleep + SPI0: Standby + SPI1: Standby + SPI2: Standby + TIMA0: Sleep + TIMA1: Sleep + TIMG12: Sleep + TIMG6: Sleep + TIMG7: Sleep + TRNG: Sleep + UART3: Standby + UART4: Standby + UART5: Standby + UART6: Standby + +usable_through: + ADC0: Sleep + ADC1: Sleep + AESADV: Sleep + COMP0: Standby + COMP1: Standby + COMP2: Standby + CPUSS: Run + CRCP0: Sleep + DAC0: Sleep + DMA: Sleep + GPIOA: Standby + GPIOB: Standby + GPIOC: Standby + I2C0: Standby + I2C1: Standby + I2C2: Standby + MATHACL: Sleep + RTC_B: Standby + SPI0: Sleep + SPI1: Sleep + SPI2: Sleep + TIMA0: Sleep + TIMA1: Sleep + TIMG0: Standby + TIMG12: Sleep + TIMG14: Standby + TIMG6: Sleep + TIMG7: Sleep + TIMG8: Standby + TIMG9: Standby + TRNG: Sleep + UART0: Standby + UART1: Standby + UART3: Sleep + UART4: Sleep + UART5: Sleep + UART6: Sleep + UART7: Standby + VREF: Standby + WWDT0: Stop + WWDT1: Stop diff --git a/data/operating_modes/mspm0g310x.yaml b/data/operating_modes/mspm0g310x.yaml new file mode 100644 index 0000000..883e2c5 --- /dev/null +++ b/data/operating_modes/mspm0g310x.yaml @@ -0,0 +1,62 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0G3107 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + AES: Sleep + CANFD0: Sleep + CPUSS: Standby + CRC: Standby + DMA: Standby + SPI0: Standby + SPI1: Standby + TIMA0: Sleep + TIMA1: Sleep + TIMG12: Sleep + TIMG6: Sleep + TIMG7: Sleep + TRNG: Sleep + UART3: Standby + +usable_through: + ADC0: Sleep + ADC1: Sleep + AES: Sleep + CPUSS: Run + CRC: Sleep + DMA: Sleep + GPAMP: Sleep + GPIOA: Standby + GPIOB: Standby + I2C0: Standby + I2C1: Standby + RTC: Standby + SPI0: Sleep + SPI1: Sleep + TIMA0: Sleep + TIMA1: Sleep + TIMG0: Standby + TIMG12: Sleep + TIMG6: Sleep + TIMG7: Sleep + TIMG8: Standby + TRNG: Sleep + UART0: Standby + UART1: Standby + UART2: Standby + UART3: Sleep + VREF: Standby + WWDT0: Stop + WWDT1: Stop diff --git a/data/operating_modes/mspm0g350x.yaml b/data/operating_modes/mspm0g350x.yaml new file mode 100644 index 0000000..9842ec8 --- /dev/null +++ b/data/operating_modes/mspm0g350x.yaml @@ -0,0 +1,68 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0G3507 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + AES: Sleep + CANFD0: Sleep + CPUSS: Standby + CRC: Standby + DMA: Standby + MATHACL: Sleep + SPI0: Standby + SPI1: Standby + TIMA0: Sleep + TIMA1: Sleep + TIMG12: Sleep + TIMG6: Sleep + TIMG7: Sleep + TRNG: Sleep + UART3: Standby + +usable_through: + ADC0: Sleep + ADC1: Sleep + AES: Sleep + COMP0: Standby + COMP1: Standby + COMP2: Standby + CPUSS: Run + CRC: Sleep + DAC0: Sleep + DMA: Sleep + GPAMP: Sleep + GPIOA: Standby + GPIOB: Standby + I2C0: Standby + I2C1: Standby + MATHACL: Sleep + RTC: Standby + SPI0: Sleep + SPI1: Sleep + TIMA0: Sleep + TIMA1: Sleep + TIMG0: Standby + TIMG12: Sleep + TIMG6: Sleep + TIMG7: Sleep + TIMG8: Standby + TRNG: Sleep + UART0: Standby + UART1: Standby + UART2: Standby + UART3: Sleep + VREF: Standby + WWDT0: Stop + WWDT1: Stop diff --git a/data/operating_modes/mspm0g351x.yaml b/data/operating_modes/mspm0g351x.yaml new file mode 100644 index 0000000..72e76b1 --- /dev/null +++ b/data/operating_modes/mspm0g351x.yaml @@ -0,0 +1,80 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0G3519 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + AESADV: Sleep + CANFD0: Sleep + CANFD1: Sleep + CPUSS: Standby + CRCP0: Standby + DMA: Standby + MATHACL: Sleep + SPI0: Standby + SPI1: Standby + SPI2: Standby + TIMA0: Sleep + TIMA1: Sleep + TIMG12: Sleep + TIMG6: Sleep + TIMG7: Sleep + TRNG: Sleep + UART3: Standby + UART4: Standby + UART5: Standby + UART6: Standby + +usable_through: + ADC0: Sleep + ADC1: Sleep + AESADV: Sleep + COMP0: Standby + COMP1: Standby + COMP2: Standby + CPUSS: Run + CRCP0: Sleep + DAC0: Sleep + DMA: Sleep + GPIOA: Standby + GPIOB: Standby + GPIOC: Standby + I2C0: Standby + I2C1: Standby + I2C2: Standby + MATHACL: Sleep + RTC_B: Standby + SPI0: Sleep + SPI1: Sleep + SPI2: Sleep + TIMA0: Sleep + TIMA1: Sleep + TIMG0: Standby + TIMG12: Sleep + TIMG14: Standby + TIMG6: Sleep + TIMG7: Sleep + TIMG8: Standby + TIMG9: Standby + TRNG: Sleep + UART0: Standby + UART1: Standby + UART3: Sleep + UART4: Sleep + UART5: Sleep + UART6: Sleep + UART7: Standby + VREF: Standby + WWDT0: Stop + WWDT1: Stop diff --git a/data/operating_modes/mspm0g518x.yaml b/data/operating_modes/mspm0g518x.yaml new file mode 100644 index 0000000..3afdc45 --- /dev/null +++ b/data/operating_modes/mspm0g518x.yaml @@ -0,0 +1,53 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0G5187 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + AESADV: Sleep + CPUSS: Standby + CRCP0: Standby + DMA: Standby + I2S0: Sleep + NPU: Sleep + TIMA0: Sleep + TIMG6: Sleep + TIMG7: Sleep + UC2: Sleep + UC3: Sleep + USBFS0: Sleep + +usable_through: + ADC0: Sleep + AESADV: Sleep + COMP0: Standby + CPUSS: Run + CRCP0: Sleep + DMA: Sleep + GPIOA: Standby + GPIOB: Standby + IWDT: Standby + NPU: Sleep + RTC_B: Standby + TIMA0: Sleep + TIMB0: Standby + TIMG0: Standby + TIMG6: Sleep + TIMG7: Sleep + UC0: Standby + UC1: Standby + UC2: Sleep + UC3: Sleep + WWDT0: Stop + WWDT1: Stop diff --git a/data/operating_modes/mspm0h321x.yaml b/data/operating_modes/mspm0h321x.yaml new file mode 100644 index 0000000..124dbcc --- /dev/null +++ b/data/operating_modes/mspm0h321x.yaml @@ -0,0 +1,42 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0H3216 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + CPUSS: Standby + CRC: Standby + DMA: Standby + SPI0: Standby + +usable_through: + ADC0: Sleep + CPUSS: Run + CRC: Sleep + DMA: Sleep + GPIOA: Standby + GPIOB: Standby + I2C0: Standby + I2C1: Standby + SPI0: Sleep + TIMA0: Standby + TIMG1: Standby + TIMG14: Standby + TIMG2: Standby + TIMG8: Standby + UART0: Standby + UART1: Standby + UART2: Standby + VREF: Sleep + WWDT0: Standby diff --git a/data/operating_modes/mspm0l110x.yaml b/data/operating_modes/mspm0l110x.yaml new file mode 100644 index 0000000..f317935 --- /dev/null +++ b/data/operating_modes/mspm0l110x.yaml @@ -0,0 +1,38 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0L1106 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + CPUSS: Standby + CRC: Standby + DMA: Standby + SPI0: Standby + +usable_through: + ADC0: Sleep + CPUSS: Run + CRC: Sleep + DMA: Sleep + GPAMP: Sleep + GPIOA: Standby + I2C0: Standby + SPI0: Sleep + TIMG0: Standby + TIMG1: Standby + TIMG2: Standby + TIMG4: Standby + UART0: Standby + UART1: Standby + WWDT0: Stop diff --git a/data/operating_modes/mspm0l112x.yaml b/data/operating_modes/mspm0l112x.yaml new file mode 100644 index 0000000..8d3164e --- /dev/null +++ b/data/operating_modes/mspm0l112x.yaml @@ -0,0 +1,43 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0L2117 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + AESADV: Standby + CPUSS: Standby + DMA: Standby + +usable_through: + ADC0: Sleep + AESADV: Sleep + COMP0: Standby + CPUSS: Run + CRC0: Sleep + DMA: Sleep + GPIOA: Standby + GPIOB: Standby + IWDT: Standby + RTC_B: Standby + TIMA0: Sleep + TIMG1: Standby + TIMG14: Standby + TIMG2: Standby + UC11: Sleep + UC4: Standby + UC6: Standby + UC7: Standby + UC8: Sleep + VREF: Sleep + WWDT0: Stop diff --git a/data/operating_modes/mspm0l122x.yaml b/data/operating_modes/mspm0l122x.yaml new file mode 100644 index 0000000..3bd51f6 --- /dev/null +++ b/data/operating_modes/mspm0l122x.yaml @@ -0,0 +1,55 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0L1228 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + AESADV: Standby + CPUSS: Standby + DMA: Standby + SPI0: Standby + SPI1: Standby + TRNG: Sleep + + # Not derivable from the table: these rows give one value spanning every mode, + # which says the peripheral is available but not whether its configuration + # survives. Entered by hand and preserved across regeneration. + CRCP0: Standby + +usable_through: + ADC0: Sleep + AESADV: Standby + COMP0: Standby + CPUSS: Run + DMA: Sleep + GPIOA: Standby + GPIOB: Standby + GPIOC: Standby + I2C0: Standby + I2C1: Standby + SPI0: Standby + SPI1: Standby + TIMA0: Standby + TIMG0: Standby + TIMG12: Standby + TIMG4: Standby + TIMG5: Standby + TIMG8: Standby + TRNG: Standby + UART0: Standby + UART1: Standby + UART2: Standby + UART3: Standby + UART4: Standby + WWDT0: Stop diff --git a/data/operating_modes/mspm0l130x.yaml b/data/operating_modes/mspm0l130x.yaml new file mode 100644 index 0000000..b0714f3 --- /dev/null +++ b/data/operating_modes/mspm0l130x.yaml @@ -0,0 +1,40 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0L1306 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + CPUSS: Standby + CRC: Standby + DMA: Standby + SPI0: Standby + +usable_through: + ADC0: Sleep + COMP0: Standby + CPUSS: Run + CRC: Sleep + DMA: Sleep + GPAMP: Sleep + GPIOA: Standby + I2C0: Standby + I2C1: Standby + SPI0: Sleep + TIMG0: Standby + TIMG1: Standby + TIMG2: Standby + TIMG4: Standby + UART0: Standby + UART1: Standby + WWDT0: Stop diff --git a/data/operating_modes/mspm0l134x.yaml b/data/operating_modes/mspm0l134x.yaml new file mode 100644 index 0000000..ce11146 --- /dev/null +++ b/data/operating_modes/mspm0l134x.yaml @@ -0,0 +1,40 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0L1346 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + CPUSS: Standby + CRC: Standby + DMA: Standby + SPI0: Standby + +usable_through: + ADC0: Sleep + COMP0: Standby + CPUSS: Run + CRC: Sleep + DMA: Sleep + GPAMP: Sleep + GPIOA: Standby + I2C0: Standby + I2C1: Standby + SPI0: Sleep + TIMG0: Standby + TIMG1: Standby + TIMG2: Standby + TIMG4: Standby + UART0: Standby + UART1: Standby + WWDT0: Stop diff --git a/data/operating_modes/mspm0l211x.yaml b/data/operating_modes/mspm0l211x.yaml new file mode 100644 index 0000000..0e47e7f --- /dev/null +++ b/data/operating_modes/mspm0l211x.yaml @@ -0,0 +1,44 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0L2117 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + AESADV: Standby + CPUSS: Standby + DMA: Standby + +usable_through: + ADC0: Sleep + AESADV: Sleep + COMP0: Standby + CPUSS: Run + CRC0: Sleep + DMA: Sleep + GPIOA: Standby + GPIOB: Standby + IWDT: Standby + LCD: Standby + RTC_B: Standby + TIMA0: Sleep + TIMG1: Standby + TIMG14: Standby + TIMG2: Standby + UC11: Sleep + UC4: Standby + UC6: Standby + UC7: Standby + UC8: Sleep + VREF: Sleep + WWDT0: Stop diff --git a/data/operating_modes/mspm0l222x.yaml b/data/operating_modes/mspm0l222x.yaml new file mode 100644 index 0000000..0688d70 --- /dev/null +++ b/data/operating_modes/mspm0l222x.yaml @@ -0,0 +1,56 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0L2228 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + AESADV: Standby + CPUSS: Standby + DMA: Standby + SPI0: Standby + SPI1: Standby + TRNG: Sleep + + # Not derivable from the table: these rows give one value spanning every mode, + # which says the peripheral is available but not whether its configuration + # survives. Entered by hand and preserved across regeneration. + CRCP0: Standby + +usable_through: + ADC0: Sleep + AESADV: Standby + COMP0: Standby + CPUSS: Run + DMA: Sleep + GPIOA: Standby + GPIOB: Standby + GPIOC: Standby + I2C0: Standby + I2C1: Standby + LCD: Standby + SPI0: Standby + SPI1: Standby + TIMA0: Standby + TIMG0: Standby + TIMG12: Standby + TIMG4: Standby + TIMG5: Standby + TIMG8: Standby + TRNG: Standby + UART0: Standby + UART1: Standby + UART2: Standby + UART3: Standby + UART4: Standby + WWDT0: Stop diff --git a/data/operating_modes/msps003fx.yaml b/data/operating_modes/msps003fx.yaml new file mode 100644 index 0000000..36001a2 --- /dev/null +++ b/data/operating_modes/msps003fx.yaml @@ -0,0 +1,35 @@ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the MSPM0C1104 datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. + +retained_through: + CPUSS: Standby + CRC: Standby + DMA: Standby + SPI0: Standby + +usable_through: + ADC0: Sleep + CPUSS: Run + CRC: Sleep + DMA: Sleep + GPIOA: Standby + SPI0: Sleep + TIMA0: Standby + TIMG14: Standby + TIMG8: Standby + UART0: Standby + VREF: Sleep + WWDT0: Stop diff --git a/data/parts.yaml b/data/parts.yaml index 90ce05e..703a8c7 100644 --- a/data/parts.yaml +++ b/data/parts.yaml @@ -19,6 +19,10 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0C1104 datasheet. max_mclk_hz: 24000000 max_ulpclk_hz: 24000000 + standby1_timers: + - TIMA0 + - TIMG8 + - TIMG14 part_numbers: - name: msps003f3 # Part number memory: @@ -51,6 +55,10 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0C1104 datasheet. max_mclk_hz: 24000000 max_ulpclk_hz: 24000000 + standby1_timers: + - TIMA0 + - TIMG8 + - TIMG14 part_numbers: - name: mspm0c1103 memory: @@ -89,6 +97,9 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0C1106 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + standby1_timers: + - TIMG8 + - TIMG14 part_numbers: - name: mspm0c1105 memory: @@ -139,6 +150,9 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0G1107 datasheet. max_mclk_hz: 80000000 max_ulpclk_hz: 40000000 + standby1_timers: + - TIMG0 + - TIMG8 part_numbers: - name: mspm0g1105 memory: @@ -197,6 +211,9 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0G1507 datasheet. max_mclk_hz: 80000000 max_ulpclk_hz: 40000000 + standby1_timers: + - TIMG0 + - TIMG8 part_numbers: - name: mspm0g1505 memory: @@ -255,6 +272,11 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0G1519 datasheet. max_mclk_hz: 80000000 max_ulpclk_hz: 40000000 + standby1_timers: + - TIMG0 + - TIMG8 + - TIMG9 + - TIMG14 part_numbers: - name: mspm0g1518 memory: @@ -272,6 +294,11 @@ families: - name: RAM_BANK length: 64 address: 0x20210000 + # The upper RAM bank is wiped out when the device enters any low-power mode stronger than + # SLEEP. The datasheet is finer grained than this -- Table 8-1 gives `DIS / OFF` for STOP, + # meaning it depends on the configured SRAM power level, and `OFF` for STANDBY -- so this is + # the conservative reading. + retained_through: Sleep packages: - PM - PN @@ -297,6 +324,11 @@ families: - name: RAM_BANK length: 64 address: 0x20210000 + # The upper RAM bank is wiped out when the device enters any low-power mode stronger than + # SLEEP. The datasheet is finer grained than this -- Table 8-1 gives `DIS / OFF` for STOP, + # meaning it depends on the configured SRAM power level, and `OFF` for STANDBY -- so this is + # the conservative reading. + retained_through: Sleep packages: - PM - PN @@ -314,6 +346,9 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0G3107 datasheet. max_mclk_hz: 80000000 max_ulpclk_hz: 40000000 + standby1_timers: + - TIMG0 + - TIMG8 part_numbers: - name: mspm0g3105 memory: @@ -362,6 +397,9 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0G3507 datasheet. max_mclk_hz: 80000000 max_ulpclk_hz: 40000000 + standby1_timers: + - TIMG0 + - TIMG8 part_numbers: - name: mspm0g3505 memory: @@ -416,6 +454,11 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0G3519 datasheet. max_mclk_hz: 80000000 max_ulpclk_hz: 40000000 + standby1_timers: + - TIMG0 + - TIMG8 + - TIMG9 + - TIMG14 part_numbers: - name: mspm0g3518 memory: @@ -433,6 +476,11 @@ families: - name: RAM_BANK length: 64 address: 0x20210000 + # The upper RAM bank is wiped out when the device enters any low-power mode stronger than + # SLEEP. The datasheet is finer grained than this -- Table 8-1 gives `DIS / OFF` for STOP, + # meaning it depends on the configured SRAM power level, and `OFF` for STANDBY -- so this is + # the conservative reading. + retained_through: Sleep packages: - PM - PN @@ -457,6 +505,11 @@ families: - name: RAM_BANK length: 64 address: 0x20210000 + # The upper RAM bank is wiped out when the device enters any low-power mode stronger than + # SLEEP. The datasheet is finer grained than this -- Table 8-1 gives `DIS / OFF` for STOP, + # meaning it depends on the configured SRAM power level, and `OFF` for STANDBY -- so this is + # the conservative reading. + retained_through: Sleep packages: - PM - PN @@ -475,6 +528,9 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0G5187 datasheet. max_mclk_hz: 80000000 max_ulpclk_hz: 40000000 + standby1_timers: + - TIMG0 + - TIMB0 part_numbers: - name: mspm0g5187 memory: @@ -503,6 +559,8 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0H3216 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + standby1_timers: + - TIMG14 part_numbers: - name: mspm0h3215 memory: @@ -535,6 +593,9 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0L1106 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + standby1_timers: + - TIMG0 + - TIMG1 part_numbers: - name: mspm0l1105 memory: @@ -576,6 +637,12 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0L2117 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + standby1_timers: + - TIMG14 + - TIMB0 + - TIMB1 + - TIMB2 + - TIMB3 part_numbers: - name: mspm0l1126 memory: @@ -625,6 +692,13 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0L1228 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + standby1_timers: + - TIMA0 + - TIMG0 + - TIMG4 + - TIMG5 + - TIMG8 + - TIMG12 part_numbers: - name: mspm0l1227 memory: @@ -666,6 +740,9 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0L1306 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + standby1_timers: + - TIMG0 + - TIMG1 part_numbers: - name: mspm0l1303 memory: @@ -733,6 +810,9 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0L1346 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + standby1_timers: + - TIMG0 + - TIMG1 part_numbers: - name: mspm0l1343 memory: @@ -786,6 +866,12 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0L2117 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + standby1_timers: + - TIMG14 + - TIMB0 + - TIMB1 + - TIMB2 + - TIMB3 part_numbers: - name: mspm0l2116 memory: @@ -835,6 +921,13 @@ families: # Clock ceilings and STANDBY1 timers come from the MSPM0L2228 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + standby1_timers: + - TIMA0 + - TIMG0 + - TIMG4 + - TIMG5 + - TIMG8 + - TIMG12 part_numbers: - name: mspm0l2227 memory: diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 54484a7..407dfc8 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -9,13 +9,14 @@ use std::{ use anyhow::{anyhow, bail, ensure, Context}; use mspm0_data_types::{ Chip, DmaChannel, Interrupt, Memory, MemoryKind, Package, PackagePin, Peripheral, - PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, + PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, }; use regex::Regex; use crate::{ header::{Header, Headers}, int_group::Groups, + operating_modes::OperatingModes, parts::{PartFamily, PartMemory, PartsFile}, perimap::PERIMAP, svd::{Svd, Svds}, @@ -28,6 +29,7 @@ pub fn generate( headers: &Headers, sysconfig: &Sysconfig, svds: &Svds, + operating_modes: &BTreeMap, int_groups: &BTreeMap, ) -> anyhow::Result<()> { fs::create_dir_all("./build/data/").unwrap(); @@ -50,13 +52,20 @@ pub fn generate( .get(&header_name.to_lowercase()) .context(format!("Could not lookup header for {}", header_name))?; - // SVDs are named after the family, but TI does not publish one for every family: as of - // the pinned data sources mspm0c1105_c1106, mspm0l112x and mspm0l211x have none. The - // facts taken from the SVD are optional for that reason, and `verify` reports the gap. + // SVDs are named after the family, but TI does not publish one for every family: as of the + // pinned data sources mspm0c1105_c1106, mspm0l112x and mspm0l211x have none. The facts taken + // from the SVD are optional for that reason, and `verify` reports the gap. let svd = svds.files.get(&family.family); - generate_family(family, header, sysconfig, svd, int_groups) - .context(format!("Error when generating family: {}", family.family))?; + generate_family( + family, + header, + sysconfig, + svd, + operating_modes.get(&family.family), + int_groups, + ) + .context(format!("Error when generating family: {}", family.family))?; } Ok(()) @@ -67,6 +76,7 @@ fn generate_family( header: &Header, sysconfig: &SysconfigFile, svd: Option<&Svd>, + operating_modes: Option<&OperatingModes>, int_groups: &BTreeMap, ) -> anyhow::Result<()> { // Data shared across all chips in a family. @@ -79,9 +89,11 @@ fn generate_family( let adc_memctl = generate_adc_memctl_dim(&family.family, sysconfig)?; let backup_domain = has_backup_domain(&family.family, sysconfig, &peripherals)?; - // Facts which are easier to attach once every peripheral is known. + // Low power facts which are easier to attach once every peripheral is known. apply_peripheral_interrupts(&mut peripherals, &interrupts); apply_block_async(&mut peripherals, svd); + apply_operating_modes(operating_modes, &mut peripherals); + apply_standby1_timers(family, &mut peripherals)?; for part_number in family.part_numbers.iter() { // Filter for package types available on the part number. @@ -302,8 +314,12 @@ fn generate_peripherals2( power_domain, pins: vec![], sys_fentries, + // Filled in by the `apply_*` passes once every peripheral is known. interrupt: None, block_async: None, + retained_through: None, + usable_through: None, + clocked_in_standby1: None, }; // Lookup the pins @@ -554,6 +570,9 @@ fn generate_missing( sys_fentries: None, interrupt: None, block_async: None, + retained_through: None, + usable_through: None, + clocked_in_standby1: None, }, ); @@ -587,6 +606,9 @@ fn generate_missing( sys_fentries: None, interrupt: None, block_async: None, + retained_through: None, + usable_through: None, + clocked_in_standby1: None, }); let pin = device_pin @@ -919,6 +941,41 @@ fn skip_peripheral_pin(pin_name: &String, chip_name: &str) -> bool { false } +fn convert_memory(memory: &PartMemory) -> anyhow::Result { + let kind = match memory.name.as_str() { + "FLASH" => MemoryKind::Flash, + "RAM" | "RAM_BANK" => MemoryKind::Ram, + name => bail!("Unknown memory partition `{name}`, cannot tell what kind of memory it is"), + }; + + // Flash is non-volatile, and SRAM survives everything short of SHUTDOWN. Only the parts with a + // second RAM bank differ, and they say so in parts.yaml. + let retained_through = memory.retained_through.unwrap_or(match kind { + MemoryKind::Flash => PowerMode::Shutdown, + MemoryKind::Ram => PowerMode::Standby, + }); + + Ok(Memory { + name: memory.name.clone(), + kind, + length: memory.length, + address: memory.address, + retained_through, + }) +} + +/// Device pins which have wakeup logic, and can therefore wake the device from SHUTDOWN. +fn generate_wakeup_pins(sysconfig: &SysconfigFile) -> BTreeSet { + sysconfig + .device_pins + .values() + .filter(|pin| pin.attributes.io_wakeup.unwrap_or(false)) + // Multi-bonded pins are excluded everywhere else too, see `generate_pincm`. + .filter(|pin| !pin.name.contains('/')) + .map(|pin| pin.name.clone()) + .collect() +} + /// Whether the chip has an independent `VBAT` supply, and therefore a real backup power domain. /// /// The presence of a `VBAT` device pin is the authoritative answer: TRM ยง30 distinguishes the RTC @@ -944,24 +1001,13 @@ fn has_backup_domain( ensure!( vbat == backup_peripherals, - "{chip_name}: VBAT pin present is {vbat} but a peripheral in the backup power domain present is {backup_peripherals}" + "{chip_name}: VBAT pin present is {vbat} but a peripheral in the backup power domain \ + present is {backup_peripherals}" ); Ok(vbat) } -/// Device pins which have wakeup logic, and can therefore wake the device from SHUTDOWN. -fn generate_wakeup_pins(sysconfig: &SysconfigFile) -> BTreeSet { - sysconfig - .device_pins - .values() - .filter(|pin| pin.attributes.io_wakeup.unwrap_or(false)) - // Multi-bonded pins are excluded everywhere else too, see `generate_pincm`. - .filter(|pin| !pin.name.contains('/')) - .map(|pin| pin.name.clone()) - .collect() -} - /// Attach each peripheral to the interrupt it raises. /// /// A peripheral either owns an NVIC interrupt of its own or sits inside an `INT_GROUP` and shares @@ -1008,17 +1054,57 @@ fn apply_block_async(peripherals: &mut BTreeMap, svd: Option } } -fn convert_memory(memory: &PartMemory) -> anyhow::Result { - let kind = match memory.name.as_str() { - "FLASH" => MemoryKind::Flash, - "RAM" | "RAM_BANK" => MemoryKind::Ram, - name => bail!("Unknown memory partition `{name}`, cannot tell what kind of memory it is"), +/// Record how deep a sleep each PD1 peripheral keeps its configuration through. +/// +/// Only PD1 peripherals are automatically disabled by SYSCTL, so the question does not arise +/// anywhere else and the field stays `None`. + +/// Record what the datasheet's operating-mode table says about each peripheral. +/// +/// Retention applies only to PD1, since nothing else is automatically disabled by SYSCTL. Usability +/// applies to any peripheral the table has a row for. +fn apply_operating_modes( + modes: Option<&OperatingModes>, + peripherals: &mut BTreeMap, +) { + let Some(modes) = modes else { + return; }; - Ok(Memory { - name: memory.name.clone(), - kind, - length: memory.length, - address: memory.address, - }) + for (name, peripheral) in peripherals.iter_mut() { + if peripheral.power_domain == PowerDomain::Pd1 { + peripheral.retained_through = modes.retained_through.get(name).copied(); + } + + peripheral.usable_through = modes.usable_through.get(name).copied(); + } +} + +/// Record which timers keep receiving a clock in STANDBY1. +fn apply_standby1_timers( + family: &PartFamily, + peripherals: &mut BTreeMap, +) -> anyhow::Result<()> { + for timer in family.standby1_timers.iter() { + let peripheral = peripherals.get(timer).context(format!( + "{}: standby1_timers names {timer}, which is not a peripheral of this family", + family.family + ))?; + + ensure!( + peripheral.ty == PeripheralType::Tim, + "{}: standby1_timers names {timer}, which is not a timer", + family.family + ); + } + + for (name, peripheral) in peripherals.iter_mut() { + if peripheral.ty != PeripheralType::Tim { + continue; + } + + peripheral.clocked_in_standby1 = Some(family.standby1_timers.contains(name)); + } + + Ok(()) } diff --git a/mspm0-data-gen/src/main.rs b/mspm0-data-gen/src/main.rs index 1aa6ba7..77a1709 100644 --- a/mspm0-data-gen/src/main.rs +++ b/mspm0-data-gen/src/main.rs @@ -2,6 +2,7 @@ mod clock_tree; mod generate; mod header; mod int_group; +mod operating_modes; mod parts; mod perimap; mod svd; @@ -68,12 +69,20 @@ fn main() -> anyhow::Result<()> { stopwatch.section("Read interrupt group mappings"); let int_groups = int_group::Groups::parse()?; + let operating_modes = operating_modes::OperatingModes::parse()?; let parts = parts::PartsFile::read()?; // TODO: Expanded family names (ex. C110X -> C1103 & C1104) stopwatch.section("Generate data"); - generate::generate(&parts, &headers, &sysconfig, &svds, &int_groups)?; + generate::generate( + &parts, + &headers, + &sysconfig, + &svds, + &operating_modes, + &int_groups, + )?; stopwatch.stop(); diff --git a/mspm0-data-gen/src/operating_modes.rs b/mspm0-data-gen/src/operating_modes.rs new file mode 100644 index 0000000..9a3b603 --- /dev/null +++ b/mspm0-data-gen/src/operating_modes.rs @@ -0,0 +1,68 @@ +//! How deep a sleep each PD1 peripheral keeps its configuration through. +//! +//! SYSCTL forces every PD1 peripheral to a disabled state upon entry into STOP or STANDBY (TRM +//! ยง2.2.6.1, "Automatic Peripheral Disable in Low Power Modes"). *Most* of them retain their +//! configuration while disabled, and the TRM defers the specifics to the per-peripheral chapters, +//! so there is no single table in the TRM and no machine-readable source at all. +//! +//! The authoritative source is the "Supported Functionality by Operating Mode" table in each device +//! datasheet, whose legend is exact: `DIS` means "disabled (either clock or power gated) ... but the +//! function's configuration is retained", and `OFF` means "fully powered off ... and no +//! configuration information is retained". `tools/operating_modes.py` extracts that table, so the +//! values in `data/retention/` can be re-derived rather than taken on trust. +//! +//! That matters, because the set of peripherals for which the SDK ships +//! `DL_*_saveConfiguration`/`restoreConfiguration` (AES, MCAN, SPI, the timers, TRNG and UART) is +//! *not* the set which loses configuration. The datasheets mark PD1 UART and SPI as `DIS`, and mark +//! MATHACL โ€” which has no save/restore API โ€” as `OFF`. TI's own SysConfig points at the datasheet +//! too: the description of its `enableRetention` option reads "Some MSPM0G peripherals residing in +//! PD1 domain do not retain register contents when entering STOP or STANDBY modes. Please view the +//! datasheet for more details." +//! +//! The answer is per family and per instance rather than per IP block โ€” `AESADV` is `OFF` on +//! mspm0g518x but `DIS` on mspm0l211x, and on the G devices the PD1 timers are `OFF` while the PD1 +//! UARTs are `DIS` โ€” so `data/retention/.yaml` names instances explicitly rather than +//! matching patterns. `verify` reports both directions: a PD1 peripheral missing from its family's +//! file, and an entry naming a peripheral which is absent or not in PD1. + +use std::{collections::BTreeMap, fs}; + +use anyhow::Context; +use mspm0_data_types::PowerMode; +use serde::Deserialize; + +#[derive(Debug, Default, Deserialize)] +pub struct OperatingModes { + /// Peripheral instance name to the deepest mode its configuration survives. + /// + /// `Sleep` for the peripherals a datasheet marks `OFF` in the STOP/STANDBY columns, `Standby` + /// for those it marks `DIS`. + pub retained_through: BTreeMap, + + /// Peripheral instance name to the deepest mode it can be used in. + #[serde(default)] + pub usable_through: BTreeMap, +} + +impl OperatingModes { + pub fn parse() -> anyhow::Result> { + let mut map = BTreeMap::new(); + + for f in glob::glob("data/operating_modes/*.yaml")? { + let f = f?; + let content = fs::read_to_string(&f)?; + let modes = serde_yaml::from_str::(&content) + .context(format!("Error reading {}", f.display()))?; + + map.insert( + f.file_stem() + .context("File has no stem")? + .to_string_lossy() + .to_string(), + modes, + ); + } + + Ok(map) + } +} diff --git a/mspm0-data-gen/src/parts.rs b/mspm0-data-gen/src/parts.rs index 9b1c172..f0f8861 100644 --- a/mspm0-data-gen/src/parts.rs +++ b/mspm0-data-gen/src/parts.rs @@ -1,5 +1,6 @@ use std::fs; +use mspm0_data_types::PowerMode; use serde::Deserialize; #[derive(Debug, Deserialize)] @@ -39,6 +40,13 @@ pub struct PartFamily { /// Maximum frequency of ULPCLK, in Hz. pub max_ulpclk_hz: u32, + /// Timers which keep receiving ULPCLK or LFCLK in STANDBY1. + /// + /// Only the timers named here can wake the core from STANDBY1. This comes from the footnote to + /// the "Supported Functionality by Operating Mode" table in the device datasheet; there is no + /// machine-readable source for it. + pub standby1_timers: Vec, + /// Part numbers in this family. pub part_numbers: Vec, } @@ -65,4 +73,11 @@ pub struct PartMemory { /// Address of the memory. pub address: u32, + + /// The deepest mode through which the contents of this partition survive. + /// + /// Defaults by kind โ€” flash is non-volatile and SRAM survives to STANDBY โ€” so only the parts + /// with a second RAM bank need to say anything. + #[serde(default)] + pub retained_through: Option, } diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index c625e65..f5ce699 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -1,7 +1,7 @@ use std::{collections::HashSet, sync::LazyLock}; use anyhow::{bail, Context}; -use mspm0_data_types::{Chip, PowerDomain}; +use mspm0_data_types::{Chip, PeripheralType, PowerDomain}; use regex::Regex; pub fn verify(chip: &Chip, name: &str) -> anyhow::Result<()> { @@ -16,6 +16,8 @@ pub fn verify(chip: &Chip, name: &str) -> anyhow::Result<()> { // Low power data which is only as complete as the data sources block_async_known(chip, name)?; + standby1_timer_exists(chip, name)?; + retention_known(chip, name)?; // Power domains verify_aesadv_power_domain(chip, name)?; @@ -93,6 +95,65 @@ fn block_async_known(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// Report PD1 peripherals which do not say how deep a sleep they are retained through. +/// +/// Only PD1 peripherals are forced to a disabled state by SYSCTL, so they are the only ones for +/// which the question arises. Reported rather than fatal, because some of these gaps are genuine +/// disagreements between the datasheet and sysconfig rather than something this repo can fix. +fn retention_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + let unknown = chip + .peripherals + .values() + .filter(|peripheral| peripheral.power_domain == PowerDomain::Pd1) + .filter(|peripheral| peripheral.retained_through.is_none()) + .map(|peripheral| peripheral.name.as_str()) + .collect::>(); + + if !unknown.is_empty() { + bail!( + "{name}: PD1 peripherals missing from data/operating_modes/{}.yaml: {}", + chip.family, + unknown.join(", ") + ); + } + + Ok(()) +} + +/// Verify that every timer knows whether it is clocked in STANDBY1, and that at least one is. +/// +/// A family with no STANDBY1 timer at all would mean nothing can wake the core from the deepest +/// sleep, which is true of no MSPM0 device and therefore means the data is missing. +fn standby1_timer_exists(chip: &Chip, name: &str) -> anyhow::Result<()> { + let timers = chip + .peripherals + .values() + .filter(|peripheral| peripheral.ty == PeripheralType::Tim) + .collect::>(); + + if timers.is_empty() { + return Ok(()); + } + + for timer in timers.iter() { + if timer.clocked_in_standby1.is_none() { + bail!( + "{name}, {}: timer does not say whether it is clocked in STANDBY1", + timer.name + ); + } + } + + if !timers + .iter() + .any(|timer| timer.clocked_in_standby1 == Some(true)) + { + bail!("{name}: no timer is clocked in STANDBY1, so nothing can wake the core from it"); + } + + Ok(()) +} + fn pin_names(chip: &Chip, name: &str) -> anyhow::Result<()> { for peripheral in chip.peripherals.values() { for pin in peripheral.pins.iter() { diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 5100cb6..1199ea4 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -292,10 +292,62 @@ pub struct Peripheral { /// `false` does not mean the peripheral cannot raise a request: GPIO, the general purpose /// timers and the ADC all can, but have no per-instance mask and are gated only by /// `BLOCKASYNCALL`. `None` means no SVD is published for this family yet, so the answer is - /// unknown rather than negative. - /// This is deliberately per-instance: on mspm0g120x only `UC0`, `UC2`, `UC4`, `UC5` and - /// `UC9` have the bit despite every `UC` being the same IP. + /// unknown rather than negative. This is deliberately per-instance: on mspm0g120x only `UC0`, + /// `UC2`, `UC4`, `UC5` and `UC9` have the bit despite every `UC` being the same IP. pub block_async: Option, + + /// The deepest mode through which this peripheral keeps its configuration. + /// + /// [`PowerMode::Standby`] means the configuration survives everything short of SHUTDOWN; + /// [`PowerMode::Sleep`] means it is already gone in STOP, so the peripheral must be fully + /// reconfigured on wake, either by re-running its init or by saving and restoring its registers + /// around the low-power mode. + /// + /// Note that SYSCTL forces *every* PD1 peripheral to a disabled state on entry to STOP or + /// STANDBY (TRM ยง2.2.6.1), so a driver has to re-enable the peripheral on wake either way. That + /// is a property of PD1 rather than of any one peripheral, and is not encoded here. + /// + /// `None` when `power_domain` is not [`PowerDomain::Pd1`], where the question does not arise: + /// only PD1 peripherals are automatically disabled. `None` on a PD1 peripheral means the answer + /// is not known โ€” the vendor data does not say, or its sources disagree. + #[serde(skip_serializing_if = "Option::is_none")] + pub retained_through: Option, + + /// The deepest mode in which the datasheet says this peripheral can be used. + /// + /// Derived from the same table as [`Peripheral::retained_through`], reading `EN` and `OPT` as + /// usable and `DIS`, `OFF` and `NS` as not. `NS` matters here: it means the peripheral is not + /// automatically disabled but its use in that mode is unsupported, which a boolean would hide. + /// + /// `None` where the table cannot answer at the resolution it can be read โ€” either the row is + /// not uniform across the policies of a mode group, or it gives one value spanning every column. + #[serde(skip_serializing_if = "Option::is_none")] + pub usable_through: Option, + + /// Whether this timer keeps receiving ULPCLK or LFCLK in STANDBY1. + /// + /// STANDBY1 unclocks all of PD0 except a handful of general purpose timers, so these are the + /// only timers which can wake the core from the deepest sleep. `None` for peripherals which are + /// not timers. + #[serde(skip_serializing_if = "Option::is_none")] + pub clocked_in_standby1: Option, +} + +/// An operating mode, ordered from shallowest to deepest. +/// +/// Ordering is meaningful and is what makes retention comparable: a peripheral retained through +/// [`PowerMode::Standby`] is also retained in every shallower mode, so a consumer can ask +/// `retained_through >= PowerMode::Stop` rather than enumerating cases. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +pub enum PowerMode { + Run, + Sleep, + Stop, + Standby, + + /// Nothing but the `SHUTDNSTORE` bytes in SYSCTL survives this, so it appears only for + /// non-volatile memory. + Shutdown, } /// The interrupt raised by a peripheral. @@ -360,6 +412,13 @@ pub struct Memory { /// Address of the memory. pub address: u32, + + /// The deepest mode through which the contents of this partition survive. + /// + /// Flash is non-volatile, so it is [`PowerMode::Shutdown`]. SRAM is normally + /// [`PowerMode::Standby`], since only the `SHUTDNSTORE` bytes in SYSCTL survive SHUTDOWN. The + /// upper SRAM bank of the parts which have one is the exception. + pub retained_through: PowerMode, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 32413e2..06471b1 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -54,6 +54,52 @@ pub struct Peripheral { /// `BLOCKASYNCALL`. `None` means no SVD is published for this family yet, so the answer is /// unknown rather than negative. pub block_async: Option, + + /// The deepest mode through which this peripheral keeps its configuration. + /// + /// `Standby` means the configuration survives everything short of SHUTDOWN; `Sleep` means it is + /// already gone in STOP, so the peripheral must be fully reconfigured on wake. + /// + /// SYSCTL forces *every* PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM + /// ยง2.2.6.1), so a driver must re-enable it on wake either way; that is a property of PD1, not + /// of any one peripheral, and is not encoded here. + /// + /// `None` when `power_domain` is not `Pd1`, where the question does not arise. `None` on a PD1 + /// peripheral means the answer is not known โ€” the vendor data does not say, or sources disagree. + pub retained_through: Option, + + /// The deepest mode in which the datasheet says this peripheral can be used. + /// + /// From the same table as `retained_through`, reading `EN` and `OPT` as usable and `DIS`, `OFF` + /// and `NS` as not. `NS` matters here: it means the peripheral is not automatically disabled + /// but its use in that mode is unsupported, which a boolean would hide. + /// + /// `None` where the table cannot answer at the resolution it can be read. + pub usable_through: Option, + + /// Whether this timer keeps receiving ULPCLK or LFCLK in STANDBY1. + /// + /// STANDBY1 unclocks all of PD0 except a handful of general purpose timers, so these are the + /// only timers which can wake the core from the deepest sleep. `None` for peripherals which are + /// not timers. + pub clocked_in_standby1: Option, +} + +/// An operating mode, ordered from shallowest to deepest. +/// +/// Ordering is meaningful and is what makes retention comparable: something retained through +/// `PowerMode::Standby` is also retained in every shallower mode, so a consumer can ask +/// `retained_through >= PowerMode::Stop` rather than enumerating cases. +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)] +pub enum PowerMode { + Run, + Sleep, + Stop, + Standby, + + /// Nothing but the `SHUTDNSTORE` bytes in SYSCTL survives this, so it appears only for + /// non-volatile memory. + Shutdown, } /// The interrupt raised by a peripheral. @@ -82,6 +128,12 @@ pub struct MemoryRegion { /// Size of the region in bytes. pub size: u32, + + /// The deepest mode through which the contents of this region survive. + /// + /// Flash is non-volatile, so it is `Shutdown`. SRAM is normally `Standby`, since only the + /// `SHUTDNSTORE` bytes in SYSCTL survive SHUTDOWN. + pub retained_through: PowerMode, } #[derive(Debug, Eq, PartialEq, Clone, Copy)] diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 9dd9593..412a13c 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -1,6 +1,8 @@ use std::{collections::HashSet, sync::LazyLock}; -use mspm0_data_types::{Chip, MemoryKind, Package, Peripheral, PeripheralType, PowerDomain}; +use mspm0_data_types::{ + Chip, MemoryKind, Package, Peripheral, PeripheralType, PowerDomain, PowerMode, +}; use proc_macro2::{Literal, TokenStream}; use quote::quote; use regex::Regex; @@ -43,6 +45,7 @@ pub fn memory(chip: &Chip) -> TokenStream { let address = Literal::u32_unsuffixed(region.address); // Sizes are in KB in the chip data, but bytes are what consumers need. let size = Literal::u32_unsuffixed(region.length * 1024); + let retained_through = power_mode(region.retained_through); quote! { MemoryRegion { @@ -50,6 +53,7 @@ pub fn memory(chip: &Chip) -> TokenStream { kind: #kind, address: #address, size: #size, + retained_through: #retained_through, } } }); @@ -167,6 +171,16 @@ pub fn interrupt_groups(chip: &Chip) -> TokenStream { } } +fn power_mode(mode: PowerMode) -> TokenStream { + match mode { + PowerMode::Run => quote! { PowerMode::Run }, + PowerMode::Sleep => quote! { PowerMode::Sleep }, + PowerMode::Stop => quote! { PowerMode::Stop }, + PowerMode::Standby => quote! { PowerMode::Standby }, + PowerMode::Shutdown => quote! { PowerMode::Shutdown }, + } +} + fn skip_peripheral(ty: PeripheralType) -> bool { matches!(ty, PeripheralType::Unknown) } @@ -264,6 +278,27 @@ fn generate_peripheral( None => quote! { None }, }; + let retained_through = match peripheral.retained_through { + Some(mode) => { + let mode = power_mode(mode); + quote! { Some(#mode) } + } + None => quote! { None }, + }; + + let usable_through = match peripheral.usable_through { + Some(mode) => { + let mode = power_mode(mode); + quote! { Some(#mode) } + } + None => quote! { None }, + }; + + let clocked_in_standby1 = match peripheral.clocked_in_standby1 { + Some(clocked) => quote! { Some(#clocked) }, + None => quote! { None }, + }; + Some(quote! { Peripheral { name: #name, @@ -274,6 +309,9 @@ fn generate_peripheral( sys_fentries: #sys_fentries, interrupt: #interrupt, block_async: #block_async, + retained_through: #retained_through, + usable_through: #usable_through, + clocked_in_standby1: #clocked_in_standby1, } }) } diff --git a/tools/operating_modes.py b/tools/operating_modes.py new file mode 100644 index 0000000..a4a03f8 --- /dev/null +++ b/tools/operating_modes.py @@ -0,0 +1,358 @@ +"""Extract the "Supported Functionality by Operating Mode" table from MSPM0 datasheets. + +This is the source for the low power data which has no machine-readable origin: how deep a sleep each +PD1 peripheral keeps its configuration through, and how deep each peripheral stays usable. It writes +`data/operating_modes/.yaml`, so those values have a traceable path back to the datasheet +instead of being hand-transcribed. + +Like `transforms/`, this is an offline aid rather than part of the build. Re-run it after a data +source bump rather than editing the YAML by hand. + +Usage: + python tools/operating_modes.py [...] # print the table + python tools/operating_modes.py --write # regenerate data/operating_modes + +Requires `pdfplumber`. + +## How the table is read + +The table is read from its **ruling lines** rather than from extracted text. `pdftotext` flattens the +page to a character grid, which destroys the one thing that matters here: a value covering several +modes is printed once, and its position does not identify the columns it spans. Reconstructing spans +from text positions does not work -- neither a partition fitted to the header columns nor one fitted +to column anchors recovers the true boundaries, and both fail silently by producing a plausible +answer. + +`pdfplumber`'s lattice mode instead recovers the drawn cell rectangles. A merged cell is one +rectangle covering several grid positions, so the spans come from the geometry: + + row.cells[i] is None <=> column i is covered by a merge that began earlier + +which makes a forward fill from the last real cell exact. Row spans work the same way, so the group +column ("PD1 Peripherals", "Core Functions") needs no special handling, and labels wrapped over +several lines stay within their own cell. + +Column identity comes from the header row, which names the mode groups (RUN, SLEEP, STOP, STANDBY, +SHUTDOWN) as merged cells; the policies within a group follow in order. Not every device has every +policy -- the C and H series have no STOP1 -- so this is derived per file rather than assumed. +""" + +import glob +import json +import re +import sys +from pathlib import Path + +try: + import pdfplumber +except ImportError: # pragma: no cover + raise SystemExit("pdfplumber is required: python -m pip install pdfplumber") + +CAPTION = "Supported Functionality by Operating Mode" + +LATTICE = {"vertical_strategy": "lines", "horizontal_strategy": "lines"} + +#: Mode groups in order of increasing depth. The datasheet names these as merged header cells. +GROUPS = ["RUN", "SLEEP", "STOP", "STANDBY", "SHUTDOWN"] + +#: Per the table's legend: EN and OPT mean the function is usable, DIS/OFF/NS do not. `NS` is "not +#: automatically disabled in the specified mode, but its use is not supported", which is the case a +#: boolean would hide. Only OFF loses configuration. +USABLE = ("EN", "OPT") + +STATE = re.compile(r"^(EN|DIS|OPT|NS|OFF)\b") + +#: Row labels which do not use the peripheral's own name. +ALIASES = { + "MCAN": "CANFD", # the datasheets call the CAN-FD peripheral MCAN + "CRC-P": "CRCP", + "CRCP": "CRCP", + "USB2.0-FS": "USBFS", + "USB": "USBFS", + "I2S/TDM": "I2S", + "CPU": "CPUSS", # the "Core Functions" row covering the CPU subsystem +} + + +def read_tables(path): + """Yield (column_groups, rows) for each page of the operating-mode table. + + `column_groups` maps a column index to its mode group; `rows` is a list of (label, states) where + `states` is one entry per mode column, merges already resolved. + """ + with pdfplumber.open(str(path)) as pdf: + for page in pdf.pages: + if CAPTION not in (page.extract_text() or ""): + continue + + for table in page.find_tables(LATTICE): + if len(table.rows) < 6 or max(len(r.cells) for r in table.rows) < 10: + continue + data = table.extract() + + # The header row names each group in the column it starts at; the columns it spans + # follow as `None` cells. Anything past the last named group is SHUTDOWN, which some + # datasheets leave unlabelled. + header = [(c or "").replace("\n", "").strip() for c in data[0]] + column_group, current = {}, None + for i, cell in enumerate(header): + if i < 2: + continue # group label and row label columns + name = next((g for g in GROUPS if cell.upper().startswith(g)), None) + if name: + current = name + elif cell and current == "STANDBY": + current = "SHUTDOWN" # trailing unlabelled column + if current: + column_group[i] = current + # A trailing column after STANDBY with no header of its own is SHUTDOWN. + tail = max(column_group, default=1) + if column_group.get(tail) == "STANDBY" and tail == len(header) - 1: + column_group[tail] = "SHUTDOWN" + + rows = [] + for ri, row in enumerate(table.rows[1:], start=1): + # A label wrapped inside its cell breaks mid-word ("MATHA"/"CL", "SYSOS"/"C", + # "TIMG6/7"/"/12"), so the lines join with no separator. Comma-separated lists + # keep their commas, so nothing is lost by not inserting spaces. + label = (data[ri][1] or "").replace("\n", "").strip() + if not label: + continue + + states, held = {}, None + for ci in sorted(column_group): + if ci >= len(row.cells): + continue + if row.cells[ci] is not None: + # A real cell boundary starts a new value here. + text = (data[ri][ci] or "").replace("\n", " ").strip() + m = STATE.match(text) + held = m.group(1) if m else None + states[ci] = held + rows.append((label, states)) + + yield column_group, rows + + +def group_states(column_group, states): + """States seen in each mode group, as {group: set of states}.""" + out = {} + for ci, group in column_group.items(): + s = states.get(ci) + if s: + out.setdefault(group, set()).add(s) + return out + + +def retained_through(by_group): + """Deepest mode a PD1 peripheral's configuration survives, or None if the table is silent. + + `OFF` is the only state which loses configuration; `EN`, `OPT` and `DIS` all keep it. + """ + low = {s for g in ("STOP", "STANDBY") for s in by_group.get(g, set())} + if not low: + return None + return "Sleep" if "OFF" in low else "Standby" + + +def usable_through(by_group): + """Deepest mode group in which every policy is usable, or None if the table is silent.""" + deepest = None + for group in ("RUN", "SLEEP", "STOP", "STANDBY"): + seen = by_group.get(group) + if not seen: + break + if not seen <= set(USABLE): + break + deepest = group + return {"RUN": "Run", "SLEEP": "Sleep", "STOP": "Stop", "STANDBY": "Standby"}.get(deepest) + + +def expand(label): + """Instance names a row label refers to. + + Rows cover several instances at once, written as a shared prefix with the varying suffixes + separated by slashes or commas: `TIMG6/7/12`, `SPI0/1`, `UART0, UART1`, `GPIOA/B`, `MCAN0/1`. + """ + label = re.sub(r"\(\d\)", "", label).strip() + if not label: + return [] + + for alias, real in ALIASES.items(): + if label == alias or label.startswith(alias + "/") or label.startswith(alias + "0"): + label = label.replace(alias, real, 1) + break + + if "," in label: + return [p.strip() for p in label.split(",") if p.strip()] + + parts = label.split("/") + head = parts[0].strip() + # Non-greedy so bases containing digits work: "I2S" -> ("I2S", ""), "TIMG6" -> ("TIMG", "6"). + m = re.match(r"^([A-Za-z_][A-Za-z_0-9]*?)(\d*)$", head) + if not m: + return [head] if head else [] + + base, first = m.group(1), m.group(2) + names = [head] + for suffix in parts[1:]: + suffix = suffix.strip() + if suffix.isdigit(): + names.append(base + suffix) # TIMG6/7 -> TIMG7 + elif re.match(r"^[A-Za-z]$", suffix) and head[-1:].isalpha(): + names.append(head[:-1] + suffix) # GPIOA/B -> GPIOB + if not first: + names.append(base + "0") # CRCP -> CRCP0 + return names + + +HEADER = """\ +# How deep a sleep each peripheral is retained through and stays usable in. +# +# GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by +# Operating Mode", of the {ds} datasheet. Re-run the tool rather than editing by hand. +# +# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL +# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a +# driver must re-enable any of them on wake regardless; what differs is whether the rest of the +# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone +# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals +# appear: nothing else is automatically disabled, so the question does not arise. +# +# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and +# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the +# specified mode, but its use is not supported" -- the case a boolean would hide. +""" + + +def load_overrides(): + """Values from `data/operating_mode_overrides.yaml`, where a better source beats the table. + + Parsed without a YAML library to keep this script dependency-light; the file is a fixed + two-level shape (family -> field -> name: value). + """ + path = Path("data/operating_mode_overrides.yaml") + if not path.exists(): + return {} + + out, family, field = {}, None, None + for line in path.read_text(encoding="utf-8").splitlines(): + if not line.strip() or line.lstrip().startswith("#"): + continue + indent = len(line) - len(line.lstrip()) + key = line.strip().rstrip(":") + if indent == 0: + family, field = key, None + elif indent == 2: + field = key + elif indent == 4 and family and field: + name, _, value = key.partition(":") + out.setdefault(family, {}).setdefault(field, {})[name.strip()] = value.strip() + return out + + +def write(datasheets): + """Regenerate data/operating_modes/*.yaml. Returns the number of problems reported.""" + families = re.findall( + r"^- family: (\S+)\n(?:.*\n)*? datasheet_url: \S+/(\S+)$", + Path("data/parts.yaml").read_text(encoding="utf-8"), + re.M, + ) + + pd1, known = {}, {} + for f in glob.glob("build/data/*.json"): + chip = json.loads(Path(f).read_text(encoding="utf-8")) + pd1.setdefault(chip["family"], set()).update( + n for n, p in chip["peripherals"].items() if p["power_domain"] == "Pd1" + ) + known.setdefault(chip["family"], set()).update(chip["peripherals"]) + if not pd1: + raise SystemExit("build/data is empty -- run ./d gen first") + + Path("data/operating_modes").mkdir(parents=True, exist_ok=True) + overrides = load_overrides() + problems = 0 + + for family, gpn in families: + pdf = Path(datasheets) / f"{gpn}_datasheet.pdf" + if not pdf.exists(): + print(f"{family}: no datasheet for {gpn} in {datasheets}") + problems += 1 + continue + + retained, usable = {}, {} + for column_group, rows in read_tables(pdf): + for label, states in rows: + by_group = group_states(column_group, states) + r, u = retained_through(by_group), usable_through(by_group) + for name in expand(label): + if name not in known.get(family, ()): + continue + if r and name in pd1.get(family, ()): + retained[name] = r + if u: + usable[name] = u + + # A better source than the table, where one exists. + overridden = overrides.get(family, {}) + for name, value in overridden.get("retained_through", {}).items(): + retained[name] = value + for name, value in overridden.get("usable_through", {}).items(): + usable[name] = value + + # Entries the table cannot answer are reasoned out elsewhere; keep whatever is already + # recorded rather than dropping it. + path = Path(f"data/operating_modes/{family}.yaml") + existing = ( + dict(re.findall(r"^ (\w+): (\w+)$", path.read_text(encoding="utf-8"), re.M)) + if path.exists() else {} + ) + missing = sorted(set(pd1.get(family, ())) - set(retained)) + carried = {n: existing[n] for n in missing if n in existing} + for name in missing: + if name not in carried: + print(f"{family}: {name} is in PD1 but no row of the {gpn} table covers it") + problems += 1 + + body = "".join(f" {n}: {retained[n]}\n" for n in sorted(retained)) + if carried: + body += ( + "\n # Not derivable from the table: these rows give one value spanning every mode,\n" + " # which says the peripheral is available but not whether its configuration\n" + " # survives. Entered by hand and preserved across regeneration.\n" + + "".join(f" {n}: {carried[n]}\n" for n in sorted(carried)) + ) + + with open(path, "w", encoding="utf-8", newline="\n") as out: + out.write( + HEADER.format(ds=gpn.upper()) + + "\nretained_through:\n" + body + + "\nusable_through:\n" + + "".join(f" {n}: {usable[n]}\n" for n in sorted(usable)) + ) + print(f"{family}: {len(retained)} retained, {len(usable)} usable, from {pdf.name}") + + return problems + + +def main(argv): + if "--write" in argv: + i = argv.index("--write") + raise SystemExit(1 if write(argv[i + 1] if i + 1 < len(argv) else ".") else 0) + + paths = [Path(a) for a in argv if not a.startswith("--")] + if not paths: + raise SystemExit(__doc__) + + for path in paths: + print(f"########## {path.name}") + for column_group, rows in read_tables(path): + columns = sorted(column_group) + print(" " + " ".join(f"{column_group[c][:4]:<5}" for c in columns)) + for label, states in rows: + print(f" {' '.join(f'{states.get(c) or chr(45):<5}' for c in columns)} {label}") + print() + + +if __name__ == "__main__": + main(sys.argv[1:]) From f91cc2cb2dda70785d40b9642c1ce50569351bd6 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Wed, 29 Jul 2026 01:20:22 +0100 Subject: [PATCH 009/134] Simplify comments --- mspm0-data-gen/src/generate.rs | 5 ---- mspm0-data-gen/src/operating_modes.rs | 33 ++++----------------------- mspm0-data-gen/src/parts.rs | 7 +----- mspm0-data-gen/src/verify.rs | 6 ++--- mspm0-data-types/src/lib.rs | 9 ++------ mspm0-metapac-gen/res/metadata.rs | 6 ++--- 6 files changed, 13 insertions(+), 53 deletions(-) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 407dfc8..1fba7e2 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -1054,11 +1054,6 @@ fn apply_block_async(peripherals: &mut BTreeMap, svd: Option } } -/// Record how deep a sleep each PD1 peripheral keeps its configuration through. -/// -/// Only PD1 peripherals are automatically disabled by SYSCTL, so the question does not arise -/// anywhere else and the field stays `None`. - /// Record what the datasheet's operating-mode table says about each peripheral. /// /// Retention applies only to PD1, since nothing else is automatically disabled by SYSCTL. Usability diff --git a/mspm0-data-gen/src/operating_modes.rs b/mspm0-data-gen/src/operating_modes.rs index 9a3b603..a3c283c 100644 --- a/mspm0-data-gen/src/operating_modes.rs +++ b/mspm0-data-gen/src/operating_modes.rs @@ -1,29 +1,9 @@ //! How deep a sleep each PD1 peripheral keeps its configuration through. //! -//! SYSCTL forces every PD1 peripheral to a disabled state upon entry into STOP or STANDBY (TRM -//! ยง2.2.6.1, "Automatic Peripheral Disable in Low Power Modes"). *Most* of them retain their -//! configuration while disabled, and the TRM defers the specifics to the per-peripheral chapters, -//! so there is no single table in the TRM and no machine-readable source at all. -//! -//! The authoritative source is the "Supported Functionality by Operating Mode" table in each device -//! datasheet, whose legend is exact: `DIS` means "disabled (either clock or power gated) ... but the -//! function's configuration is retained", and `OFF` means "fully powered off ... and no -//! configuration information is retained". `tools/operating_modes.py` extracts that table, so the -//! values in `data/retention/` can be re-derived rather than taken on trust. -//! -//! That matters, because the set of peripherals for which the SDK ships -//! `DL_*_saveConfiguration`/`restoreConfiguration` (AES, MCAN, SPI, the timers, TRNG and UART) is -//! *not* the set which loses configuration. The datasheets mark PD1 UART and SPI as `DIS`, and mark -//! MATHACL โ€” which has no save/restore API โ€” as `OFF`. TI's own SysConfig points at the datasheet -//! too: the description of its `enableRetention` option reads "Some MSPM0G peripherals residing in -//! PD1 domain do not retain register contents when entering STOP or STANDBY modes. Please view the -//! datasheet for more details." -//! -//! The answer is per family and per instance rather than per IP block โ€” `AESADV` is `OFF` on -//! mspm0g518x but `DIS` on mspm0l211x, and on the G devices the PD1 timers are `OFF` while the PD1 -//! UARTs are `DIS` โ€” so `data/retention/.yaml` names instances explicitly rather than -//! matching patterns. `verify` reports both directions: a PD1 peripheral missing from its family's -//! file, and an entry naming a peripheral which is absent or not in PD1. +//! SYSCTL forces every PD1 peripheral to a disabled state upon entry into STOP or STANDBY, but only +//! some of the peripherals retain their configuration while disabled. +//! This information isn't available in any machine-readable format, so it's extracted from the +//! datasheet's "Supported Functionality by Operating Mode" table using `tools/operating_modes.py`. use std::{collections::BTreeMap, fs}; @@ -33,10 +13,7 @@ use serde::Deserialize; #[derive(Debug, Default, Deserialize)] pub struct OperatingModes { - /// Peripheral instance name to the deepest mode its configuration survives. - /// - /// `Sleep` for the peripherals a datasheet marks `OFF` in the STOP/STANDBY columns, `Standby` - /// for those it marks `DIS`. + /// Maps peripheral names to the deepest mode its configuration survives. pub retained_through: BTreeMap, /// Peripheral instance name to the deepest mode it can be used in. diff --git a/mspm0-data-gen/src/parts.rs b/mspm0-data-gen/src/parts.rs index f0f8861..3f0ea21 100644 --- a/mspm0-data-gen/src/parts.rs +++ b/mspm0-data-gen/src/parts.rs @@ -42,9 +42,7 @@ pub struct PartFamily { /// Timers which keep receiving ULPCLK or LFCLK in STANDBY1. /// - /// Only the timers named here can wake the core from STANDBY1. This comes from the footnote to - /// the "Supported Functionality by Operating Mode" table in the device datasheet; there is no - /// machine-readable source for it. + /// Only the timers named here can wake the core from STANDBY1. pub standby1_timers: Vec, /// Part numbers in this family. @@ -75,9 +73,6 @@ pub struct PartMemory { pub address: u32, /// The deepest mode through which the contents of this partition survive. - /// - /// Defaults by kind โ€” flash is non-volatile and SRAM survives to STANDBY โ€” so only the parts - /// with a second RAM bank need to say anything. #[serde(default)] pub retained_through: Option, } diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index f5ce699..1988b0f 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -98,8 +98,8 @@ fn block_async_known(chip: &Chip, name: &str) -> anyhow::Result<()> { /// Report PD1 peripherals which do not say how deep a sleep they are retained through. /// /// Only PD1 peripherals are forced to a disabled state by SYSCTL, so they are the only ones for -/// which the question arises. Reported rather than fatal, because some of these gaps are genuine -/// disagreements between the datasheet and sysconfig rather than something this repo can fix. +/// which the question arises. Not fatal, because some of these gaps are genuine disagreements +/// between the datasheet and sysconfig rather than something we can fix. fn retention_known(chip: &Chip, name: &str) -> anyhow::Result<()> { let unknown = chip .peripherals @@ -123,7 +123,7 @@ fn retention_known(chip: &Chip, name: &str) -> anyhow::Result<()> { /// Verify that every timer knows whether it is clocked in STANDBY1, and that at least one is. /// /// A family with no STANDBY1 timer at all would mean nothing can wake the core from the deepest -/// sleep, which is true of no MSPM0 device and therefore means the data is missing. +/// sleep, which is true of no MSPM0 device and means the data is missing. fn standby1_timer_exists(chip: &Chip, name: &str) -> anyhow::Result<()> { let timers = chip .peripherals diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 1199ea4..f968415 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -277,8 +277,6 @@ pub struct Peripheral { pub sys_fentries: Option, /// The interrupt raised by this peripheral. - /// - /// `None` if the peripheral has no interrupt of its own. #[serde(skip_serializing_if = "Option::is_none")] pub interrupt: Option, @@ -292,8 +290,7 @@ pub struct Peripheral { /// `false` does not mean the peripheral cannot raise a request: GPIO, the general purpose /// timers and the ADC all can, but have no per-instance mask and are gated only by /// `BLOCKASYNCALL`. `None` means no SVD is published for this family yet, so the answer is - /// unknown rather than negative. This is deliberately per-instance: on mspm0g120x only `UC0`, - /// `UC2`, `UC4`, `UC5` and `UC9` have the bit despite every `UC` being the same IP. + /// unknown rather than negative. pub block_async: Option, /// The deepest mode through which this peripheral keeps its configuration. @@ -307,9 +304,7 @@ pub struct Peripheral { /// STANDBY (TRM ยง2.2.6.1), so a driver has to re-enable the peripheral on wake either way. That /// is a property of PD1 rather than of any one peripheral, and is not encoded here. /// - /// `None` when `power_domain` is not [`PowerDomain::Pd1`], where the question does not arise: - /// only PD1 peripherals are automatically disabled. `None` on a PD1 peripheral means the answer - /// is not known โ€” the vendor data does not say, or its sources disagree. + /// `None` when `power_domain` is not [`PowerDomain::Pd1`]. #[serde(skip_serializing_if = "Option::is_none")] pub retained_through: Option, diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 06471b1..82d7521 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -64,15 +64,13 @@ pub struct Peripheral { /// ยง2.2.6.1), so a driver must re-enable it on wake either way; that is a property of PD1, not /// of any one peripheral, and is not encoded here. /// - /// `None` when `power_domain` is not `Pd1`, where the question does not arise. `None` on a PD1 - /// peripheral means the answer is not known โ€” the vendor data does not say, or sources disagree. + /// `None` when `power_domain` is not `Pd1`. pub retained_through: Option, /// The deepest mode in which the datasheet says this peripheral can be used. /// /// From the same table as `retained_through`, reading `EN` and `OPT` as usable and `DIS`, `OFF` - /// and `NS` as not. `NS` matters here: it means the peripheral is not automatically disabled - /// but its use in that mode is unsupported, which a boolean would hide. + /// and `NS` as not. /// /// `None` where the table cannot answer at the resolution it can be read. pub usable_through: Option, From f01abfa52a65e865d5e3b5cd32bacebba71e6267 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Wed, 29 Jul 2026 15:29:33 +0100 Subject: [PATCH 010/134] Report every failing verify check --- mspm0-data-gen/src/generate.rs | 6 ++-- mspm0-data-gen/src/verify.rs | 56 ++++++++++++++++++---------------- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 1fba7e2..ca9f56b 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -143,9 +143,9 @@ fn generate_family( backup_domain, }; - if let Err(err) = verify::verify(&chip, &part_number.name) { + for err in verify::verify(&chip, &part_number.name) { eprintln!("{err}"); - }; + } let data = serde_json::to_string_pretty(&chip) .context(format!("Serializing chip {}", part_number.name))?; @@ -978,7 +978,7 @@ fn generate_wakeup_pins(sysconfig: &SysconfigFile) -> BTreeSet { /// Whether the chip has an independent `VBAT` supply, and therefore a real backup power domain. /// -/// The presence of a `VBAT` device pin is the authoritative answer: TRM ยง30 distinguishes the RTC +/// The presence of a `VBAT` device pin is the authoritative answer: TRM ร‚ยง30 distinguishes the RTC /// variants by exactly this ("In devices for which the LFSS is powered by an independent VBAT supply /// pin to support the backup-battery power domain (PDB), the RTC variant has extended features and /// is referred to as RTC_A"). diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 1988b0f..649cf3e 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -4,32 +4,36 @@ use anyhow::{bail, Context}; use mspm0_data_types::{Chip, PeripheralType, PowerDomain}; use regex::Regex; -pub fn verify(chip: &Chip, name: &str) -> anyhow::Result<()> { - core_peripherals(chip, name)?; - - pin_names(chip, name)?; - - gpio_no_duplicates(chip, name)?; - - // Peripherals which don't actually exist - no_gpamp_c110x_l151x(chip, name)?; - - // Low power data which is only as complete as the data sources - block_async_known(chip, name)?; - standby1_timer_exists(chip, name)?; - retention_known(chip, name)?; - - // Power domains - verify_aesadv_power_domain(chip, name)?; - verify_cpuss_power_domain(chip, name)?; - verify_crc_power_domain(chip, name)?; - verify_gpamp_power_domain(chip, name)?; - verify_spi_power_domain(chip, name)?; - // Timers may be in either power domain: add checks if something is wrong - verify_trng_power_domain(chip, name)?; - // UART may be in either power domain: add checks if something is wrong - - Ok(()) +/// Run every check, returning one error per failure. +/// +/// Every check runs: a failing one must not mask those after it, or the families with a gap in their +/// data sources would go otherwise unverified. +pub fn verify(chip: &Chip, name: &str) -> Vec { + const CHECKS: &[fn(&Chip, &str) -> anyhow::Result<()>] = &[ + core_peripherals, + pin_names, + gpio_no_duplicates, + // Peripherals which don't actually exist + no_gpamp_c110x_l151x, + // Low power data which is only as complete as the data sources + block_async_known, + standby1_timer_exists, + retention_known, + // Power domains + verify_aesadv_power_domain, + verify_cpuss_power_domain, + verify_crc_power_domain, + verify_gpamp_power_domain, + verify_spi_power_domain, + // Timers may be in either power domain: add checks if something is wrong + verify_trng_power_domain, + // UART may be in either power domain: add checks if something is wrong + ]; + + CHECKS + .iter() + .filter_map(|check| check(chip, name).err()) + .collect() } /// Verify all core peripherals are present. From f51c3624320352d83911adf74df625b80073e44a Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Wed, 29 Jul 2026 15:29:56 +0100 Subject: [PATCH 011/134] Distinguish unknown wake-capable pins from none --- mspm0-data-gen/src/generate.rs | 25 +++++++++++++++++++------ mspm0-data-gen/src/verify.rs | 14 ++++++++++++++ mspm0-data-types/src/lib.rs | 6 +++++- mspm0-metapac-gen/res/metadata.rs | 5 ++++- mspm0-metapac-gen/src/metadata.rs | 8 +++++++- 5 files changed, 49 insertions(+), 9 deletions(-) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index ca9f56b..705ae46 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -965,20 +965,33 @@ fn convert_memory(memory: &PartMemory) -> anyhow::Result { } /// Device pins which have wakeup logic, and can therefore wake the device from SHUTDOWN. -fn generate_wakeup_pins(sysconfig: &SysconfigFile) -> BTreeSet { - sysconfig +/// +/// `None` when the family's sysconfig omits `io_wakeup` entirely, which is a gap in the vendor data +/// rather than a device with no wake-capable pin. +fn generate_wakeup_pins(sysconfig: &SysconfigFile) -> Option> { + let pins = sysconfig .device_pins .values() - .filter(|pin| pin.attributes.io_wakeup.unwrap_or(false)) // Multi-bonded pins are excluded everywhere else too, see `generate_pincm`. .filter(|pin| !pin.name.contains('/')) - .map(|pin| pin.name.clone()) - .collect() + .collect::>(); + + // Missing and `false` are different answers, and only GPIO pins carry the attribute at all. + if pins.iter().all(|pin| pin.attributes.io_wakeup.is_none()) { + return None; + } + + Some( + pins.iter() + .filter(|pin| pin.attributes.io_wakeup.unwrap_or(false)) + .map(|pin| pin.name.clone()) + .collect(), + ) } /// Whether the chip has an independent `VBAT` supply, and therefore a real backup power domain. /// -/// The presence of a `VBAT` device pin is the authoritative answer: TRM ร‚ยง30 distinguishes the RTC +/// The presence of a `VBAT` device pin is the authoritative answer: TRM ยง30 distinguishes the RTC /// variants by exactly this ("In devices for which the LFSS is powered by an independent VBAT supply /// pin to support the backup-battery power domain (PDB), the RTC variant has extended features and /// is referred to as RTC_A"). diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 649cf3e..9c3c460 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -19,6 +19,7 @@ pub fn verify(chip: &Chip, name: &str) -> Vec { block_async_known, standby1_timer_exists, retention_known, + wakeup_pins_known, // Power domains verify_aesadv_power_domain, verify_cpuss_power_domain, @@ -124,6 +125,19 @@ fn retention_known(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// Report families whose sysconfig does not describe pin wakeup logic, which is not the same as the +/// family having no wake-capable pin. +fn wakeup_pins_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + if chip.wakeup_pins.is_none() { + bail!( + "{name}: sysconfig has no io_wakeup attribute for family {}, so wake-capable pins are unknown", + chip.family + ); + } + + Ok(()) +} + /// Verify that every timer knows whether it is clocked in STANDBY1, and that at least one is. /// /// A family with no STANDBY1 timer at all would mean nothing can wake the core from the deepest diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index f968415..9f712d8 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -38,7 +38,11 @@ pub struct Chip { /// /// The `FASTWAKE` mechanism, which wakes the device from STOP and STANDBY, works on any GPIO /// pin and is therefore not described here. - pub wakeup_pins: BTreeSet, + /// + /// `None` when sysconfig does not describe wakeup logic for this family, which is not the same as + /// the family having no wake-capable pin. + #[serde(skip_serializing_if = "Option::is_none")] + pub wakeup_pins: Option>, /// The peripherals available on the chip. pub peripherals: BTreeMap, diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 82d7521..c17a27d 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -149,7 +149,10 @@ pub struct Pin { /// /// This is not the same as the pin being able to wake the device at all: `FASTWAKE` works on /// any GPIO pin, but only down to STANDBY. - pub wakeup: bool, + /// + /// `None` when the vendor data does not describe wakeup logic for this chip, which is not the + /// same as `Some(false)`. + pub wakeup: Option, } #[derive(Debug, Eq, PartialEq, Clone, Copy)] diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 412a13c..2671817 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -27,7 +27,13 @@ pub fn pins(chip: &Chip, package: &Package) -> TokenStream { .get(signal) .expect("Signal did not have an iomux pincm entry"); let pincm = Literal::u8_suffixed(*pincm as u8); - let wakeup = chip.wakeup_pins.contains(signal); + let wakeup = match &chip.wakeup_pins { + Some(wakeup_pins) => { + let wakeup = wakeup_pins.contains(signal); + quote! { Some(#wakeup) } + } + None => quote! { None }, + }; Some(quote! { Pin { pin: #signal, pincm: #pincm, wakeup: #wakeup } }) }); From a43f33363b8f3fd1cf3b7558bd593a3bf4d54bae Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Wed, 29 Jul 2026 15:30:06 +0100 Subject: [PATCH 012/134] Read retention separately for STOP and STANDBY --- data/operating_modes/mspm0c1105_c1106.yaml | 8 +++++--- data/operating_modes/mspm0c110x.yaml | 8 +++++--- data/operating_modes/mspm0g110x.yaml | 8 +++++--- data/operating_modes/mspm0g150x.yaml | 8 +++++--- data/operating_modes/mspm0g151x.yaml | 8 +++++--- data/operating_modes/mspm0g310x.yaml | 8 +++++--- data/operating_modes/mspm0g350x.yaml | 8 +++++--- data/operating_modes/mspm0g351x.yaml | 8 +++++--- data/operating_modes/mspm0g518x.yaml | 8 +++++--- data/operating_modes/mspm0h321x.yaml | 8 +++++--- data/operating_modes/mspm0l110x.yaml | 8 +++++--- data/operating_modes/mspm0l112x.yaml | 8 +++++--- data/operating_modes/mspm0l122x.yaml | 8 +++++--- data/operating_modes/mspm0l130x.yaml | 8 +++++--- data/operating_modes/mspm0l134x.yaml | 8 +++++--- data/operating_modes/mspm0l211x.yaml | 8 +++++--- data/operating_modes/mspm0l222x.yaml | 8 +++++--- data/operating_modes/msps003fx.yaml | 8 +++++--- tools/operating_modes.py | 24 +++++++++++++++------- 19 files changed, 107 insertions(+), 61 deletions(-) diff --git a/data/operating_modes/mspm0c1105_c1106.yaml b/data/operating_modes/mspm0c1105_c1106.yaml index e0c98ca..2f3c4be 100644 --- a/data/operating_modes/mspm0c1105_c1106.yaml +++ b/data/operating_modes/mspm0c1105_c1106.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/mspm0c110x.yaml b/data/operating_modes/mspm0c110x.yaml index 36001a2..236b324 100644 --- a/data/operating_modes/mspm0c110x.yaml +++ b/data/operating_modes/mspm0c110x.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/mspm0g110x.yaml b/data/operating_modes/mspm0g110x.yaml index 2d5e637..b370cfe 100644 --- a/data/operating_modes/mspm0g110x.yaml +++ b/data/operating_modes/mspm0g110x.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/mspm0g150x.yaml b/data/operating_modes/mspm0g150x.yaml index cec3422..c147a16 100644 --- a/data/operating_modes/mspm0g150x.yaml +++ b/data/operating_modes/mspm0g150x.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/mspm0g151x.yaml b/data/operating_modes/mspm0g151x.yaml index b246d0f..ff13264 100644 --- a/data/operating_modes/mspm0g151x.yaml +++ b/data/operating_modes/mspm0g151x.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/mspm0g310x.yaml b/data/operating_modes/mspm0g310x.yaml index 883e2c5..d7352d3 100644 --- a/data/operating_modes/mspm0g310x.yaml +++ b/data/operating_modes/mspm0g310x.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/mspm0g350x.yaml b/data/operating_modes/mspm0g350x.yaml index 9842ec8..006dc37 100644 --- a/data/operating_modes/mspm0g350x.yaml +++ b/data/operating_modes/mspm0g350x.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/mspm0g351x.yaml b/data/operating_modes/mspm0g351x.yaml index 72e76b1..3cd54e1 100644 --- a/data/operating_modes/mspm0g351x.yaml +++ b/data/operating_modes/mspm0g351x.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/mspm0g518x.yaml b/data/operating_modes/mspm0g518x.yaml index 3afdc45..e1092e9 100644 --- a/data/operating_modes/mspm0g518x.yaml +++ b/data/operating_modes/mspm0g518x.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/mspm0h321x.yaml b/data/operating_modes/mspm0h321x.yaml index 124dbcc..9633443 100644 --- a/data/operating_modes/mspm0h321x.yaml +++ b/data/operating_modes/mspm0h321x.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/mspm0l110x.yaml b/data/operating_modes/mspm0l110x.yaml index f317935..0b8c23c 100644 --- a/data/operating_modes/mspm0l110x.yaml +++ b/data/operating_modes/mspm0l110x.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/mspm0l112x.yaml b/data/operating_modes/mspm0l112x.yaml index 8d3164e..b25ec1d 100644 --- a/data/operating_modes/mspm0l112x.yaml +++ b/data/operating_modes/mspm0l112x.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/mspm0l122x.yaml b/data/operating_modes/mspm0l122x.yaml index 3bd51f6..8af1af3 100644 --- a/data/operating_modes/mspm0l122x.yaml +++ b/data/operating_modes/mspm0l122x.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/mspm0l130x.yaml b/data/operating_modes/mspm0l130x.yaml index b0714f3..a051c74 100644 --- a/data/operating_modes/mspm0l130x.yaml +++ b/data/operating_modes/mspm0l130x.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/mspm0l134x.yaml b/data/operating_modes/mspm0l134x.yaml index ce11146..4eea4dd 100644 --- a/data/operating_modes/mspm0l134x.yaml +++ b/data/operating_modes/mspm0l134x.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/mspm0l211x.yaml b/data/operating_modes/mspm0l211x.yaml index 0e47e7f..36a938e 100644 --- a/data/operating_modes/mspm0l211x.yaml +++ b/data/operating_modes/mspm0l211x.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/mspm0l222x.yaml b/data/operating_modes/mspm0l222x.yaml index 0688d70..f403183 100644 --- a/data/operating_modes/mspm0l222x.yaml +++ b/data/operating_modes/mspm0l222x.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/data/operating_modes/msps003fx.yaml b/data/operating_modes/msps003fx.yaml index 36001a2..236b324 100644 --- a/data/operating_modes/msps003fx.yaml +++ b/data/operating_modes/msps003fx.yaml @@ -6,9 +6,11 @@ # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups +# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` +# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout +# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing +# else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the diff --git a/tools/operating_modes.py b/tools/operating_modes.py index a4a03f8..1a70175 100644 --- a/tools/operating_modes.py +++ b/tools/operating_modes.py @@ -147,12 +147,22 @@ def group_states(column_group, states): def retained_through(by_group): """Deepest mode a PD1 peripheral's configuration survives, or None if the table is silent. - `OFF` is the only state which loses configuration; `EN`, `OPT` and `DIS` all keep it. + `OFF` is the only state which loses configuration; `EN`, `OPT` and `DIS` all keep it. STOP and + STANDBY are read separately, so losing it only in STANDBY reports `Stop` rather than `Sleep`. + + A blank group is answered conservatively: retention in a deeper mode implies it in a shallower one, + but not the reverse. """ - low = {s for g in ("STOP", "STANDBY") for s in by_group.get(g, set())} - if not low: + stop = by_group.get("STOP", set()) + standby = by_group.get("STANDBY", set()) + if not stop and not standby: return None - return "Sleep" if "OFF" in low else "Standby" + + if "OFF" in stop: + return "Sleep" + if "OFF" in standby: + return "Stop" if stop else "Sleep" + return "Standby" if standby else "Stop" def usable_through(by_group): @@ -215,9 +225,9 @@ def expand(label): # `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL # forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a # driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it (`Sleep` here, meaning it is already gone -# in STOP); `DIS` keeps it (`Standby`, surviving everything short of SHUTDOWN). Only PD1 peripherals -# appear: nothing else is automatically disabled, so the question does not arise. +# configuration is still there. `OFF` loses it: under STOP that is `Sleep`, under STANDBY alone +# `Stop`, and `EN`/`OPT`/`DIS` throughout is `Standby`, surviving everything short of SHUTDOWN. Only +# PD1 peripherals appear: nothing else is automatically disabled, so the question does not arise. # # `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and # `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the From ba68745da0b3810593232fa422d4f4444a2e4ae0 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Wed, 29 Jul 2026 17:19:40 +0100 Subject: [PATCH 013/134] Simplify comments some more --- data/operating_modes/mspm0c1105_c1106.yaml | 18 +++-------- data/operating_modes/mspm0c110x.yaml | 18 +++-------- data/operating_modes/mspm0g110x.yaml | 18 +++-------- data/operating_modes/mspm0g150x.yaml | 18 +++-------- data/operating_modes/mspm0g151x.yaml | 18 +++-------- data/operating_modes/mspm0g310x.yaml | 18 +++-------- data/operating_modes/mspm0g350x.yaml | 18 +++-------- data/operating_modes/mspm0g351x.yaml | 18 +++-------- data/operating_modes/mspm0g518x.yaml | 18 +++-------- data/operating_modes/mspm0h321x.yaml | 18 +++-------- data/operating_modes/mspm0l110x.yaml | 18 +++-------- data/operating_modes/mspm0l112x.yaml | 18 +++-------- data/operating_modes/mspm0l122x.yaml | 18 +++-------- data/operating_modes/mspm0l130x.yaml | 18 +++-------- data/operating_modes/mspm0l134x.yaml | 18 +++-------- data/operating_modes/mspm0l211x.yaml | 18 +++-------- data/operating_modes/mspm0l222x.yaml | 18 +++-------- data/operating_modes/msps003fx.yaml | 18 +++-------- mspm0-data-gen/src/generate.rs | 9 ++---- mspm0-data-gen/src/header.rs | 7 ++-- mspm0-data-gen/src/svd.rs | 15 +++------ mspm0-data-gen/src/verify.rs | 15 ++------- mspm0-data-types/src/lib.rs | 28 +++++----------- mspm0-metapac-gen/res/metadata.rs | 6 ++-- mspm0-metapac-gen/src/main.rs | 3 +- mspm0-metapac-gen/src/metadata.rs | 3 +- tools/operating_modes.py | 37 ++++------------------ 27 files changed, 101 insertions(+), 346 deletions(-) diff --git a/data/operating_modes/mspm0c1105_c1106.yaml b/data/operating_modes/mspm0c1105_c1106.yaml index 2f3c4be..f52880a 100644 --- a/data/operating_modes/mspm0c1105_c1106.yaml +++ b/data/operating_modes/mspm0c1105_c1106.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0C1106 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0C1106 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: CPUSS: Standby diff --git a/data/operating_modes/mspm0c110x.yaml b/data/operating_modes/mspm0c110x.yaml index 236b324..a15cf6a 100644 --- a/data/operating_modes/mspm0c110x.yaml +++ b/data/operating_modes/mspm0c110x.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0C1104 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0C1104 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: CPUSS: Standby diff --git a/data/operating_modes/mspm0g110x.yaml b/data/operating_modes/mspm0g110x.yaml index b370cfe..da6ac42 100644 --- a/data/operating_modes/mspm0g110x.yaml +++ b/data/operating_modes/mspm0g110x.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0G1107 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0G1107 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: CPUSS: Standby diff --git a/data/operating_modes/mspm0g150x.yaml b/data/operating_modes/mspm0g150x.yaml index c147a16..0f9d991 100644 --- a/data/operating_modes/mspm0g150x.yaml +++ b/data/operating_modes/mspm0g150x.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0G1507 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0G1507 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: AES: Sleep diff --git a/data/operating_modes/mspm0g151x.yaml b/data/operating_modes/mspm0g151x.yaml index ff13264..5069424 100644 --- a/data/operating_modes/mspm0g151x.yaml +++ b/data/operating_modes/mspm0g151x.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0G1519 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0G1519 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: AESADV: Sleep diff --git a/data/operating_modes/mspm0g310x.yaml b/data/operating_modes/mspm0g310x.yaml index d7352d3..948af74 100644 --- a/data/operating_modes/mspm0g310x.yaml +++ b/data/operating_modes/mspm0g310x.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0G3107 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0G3107 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: AES: Sleep diff --git a/data/operating_modes/mspm0g350x.yaml b/data/operating_modes/mspm0g350x.yaml index 006dc37..a3ee21e 100644 --- a/data/operating_modes/mspm0g350x.yaml +++ b/data/operating_modes/mspm0g350x.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0G3507 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0G3507 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: AES: Sleep diff --git a/data/operating_modes/mspm0g351x.yaml b/data/operating_modes/mspm0g351x.yaml index 3cd54e1..5f38bcf 100644 --- a/data/operating_modes/mspm0g351x.yaml +++ b/data/operating_modes/mspm0g351x.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0G3519 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0G3519 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: AESADV: Sleep diff --git a/data/operating_modes/mspm0g518x.yaml b/data/operating_modes/mspm0g518x.yaml index e1092e9..0518937 100644 --- a/data/operating_modes/mspm0g518x.yaml +++ b/data/operating_modes/mspm0g518x.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0G5187 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0G5187 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: AESADV: Sleep diff --git a/data/operating_modes/mspm0h321x.yaml b/data/operating_modes/mspm0h321x.yaml index 9633443..e81bb2d 100644 --- a/data/operating_modes/mspm0h321x.yaml +++ b/data/operating_modes/mspm0h321x.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0H3216 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0H3216 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: CPUSS: Standby diff --git a/data/operating_modes/mspm0l110x.yaml b/data/operating_modes/mspm0l110x.yaml index 0b8c23c..7117bd3 100644 --- a/data/operating_modes/mspm0l110x.yaml +++ b/data/operating_modes/mspm0l110x.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0L1106 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0L1106 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: CPUSS: Standby diff --git a/data/operating_modes/mspm0l112x.yaml b/data/operating_modes/mspm0l112x.yaml index b25ec1d..10de054 100644 --- a/data/operating_modes/mspm0l112x.yaml +++ b/data/operating_modes/mspm0l112x.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0L2117 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0L2117 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: AESADV: Standby diff --git a/data/operating_modes/mspm0l122x.yaml b/data/operating_modes/mspm0l122x.yaml index 8af1af3..0221b92 100644 --- a/data/operating_modes/mspm0l122x.yaml +++ b/data/operating_modes/mspm0l122x.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0L1228 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0L1228 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: AESADV: Standby diff --git a/data/operating_modes/mspm0l130x.yaml b/data/operating_modes/mspm0l130x.yaml index a051c74..4e1e3b5 100644 --- a/data/operating_modes/mspm0l130x.yaml +++ b/data/operating_modes/mspm0l130x.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0L1306 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0L1306 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: CPUSS: Standby diff --git a/data/operating_modes/mspm0l134x.yaml b/data/operating_modes/mspm0l134x.yaml index 4eea4dd..bd317c2 100644 --- a/data/operating_modes/mspm0l134x.yaml +++ b/data/operating_modes/mspm0l134x.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0L1346 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0L1346 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: CPUSS: Standby diff --git a/data/operating_modes/mspm0l211x.yaml b/data/operating_modes/mspm0l211x.yaml index 36a938e..3019f29 100644 --- a/data/operating_modes/mspm0l211x.yaml +++ b/data/operating_modes/mspm0l211x.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0L2117 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0L2117 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: AESADV: Standby diff --git a/data/operating_modes/mspm0l222x.yaml b/data/operating_modes/mspm0l222x.yaml index f403183..438e829 100644 --- a/data/operating_modes/mspm0l222x.yaml +++ b/data/operating_modes/mspm0l222x.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0L2228 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0L2228 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: AESADV: Standby diff --git a/data/operating_modes/msps003fx.yaml b/data/operating_modes/msps003fx.yaml index 236b324..a15cf6a 100644 --- a/data/operating_modes/msps003fx.yaml +++ b/data/operating_modes/msps003fx.yaml @@ -1,20 +1,10 @@ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the MSPM0C1104 datasheet. Re-run the tool rather than editing by hand. +# Operating Mode", of the MSPM0C1104 datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` in the table loses it, and the STOP and STANDBY column groups -# are read separately: `OFF` under STOP means the configuration is already gone there (`Sleep`), `OFF` -# under STANDBY alone means it survives STOP but not STANDBY (`Stop`), and `EN`/`OPT`/`DIS` throughout -# means it survives everything short of SHUTDOWN (`Standby`). Only PD1 peripherals appear: nothing -# else is automatically disabled, so the question does not arise. -# -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: CPUSS: Standby diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 705ae46..452cb1b 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -52,9 +52,6 @@ pub fn generate( .get(&header_name.to_lowercase()) .context(format!("Could not lookup header for {}", header_name))?; - // SVDs are named after the family, but TI does not publish one for every family: as of the - // pinned data sources mspm0c1105_c1106, mspm0l112x and mspm0l211x have none. The facts taken - // from the SVD are optional for that reason, and `verify` reports the gap. let svd = svds.files.get(&family.family); generate_family( @@ -991,10 +988,8 @@ fn generate_wakeup_pins(sysconfig: &SysconfigFile) -> Option> { /// Whether the chip has an independent `VBAT` supply, and therefore a real backup power domain. /// -/// The presence of a `VBAT` device pin is the authoritative answer: TRM ยง30 distinguishes the RTC -/// variants by exactly this ("In devices for which the LFSS is powered by an independent VBAT supply -/// pin to support the backup-battery power domain (PDB), the RTC variant has extended features and -/// is referred to as RTC_A"). +/// The presence of a `VBAT` device pin is the authoritative answer, and is what TRM ยง30 uses to +/// distinguish the RTC variants. fn has_backup_domain( chip_name: &str, sysconfig: &SysconfigFile, diff --git a/mspm0-data-gen/src/header.rs b/mspm0-data-gen/src/header.rs index d23d9a1..3d4574f 100644 --- a/mspm0-data-gen/src/header.rs +++ b/mspm0-data-gen/src/header.rs @@ -68,11 +68,8 @@ impl Header { /// Read the NVIC priority bit count from the CMSIS device header. /// - /// Deliberately *not* taken from the SVD, which also carries this as ``: the SVDs - /// say 3 for every MSPM0, which is wrong. The CMSIS header says 2, and the datasheets agree - /// ("Nested vectored interrupt controller (NVIC) with four programmable priority levels" โ€” four - /// levels is two bits). Taking the SVD's value would give consumers twice the priority levels - /// the hardware has. + /// Deliberately *not* taken from the SVDs, whose `` says 3 for every MSPM0 and is + /// wrong; the header and the datasheets agree on 2. fn get_nvic_priority_bits(chip_name: &str, content: &str) -> anyhow::Result { /// Example: /// ```c,no_run diff --git a/mspm0-data-gen/src/svd.rs b/mspm0-data-gen/src/svd.rs index ac0a3d5..2cdc8df 100644 --- a/mspm0-data-gen/src/svd.rs +++ b/mspm0-data-gen/src/svd.rs @@ -1,14 +1,9 @@ -//! Facts which only the SVDs carry. +//! Reads the SVDs for facts which vary between instances of one peripheral version, and so cannot +//! come from the register YAMLs. //! -//! The SVDs are normally consumed offline by chiptool to produce `data/registers/*.yaml`, and the -//! register YAMLs are shared between chips of the same peripheral version. That makes them useless -//! for facts which vary between *instances* of the same peripheral version, which is why this reads -//! the SVDs directly. -//! -//! Only one fact is read, and it is a presence test on a single field name, so the SVDs are scanned -//! textually rather than parsed. Parsing them properly would mean depending on `svd-parser` -//! directly: chiptool does not re-export it, and it is a pinned fork rather than a release, so it -//! would have to track chiptool's exact revision. +//! The SVDs are scanned textually rather than parsed, since every fact read here is a presence test +//! on a field name. Parsing properly would mean depending on `svd-parser` directly: chiptool does not +//! re-export it, and is itself pinned to a commit, so the dependency would have to track that commit. use std::{ collections::{BTreeMap, BTreeSet}, diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 9c3c460..663cad7 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -5,9 +5,6 @@ use mspm0_data_types::{Chip, PeripheralType, PowerDomain}; use regex::Regex; /// Run every check, returning one error per failure. -/// -/// Every check runs: a failing one must not mask those after it, or the families with a gap in their -/// data sources would go otherwise unverified. pub fn verify(chip: &Chip, name: &str) -> Vec { const CHECKS: &[fn(&Chip, &str) -> anyhow::Result<()>] = &[ core_peripherals, @@ -81,10 +78,6 @@ fn core_peripherals(chip: &Chip, name: &str) -> anyhow::Result<()> { /// Report families for which no SVD is published, and whose `BLOCKASYNC` bits are therefore /// unknown. -/// -/// This is not an error: TI publishes SVDs later than the rest of the metadata, so a newly added -/// family legitimately has none. It is reported so the gap is visible and gets closed on the next -/// data source bump rather than silently persisting. fn block_async_known(chip: &Chip, name: &str) -> anyhow::Result<()> { if chip .peripherals @@ -102,9 +95,8 @@ fn block_async_known(chip: &Chip, name: &str) -> anyhow::Result<()> { /// Report PD1 peripherals which do not say how deep a sleep they are retained through. /// -/// Only PD1 peripherals are forced to a disabled state by SYSCTL, so they are the only ones for -/// which the question arises. Not fatal, because some of these gaps are genuine disagreements -/// between the datasheet and sysconfig rather than something we can fix. +/// Not fatal, because some of these gaps are genuine disagreements between the +/// datasheet and sysconfig rather than something we can fix. fn retention_known(chip: &Chip, name: &str) -> anyhow::Result<()> { let unknown = chip .peripherals @@ -139,9 +131,6 @@ fn wakeup_pins_known(chip: &Chip, name: &str) -> anyhow::Result<()> { } /// Verify that every timer knows whether it is clocked in STANDBY1, and that at least one is. -/// -/// A family with no STANDBY1 timer at all would mean nothing can wake the core from the deepest -/// sleep, which is true of no MSPM0 device and means the data is missing. fn standby1_timer_exists(chip: &Chip, name: &str) -> anyhow::Result<()> { let timers = chip .peripherals diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 9f712d8..a6a4443 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -284,29 +284,18 @@ pub struct Peripheral { #[serde(skip_serializing_if = "Option::is_none")] pub interrupt: Option, - /// Whether this peripheral instance has its own `CLKCFG.BLOCKASYNC` bit. + /// Whether this peripheral instance has its own `CLKCFG.BLOCKASYNC` bit, masking the asynchronous + /// fast clock request it raises. /// - /// An asynchronous fast clock request temporarily suspends a low-power mode and brings MCLK and - /// ULPCLK back to full rate, which is how a PD0 peripheral wakes the system on an external - /// event while not being clocked. `BLOCKASYNC` masks the request for one instance and must be - /// clear to arm such a wake; `SYSCTL.SYSOSCCFG.BLOCKASYNCALL` masks every request at once. - /// - /// `false` does not mean the peripheral cannot raise a request: GPIO, the general purpose - /// timers and the ADC all can, but have no per-instance mask and are gated only by - /// `BLOCKASYNCALL`. `None` means no SVD is published for this family yet, so the answer is - /// unknown rather than negative. + /// `None` when no SVD is published for the family. `mspm0-metapac-gen/res/metadata.rs` documents + /// what `false` does not mean. pub block_async: Option, /// The deepest mode through which this peripheral keeps its configuration. /// /// [`PowerMode::Standby`] means the configuration survives everything short of SHUTDOWN; /// [`PowerMode::Sleep`] means it is already gone in STOP, so the peripheral must be fully - /// reconfigured on wake, either by re-running its init or by saving and restoring its registers - /// around the low-power mode. - /// - /// Note that SYSCTL forces *every* PD1 peripheral to a disabled state on entry to STOP or - /// STANDBY (TRM ยง2.2.6.1), so a driver has to re-enable the peripheral on wake either way. That - /// is a property of PD1 rather than of any one peripheral, and is not encoded here. + /// reconfigured on wake. All PD1 peripherals need re-enabling after STOP or STANDBY regardless. /// /// `None` when `power_domain` is not [`PowerDomain::Pd1`]. #[serde(skip_serializing_if = "Option::is_none")] @@ -315,11 +304,10 @@ pub struct Peripheral { /// The deepest mode in which the datasheet says this peripheral can be used. /// /// Derived from the same table as [`Peripheral::retained_through`], reading `EN` and `OPT` as - /// usable and `DIS`, `OFF` and `NS` as not. `NS` matters here: it means the peripheral is not - /// automatically disabled but its use in that mode is unsupported, which a boolean would hide. + /// usable and `DIS`, `OFF` and `NS` as not. /// - /// `None` where the table cannot answer at the resolution it can be read โ€” either the row is - /// not uniform across the policies of a mode group, or it gives one value spanning every column. + /// `None` when the row does not resolve to a single mode: either its values differ between the + /// policies within a mode group, or one value spans every column. #[serde(skip_serializing_if = "Option::is_none")] pub usable_through: Option, diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index c17a27d..08a3675 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -60,9 +60,7 @@ pub struct Peripheral { /// `Standby` means the configuration survives everything short of SHUTDOWN; `Sleep` means it is /// already gone in STOP, so the peripheral must be fully reconfigured on wake. /// - /// SYSCTL forces *every* PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM - /// ยง2.2.6.1), so a driver must re-enable it on wake either way; that is a property of PD1, not - /// of any one peripheral, and is not encoded here. + /// All PD1 peripherals need re-enabling after STOP or STANDBY regardless (TRM ยง2.2.6.1). /// /// `None` when `power_domain` is not `Pd1`. pub retained_through: Option, @@ -72,7 +70,7 @@ pub struct Peripheral { /// From the same table as `retained_through`, reading `EN` and `OPT` as usable and `DIS`, `OFF` /// and `NS` as not. /// - /// `None` where the table cannot answer at the resolution it can be read. + /// `None` when the row does not resolve to a single mode. pub usable_through: Option, /// Whether this timer keeps receiving ULPCLK or LFCLK in STANDBY1. diff --git a/mspm0-metapac-gen/src/main.rs b/mspm0-metapac-gen/src/main.rs index 073cffb..916e426 100644 --- a/mspm0-metapac-gen/src/main.rs +++ b/mspm0-metapac-gen/src/main.rs @@ -155,8 +155,7 @@ fn generate_chip_metadata( let max_ulpclk_hz = &chip.max_ulpclk_hz; let backup_domain = &chip.backup_domain; - // Memory is the one part of the metadata which varies by part number rather than by family, so - // it is emitted here instead of in the deduplicated body above. + // Memory is the one part of the metadata which varies by part number rather than by family let memory = metadata::memory(chip); write!( diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 2671817..5cc3d2a 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -49,8 +49,7 @@ pub fn memory(chip: &Chip) -> TokenStream { MemoryKind::Ram => quote! { MemoryKind::Ram }, }; let address = Literal::u32_unsuffixed(region.address); - // Sizes are in KB in the chip data, but bytes are what consumers need. - let size = Literal::u32_unsuffixed(region.length * 1024); + let size = Literal::u32_unsuffixed(region.length * 1024); // Convert from KB to B let retained_through = power_mode(region.retained_through); quote! { diff --git a/tools/operating_modes.py b/tools/operating_modes.py index 1a70175..a473ac0 100644 --- a/tools/operating_modes.py +++ b/tools/operating_modes.py @@ -14,27 +14,10 @@ Requires `pdfplumber`. -## How the table is read - -The table is read from its **ruling lines** rather than from extracted text. `pdftotext` flattens the -page to a character grid, which destroys the one thing that matters here: a value covering several -modes is printed once, and its position does not identify the columns it spans. Reconstructing spans -from text positions does not work -- neither a partition fitted to the header columns nor one fitted -to column anchors recovers the true boundaries, and both fail silently by producing a plausible -answer. - -`pdfplumber`'s lattice mode instead recovers the drawn cell rectangles. A merged cell is one -rectangle covering several grid positions, so the spans come from the geometry: +Cells spanning several rows or columns mean the table needs lattice reconstruction from its ruling +lines; reading extracted text instead silently loses the spans. row.cells[i] is None <=> column i is covered by a merge that began earlier - -which makes a forward fill from the last real cell exact. Row spans work the same way, so the group -column ("PD1 Peripherals", "Core Functions") needs no special handling, and labels wrapped over -several lines stay within their own cell. - -Column identity comes from the header row, which names the mode groups (RUN, SLEEP, STOP, STANDBY, -SHUTDOWN) as merged cells; the policies within a group follow in order. Not every device has every -policy -- the C and H series have no STOP1 -- so this is derived per file rather than assumed. """ import glob @@ -217,21 +200,13 @@ def expand(label): HEADER = """\ -# How deep a sleep each peripheral is retained through and stays usable in. +# How deep a sleep each peripheral keeps its configuration through, and stays usable in. # # GENERATED by `tools/operating_modes.py --write` from Table 8-1, "Supported Functionality by -# Operating Mode", of the {ds} datasheet. Re-run the tool rather than editing by hand. -# -# `retained_through` is how deep a sleep a PD1 peripheral keeps its configuration through. SYSCTL -# forces every PD1 peripheral to a disabled state on entry to STOP or STANDBY (TRM 2.2.6.1), so a -# driver must re-enable any of them on wake regardless; what differs is whether the rest of the -# configuration is still there. `OFF` loses it: under STOP that is `Sleep`, under STANDBY alone -# `Stop`, and `EN`/`OPT`/`DIS` throughout is `Standby`, surviving everything short of SHUTDOWN. Only -# PD1 peripherals appear: nothing else is automatically disabled, so the question does not arise. +# Operating Mode", of the {ds} datasheet. Re-run the tool rather than editing by hand; see +# `tools/operating_modes.py` for how the table's states map onto these values. # -# `usable_through` is the deepest mode the table marks the peripheral usable in, reading `EN` and -# `OPT` as usable and `DIS`, `OFF` and `NS` as not. `NS` is "not automatically disabled in the -# specified mode, but its use is not supported" -- the case a boolean would hide. +# `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. """ From d63bb5fbf783422f50c1a9047c22214742f46a23 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Thu, 30 Jul 2026 09:38:59 +0100 Subject: [PATCH 014/134] Improve the operating mode extraction script --- tools/operating_modes.py | 123 ++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 54 deletions(-) diff --git a/tools/operating_modes.py b/tools/operating_modes.py index a473ac0..3eaa4f5 100644 --- a/tools/operating_modes.py +++ b/tools/operating_modes.py @@ -9,10 +9,10 @@ source bump rather than editing the YAML by hand. Usage: - python tools/operating_modes.py [...] # print the table - python tools/operating_modes.py --write # regenerate data/operating_modes + uv run tools/operating_modes.py [...] # print the table + uv run tools/operating_modes.py --write # regenerate data/operating_modes -Requires `pdfplumber`. +`uv run` installs the dependencies below on its own; a bare `python` needs them on the path. Cells spanning several rows or columns mean the table needs lattice reconstruction from its ruling lines; reading extracted text instead silently loses the spans. @@ -20,16 +20,30 @@ row.cells[i] is None <=> column i is covered by a merge that began earlier """ +# /// script +# requires-python = ">=3.10" +# dependencies = ["pdfplumber", "pypdfium2", "pyyaml"] +# /// + import glob import json import re import sys +from collections.abc import Iterator from pathlib import Path try: import pdfplumber -except ImportError: # pragma: no cover - raise SystemExit("pdfplumber is required: python -m pip install pdfplumber") + import pypdfium2 + import yaml +except ImportError as e: # pragma: no cover + raise SystemExit(f"{e.name} is missing; run this with `uv run` instead of `python`") + +ColumnGroups = dict[int, str] # column index -> mode group +States = dict[int, str | None] # column index -> state, merges resolved +Rows = list[tuple[str, States]] # (row label, states) +ByGroup = dict[str, set[str]] # mode group -> states seen in it +Overrides = dict[str, dict[str, dict[str, str]]] # family -> field -> name -> value CAPTION = "Supported Functionality by Operating Mode" @@ -57,17 +71,31 @@ } -def read_tables(path): +def caption_pages(path: Path) -> list[int]: + """Indices of the pages carrying the table's caption. + + Located with PDFium rather than pdfplumber: pdfminer's content-stream parse costs ~90ms a page, + and only the two or three pages found here need it. + """ + doc = pypdfium2.PdfDocument(str(path)) + try: + return [i for i in range(len(doc)) if CAPTION in doc[i].get_textpage().get_text_range()] + finally: + doc.close() + + +def read_tables(path: Path) -> Iterator[tuple[ColumnGroups, Rows]]: """Yield (column_groups, rows) for each page of the operating-mode table. `column_groups` maps a column index to its mode group; `rows` is a list of (label, states) where `states` is one entry per mode column, merges already resolved. """ - with pdfplumber.open(str(path)) as pdf: - for page in pdf.pages: - if CAPTION not in (page.extract_text() or ""): - continue + pages = caption_pages(path) + if not pages: + return + with pdfplumber.open(str(path)) as pdf: + for page in (pdf.pages[i] for i in pages): for table in page.find_tables(LATTICE): if len(table.rows) < 6 or max(len(r.cells) for r in table.rows) < 10: continue @@ -77,7 +105,8 @@ def read_tables(path): # follow as `None` cells. Anything past the last named group is SHUTDOWN, which some # datasheets leave unlabelled. header = [(c or "").replace("\n", "").strip() for c in data[0]] - column_group, current = {}, None + column_group: ColumnGroups = {} + current: str | None = None for i, cell in enumerate(header): if i < 2: continue # group label and row label columns @@ -93,7 +122,7 @@ def read_tables(path): if column_group.get(tail) == "STANDBY" and tail == len(header) - 1: column_group[tail] = "SHUTDOWN" - rows = [] + rows: Rows = [] for ri, row in enumerate(table.rows[1:], start=1): # A label wrapped inside its cell breaks mid-word ("MATHA"/"CL", "SYSOS"/"C", # "TIMG6/7"/"/12"), so the lines join with no separator. Comma-separated lists @@ -102,7 +131,8 @@ def read_tables(path): if not label: continue - states, held = {}, None + states: States = {} + held: str | None = None for ci in sorted(column_group): if ci >= len(row.cells): continue @@ -117,9 +147,9 @@ def read_tables(path): yield column_group, rows -def group_states(column_group, states): +def group_states(column_group: ColumnGroups, states: States) -> ByGroup: """States seen in each mode group, as {group: set of states}.""" - out = {} + out: ByGroup = {} for ci, group in column_group.items(): s = states.get(ci) if s: @@ -127,7 +157,7 @@ def group_states(column_group, states): return out -def retained_through(by_group): +def retained_through(by_group: ByGroup) -> str | None: """Deepest mode a PD1 peripheral's configuration survives, or None if the table is silent. `OFF` is the only state which loses configuration; `EN`, `OPT` and `DIS` all keep it. STOP and @@ -148,9 +178,9 @@ def retained_through(by_group): return "Standby" if standby else "Stop" -def usable_through(by_group): +def usable_through(by_group: ByGroup) -> str | None: """Deepest mode group in which every policy is usable, or None if the table is silent.""" - deepest = None + deepest: str | None = None for group in ("RUN", "SLEEP", "STOP", "STANDBY"): seen = by_group.get(group) if not seen: @@ -161,7 +191,7 @@ def usable_through(by_group): return {"RUN": "Run", "SLEEP": "Sleep", "STOP": "Stop", "STANDBY": "Standby"}.get(deepest) -def expand(label): +def expand(label: str) -> list[str]: """Instance names a row label refers to. Rows cover several instances at once, written as a shared prefix with the varying suffixes @@ -210,41 +240,25 @@ def expand(label): """ -def load_overrides(): - """Values from `data/operating_mode_overrides.yaml`, where a better source beats the table. - - Parsed without a YAML library to keep this script dependency-light; the file is a fixed - two-level shape (family -> field -> name: value). - """ +def load_overrides() -> Overrides: + """Values from `data/operating_mode_overrides.yaml`, where a better source beats the table.""" path = Path("data/operating_mode_overrides.yaml") if not path.exists(): return {} + return yaml.safe_load(path.read_text(encoding="utf-8")) or {} - out, family, field = {}, None, None - for line in path.read_text(encoding="utf-8").splitlines(): - if not line.strip() or line.lstrip().startswith("#"): - continue - indent = len(line) - len(line.lstrip()) - key = line.strip().rstrip(":") - if indent == 0: - family, field = key, None - elif indent == 2: - field = key - elif indent == 4 and family and field: - name, _, value = key.partition(":") - out.setdefault(family, {}).setdefault(field, {})[name.strip()] = value.strip() - return out - -def write(datasheets): +def write(datasheets: str) -> int: """Regenerate data/operating_modes/*.yaml. Returns the number of problems reported.""" - families = re.findall( - r"^- family: (\S+)\n(?:.*\n)*? datasheet_url: \S+/(\S+)$", - Path("data/parts.yaml").read_text(encoding="utf-8"), - re.M, - ) - - pd1, known = {}, {} + parts = yaml.safe_load(Path("data/parts.yaml").read_text(encoding="utf-8")) + families = [ + (f["family"], f["datasheet_url"].rsplit("/", 1)[-1]) + for f in parts["families"] + if f.get("datasheet_url") + ] + + pd1: dict[str, set[str]] = {} + known: dict[str, set[str]] = {} for f in glob.glob("build/data/*.json"): chip = json.loads(Path(f).read_text(encoding="utf-8")) pd1.setdefault(chip["family"], set()).update( @@ -265,7 +279,8 @@ def write(datasheets): problems += 1 continue - retained, usable = {}, {} + retained: dict[str, str] = {} + usable: dict[str, str] = {} for column_group, rows in read_tables(pdf): for label, states in rows: by_group = group_states(column_group, states) @@ -288,10 +303,10 @@ def write(datasheets): # Entries the table cannot answer are reasoned out elsewhere; keep whatever is already # recorded rather than dropping it. path = Path(f"data/operating_modes/{family}.yaml") - existing = ( - dict(re.findall(r"^ (\w+): (\w+)$", path.read_text(encoding="utf-8"), re.M)) - if path.exists() else {} - ) + existing: dict[str, str] = {} + if path.exists(): + loaded = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + existing = loaded.get("retained_through") or {} missing = sorted(set(pd1.get(family, ())) - set(retained)) carried = {n: existing[n] for n in missing if n in existing} for name in missing: @@ -320,7 +335,7 @@ def write(datasheets): return problems -def main(argv): +def main(argv: list[str]) -> None: if "--write" in argv: i = argv.index("--write") raise SystemExit(1 if write(argv[i + 1] if i + 1 < len(argv) else ".") else 0) From eacde3ba7d13b68d7045fc3d564b710103668862 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Sat, 1 Aug 2026 11:41:43 +0100 Subject: [PATCH 015/134] Add low-power metadata to FactoryRegion --- mspm0-data-gen/src/generate.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 5473d1c..ce878b5 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -590,9 +590,14 @@ fn generate_missing( version, address: Some(address), // FACTORYREGION is read-only flash which is always available. - power_domain: PowerDomain::Pd1, + power_domain: PowerDomain::Pd0, pins: vec![], sys_fentries: None, + interrupt: None, + block_async: None, + retained_through: None, + usable_through: None, + clocked_in_standby1: None, }, ); From 382732ce37ac72a240bbd96343b75ae712fcbc3b Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Sat, 1 Aug 2026 11:43:42 +0100 Subject: [PATCH 016/134] Clarify todo comment --- mspm0-data-gen/src/verify.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 663cad7..1a06f21 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -23,9 +23,8 @@ pub fn verify(chip: &Chip, name: &str) -> Vec { verify_crc_power_domain, verify_gpamp_power_domain, verify_spi_power_domain, - // Timers may be in either power domain: add checks if something is wrong verify_trng_power_domain, - // UART may be in either power domain: add checks if something is wrong + // TODO: UART may be in either power domain, add checks if something is wrong ]; CHECKS From d04c1e43f05cc6e37a98beb9b2407f860a26509d Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Tue, 4 Aug 2026 20:53:30 +0100 Subject: [PATCH 017/134] Correct which binary owns the perimap --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 93513e6..7957a32 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ SVDs have some widespread annoyances that should be fixed when adding register Y ## Peripheral mapping (perimap) -The `mspm0-metapac-gen` binary has a map to match peripherals to the right version in all chips, the [perimap](./mspm0-data-gen/src/perimap.rs). +The `mspm0-data-gen` binary has a map to match peripherals to the right version in all chips, the [perimap](./mspm0-data-gen/src/perimap.rs). When parsing a chip, for each peripheral a "key" string is constructed using this format: `FAMILY:PERIPHERAL_NAME`, where: From e4cffc2e85d2a29ab94a75d06857280678821b18 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Tue, 4 Aug 2026 20:53:39 +0100 Subject: [PATCH 018/134] Fix software BOOTRST encoding on sysctl_l110x_l130x_l134x SLAU847 contradicts itself: its Reset Causes table lists 10 as reserved and 13 as the software-triggered BOOTRST, while the RSTCAUSE field description for this SYSCTL variant says 10, and the SVD was generated from the latter. Every other family, and SLAU846 throughout, agree on 13. BOOTWWDT0 was already corrected against the same table. --- data/registers/sysctl_l110x_l130x_l134x.yaml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/data/registers/sysctl_l110x_l130x_l134x.yaml b/data/registers/sysctl_l110x_l130x_l134x.yaml index 5edc62b..e934785 100644 --- a/data/registers/sysctl_l110x_l130x_l134x.yaml +++ b/data/registers/sysctl_l110x_l130x_l134x.yaml @@ -804,12 +804,16 @@ enum/ID: - name: BOOTCLKFAIL description: Fatal clock failure. value: 9 - - name: BOOTSW - description: Software triggered BOOTRST. - value: 10 - name: BOOTEXNRST description: NRST triggered BOOTRST (<1s hold). value: 12 + # The SVD puts this at 10, copied from SLAU847's RSTCAUSE field description for this SYSCTL + # variant. That description contradicts the same TRM's "Reset Causes" table, which lists 10 as + # reserved and 13 as the software-triggered BOOTRST, and every other family agrees with the table. + # BOOTWWDT0 below was already corrected against that table for the same reason. + - name: BOOTSW + description: Software triggered BOOTRST. + value: 13 - name: BOOTWWDT0 description: WWDT0 violation. value: 14 From 15cbf30480679b09ea21b8992af0d81210940f37 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Tue, 4 Aug 2026 20:53:47 +0100 Subject: [PATCH 019/134] Fix three C-series SYSCTL field offsets SLAU893 puts all three at bit 0, as do every other family's register YAML and SVD; only the C-series SVD says bit 2. SETUSEEXLF at bit 2 means LFCLK_IN can never be selected, since the write lands on a reserved bit. --- data/registers/sysctl_c110x.yaml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/data/registers/sysctl_c110x.yaml b/data/registers/sysctl_c110x.yaml index d850b3a..6a7679f 100644 --- a/data/registers/sysctl_c110x.yaml +++ b/data/registers/sysctl_c110x.yaml @@ -215,7 +215,9 @@ fieldset/EXLFCTL: fields: - name: SETUSEEXLF description: Use external LF CLK IN. - bit_offset: 2 + # The C-series SVD puts this at bit 2, which contradicts SLAU893 and every other family's + # SYSCTL. A write at bit 2 would never take effect. + bit_offset: 0 bit_size: 1 - name: KEY bit_offset: 24 @@ -226,7 +228,9 @@ fieldset/EXRSTPIN: fields: - name: DISABLE description: Disable External Reset. - bit_offset: 2 + # The C-series SVD puts this at bit 2, which contradicts SLAU893 and every other family's + # SYSCTL. A write at bit 2 would never take effect. + bit_offset: 0 bit_size: 1 - name: KEY bit_offset: 24 @@ -499,7 +503,9 @@ fieldset/SYSSTATUSCLR: fields: - name: ALLECC description: Clear ALL ECC related SYSSTATUS indicators. - bit_offset: 2 + # The C-series SVD puts this at bit 2, which contradicts SLAU893 and every other family's + # SYSCTL. A write at bit 2 would never take effect. + bit_offset: 0 bit_size: 1 - name: KEY bit_offset: 24 From 4722c4dce86adf90c97a23696318561e8a6f0ace Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Tue, 4 Aug 2026 20:54:24 +0100 Subject: [PATCH 020/134] Add a SYSCTL block for MSPM0C1105/C1106 SLAU893 documents two C-series SYSCTLs by name, SYSCTL_C1103_C1104 and SYSCTL_C1105_C1106, and sysctl_c110x describes the first. Sharing it left MSPM0C1105/C1106 unable to reach its crystal or its 1-wait-state flash band. Transcribed from SLAU893 2.7 and checked field-by-field against it. --- data/registers/sysctl_c1105_c1106.yaml | 988 +++++++++++++++++++++++++ mspm0-data-gen/src/perimap.rs | 5 +- 2 files changed, 992 insertions(+), 1 deletion(-) create mode 100644 data/registers/sysctl_c1105_c1106.yaml diff --git a/data/registers/sysctl_c1105_c1106.yaml b/data/registers/sysctl_c1105_c1106.yaml new file mode 100644 index 0000000..8cf7916 --- /dev/null +++ b/data/registers/sysctl_c1105_c1106.yaml @@ -0,0 +1,988 @@ +block/SYSCTL: + description: SYSCTL. + items: + - name: IIDX + description: Event IIDX. + byte_offset: 4128 + access: Read + fieldset: IIDX + - name: IMASK + description: Event IMASK. + byte_offset: 4136 + fieldset: INT + - name: RIS + description: Event RIS. + byte_offset: 4144 + access: Read + fieldset: INT + - name: MIS + description: Event MIS. + byte_offset: 4152 + access: Read + fieldset: INT + - name: ISET + description: Event ISET. + byte_offset: 4160 + access: Write + fieldset: INT + - name: ICLR + description: Event ICLR. + byte_offset: 4168 + access: Write + fieldset: INT + - name: NMI_IIDX + byte_offset: 4176 + fieldset: NMI_IIDX + - name: NMI_RIS + byte_offset: 4192 + fieldset: NMI + - name: NMI_ISET + byte_offset: 4208 + fieldset: NMI + - name: NMI_ICLR + byte_offset: 4216 + fieldset: NMI + - name: SYSOSCCFG + description: SYSOSC Configuration. + byte_offset: 4352 + fieldset: SYSOSCCFG + - name: MCLKCFG + description: Configuration related to MCLK. + byte_offset: 4356 + fieldset: MCLKCFG + - name: HSCLKEN + description: High Speed Clock Configuration. + byte_offset: 4360 + fieldset: HSCLKEN + - name: HSCLKCFG + description: High Speed Clock Source Selection. + byte_offset: 4364 + fieldset: HSCLKCFG + - name: HFCLKCLKCFG + description: HFCLK Clock Configuration. + byte_offset: 4368 + fieldset: HFCLKCLKCFG + - name: LFCLKCFG + description: LFCLK Clock Configuration. + byte_offset: 4372 + fieldset: LFCLKCFG + - name: GENCLKCFG + description: General Clock Configuration. + byte_offset: 4408 + fieldset: GENCLKCFG + - name: GENCLKEN + description: General Clock Enables. + byte_offset: 4412 + fieldset: GENCLKEN + - name: PMODECFG + description: Power Mode Configurations. + byte_offset: 4416 + fieldset: PMODECFG + - name: FCC + byte_offset: 4432 + fieldset: FCC + - name: SRAMBOUNDARY + byte_offset: 4472 + fieldset: SRAMBOUNDARY + - name: SYSTEMCFG + description: SRAM Write Protect. + byte_offset: 4480 + fieldset: SYSTEMCFG + - name: WRITELOCK + description: SYSOSC Trim Values specified by user. + byte_offset: 4608 + fieldset: WRITELOCK + - name: CLKSTATUS + description: Useful debug status of clock selections. + byte_offset: 4612 + access: Read + fieldset: CLKSTATUS + - name: SYSSTATUS + description: Useful System Status Data. + byte_offset: 4616 + access: Read + fieldset: SYSSTATUS + - name: RSTCAUSE + byte_offset: 4640 + fieldset: RSTCAUSE + - name: RESETLEVEL + description: Reset Level for Application Reset Command. + byte_offset: 4864 + fieldset: RESETLEVEL + - name: RESETCMD + description: Execute Reset Command. + byte_offset: 4868 + access: Write + fieldset: RESETCMD + - name: BORTHRESHOLD + description: BOR Threshold Level. + byte_offset: 4872 + fieldset: BORTHRESHOLD + - name: BORCLRCMD + byte_offset: 4876 + fieldset: BORCLRCMD + - name: SYSOSCFCLCTL + description: SYSOSC Frequency Control Loop with External Resistor. + byte_offset: 4880 + access: Write + fieldset: SYSOSCFCLCTL + - name: LFXTCTL + description: LFXT Control -- Only BOR Level Reset will clear. + byte_offset: 4884 + access: Write + fieldset: LFXTCTL + - name: EXLFCTL + description: EX LF Control -- Only BOR Level Reset will clear. + byte_offset: 4888 + access: Write + fieldset: EXLFCTL + - name: SHDNIOREL + description: Shutdown IO Release Command. + byte_offset: 4892 + access: Write + fieldset: SHDNIOREL + - name: EXRSTPIN + description: Disable use of external Reset Pin. + byte_offset: 4896 + access: Write + fieldset: EXRSTPIN + - name: SYSSTATUSCLR + description: Clear sticky bits of SYSSTATUS. + byte_offset: 4900 + access: Write + fieldset: SYSSTATUSCLR + - name: SWDCFG + description: Disable SWD. + byte_offset: 4904 + access: Write + fieldset: SWDCFG + - name: FCCCMD + byte_offset: 4908 + fieldset: FCCCMD + - name: SHUTDNSTORE + description: Shutdown Storage Byte 0. + array: + len: 4 + stride: 4 + byte_offset: 5120 + fieldset: SHUTDNSTORE +fieldset/BORCLRCMD: + fields: + - name: GO + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: BORCLRCMD_KEY +fieldset/BORTHRESHOLD: + description: BOR Threshold Level. + fields: + - name: LEVEL + description: 'BOR Threshold Level 0: Full Re-boot This level is always enforced regardless of MMR seting 1-3: Generates interrupt.' + bit_offset: 0 + bit_size: 2 +fieldset/CLKSTATUS: + description: Useful debug status of clock selections. + fields: + - name: SYSOSCFREQ + description: Current SYSOSC frequency setting. + bit_offset: 0 + bit_size: 2 + enum: SYSOSCFREQ + - name: HSCLKMUX + description: High Speed Clock Selected for MCLK (HFCLK, PLL,...). + bit_offset: 4 + bit_size: 1 + enum: HSCLKMUX + - name: LFCLKMUX + description: Low Frequency Clock Selected. + bit_offset: 6 + bit_size: 2 + enum: LFCLKMUX + - name: LFOSCGOOD + description: LFOSC is Valid. + bit_offset: 11 + bit_size: 1 + - name: HFCLKGOOD + description: HFCLK started correctly. + bit_offset: 8 + bit_size: 1 + - name: LFXTGOOD + description: LFXT started correctly. + bit_offset: 10 + bit_size: 1 + - name: HSCLKSOFF + description: All PLLs, HFCLKs are OFF or DEAD. + bit_offset: 12 + bit_size: 1 + - name: HFCLKOFF + description: HFCLKs is OFF or DEAD. + bit_offset: 13 + bit_size: 1 + - name: CURHSCLKSEL + description: Current HSCLK source, matching HSCLKCFG.HSCLKSEL. + bit_offset: 16 + bit_size: 1 + enum: HSCLKSEL + - name: CURMCLKSEL + description: 'MCLK Clock Source 0: NOT LFCLK, 1:LFCLK.' + bit_offset: 17 + bit_size: 1 + - name: HSCLKDEAD + description: The HSCLK source did not start correctly. + bit_offset: 20 + bit_size: 1 + - name: HSCLKGOOD + description: The HSCLK source started correctly. + bit_offset: 21 + bit_size: 1 + - name: LFCLKFAIL + description: The continuous LFCLK monitor detected a stuck fault. + bit_offset: 23 + bit_size: 1 + - name: FCLMODE + description: SYSOSC FCL MODE ON. + bit_offset: 24 + bit_size: 1 + - name: HFCLKBLKUPD + description: Writes to HFCLKCLKCFG are blocked. + bit_offset: 28 + bit_size: 1 + - name: FCCDONE + bit_offset: 25 + bit_size: 1 + - name: ANACLKERR + description: Error with Anacomp High Speed CP Clock Generation - SYSOSC must not run at 4MHz. + bit_offset: 31 + bit_size: 1 +fieldset/EXLFCTL: + description: EX LF Control -- Only BOR Level Reset will clear. + fields: + - name: SETUSEEXLF + description: Use external LF CLK IN. + # The C-series SVD puts this at bit 2, which contradicts SLAU893 and every other family's + # SYSCTL. A write at bit 2 would never take effect. + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: EXLFCTL_KEY +fieldset/EXRSTPIN: + description: Disable use of external Reset Pin. + fields: + - name: DISABLE + description: Disable External Reset. + # The C-series SVD puts this at bit 2, which contradicts SLAU893 and every other family's + # SYSCTL. A write at bit 2 would never take effect. + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: EXRSTPIN_KEY +fieldset/FCC: + fields: + - name: DATA + bit_offset: 0 + bit_size: 22 +fieldset/FCCCMD: + fields: + - name: GO + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: FCCCMD_KEY +fieldset/GENCLKCFG: + description: General Clock Configuration. + fields: + - name: EXCLKSRC + description: External Clock Source Select. + bit_offset: 0 + bit_size: 3 + enum: EXCLKSRC + - name: EXCLKDIVVAL + bit_offset: 4 + bit_size: 3 + enum: EXCLKDIVVAL + - name: EXCLKDIVEN + description: '1: Enable divide 0: Pass Src Clock Through.' + bit_offset: 7 + bit_size: 1 + - name: MFPCLKSRC + description: Source for MFPCLK Clock. + bit_offset: 9 + bit_size: 1 + enum: MFPCLKSRC + - name: HFCLK4MFPCLKDIV + description: HFCLK Divider 1..16. + bit_offset: 12 + bit_size: 4 + - name: FCCSELCLK + bit_offset: 16 + bit_size: 4 + enum: FCCSELCLK + - name: FCCTRIGSRC + bit_offset: 20 + bit_size: 1 + enum: FCCTRIGSRC + - name: FCCLVLTRIG + bit_offset: 21 + bit_size: 1 + enum: FCCLVLTRIG + - name: ANACPUMPCFG + bit_offset: 22 + bit_size: 2 + enum: ANACPUMPCFG + - name: FCCTRIGCNT + bit_offset: 24 + bit_size: 5 + - name: FCCLFCLKSRC + description: Select between the system LFCLK and the external LFCLK for the FCC. + bit_offset: 29 + bit_size: 1 +fieldset/GENCLKEN: + description: General Clock Enables. + fields: + - name: EXCLKEN + description: Enable the External Clock Output. + bit_offset: 0 + bit_size: 1 + - name: MFPCLKEN + description: Enable the MFCLK. + bit_offset: 4 + bit_size: 1 +fieldset/HFCLKCLKCFG: + description: HFCLK Clock Configuration. + fields: + - name: HFXTTIME + description: HFXT startup time in 64us resolution. + bit_offset: 0 + bit_size: 8 + - name: HFXTRSEL + description: HFXT range select, which must match the crystal's frequency. + bit_offset: 12 + bit_size: 2 + enum: HFXTRSEL + - name: HFCLKFLTCHK + description: Enables the HFCLK startup monitor. + bit_offset: 28 + bit_size: 1 +fieldset/HSCLKCFG: + description: High Speed Clock Source Selection. + fields: + - name: HSCLKSEL + description: Selects the HSCLK source. + bit_offset: 0 + bit_size: 1 + enum: HSCLKSEL +fieldset/HSCLKEN: + description: High Speed Clock Configuration. + fields: + - name: HFXTEN + description: HFXTEN enables or disables the high frequency crystal oscillator (HFXT). + bit_offset: 0 + bit_size: 1 + - name: USEEXTHFCLK + description: Use External Pin as High Frequency Oscillator Source(HFCLK). + bit_offset: 16 + bit_size: 1 +fieldset/IIDX: + description: Event IIDX. + fields: + - name: STAT + description: Interrupt Index Register -- Read Only. + bit_offset: 0 + bit_size: 4 + enum: STAT +fieldset/INT: + description: Event ICLR. + fields: + - name: LFOSCGOOD + description: LFOSC GOOD. + bit_offset: 0 + bit_size: 1 + - name: ANACLKERR + bit_offset: 1 + bit_size: 1 + - name: LFXTGOOD + description: LFXT GOOD. + bit_offset: 2 + bit_size: 1 + - name: HFCLKGOOD + description: HFCLK GOOD. + bit_offset: 3 + bit_size: 1 + - name: HSCLKGOOD + description: HSCLK GOOD. + bit_offset: 4 + bit_size: 1 +fieldset/LFCLKCFG: + description: LFCLK Clock Configuration. + fields: + - name: XT1DRIVE + description: LFXT drive strength. + bit_offset: 0 + bit_size: 2 + enum: XT1DRIVE + - name: MONITOR + description: Enables the LFCLK monitor. + bit_offset: 4 + bit_size: 1 + - name: LOWCAP + description: Enables LFXT low capacitance mode, for a load capacitance under 3pF. + bit_offset: 8 + bit_size: 1 +fieldset/LFXTCTL: + description: LFXT Control. + fields: + - name: STARTLFXT + description: Set to start the low frequency crystal oscillator. Remains set until the next BOR level reset. + bit_offset: 0 + bit_size: 1 + - name: SETUSELFXT + description: Set to switch LFCLK to LFXT. Once set, SETUSELFXT remains set until the next BOR level reset. + bit_offset: 1 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: LFXTCTL_KEY +fieldset/MCLKCFG: + description: Configuration related to MCLK. + fields: + - name: MDIV + description: MDIV Divider 1..16 when RUN-SLEEP. + bit_offset: 0 + bit_size: 4 + - name: FLASHWAIT + description: FLASHWAIT specifies the number of flash wait states when MCLK is sourced from HSCLK. FLASHWAIT has no effect when MCLK is sourced from SYSOSC or LFCLK. + bit_offset: 8 + bit_size: 4 + enum: FLASHWAIT + - name: USEMFTICK + description: USE the MFTICK feature (this will limit MDIV options). + bit_offset: 12 + bit_size: 1 + - name: USEHSCLK + description: High Speed Clock Selected for MCLK (HFCLK, PLL,...). + bit_offset: 16 + bit_size: 1 + - name: USELFCLK + description: Low Frequency Clock Selected for MCLK. + bit_offset: 20 + bit_size: 1 + - name: STOPCLKSTBY + description: STOP ULPCLK Root during STANDBY, Only wake on ASYNC IO Events. + bit_offset: 21 + bit_size: 1 + - name: MCLKDEADCHK + bit_offset: 22 + bit_size: 1 +fieldset/NMI: + fields: + - name: BORLVL + bit_offset: 0 + bit_size: 1 + - name: WWDT0 + bit_offset: 1 + bit_size: 1 + - name: LFCLKFAIL + description: LFXT or EXLF monitor fault. + bit_offset: 2 + bit_size: 1 +fieldset/NMI_IIDX: + fields: + - name: STAT + bit_offset: 0 + bit_size: 3 + enum: NMI_STAT +fieldset/PMODECFG: + description: Power Mode Configurations. + fields: + - name: DSLEEP + description: 'Action to be taken on DEEPSLEEP 0: STOP, 1:STANDBY, 2: SHUTDOWN 3: Reserved.' + bit_offset: 0 + bit_size: 2 + enum: DSLEEP +fieldset/RESETCMD: + description: Execute Reset Command. + fields: + - name: GO + description: Execute Reset defined in RESETLEVEL. + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: RESETCMD_KEY +fieldset/RESETLEVEL: + description: Reset Level for Application Reset Command. + fields: + - name: LEVEL + description: 'Reset Level 0: CPU plus peripherals, 1: BOR level reset to BOOTCODE, 2: Do CPU plus Peripheral reset then BOOTLOADER, 3: Full Power On Reset -- vcore disabled.' + bit_offset: 0 + bit_size: 2 + enum: RESETLEVEL_LEVEL +fieldset/RSTCAUSE: + fields: + - name: ID + bit_offset: 0 + bit_size: 5 + enum: ID +fieldset/SHDNIOREL: + description: Shutdown IO Release Command. + fields: + - name: RELEASE + description: Release IO after Shutdown. + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: SHDNIOREL_KEY +fieldset/SHUTDNSTORE: + description: Shutdown Storage Byte 0. + fields: + - name: DATA + description: Shutdown Storage Byte 0. + bit_offset: 0 + bit_size: 8 +fieldset/SRAMBOUNDARY: + description: SRAM Write Boundary + fields: + - name: ADDR + bit_offset: 5 + bit_size: 15 +fieldset/SWDCFG: + description: Disable SWD. + fields: + - name: DISABLE + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: SWDCFG_KEY +fieldset/SYSOSCCFG: + description: SYSOSC Configuration. + fields: + - name: FREQ + description: 'Freq Target: 0: BASE 1: 4M 2: USER 3: Reserved for Turbo 48MHz.' + bit_offset: 0 + bit_size: 2 + enum: SYSOSCCFG_FREQ + - name: DISABLESTOP + description: Disable SYSOSC when in STOP mode to allow STOP with LFCLK. + bit_offset: 9 + bit_size: 1 + - name: DISABLE + description: Disable SYSOSC to run all power modes with LFCLK. + bit_offset: 10 + bit_size: 1 + - name: BLOCKASYNCALL + description: Block all async requests from enabling SYSOSC via hardware, ie keep running from LFCLK. + bit_offset: 16 + bit_size: 1 + - name: FASTCPUEVENT + description: Block all UART async requests from enabling SYSOSC via hardware, ie keep running from LFCLK if UART is requester. + bit_offset: 17 + bit_size: 1 +fieldset/SYSOSCFCLCTL: + description: SYSOSC Frequency Control Loop with External Resistor. + fields: + - name: SETUSEFCL + description: Use Freq Control Loop. + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: SYSOSCFCLCTL_KEY +fieldset/SYSSTATUS: + fields: + - name: BORCURTHRESHOLD + bit_offset: 2 + bit_size: 2 + - name: BORLVL + bit_offset: 4 + bit_size: 1 + - name: ANACPUMPGOOD + bit_offset: 5 + bit_size: 1 + - name: PMUIREFGOOD + bit_offset: 6 + bit_size: 1 + - name: EXTRSTPINDIS + bit_offset: 12 + bit_size: 1 + - name: SWDCFGDIS + bit_offset: 13 + bit_size: 1 + - name: SHDNIOLOCK + bit_offset: 14 + bit_size: 1 + - name: REBOOTATTEMPTS + bit_offset: 30 + bit_size: 2 +fieldset/SYSSTATUSCLR: + description: Clear sticky bits of SYSSTATUS. + fields: + - name: ALLECC + description: Clear ALL ECC related SYSSTATUS indicators. + # The C-series SVD puts this at bit 2, which contradicts SLAU893 and every other family's + # SYSCTL. A write at bit 2 would never take effect. + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: SYSSTATUSCLR_KEY +fieldset/SYSTEMCFG: + description: System configuration + fields: + - name: WWDTLP0RSTDIS + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: SYSTEMCFG_KEY +fieldset/WRITELOCK: + description: SYSOSC Trim Values specified by user. + fields: + - name: ACTIVE + description: LOCK configuration MMRs from write. + bit_offset: 0 + bit_size: 1 +enum/ANACPUMPCFG: + bit_size: 2 + variants: + - name: ONDEMAND + description: VBOOST is enabled on request from a COMP, GPAMP, or OPA. + value: 0 + - name: ONACTIVE + description: VBOOST is enabled when the device is in RUN or SLEEP mode, or when a COMP/GPAMP/OPA is enabled. + value: 1 + - name: ONALWAYS + description: VBOOST is always enabled. + value: 2 +enum/BORCLRCMD_KEY: + bit_size: 8 + variants: + - name: KEY + value: 199 +enum/DSLEEP: + bit_size: 2 + variants: + - name: STOP + value: 0 + - name: STANDBY + value: 1 + - name: SHUTDOWN + value: 2 +enum/EXCLKDIVVAL: + bit_size: 3 + variants: + - name: DIV2 + value: 0 + - name: DIV4 + value: 1 + - name: DIV6 + value: 2 + - name: DIV8 + value: 3 + - name: DIV10 + value: 4 + - name: DIV12 + value: 5 + - name: DIV14 + value: 6 + - name: DIV16 + value: 7 +enum/EXCLKSRC: + bit_size: 3 + variants: + - name: SYSOSC + value: 0 + - name: ULPCLK + description: 'NOTE: This must be divided in post divider.' + value: 1 + - name: LFCLK + value: 2 + - name: MFCLK + description: 'NOTE: This must be divided in post divider.' + value: 3 + - name: HFCLK + value: 4 + - name: SYSPLLOUT1 + description: 'NOTE: Must be 48MHz or below.' + value: 5 +enum/EXLFCTL_KEY: + bit_size: 8 + variants: + - name: KEY + value: 54 +enum/EXRSTPIN_KEY: + bit_size: 8 + variants: + - name: KEY + value: 30 +enum/FCCCMD_KEY: + bit_size: 8 + variants: + - name: KEY + value: 14 +enum/FCCLVLTRIG: + bit_size: 1 + variants: + - name: RISING + value: 0 + - name: LEVEL + value: 1 +enum/FCCSELCLK: + bit_size: 4 + variants: + - name: MCLK + value: 0 + - name: SYSOSC + value: 1 + - name: HFCLK + value: 2 + - name: CLK_OUT + value: 3 + - name: FCCIN + value: 7 +enum/FCCTRIGSRC: + bit_size: 1 + variants: + - name: EXTERNAL + value: 0 + - name: LFCLK + value: 1 +enum/FLASHWAIT: + bit_size: 4 + variants: + - name: WAIT0 + description: No flash wait states are applied. + value: 0 + - name: WAIT1 + description: One flash wait state is applied. + value: 1 + - name: WAIT2 + description: 2 flash wait states are applied. + value: 2 +enum/HFXTRSEL: + bit_size: 2 + variants: + - name: RANGE4TO8 + description: 4MHz to 8MHz. + value: 0 + - name: RANGE8TO16 + description: Above 8MHz to 16MHz. + value: 1 + - name: RANGE16TO32 + description: Above 16MHz to 32MHz. + value: 2 + - name: RANGE32TO48 + description: Above 32MHz to 48MHz. + value: 3 +enum/HSCLKMUX: + bit_size: 1 + variants: + - name: SYSOSC + value: 0 + - name: HSCLK + value: 1 +enum/HSCLKSEL: + bit_size: 1 + variants: + - name: SYSPLL + description: HSCLK is sourced from the SYSPLL. + value: 0 + - name: HFCLK + description: HSCLK is sourced from HFCLK. + value: 1 +enum/ID: + bit_size: 5 + variants: + - name: NORST + description: No reset since last read. + value: 0 + - name: PORHWFAIL + description: POR- violation, SHUTDNSTOREx or PMU trim parity fault. + value: 1 + - name: POREXNRST + description: NRST triggered POR (>1s hold). + value: 2 + - name: PORSW + description: Software triggered POR. + value: 3 + - name: BORSUPPLY + description: BOR0- violation. + value: 4 + - name: BORWAKESHUTDN + description: SHUTDOWN mode exit. + value: 5 + - name: BOOTCLKFAIL + description: Fatal clock failure. + value: 9 + - name: BOOTEXNRST + description: NRST triggered BOOTRST (<1s hold). + value: 12 + - name: BOOTSW + description: Software triggered BOOTRST. + value: 13 + - name: BOOTWWDT0 + description: WWDT0 violation. + value: 14 + - name: SYSBSLEXIT + description: BSL exit. + value: 16 + - name: SYSBSLENTRY + description: BSL entry. + value: 17 + - name: SYSCPULOCK + description: CPULOCK violation. + value: 21 + - name: SYSDBG + description: Debug triggered SYSRST. + value: 26 + - name: SYSSW + description: Software triggered SYSRST. + value: 27 + - name: CPUDBG + description: Debug triggered CPURST. + value: 28 + - name: CPUSW + description: Software triggered CPURST. + value: 29 +enum/LFCLKMUX: + bit_size: 2 + variants: + - name: LFOSC + description: Internal LFOSC. + value: 0 + - name: LFXT + description: LF Crystal. + value: 1 + - name: EXLF + description: External LFCLK IN. + value: 2 +enum/LFXTCTL_KEY: + bit_size: 8 + variants: + - name: KEY + value: 145 +enum/MFPCLKSRC: + bit_size: 1 + variants: + - name: SYSOSC + value: 0 + - name: HFCLK + value: 1 +enum/NMI_STAT: + bit_size: 3 + variants: + - name: NO_INTR + description: No Interrupt. + value: 0 + - name: BORLVL + value: 1 + - name: WWDT0 + value: 2 + - name: LFCLKFAIL + value: 3 +enum/RESETCMD_KEY: + bit_size: 8 + variants: + - name: KEY + value: 228 +enum/RESETLEVEL_LEVEL: + bit_size: 2 + variants: + - name: CPU + description: Reset CPU plus peripherals only. + value: 0 + - name: BOOT + description: Reset Main Infrastructure including TRIM. + value: 1 + - name: BOOTLOADER + description: Reset Main Infrastructure and Run BOOTLOADER. + value: 2 + - name: POR + description: Reset as a Power On Reset. + value: 3 +enum/SHDNIOREL_KEY: + bit_size: 8 + variants: + - name: KEY + value: 145 +enum/STAT: + bit_size: 4 + variants: + - name: NO_INTR + description: No Interrupt. + value: 0 + - name: LFOSCGOOD + value: 1 + - name: ANACLKERR + value: 2 + - name: LFXTGOOD + value: 3 + - name: HFCLKGOOD + value: 4 + - name: HSCLKGOOD + value: 5 +enum/SWDCFG_KEY: + bit_size: 8 + variants: + - name: KEY + value: 98 +enum/XT1DRIVE: + bit_size: 2 + variants: + - name: LOWEST + value: 0 + - name: LOWER + value: 1 + - name: HIGHER + value: 2 + - name: HIGHEST + value: 3 +enum/SYSOSCCFG_FREQ: + bit_size: 2 + variants: + - name: SYSOSCBASE + value: 0 + - name: SYSOSC4M + value: 1 + - name: SYSOSCUSER + value: 2 +enum/SYSOSCFCLCTL_KEY: + bit_size: 8 + variants: + - name: KEY + value: 42 +enum/SYSOSCFREQ: + bit_size: 2 + variants: + - name: SYSOSC32M + value: 0 + - name: SYSOSC4M + value: 1 + - name: SYSOSCUSER + value: 2 +enum/SYSSTATUSCLR_KEY: + bit_size: 8 + variants: + - name: KEY + value: 206 +enum/SYSTEMCFG_KEY: + bit_size: 8 + variants: + - name: KEY + value: 27 diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index 922dd54..e120f52 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -23,8 +23,11 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ ("mspm0g..0x:rtc", "legacy"), (".*:rtc", "b"), (".*:factoryregion", "v1"), + // SLAU893 describes two C-series SYSCTLs, "SYSCTL_C1103_C1104" and "SYSCTL_C1105_C1106". The + // latter is a superset: it adds the HFXT and LFXT crystal drivers, `MCLKCFG.FLASHWAIT` and the + // HSCLK mux, which its datasheet also specifies and MSPM0C1104's does not. ("mspm0c110x:sysctl", "c110x"), - ("mspm0c1105_c1106:sysctl", "c110x"), + ("mspm0c1105_c1106:sysctl", "c1105_c1106"), ("msps003fx:sysctl", "c110x"), ("mspm0g..0x:sysctl", "g350x_g310x_g150x_g110x"), ("mspm0g..1x:sysctl", "g351x_g151x"), From c88bb15b3c1787cb6f10de594f9c7ef72a740b72 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Tue, 4 Aug 2026 20:54:24 +0100 Subject: [PATCH 021/134] Add a download-docs command to ./d The tools/ scripts read datasheets, errata sheets and reference manuals, and until now there was no way to obtain them. --- d | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/d b/d index f4d4e60..bc9c27e 100755 --- a/d +++ b/d @@ -15,6 +15,32 @@ case "$CMD" in cd ./sources/ git checkout $REV ;; + download-docs) + # Fetch the PDFs the tools/ scripts read, into ./files/. + # + # Not part of `ci`: nothing in the build reads these. They are the source for the checked-in + # data/operating_modes, data/timers and data/errata, and are needed only to regenerate those. + # + # Named after the last segment of the URL, which is the document the data is traceable to. + mkdir -p ./files/ + grep -oE '^ (datasheet|errata|reference_manual)_url: \S+' data/parts.yaml | sort -u | + while read -r kind url; do + case "$kind" in + datasheet_url:) suffix=datasheet ;; + errata_url:) suffix=errata ;; + *) suffix=trm ;; + esac + + out="./files/${url##*/}_$suffix.pdf" + if [ -s "$out" ]; then + echo "have $out" + continue + fi + + echo "fetching $out" + curl -fsSL -o "$out" "$url" || echo "FAILED $url" + done + ;; install-chiptool) cargo install --git https://github.com/embassy-rs/chiptool ;; From 37f51d693729bca603dc73ed0f86eb327c5d1a9a Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Tue, 4 Aug 2026 21:07:27 +0100 Subject: [PATCH 022/134] Add a timer capability extractor Every instance shares tim_v1, so nothing in the register YAMLs or the SVDs says which one has a repeat counter or a 32-bit counter. The datasheet's TIMx configuration table does, and is the only source which is both per-device and complete; the module docs give the counterexamples which rule out the SVDs, the TRM and sysconfig's SYS_FLAVOR. --- tools/timers.py | 302 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 tools/timers.py diff --git a/tools/timers.py b/tools/timers.py new file mode 100644 index 0000000..7167ccd --- /dev/null +++ b/tools/timers.py @@ -0,0 +1,302 @@ +"""Extract the TIMx configuration table from MSPM0 datasheets. + +Every MSPM0 timer shares the `tim_v1` register block, so nothing in the generated PAC says which +instance has a repeat counter, deadband insertion or a 32-bit counter. This reads the table that +does, writing `data/timers/.yaml`. + +Like `tools/operating_modes.py`, this is an offline aid rather than part of the build. Re-run it +after a data source bump rather than editing the YAML by hand. + +Usage: + uv run tools/timers.py [...] # print the table + uv run tools/timers.py --write # regenerate data/timers + +`uv run` installs the dependencies below on its own; a bare `python` needs them on the path. + +The datasheet table is used rather than the SVDs or the TRM because it is the only source which is +both per-device and complete: + +- The SVDs describe timers per instance, but mspm0h321x's describes `TIMG1` and `TIMG2` with the + full advanced-timer register set, which its own datasheet and its pin list both contradict. They + also omit `TIMA1`'s `RC`/`RCLD` registers, which the datasheet says it has. +- Every TRM carries the same portfolio-wide "TIMx Instance Configuration" table, keyed on instance + name, so it repeats the same mistake about mspm0h321x. +- sysconfig's `SYS_FLAVOR` is per instance, but TI reuses a flavour name for different capabilities + between families, so it cannot be read on its own either. + +Capability does not follow the instance name: `TIMG2` is a plain two-channel timer on mspm0l110x and +sysconfig calls the mspm0l112x one `flavorA`, the same flavour as `TIMA0`. +""" + +# /// script +# requires-python = ">=3.10" +# dependencies = ["pdfplumber", "pypdfium2", "pyyaml"] +# /// + +import glob +import json +import re +import sys +from collections.abc import Iterator +from pathlib import Path + +try: + import pdfplumber + import pypdfium2 + import yaml +except ImportError as e: # pragma: no cover + raise SystemExit(f"{e.name} is missing; run this with `uv run` instead of `python`") + +Timer = dict[str, int | bool] +Timers = dict[str, Timer] + +#: The table's caption, which three datasheet generations word differently. +CAPTION = re.compile( + r"TIMx Configurations|TIMx Instance Configuration|Different TIMG Configurations" +) + +LATTICE = {"vertical_strategy": "lines", "horizontal_strategy": "lines"} + +#: Column header -> field, after `key()` has stripped case, spaces and hyphens. +#: +#: Headers wrap mid-word inside their cell ("PRESCALE R", "Counter Resolutio n"), which is why the +#: whitespace goes before the lookup rather than being normalised to single spaces. +COLUMNS = { + "timername": "name", + "timname": "name", + "instance": "name", + "resolution": "bits", + "counterresolution": "bits", + "prescaler": "prescaler", + "repeatcounter": "repeat_counter", + "capture/comparechannels": "ccp_channels", + "ccpchannels(external/internal)": "ccp_channels", + "externalpwmchannels": "external_pwm_channels", + "phaseload": "phase_load", + "shadowload": "shadow_load", + "shadowcc": "shadow_ccs", + "shadowccs": "shadow_ccs", + "deadband": "deadband", + "fault": "fault_handler", + "faulthandler": "fault_handler", + "qei": "qei_hall", + "qei/hallinputmode": "qei_hall", +} + +#: Fields which are a plain yes/no in the table. +FLAGS = ( + "prescaler", + "repeat_counter", + "phase_load", + "shadow_load", + "shadow_ccs", + "deadband", + "fault_handler", + "qei_hall", +) + +#: Cell contents meaning "this instance does not have it". The dash is an en dash in most tables and +#: a hyphen in a few cells of the MSPM0L2117 one. +ABSENT = {"", "-", "โ€“", "โ€”", "n/a", "na", "no"} + +INSTANCE = re.compile(r"^TIM[A-Z]\d+$") + + +def key(header: str) -> str: + """Normalise a column header to its `COLUMNS` key.""" + return re.sub(r"[^a-z0-9/()]", "", header.lower()) + + +def present(cell: str | None) -> bool: + """Whether a yes/no cell says the instance has the feature.""" + return (cell or "").strip().lower() not in ABSENT + + +def count(cell: str | None) -> int: + """First integer in a cell, or 0 if it has none.""" + match = re.search(r"\d+", cell or "") + return int(match.group()) if match else 0 + + +def caption_pages(path: Path) -> list[int]: + """Indices of the pages carrying the table's caption. + + Located with PDFium rather than pdfplumber, for the reason `operating_modes.py` gives. + """ + doc = pypdfium2.PdfDocument(str(path)) + try: + return [ + i for i in range(len(doc)) if CAPTION.search(doc[i].get_textpage().get_text_range()) + ] + finally: + doc.close() + + +def read_tables(path: Path) -> Iterator[tuple[bool, Timers]]: + """Yield `(had_pwm_column, timers)` for each page of the table. + + The MSPM0L2117 table spans two pages and repeats its header on the second, so each page is read + on its own and the caller merges them. + """ + pages = caption_pages(path) + if not pages: + return + + with pdfplumber.open(str(path)) as pdf: + for page in (pdf.pages[i] for i in pages): + for table in page.extract_tables(LATTICE): + had_pwm_column, timers = read_table(table) + if timers: + yield had_pwm_column, timers + + +def read_table(table: list[list[str | None]]) -> tuple[bool, Timers]: + """Read one extracted table, or return nothing if it is not the timer table. + + The cross-trigger maps sit on the same pages and have an instance name in their header too, so + a table is only accepted once its header names a resolution column. + """ + if not table: + return False, {} + + columns = {} + for index, header in enumerate(table[0]): + field = COLUMNS.get(key(header or "")) + if field: + columns[field] = index + if "name" not in columns or "bits" not in columns: + return False, {} + + timers: Timers = {} + for row in table[1:]: + name = (row[columns["name"]] or "").strip() + if not INSTANCE.match(name): + continue + + # The MSPM0L2117 table drops the external PWM count from its TIMA0 row and shifts every + # column after it one to the left, which would otherwise read as a timer with no fault + # handler. A count column holding a yes/no is the tell; the MSPM0L1228 table has the same + # row intact and agrees with the corrected reading. + pwm = columns.get("external_pwm_channels") + shift = 1 if pwm is not None and (row[pwm] or "").strip().lower() == "yes" else 0 + if shift: + print(f"{name}: external PWM count missing, columns after it read one to the left") + + def cell(field: str, row=row, shift=shift) -> str | None: + index = columns.get(field) + if index is None: + return None + if shift and index > columns["external_pwm_channels"]: + index -= shift + return row[index] + + # "4/2" is four channels with a CCP output plus two compare-only ones, which only the + # L-series tables spell out. Only the first is described here: nothing has asked for the + # compare-only channels, and no other datasheet states how many there are. + external, _, _ = (cell("ccp_channels") or "").partition("/") + timer: Timer = {"bits": count(cell("bits")), "ccp_channels": count(external)} + timer.update({flag: present(cell(flag)) for flag in FLAGS}) + + # Only the L-series tables have the column, and the shifted row above has lost it. Elsewhere + # the count follows from deadband insertion, which is what pairs each channel with a + # complementary output. + if pwm is not None and not shift: + timer["external_pwm_channels"] = count(row[pwm]) + else: + timer["external_pwm_channels"] = timer["ccp_channels"] * ( + 2 if timer["deadband"] else 1 + ) + + timers[name] = timer + + return "external_pwm_channels" in columns, timers + + +HEADER = """\ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the {ds} datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. +{derived}""" + +DERIVED = """\ +# `external_pwm_channels` is not a column of this datasheet's table and is derived: deadband +# insertion is what pairs each channel with a complementary output, so it doubles the count. +""" + + +def write(datasheets: str) -> int: + """Regenerate data/timers/*.yaml. Returns the number of problems reported.""" + parts = yaml.safe_load(Path("data/parts.yaml").read_text(encoding="utf-8")) + families = [(f["family"], f["datasheet_url"].rsplit("/", 1)[-1]) for f in parts["families"]] + + known: dict[str, set[str]] = {} + for path in glob.glob("build/data/*.json"): + chip = json.loads(Path(path).read_text(encoding="utf-8")) + known.setdefault(chip["family"], set()).update( + name for name, p in chip["peripherals"].items() if p["type"] == "Tim" + ) + if not known: + raise SystemExit("build/data is empty -- run ./d gen first") + + Path("data/timers").mkdir(parents=True, exist_ok=True) + problems = 0 + + for family, gpn in families: + pdf = Path(datasheets) / f"{gpn}_datasheet.pdf" + if not pdf.exists(): + print(f"{family}: no datasheet for {gpn} in {datasheets}") + problems += 1 + continue + + timers: Timers = {} + had_pwm_column = False + for page_had_pwm_column, page in read_tables(pdf): + had_pwm_column |= page_had_pwm_column + timers.update(page) + + # The datasheet covers several families at once, so it describes instances this family does + # not have. Those are dropped; instances it has and the table misses are reported. + wanted = known.get(family, set()) + timers = {name: timer for name, timer in timers.items() if name in wanted} + for name in sorted(wanted - set(timers)): + print(f"{family}: {name} is a timer of this family but the {gpn} table has no row") + problems += 1 + + body = "" + for name in sorted(timers): + body += f"{name}:\n" + for field, value in timers[name].items(): + body += f" {field}: {str(value).lower() if isinstance(value, bool) else value}\n" + + path = Path(f"data/timers/{family}.yaml") + derived = "" if had_pwm_column else DERIVED + with open(path, "w", encoding="utf-8", newline="\n") as out: + out.write(HEADER.format(ds=gpn.upper(), derived=derived) + "\n" + body) + print(f"{family}: {len(timers)} timers, from {pdf.name}") + + return problems + + +def main(argv: list[str]) -> None: + if "--write" in argv: + i = argv.index("--write") + raise SystemExit(1 if write(argv[i + 1] if i + 1 < len(argv) else ".") else 0) + + paths = [Path(a) for a in argv if not a.startswith("--")] + if not paths: + raise SystemExit(__doc__) + + for path in paths: + print(f"########## {path.name}") + for _, timers in read_tables(path): + for name, timer in timers.items(): + print(f" {name:8} {timer}") + + +if __name__ == "__main__": + main(sys.argv[1:]) From 6e15b4718703a3df5911bfbffe8aa076ee816ce5 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Tue, 4 Aug 2026 21:07:27 +0100 Subject: [PATCH 023/134] Add the extracted timer data Generated by tools/timers.py. Nothing reads it yet. --- data/timers/mspm0c1105_c1106.yaml | 71 ++++++++++++++++++ data/timers/mspm0c110x.yaml | 47 ++++++++++++ data/timers/mspm0g110x.yaml | 95 ++++++++++++++++++++++++ data/timers/mspm0g150x.yaml | 95 ++++++++++++++++++++++++ data/timers/mspm0g151x.yaml | 119 ++++++++++++++++++++++++++++++ data/timers/mspm0g310x.yaml | 95 ++++++++++++++++++++++++ data/timers/mspm0g350x.yaml | 95 ++++++++++++++++++++++++ data/timers/mspm0g351x.yaml | 119 ++++++++++++++++++++++++++++++ data/timers/mspm0g518x.yaml | 71 ++++++++++++++++++ data/timers/mspm0h321x.yaml | 71 ++++++++++++++++++ data/timers/mspm0l110x.yaml | 57 ++++++++++++++ data/timers/mspm0l112x.yaml | 105 ++++++++++++++++++++++++++ data/timers/mspm0l122x.yaml | 81 ++++++++++++++++++++ data/timers/mspm0l130x.yaml | 57 ++++++++++++++ data/timers/mspm0l134x.yaml | 57 ++++++++++++++ data/timers/mspm0l211x.yaml | 105 ++++++++++++++++++++++++++ data/timers/mspm0l222x.yaml | 81 ++++++++++++++++++++ data/timers/msps003fx.yaml | 47 ++++++++++++ 18 files changed, 1468 insertions(+) create mode 100644 data/timers/mspm0c1105_c1106.yaml create mode 100644 data/timers/mspm0c110x.yaml create mode 100644 data/timers/mspm0g110x.yaml create mode 100644 data/timers/mspm0g150x.yaml create mode 100644 data/timers/mspm0g151x.yaml create mode 100644 data/timers/mspm0g310x.yaml create mode 100644 data/timers/mspm0g350x.yaml create mode 100644 data/timers/mspm0g351x.yaml create mode 100644 data/timers/mspm0g518x.yaml create mode 100644 data/timers/mspm0h321x.yaml create mode 100644 data/timers/mspm0l110x.yaml create mode 100644 data/timers/mspm0l112x.yaml create mode 100644 data/timers/mspm0l122x.yaml create mode 100644 data/timers/mspm0l130x.yaml create mode 100644 data/timers/mspm0l134x.yaml create mode 100644 data/timers/mspm0l211x.yaml create mode 100644 data/timers/mspm0l222x.yaml create mode 100644 data/timers/msps003fx.yaml diff --git a/data/timers/mspm0c1105_c1106.yaml b/data/timers/mspm0c1105_c1106.yaml new file mode 100644 index 0000000..dac0645 --- /dev/null +++ b/data/timers/mspm0c1105_c1106.yaml @@ -0,0 +1,71 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0C1106 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. +# `external_pwm_channels` is not a column of this datasheet's table and is derived: deadband +# insertion is what pairs each channel with a complementary output, so it doubles the count. + +TIMA0: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 8 +TIMG1: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG14: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 4 +TIMG2: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG8: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: true + external_pwm_channels: 2 diff --git a/data/timers/mspm0c110x.yaml b/data/timers/mspm0c110x.yaml new file mode 100644 index 0000000..ae0aa41 --- /dev/null +++ b/data/timers/mspm0c110x.yaml @@ -0,0 +1,47 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0C1104 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. +# `external_pwm_channels` is not a column of this datasheet's table and is derived: deadband +# insertion is what pairs each channel with a complementary output, so it doubles the count. + +TIMA0: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 8 +TIMG14: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 4 +TIMG8: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: true + external_pwm_channels: 2 diff --git a/data/timers/mspm0g110x.yaml b/data/timers/mspm0g110x.yaml new file mode 100644 index 0000000..abb032f --- /dev/null +++ b/data/timers/mspm0g110x.yaml @@ -0,0 +1,95 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0G1107 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. +# `external_pwm_channels` is not a column of this datasheet's table and is derived: deadband +# insertion is what pairs each channel with a complementary output, so it doubles the count. + +TIMA0: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 8 +TIMA1: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 4 +TIMG0: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG12: + bits: 32 + ccp_channels: 2 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG6: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG7: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG8: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: true + external_pwm_channels: 2 diff --git a/data/timers/mspm0g150x.yaml b/data/timers/mspm0g150x.yaml new file mode 100644 index 0000000..c87b38b --- /dev/null +++ b/data/timers/mspm0g150x.yaml @@ -0,0 +1,95 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0G1507 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. +# `external_pwm_channels` is not a column of this datasheet's table and is derived: deadband +# insertion is what pairs each channel with a complementary output, so it doubles the count. + +TIMA0: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 8 +TIMA1: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 4 +TIMG0: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG12: + bits: 32 + ccp_channels: 2 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG6: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG7: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG8: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: true + external_pwm_channels: 2 diff --git a/data/timers/mspm0g151x.yaml b/data/timers/mspm0g151x.yaml new file mode 100644 index 0000000..373c060 --- /dev/null +++ b/data/timers/mspm0g151x.yaml @@ -0,0 +1,119 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0G1519 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. +# `external_pwm_channels` is not a column of this datasheet's table and is derived: deadband +# insertion is what pairs each channel with a complementary output, so it doubles the count. + +TIMA0: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 8 +TIMA1: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 4 +TIMG0: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG12: + bits: 32 + ccp_channels: 2 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG14: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 4 +TIMG6: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG7: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG8: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: true + external_pwm_channels: 2 +TIMG9: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: true + external_pwm_channels: 2 diff --git a/data/timers/mspm0g310x.yaml b/data/timers/mspm0g310x.yaml new file mode 100644 index 0000000..4f84cff --- /dev/null +++ b/data/timers/mspm0g310x.yaml @@ -0,0 +1,95 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0G3107 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. +# `external_pwm_channels` is not a column of this datasheet's table and is derived: deadband +# insertion is what pairs each channel with a complementary output, so it doubles the count. + +TIMA0: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 8 +TIMA1: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 4 +TIMG0: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG12: + bits: 32 + ccp_channels: 2 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG6: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG7: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG8: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: true + external_pwm_channels: 2 diff --git a/data/timers/mspm0g350x.yaml b/data/timers/mspm0g350x.yaml new file mode 100644 index 0000000..b07a61b --- /dev/null +++ b/data/timers/mspm0g350x.yaml @@ -0,0 +1,95 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0G3507 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. +# `external_pwm_channels` is not a column of this datasheet's table and is derived: deadband +# insertion is what pairs each channel with a complementary output, so it doubles the count. + +TIMA0: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 8 +TIMA1: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 4 +TIMG0: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG12: + bits: 32 + ccp_channels: 2 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG6: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG7: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG8: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: true + external_pwm_channels: 2 diff --git a/data/timers/mspm0g351x.yaml b/data/timers/mspm0g351x.yaml new file mode 100644 index 0000000..e02d57d --- /dev/null +++ b/data/timers/mspm0g351x.yaml @@ -0,0 +1,119 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0G3519 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. +# `external_pwm_channels` is not a column of this datasheet's table and is derived: deadband +# insertion is what pairs each channel with a complementary output, so it doubles the count. + +TIMA0: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 8 +TIMA1: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 4 +TIMG0: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG12: + bits: 32 + ccp_channels: 2 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG14: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 4 +TIMG6: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG7: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG8: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: true + external_pwm_channels: 2 +TIMG9: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: true + external_pwm_channels: 2 diff --git a/data/timers/mspm0g518x.yaml b/data/timers/mspm0g518x.yaml new file mode 100644 index 0000000..ee02d41 --- /dev/null +++ b/data/timers/mspm0g518x.yaml @@ -0,0 +1,71 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0G5187 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. +# `external_pwm_channels` is not a column of this datasheet's table and is derived: deadband +# insertion is what pairs each channel with a complementary output, so it doubles the count. + +TIMA0: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 8 +TIMB0: + bits: 16 + ccp_channels: 0 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 0 +TIMG0: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG6: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG7: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 diff --git a/data/timers/mspm0h321x.yaml b/data/timers/mspm0h321x.yaml new file mode 100644 index 0000000..23fcca7 --- /dev/null +++ b/data/timers/mspm0h321x.yaml @@ -0,0 +1,71 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0H3216 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. +# `external_pwm_channels` is not a column of this datasheet's table and is derived: deadband +# insertion is what pairs each channel with a complementary output, so it doubles the count. + +TIMA0: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 8 +TIMG1: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG14: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 4 +TIMG2: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG8: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: true + external_pwm_channels: 2 diff --git a/data/timers/mspm0l110x.yaml b/data/timers/mspm0l110x.yaml new file mode 100644 index 0000000..2d1d2aa --- /dev/null +++ b/data/timers/mspm0l110x.yaml @@ -0,0 +1,57 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0L1106 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. + +TIMG0: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG1: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG2: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG4: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 diff --git a/data/timers/mspm0l112x.yaml b/data/timers/mspm0l112x.yaml new file mode 100644 index 0000000..e73f94f --- /dev/null +++ b/data/timers/mspm0l112x.yaml @@ -0,0 +1,105 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0L2117 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. + +TIMA0: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 8 +TIMB0: + bits: 16 + ccp_channels: 0 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 0 +TIMB1: + bits: 16 + ccp_channels: 0 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 0 +TIMB2: + bits: 16 + ccp_channels: 0 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 0 +TIMB3: + bits: 16 + ccp_channels: 0 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 0 +TIMG1: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 0 +TIMG14: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 0 +TIMG2: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 0 diff --git a/data/timers/mspm0l122x.yaml b/data/timers/mspm0l122x.yaml new file mode 100644 index 0000000..b41fee1 --- /dev/null +++ b/data/timers/mspm0l122x.yaml @@ -0,0 +1,81 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0L1228 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. + +TIMA0: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 8 +TIMG0: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG12: + bits: 32 + ccp_channels: 2 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG4: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG5: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG8: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: true + external_pwm_channels: 2 diff --git a/data/timers/mspm0l130x.yaml b/data/timers/mspm0l130x.yaml new file mode 100644 index 0000000..f334eed --- /dev/null +++ b/data/timers/mspm0l130x.yaml @@ -0,0 +1,57 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0L1306 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. + +TIMG0: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG1: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG2: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG4: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 diff --git a/data/timers/mspm0l134x.yaml b/data/timers/mspm0l134x.yaml new file mode 100644 index 0000000..590e9aa --- /dev/null +++ b/data/timers/mspm0l134x.yaml @@ -0,0 +1,57 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0L1346 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. + +TIMG0: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG1: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG2: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG4: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 diff --git a/data/timers/mspm0l211x.yaml b/data/timers/mspm0l211x.yaml new file mode 100644 index 0000000..e73f94f --- /dev/null +++ b/data/timers/mspm0l211x.yaml @@ -0,0 +1,105 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0L2117 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. + +TIMA0: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 8 +TIMB0: + bits: 16 + ccp_channels: 0 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 0 +TIMB1: + bits: 16 + ccp_channels: 0 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 0 +TIMB2: + bits: 16 + ccp_channels: 0 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 0 +TIMB3: + bits: 16 + ccp_channels: 0 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 0 +TIMG1: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 0 +TIMG14: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 0 +TIMG2: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 0 diff --git a/data/timers/mspm0l222x.yaml b/data/timers/mspm0l222x.yaml new file mode 100644 index 0000000..2de90fc --- /dev/null +++ b/data/timers/mspm0l222x.yaml @@ -0,0 +1,81 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0L2228 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. + +TIMA0: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 8 +TIMG0: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG12: + bits: 32 + ccp_channels: 2 + prescaler: false + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG4: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG5: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: true + shadow_ccs: true + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 2 +TIMG8: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: true + external_pwm_channels: 2 diff --git a/data/timers/msps003fx.yaml b/data/timers/msps003fx.yaml new file mode 100644 index 0000000..ae0aa41 --- /dev/null +++ b/data/timers/msps003fx.yaml @@ -0,0 +1,47 @@ +# What each timer instance of this family can do. +# +# GENERATED by `tools/timers.py --write` from the TIMx configuration table of the MSPM0C1104 datasheet. +# Re-run the tool rather than editing by hand. +# +# `ccp_channels` counts the capture/compare channels with a CCPx output, which the table calls +# external. The compare-only channels behind them (CC_45, TIMA only) are not recorded: only the +# L-series tables state how many there are. +# `external_pwm_channels` is not a column of this datasheet's table and is derived: deadband +# insertion is what pairs each channel with a complementary output, so it doubles the count. + +TIMA0: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: true + phase_load: true + shadow_load: true + shadow_ccs: true + deadband: true + fault_handler: true + qei_hall: false + external_pwm_channels: 8 +TIMG14: + bits: 16 + ccp_channels: 4 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: false + external_pwm_channels: 4 +TIMG8: + bits: 16 + ccp_channels: 2 + prescaler: true + repeat_counter: false + phase_load: false + shadow_load: false + shadow_ccs: false + deadband: false + fault_handler: false + qei_hall: true + external_pwm_channels: 2 From 3186cb7b1f86c2deb11d0cf9773bd7cb98894214 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Tue, 4 Aug 2026 21:07:27 +0100 Subject: [PATCH 024/134] Add a wake-up time extractor How long a device takes to reach RUN bounds which sleep mode is usable before a given deadline. Note the figures are typical rather than guaranteed: the datasheet's value cell spans its MIN, TYP and MAX columns. STOP2 is also not slower to leave than STOP1 everywhere, since it stops SYSOSC rather than throttling it. --- tools/wakeup.py | 248 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 tools/wakeup.py diff --git a/tools/wakeup.py b/tools/wakeup.py new file mode 100644 index 0000000..0f7b1bc --- /dev/null +++ b/tools/wakeup.py @@ -0,0 +1,248 @@ +"""Extract the wake-up timing table from MSPM0 datasheets. + +How long a device takes to reach RUN from each sleep mode is what a consumer needs to decide whether a +sleep pays off before its next deadline, and it has no machine-readable source. This writes +`data/wakeup/.yaml`. + +Like `tools/operating_modes.py`, this is an offline aid rather than part of the build. Re-run it after +a data source bump rather than editing the YAML by hand. + +Usage: + uv run tools/wakeup.py [...] # print the table + uv run tools/wakeup.py --write # regenerate data/wakeup + +`uv run` installs the dependencies below on its own; a bare `python` needs them on the path. + +Three things about the table make it awkward to read: + +- **The figure is not a maximum.** Its cell spans the MIN, TYP and MAX columns, so TI gives one + unqualified number per mode. Treat it as typical. +- **One cell can name two modes.** The MSPM0H3216 table puts "Wakeup time from STOP0 ... Wakeup time + from STOP2 ..." in a single description cell with the two figures on consecutive rows, so modes are + read from the description text and figures are matched to them in order. +- **Not every figure is a time.** Several tables give SLEEP0 as "2 cycles", which is not convertible to + a fixed duration without assuming a clock rate, so that mode is left absent. Any *other* non-time + unit is reported, since it would mean the table has changed shape. +""" + +# /// script +# requires-python = ">=3.10" +# dependencies = ["pdfplumber", "pypdfium2", "pyyaml"] +# /// + +import re +import sys +from collections.abc import Iterator +from pathlib import Path + +try: + import pdfplumber + import pypdfium2 + import yaml +except ImportError as e: # pragma: no cover + raise SystemExit(f"{e.name} is missing; run this with `uv run` instead of `python`") + +Wake = dict[str, int] + +CAPTION = "Wakeup time from" + +LATTICE = {"vertical_strategy": "lines", "horizontal_strategy": "lines"} + +#: A wake-up row's description, which names the mode being left. The sub-mode digit is absent on +#: SHUTDOWN and, in a few tables, on STANDBY. +ROW = re.compile(r"Wakeup time from (?PSLEEP|STOP|STANDBY|SHUTDOWN)(?P\d?)\s+to\s+RUN") + +#: Time units the figure may be given in, as a multiplier to nanoseconds. +UNITS = {"ns": 1, "us": 1_000, "ยตs": 1_000, "ms": 1_000_000} + +#: The unit several tables give SLEEP0 in. A count of CPU cycles is not a fixed duration, so that mode +#: is left absent rather than converted at some assumed clock rate. +CYCLES = "cycles" + +#: The modes recorded, in the order they are written out. A consumer picking a sleep level cares about +#: STOP and STANDBY; SLEEP is plain `WFI` and SHUTDOWN is a reset rather than a wake. +MODES = ("sleep0", "sleep1", "sleep2", "stop0", "stop1", "stop2", "standby0", "standby1", "shutdown") + + +def caption_pages(path: Path) -> list[int]: + """Indices of the pages carrying the wake-up table.""" + doc = pypdfium2.PdfDocument(str(path)) + try: + return [i for i in range(len(doc)) if CAPTION in doc[i].get_textpage().get_text_range()] + finally: + doc.close() + + +def read_tables(path: Path) -> Iterator[tuple[Wake, list[str]]]: + """Yield `(wake times in ns, problems)` for each wake-up table found.""" + for page_index in caption_pages(path): + with pdfplumber.open(str(path)) as pdf: + for table in pdf.pages[page_index].extract_tables(LATTICE): + if not any(CAPTION in str(cell) for row in table for cell in row): + continue + yield read_table(table) + + +def modes_named(text: str) -> list[str]: + """Sub-modes a description names. + + A few tables write `SLEEP` and `STANDBY` with no sub-mode digit where they give one figure for the + whole mode, so those expand to every sub-mode the digit could have been. + """ + found = [] + for match in ROW.finditer(text): + mode, sub = match["mode"].lower(), match["sub"] + if sub: + found.append(f"{mode}{sub}") + elif mode == "standby": + found.extend(("standby0", "standby1")) + elif mode == "sleep": + found.append("sleep0") + else: + found.append(mode) + return found + + +def read_table(table: list[list[str | None]]) -> tuple[Wake, list[str]]: + """Read one wake-up table. + + A row starts a group when its description names any mode; rows after it carrying only a figure + belong to that group, which is how the tables that merge two modes into one description cell are + read. A sub-heading row ends the group, so the startup-timing figures below the wake-up section do + not get attributed to SHUTDOWN. + """ + wake: Wake = {} + problems: list[str] = [] + modes: list[str] = [] + figures: list[tuple[float, str]] = [] + + def flush() -> None: + nonlocal modes, figures + if not modes: + return + + if len(figures) == len(modes): + pairs = list(zip(modes, figures)) + elif len(figures) == 1: + # One figure covering several sub-modes, as the digitless rows give. + pairs = [(mode, figures[0]) for mode in modes] + elif len(modes) == 1: + # One mode measured under several test conditions, such as SHUTDOWN with fast boot on and + # off. The slowest is the one worth planning around. + pairs = [(modes[0], max(figures, key=lambda f: f[0] * UNITS.get(f[1], 0)))] + else: + problems.append(f"{'/'.join(modes)}: {len(figures)} figures for {len(modes)} modes") + modes, figures = [], [] + return + + for mode, (value, unit) in pairs: + scale = UNITS.get(unit) + if scale is None: + # SLEEP0 in CPU cycles is expected and documented, not something to fix. Any other + # unit is a table change worth looking at. + if unit != CYCLES: + problems.append(f"{mode}: figure is in {unit!r}, not a time") + continue + ns = round(value * scale) + # A mode stated twice keeps the slower figure. + wake[mode] = max(wake.get(mode, 0), ns) + + modes, figures = [], [] + + unit_of_table = "" + + for row in table: + cells = [(c or "").replace("\n", " ").strip() for c in row] + numbers = [c for c in cells if re.fullmatch(r"\d+(?:\.\d+)?", c)] + found = modes_named(" ".join(cells)) + + # A sub-heading is a lone label with no figure, and closes whatever group was open. + if not found and not numbers: + flush() + continue + + # The unit is the last column. It is left empty where one unit is merged across several rows, + # so the last one seen carries forward. + if cells and cells[-1] and cells[-1] not in numbers: + unit_of_table = cells[-1] + + if found: + flush() + modes = found + + if numbers and modes: + figures.append((float(numbers[0]), unit_of_table)) + + flush() + return wake, problems + + +HEADER = """\ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the {ds} datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. +""" + + +def write(datasheets: str) -> int: + """Regenerate data/wakeup/*.yaml. Returns the number of problems reported.""" + parts = yaml.safe_load(Path("data/parts.yaml").read_text(encoding="utf-8")) + families = [(f["family"], f["datasheet_url"].rsplit("/", 1)[-1]) for f in parts["families"]] + + Path("data/wakeup").mkdir(parents=True, exist_ok=True) + problems = 0 + + for family, gpn in families: + pdf = Path(datasheets) / f"{gpn}_datasheet.pdf" + if not pdf.exists(): + print(f"{family}: no datasheet for {gpn} in {datasheets}") + problems += 1 + continue + + wake: Wake = {} + for table, reported in read_tables(pdf): + wake.update(table) + for problem in reported: + print(f"{family}: {problem}") + problems += 1 + + if not wake: + print(f"{family}: no wake-up timing table found in {pdf.name}") + problems += 1 + continue + + body = "".join(f"{mode}: {wake[mode]}\n" for mode in MODES if mode in wake) + path = Path(f"data/wakeup/{family}.yaml") + with open(path, "w", encoding="utf-8", newline="\n") as out: + out.write(HEADER.format(ds=gpn.upper()) + "\n" + body) + print(f"{family}: {len(wake)} modes, from {pdf.name}") + + return problems + + +def main(argv: list[str]) -> None: + if "--write" in argv: + i = argv.index("--write") + raise SystemExit(1 if write(argv[i + 1] if i + 1 < len(argv) else ".") else 0) + + paths = [Path(a) for a in argv if not a.startswith("--")] + if not paths: + raise SystemExit(__doc__) + + for path in paths: + print(f"########## {path.name}") + for wake, problems in read_tables(path): + for mode in MODES: + if mode in wake: + print(f" {mode:10} {wake[mode] / 1000:>8.2f} us") + for problem in problems: + print(f" ! {problem}") + + +if __name__ == "__main__": + main(sys.argv[1:]) From 706def490e2ef64a9a7baa8bfc374476a5124898 Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Tue, 4 Aug 2026 21:07:27 +0100 Subject: [PATCH 025/134] Add the extracted wake-up times Generated by tools/wakeup.py. Nothing reads it yet. --- data/wakeup/mspm0c1105_c1106.yaml | 14 ++++++++++++++ data/wakeup/mspm0c110x.yaml | 14 ++++++++++++++ data/wakeup/mspm0g110x.yaml | 18 ++++++++++++++++++ data/wakeup/mspm0g150x.yaml | 18 ++++++++++++++++++ data/wakeup/mspm0g151x.yaml | 18 ++++++++++++++++++ data/wakeup/mspm0g310x.yaml | 18 ++++++++++++++++++ data/wakeup/mspm0g350x.yaml | 18 ++++++++++++++++++ data/wakeup/mspm0g351x.yaml | 18 ++++++++++++++++++ data/wakeup/mspm0g518x.yaml | 18 ++++++++++++++++++ data/wakeup/mspm0h321x.yaml | 15 +++++++++++++++ data/wakeup/mspm0l110x.yaml | 14 ++++++++++++++ data/wakeup/mspm0l112x.yaml | 16 ++++++++++++++++ data/wakeup/mspm0l122x.yaml | 18 ++++++++++++++++++ data/wakeup/mspm0l130x.yaml | 14 ++++++++++++++ data/wakeup/mspm0l134x.yaml | 14 ++++++++++++++ data/wakeup/mspm0l211x.yaml | 16 ++++++++++++++++ data/wakeup/mspm0l222x.yaml | 18 ++++++++++++++++++ data/wakeup/msps003fx.yaml | 14 ++++++++++++++ 18 files changed, 293 insertions(+) create mode 100644 data/wakeup/mspm0c1105_c1106.yaml create mode 100644 data/wakeup/mspm0c110x.yaml create mode 100644 data/wakeup/mspm0g110x.yaml create mode 100644 data/wakeup/mspm0g150x.yaml create mode 100644 data/wakeup/mspm0g151x.yaml create mode 100644 data/wakeup/mspm0g310x.yaml create mode 100644 data/wakeup/mspm0g350x.yaml create mode 100644 data/wakeup/mspm0g351x.yaml create mode 100644 data/wakeup/mspm0g518x.yaml create mode 100644 data/wakeup/mspm0h321x.yaml create mode 100644 data/wakeup/mspm0l110x.yaml create mode 100644 data/wakeup/mspm0l112x.yaml create mode 100644 data/wakeup/mspm0l122x.yaml create mode 100644 data/wakeup/mspm0l130x.yaml create mode 100644 data/wakeup/mspm0l134x.yaml create mode 100644 data/wakeup/mspm0l211x.yaml create mode 100644 data/wakeup/mspm0l222x.yaml create mode 100644 data/wakeup/msps003fx.yaml diff --git a/data/wakeup/mspm0c1105_c1106.yaml b/data/wakeup/mspm0c1105_c1106.yaml new file mode 100644 index 0000000..9c2867a --- /dev/null +++ b/data/wakeup/mspm0c1105_c1106.yaml @@ -0,0 +1,14 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0C1106 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +stop0: 14000 +stop2: 13000 +standby0: 15000 +standby1: 15000 +shutdown: 265000 diff --git a/data/wakeup/mspm0c110x.yaml b/data/wakeup/mspm0c110x.yaml new file mode 100644 index 0000000..38a8678 --- /dev/null +++ b/data/wakeup/mspm0c110x.yaml @@ -0,0 +1,14 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0C1104 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +stop0: 14000 +stop2: 15000 +standby0: 20000 +standby1: 20000 +shutdown: 112000 diff --git a/data/wakeup/mspm0g110x.yaml b/data/wakeup/mspm0g110x.yaml new file mode 100644 index 0000000..37e73cb --- /dev/null +++ b/data/wakeup/mspm0g110x.yaml @@ -0,0 +1,18 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0G1107 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +sleep0: 1300 +sleep1: 1500 +sleep2: 2100 +stop0: 12100 +stop1: 13500 +stop2: 12900 +standby0: 15200 +standby1: 15200 +shutdown: 252000 diff --git a/data/wakeup/mspm0g150x.yaml b/data/wakeup/mspm0g150x.yaml new file mode 100644 index 0000000..5d49a33 --- /dev/null +++ b/data/wakeup/mspm0g150x.yaml @@ -0,0 +1,18 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0G1507 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +sleep0: 1300 +sleep1: 1500 +sleep2: 2100 +stop0: 12100 +stop1: 13500 +stop2: 12900 +standby0: 15200 +standby1: 15200 +shutdown: 252000 diff --git a/data/wakeup/mspm0g151x.yaml b/data/wakeup/mspm0g151x.yaml new file mode 100644 index 0000000..3c02cee --- /dev/null +++ b/data/wakeup/mspm0g151x.yaml @@ -0,0 +1,18 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0G1519 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +sleep0: 1400 +sleep1: 1600 +sleep2: 2200 +stop0: 8000 +stop1: 10000 +stop2: 10000 +standby0: 11400 +standby1: 11400 +shutdown: 315000 diff --git a/data/wakeup/mspm0g310x.yaml b/data/wakeup/mspm0g310x.yaml new file mode 100644 index 0000000..783edbb --- /dev/null +++ b/data/wakeup/mspm0g310x.yaml @@ -0,0 +1,18 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0G3107 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +sleep0: 1300 +sleep1: 1500 +sleep2: 2100 +stop0: 12100 +stop1: 13500 +stop2: 12900 +standby0: 15200 +standby1: 15200 +shutdown: 252000 diff --git a/data/wakeup/mspm0g350x.yaml b/data/wakeup/mspm0g350x.yaml new file mode 100644 index 0000000..b381af8 --- /dev/null +++ b/data/wakeup/mspm0g350x.yaml @@ -0,0 +1,18 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0G3507 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +sleep0: 1300 +sleep1: 1500 +sleep2: 2100 +stop0: 12100 +stop1: 13500 +stop2: 12900 +standby0: 15200 +standby1: 15200 +shutdown: 252000 diff --git a/data/wakeup/mspm0g351x.yaml b/data/wakeup/mspm0g351x.yaml new file mode 100644 index 0000000..c0fcef3 --- /dev/null +++ b/data/wakeup/mspm0g351x.yaml @@ -0,0 +1,18 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0G3519 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +sleep0: 1400 +sleep1: 1600 +sleep2: 2200 +stop0: 8000 +stop1: 10000 +stop2: 10000 +standby0: 11400 +standby1: 11400 +shutdown: 315000 diff --git a/data/wakeup/mspm0g518x.yaml b/data/wakeup/mspm0g518x.yaml new file mode 100644 index 0000000..2ca2c3c --- /dev/null +++ b/data/wakeup/mspm0g518x.yaml @@ -0,0 +1,18 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0G5187 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +sleep0: 1500 +sleep1: 1800 +sleep2: 2400 +stop0: 8000 +stop1: 10000 +stop2: 10000 +standby0: 10900 +standby1: 10900 +shutdown: 313000 diff --git a/data/wakeup/mspm0h321x.yaml b/data/wakeup/mspm0h321x.yaml new file mode 100644 index 0000000..d970764 --- /dev/null +++ b/data/wakeup/mspm0h321x.yaml @@ -0,0 +1,15 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0H3216 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +sleep1: 1700 +sleep2: 2300 +stop0: 8100 +stop2: 8800 +standby0: 12800 +standby1: 12800 diff --git a/data/wakeup/mspm0l110x.yaml b/data/wakeup/mspm0l110x.yaml new file mode 100644 index 0000000..963f937 --- /dev/null +++ b/data/wakeup/mspm0l110x.yaml @@ -0,0 +1,14 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0L1106 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +stop1: 14000 +stop2: 13000 +standby0: 15000 +standby1: 15000 +shutdown: 230000 diff --git a/data/wakeup/mspm0l112x.yaml b/data/wakeup/mspm0l112x.yaml new file mode 100644 index 0000000..c57fdaa --- /dev/null +++ b/data/wakeup/mspm0l112x.yaml @@ -0,0 +1,16 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0L2117 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +sleep1: 1600 +sleep2: 2200 +stop0: 7200 +stop2: 8100 +standby0: 10000 +standby1: 10000 +shutdown: 310000 diff --git a/data/wakeup/mspm0l122x.yaml b/data/wakeup/mspm0l122x.yaml new file mode 100644 index 0000000..cd5b6b9 --- /dev/null +++ b/data/wakeup/mspm0l122x.yaml @@ -0,0 +1,18 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0L1228 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +sleep0: 1500 +sleep1: 2100 +sleep2: 2500 +stop0: 12500 +stop1: 14600 +stop2: 13500 +standby0: 15700 +standby1: 15700 +shutdown: 322000 diff --git a/data/wakeup/mspm0l130x.yaml b/data/wakeup/mspm0l130x.yaml new file mode 100644 index 0000000..97bb669 --- /dev/null +++ b/data/wakeup/mspm0l130x.yaml @@ -0,0 +1,14 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0L1306 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +stop1: 14000 +stop2: 13000 +standby0: 15000 +standby1: 15000 +shutdown: 230000 diff --git a/data/wakeup/mspm0l134x.yaml b/data/wakeup/mspm0l134x.yaml new file mode 100644 index 0000000..0309561 --- /dev/null +++ b/data/wakeup/mspm0l134x.yaml @@ -0,0 +1,14 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0L1346 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +stop1: 14000 +stop2: 13000 +standby0: 15000 +standby1: 15000 +shutdown: 230000 diff --git a/data/wakeup/mspm0l211x.yaml b/data/wakeup/mspm0l211x.yaml new file mode 100644 index 0000000..c57fdaa --- /dev/null +++ b/data/wakeup/mspm0l211x.yaml @@ -0,0 +1,16 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0L2117 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +sleep1: 1600 +sleep2: 2200 +stop0: 7200 +stop2: 8100 +standby0: 10000 +standby1: 10000 +shutdown: 310000 diff --git a/data/wakeup/mspm0l222x.yaml b/data/wakeup/mspm0l222x.yaml new file mode 100644 index 0000000..1e8800f --- /dev/null +++ b/data/wakeup/mspm0l222x.yaml @@ -0,0 +1,18 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0L2228 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +sleep0: 1500 +sleep1: 2100 +sleep2: 2500 +stop0: 12500 +stop1: 14600 +stop2: 13500 +standby0: 15700 +standby1: 15700 +shutdown: 322000 diff --git a/data/wakeup/msps003fx.yaml b/data/wakeup/msps003fx.yaml new file mode 100644 index 0000000..38a8678 --- /dev/null +++ b/data/wakeup/msps003fx.yaml @@ -0,0 +1,14 @@ +# How long this family takes to reach RUN from each sleep mode, in nanoseconds. +# +# GENERATED by `tools/wakeup.py --write` from the wake-up timing table of the MSPM0C1104 datasheet. +# Re-run the tool rather than editing by hand. +# +# The datasheet gives one unqualified figure per mode -- its cell spans the MIN, TYP and MAX columns -- +# so these are typical times, not guaranteed ceilings. A mode is absent when the device does not have +# it, or when the datasheet states it in clock cycles rather than a time. + +stop0: 14000 +stop2: 15000 +standby0: 20000 +standby1: 20000 +shutdown: 112000 From 2b04ecde988ac13389a96ec9c5fa457b35077e0f Mon Sep 17 00:00:00 2001 From: CordlessCoder Date: Tue, 4 Aug 2026 21:07:27 +0100 Subject: [PATCH 026/134] Add an errata extractor A missing workaround is silent, so this is the one kind of data whose absence does not show up as a build failure. Only the functional advisories are taken. The other advisory sections start partway down pages the functional one also uses and are told apart by the section heading above each table, since not all of them are captioned. --- tools/errata.py | 173 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 tools/errata.py diff --git a/tools/errata.py b/tools/errata.py new file mode 100644 index 0000000..e11c052 --- /dev/null +++ b/tools/errata.py @@ -0,0 +1,173 @@ +"""Extract the functional advisory table from MSPM0 errata sheets. + +Which errata apply to a device is not derivable from anything else, and getting it wrong is quiet: +the symptom of a missing workaround is a device that mostly works. This writes +`data/errata/.yaml`. + +Like `tools/operating_modes.py`, this is an offline aid rather than part of the build. Re-run it after +a data source bump rather than editing the YAML by hand. + +Usage: + uv run tools/errata.py [...] # print the table + uv run tools/errata.py --write # regenerate data/errata + +`uv run` installs the dependencies below on its own; a bare `python` needs them on the path. + +Only section 1, "Functional Advisories", is read: TI defines those as the ones which "affect the +device's operation, function, or parametrics", which is the set a driver has to work around. The +sheets also carry preprogrammed-software, debug-only and fixed-by-compiler advisories, and those +sections start on the same pages, so tables are matched to their caption rather than taken in order. + +Each column of the table is a silicon revision. A device is reported as affected when **any** revision +is, since a driver built for a part has to run on whichever revision it meets. +""" + +# /// script +# requires-python = ">=3.10" +# dependencies = ["pdfplumber", "pyyaml"] +# /// + +import re +import sys +from collections.abc import Iterator +from pathlib import Path + +try: + import pdfplumber + import yaml +except ImportError as e: # pragma: no cover + raise SystemExit(f"{e.name} is missing; run this with `uv run` instead of `python`") + +SECTION = "Functional Advisories" + +#: A numbered top-level section heading, as distinct from the "Table 1-1. Functional Advisories +#: (continued)" caption which repeats the title, and from the dotted table-of-contents entry. +#: +#: The section's number is not fixed: SLAZ741 numbers its advisory sections 2 to 5 where the others +#: use 1 to 4, so the title is what identifies it. Not every advisory table has a caption at all -- +#: the debug-only table of SLAZ742 does not -- so the headings are what the sections are told apart by. +HEADING = re.compile(r"^\d+\s+(?P[A-Z][A-Za-z][A-Za-z ]*[a-z])$") + +LATTICE = {"vertical_strategy": "lines", "horizontal_strategy": "lines"} + +#: An errata identifier. `CRC/CRCP_ERR_01` is one row naming two peripherals. +ERRATUM = re.compile(r"^(?P<peripherals>[A-Z0-9]+(?:/[A-Z0-9]+)*)_ERR_(?P<number>\d+)$") + +#: A cell saying the revision in that column is affected. +AFFECTED = ("โœ“", "โœ”", "yes") + + +def read_tables(path: Path) -> Iterator[set[str]]: + """Yield the errata named by each table inside the functional advisory section. + + The later advisory sections start partway down a page the functional one also uses, so each table + is placed by the last section heading above it, tracked across pages. The section runs until the + next heading, so reading stops there rather than continuing into the per-erratum descriptions -- + those have `Errata Number` tables of their own. + """ + inside = False + + with pdfplumber.open(str(path)) as pdf: + for page in pdf.pages: + headings = [ + (line["bottom"], HEADING.match(line["text"].strip())["title"]) + for line in page.extract_text_lines() + if HEADING.match(line["text"].strip()) + ] + tables = [(table.bbox[1], table) for table in page.find_tables(LATTICE)] + + # Walk the page top to bottom so a heading applies to the tables which follow it. + for _, item in sorted(headings + tables, key=lambda entry: entry[0]): + if isinstance(item, str): + if inside and item != SECTION: + return + inside = item == SECTION + elif inside: + yield read_table(item.extract()) + + +def read_table(table: list[list[str | None]]) -> set[str]: + """Errata in one table which affect at least one silicon revision.""" + if not table or not table[0] or (table[0][0] or "").strip() != "Errata Number": + return set() + + affected = set() + for row in table[1:]: + name = (row[0] or "").replace("\n", "").strip() + match = ERRATUM.match(name) + if not match: + continue + + # Every column after the first is one silicon revision. + if not any((cell or "").strip().lower() in AFFECTED for cell in row[1:]): + continue + + number = match["number"] + affected.update(f"{p}_ERR_{number}" for p in match["peripherals"].split("/")) + + return affected + + +HEADER = """\ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of {doc}. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. +""" + + +def write(pdfs: str) -> int: + """Regenerate data/errata/*.yaml. Returns the number of problems reported.""" + parts = yaml.safe_load(Path("data/parts.yaml").read_text(encoding="utf-8")) + families = [(f["family"], f["errata_url"].rsplit("/", 1)[-1]) for f in parts["families"]] + + Path("data/errata").mkdir(parents=True, exist_ok=True) + problems = 0 + + for family, doc in families: + pdf = Path(pdfs) / f"{doc}_errata.pdf" + if not pdf.exists(): + print(f"{family}: no errata sheet for {doc} in {pdfs}") + problems += 1 + continue + + errata: set[str] = set() + for table in read_tables(pdf): + errata |= table + + if not errata: + print(f"{family}: no functional advisories found in {pdf.name}") + problems += 1 + continue + + body = "".join(f"- {name}\n" for name in sorted(errata)) + path = Path(f"data/errata/{family}.yaml") + with open(path, "w", encoding="utf-8", newline="\n") as out: + out.write(HEADER.format(doc=doc.upper()) + "\n" + body) + print(f"{family}: {len(errata)} errata, from {pdf.name}") + + return problems + + +def main(argv: list[str]) -> None: + if "--write" in argv: + i = argv.index("--write") + raise SystemExit(1 if write(argv[i + 1] if i + 1 < len(argv) else ".") else 0) + + paths = [Path(a) for a in argv if not a.startswith("--")] + if not paths: + raise SystemExit(__doc__) + + for path in paths: + print(f"########## {path.name}") + errata: set[str] = set() + for table in read_tables(path): + errata |= table + print(" " + " ".join(sorted(errata))) + + +if __name__ == "__main__": + main(sys.argv[1:]) From 886f4252bd20319b4ea9a76260d572a8a76021a7 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Tue, 4 Aug 2026 21:07:27 +0100 Subject: [PATCH 027/134] Add the extracted errata Generated by tools/errata.py. Nothing reads it yet. --- data/errata/mspm0c1105_c1106.yaml | 41 ++++++++++++++ data/errata/mspm0c110x.yaml | 64 ++++++++++++++++++++++ data/errata/mspm0g110x.yaml | 90 +++++++++++++++++++++++++++++++ data/errata/mspm0g150x.yaml | 90 +++++++++++++++++++++++++++++++ data/errata/mspm0g151x.yaml | 78 +++++++++++++++++++++++++++ data/errata/mspm0g310x.yaml | 90 +++++++++++++++++++++++++++++++ data/errata/mspm0g350x.yaml | 90 +++++++++++++++++++++++++++++++ data/errata/mspm0g351x.yaml | 78 +++++++++++++++++++++++++++ data/errata/mspm0g518x.yaml | 46 ++++++++++++++++ data/errata/mspm0h321x.yaml | 49 +++++++++++++++++ data/errata/mspm0l110x.yaml | 89 ++++++++++++++++++++++++++++++ data/errata/mspm0l112x.yaml | 43 +++++++++++++++ data/errata/mspm0l122x.yaml | 83 ++++++++++++++++++++++++++++ data/errata/mspm0l130x.yaml | 89 ++++++++++++++++++++++++++++++ data/errata/mspm0l134x.yaml | 89 ++++++++++++++++++++++++++++++ data/errata/mspm0l211x.yaml | 43 +++++++++++++++ data/errata/mspm0l222x.yaml | 83 ++++++++++++++++++++++++++++ data/errata/msps003fx.yaml | 64 ++++++++++++++++++++++ 18 files changed, 1299 insertions(+) create mode 100644 data/errata/mspm0c1105_c1106.yaml create mode 100644 data/errata/mspm0c110x.yaml create mode 100644 data/errata/mspm0g110x.yaml create mode 100644 data/errata/mspm0g150x.yaml create mode 100644 data/errata/mspm0g151x.yaml create mode 100644 data/errata/mspm0g310x.yaml create mode 100644 data/errata/mspm0g350x.yaml create mode 100644 data/errata/mspm0g351x.yaml create mode 100644 data/errata/mspm0g518x.yaml create mode 100644 data/errata/mspm0h321x.yaml create mode 100644 data/errata/mspm0l110x.yaml create mode 100644 data/errata/mspm0l112x.yaml create mode 100644 data/errata/mspm0l122x.yaml create mode 100644 data/errata/mspm0l130x.yaml create mode 100644 data/errata/mspm0l134x.yaml create mode 100644 data/errata/mspm0l211x.yaml create mode 100644 data/errata/mspm0l222x.yaml create mode 100644 data/errata/msps003fx.yaml diff --git a/data/errata/mspm0c1105_c1106.yaml b/data/errata/mspm0c1105_c1106.yaml new file mode 100644 index 0000000..f5eb852 --- /dev/null +++ b/data/errata/mspm0c1105_c1106.yaml @@ -0,0 +1,41 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ761. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- CPU_ERR_02 +- CPU_ERR_03 +- FLASH_ERR_02 +- FLASH_ERR_04 +- FLASH_ERR_05 +- FLASH_ERR_08 +- I2C_ERR_04 +- I2C_ERR_05 +- I2C_ERR_06 +- I2C_ERR_07 +- I2C_ERR_08 +- I2C_ERR_09 +- I2C_ERR_10 +- PMCU_ERR_13 +- RST_ERR_01 +- RTC_ERR_01 +- SPI_ERR_04 +- SPI_ERR_05 +- SPI_ERR_06 +- SPI_ERR_07 +- SYSCTL_ERR_01 +- SYSCTL_ERR_02 +- SYSCTL_ERR_03 +- SYSOSC_ERR_02 +- TIMER_ERR_04 +- TIMER_ERR_06 +- UART_ERR_01 +- UART_ERR_02 +- UART_ERR_04 +- UART_ERR_05 +- UART_ERR_06 +- UART_ERR_07 +- UART_ERR_08 diff --git a/data/errata/mspm0c110x.yaml b/data/errata/mspm0c110x.yaml new file mode 100644 index 0000000..d4e55b7 --- /dev/null +++ b/data/errata/mspm0c110x.yaml @@ -0,0 +1,64 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ753. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- ADC_ERR_05 +- ADC_ERR_09 +- CLK_ERR_02 +- CPU_ERR_01 +- CPU_ERR_02 +- CPU_ERR_03 +- CPU_ERR_04 +- CRCP_ERR_01 +- CRC_ERR_01 +- FCC_ERR_01 +- FLASH_ERR_03 +- FLASH_ERR_05 +- FLASH_ERR_06 +- FLASH_ERR_08 +- GPIO_ERR_03 +- GPIO_ERR_04 +- GPIO_ERR_09 +- I2C_ERR_03 +- I2C_ERR_04 +- I2C_ERR_05 +- I2C_ERR_06 +- I2C_ERR_07 +- I2C_ERR_08 +- I2C_ERR_09 +- I2C_ERR_10 +- I2C_ERR_13 +- I2C_ERR_15 +- RST_ERR_01 +- SPI_ERR_03 +- SPI_ERR_04 +- SPI_ERR_05 +- SPI_ERR_06 +- SPI_ERR_07 +- SPI_ERR_10 +- SYSCTL_ERR_03 +- SYSCTL_ERR_05 +- SYSCTL_ERR_06 +- SYSCTL_ERR_07 +- SYSOSC_ERR_02 +- SYSOSC_ERR_04 +- SYSOSC_ERR_05 +- SYSOSC_ERR_06 +- TIMER_ERR_01 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UART_ERR_01 +- UART_ERR_02 +- UART_ERR_04 +- UART_ERR_05 +- UART_ERR_06 +- UART_ERR_07 +- UART_ERR_08 +- UART_ERR_09 +- UART_ERR_10 +- UART_ERR_11 diff --git a/data/errata/mspm0g110x.yaml b/data/errata/mspm0g110x.yaml new file mode 100644 index 0000000..5a77972 --- /dev/null +++ b/data/errata/mspm0g110x.yaml @@ -0,0 +1,90 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ742. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- ADC_ERR_01 +- ADC_ERR_02 +- ADC_ERR_05 +- ADC_ERR_06 +- BSL_ERR_01 +- CLK_ERR_01 +- COMP_ERR_02 +- COMP_ERR_03 +- CPU_ERR_01 +- CPU_ERR_02 +- CPU_ERR_03 +- CPU_ERR_04 +- CRCP_ERR_01 +- CRC_ERR_01 +- DAC_ERR_01 +- DMA_ERR_01 +- FLASH_ERR_02 +- FLASH_ERR_04 +- FLASH_ERR_05 +- FLASH_ERR_06 +- FLASH_ERR_08 +- GPIO_ERR_01 +- GPIO_ERR_04 +- GPIO_ERR_06 +- I2C_ERR_01 +- I2C_ERR_02 +- I2C_ERR_03 +- I2C_ERR_04 +- I2C_ERR_05 +- I2C_ERR_06 +- I2C_ERR_07 +- I2C_ERR_08 +- I2C_ERR_09 +- I2C_ERR_10 +- I2C_ERR_13 +- I2C_ERR_15 +- MATHACL_ERR_01 +- MATHACL_ERR_02 +- PMCU_ERR_08 +- PMCU_ERR_10 +- PWREN_ERR_01 +- RST_ERR_01 +- RTC_ERR_01 +- SPI_ERR_01 +- SPI_ERR_02 +- SPI_ERR_03 +- SPI_ERR_04 +- SPI_ERR_05 +- SPI_ERR_06 +- SPI_ERR_07 +- SPI_ERR_10 +- SRAM_ERR_02 +- SYSCTL_ERR_02 +- SYSCTL_ERR_03 +- SYSCTL_ERR_04 +- SYSCTL_ERR_05 +- SYSCTL_ERR_06 +- SYSOSC_ERR_01 +- SYSOSC_ERR_02 +- SYSOSC_ERR_04 +- SYSOSC_ERR_05 +- SYSOSC_ERR_06 +- SYSPLL_ERR_01 +- TIMER_ERR_01 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UART_ERR_01 +- UART_ERR_02 +- UART_ERR_04 +- UART_ERR_05 +- UART_ERR_06 +- UART_ERR_07 +- UART_ERR_08 +- UART_ERR_09 +- UART_ERR_10 +- UART_ERR_11 +- VREF_ERR_01 +- VREF_ERR_02 +- VREF_ERR_04 +- WWDT_ERR_01 +- WWDT_ERR_02 diff --git a/data/errata/mspm0g150x.yaml b/data/errata/mspm0g150x.yaml new file mode 100644 index 0000000..5a77972 --- /dev/null +++ b/data/errata/mspm0g150x.yaml @@ -0,0 +1,90 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ742. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- ADC_ERR_01 +- ADC_ERR_02 +- ADC_ERR_05 +- ADC_ERR_06 +- BSL_ERR_01 +- CLK_ERR_01 +- COMP_ERR_02 +- COMP_ERR_03 +- CPU_ERR_01 +- CPU_ERR_02 +- CPU_ERR_03 +- CPU_ERR_04 +- CRCP_ERR_01 +- CRC_ERR_01 +- DAC_ERR_01 +- DMA_ERR_01 +- FLASH_ERR_02 +- FLASH_ERR_04 +- FLASH_ERR_05 +- FLASH_ERR_06 +- FLASH_ERR_08 +- GPIO_ERR_01 +- GPIO_ERR_04 +- GPIO_ERR_06 +- I2C_ERR_01 +- I2C_ERR_02 +- I2C_ERR_03 +- I2C_ERR_04 +- I2C_ERR_05 +- I2C_ERR_06 +- I2C_ERR_07 +- I2C_ERR_08 +- I2C_ERR_09 +- I2C_ERR_10 +- I2C_ERR_13 +- I2C_ERR_15 +- MATHACL_ERR_01 +- MATHACL_ERR_02 +- PMCU_ERR_08 +- PMCU_ERR_10 +- PWREN_ERR_01 +- RST_ERR_01 +- RTC_ERR_01 +- SPI_ERR_01 +- SPI_ERR_02 +- SPI_ERR_03 +- SPI_ERR_04 +- SPI_ERR_05 +- SPI_ERR_06 +- SPI_ERR_07 +- SPI_ERR_10 +- SRAM_ERR_02 +- SYSCTL_ERR_02 +- SYSCTL_ERR_03 +- SYSCTL_ERR_04 +- SYSCTL_ERR_05 +- SYSCTL_ERR_06 +- SYSOSC_ERR_01 +- SYSOSC_ERR_02 +- SYSOSC_ERR_04 +- SYSOSC_ERR_05 +- SYSOSC_ERR_06 +- SYSPLL_ERR_01 +- TIMER_ERR_01 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UART_ERR_01 +- UART_ERR_02 +- UART_ERR_04 +- UART_ERR_05 +- UART_ERR_06 +- UART_ERR_07 +- UART_ERR_08 +- UART_ERR_09 +- UART_ERR_10 +- UART_ERR_11 +- VREF_ERR_01 +- VREF_ERR_02 +- VREF_ERR_04 +- WWDT_ERR_01 +- WWDT_ERR_02 diff --git a/data/errata/mspm0g151x.yaml b/data/errata/mspm0g151x.yaml new file mode 100644 index 0000000..662a867 --- /dev/null +++ b/data/errata/mspm0g151x.yaml @@ -0,0 +1,78 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ758. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- ADC_ERR_06 +- ADC_ERR_10 +- AES_ERR_01 +- AES_ERR_02 +- CPU_ERR_02 +- CPU_ERR_03 +- CPU_ERR_04 +- DAC_ERR_01 +- DMA_ERR_02 +- FCC_ERR_01 +- FLASH_ERR_01 +- FLASH_ERR_03 +- FLASH_ERR_04 +- FLASH_ERR_05 +- FLASH_ERR_08 +- FLASH_ERR_09 +- GPIO_ERR_04 +- GPIO_ERR_06 +- GPIO_ERR_08 +- I2C_ERR_04 +- I2C_ERR_05 +- I2C_ERR_06 +- I2C_ERR_07 +- I2C_ERR_08 +- I2C_ERR_09 +- I2C_ERR_10 +- I2C_ERR_13 +- I2C_ERR_15 +- KEYSTORE_ERR_01 +- MATHACL_ERR_01 +- MATHACL_ERR_02 +- PMCU_ERR_09 +- PMCU_ERR_10 +- PMCU_ERR_11 +- PMCU_ERR_12 +- RST_ERR_01 +- RST_ERR_02 +- RTC_ERR_01 +- SPI_ERR_02 +- SPI_ERR_04 +- SPI_ERR_05 +- SPI_ERR_06 +- SPI_ERR_07 +- SPI_ERR_10 +- SRAM_ERR_03 +- SYSCTL_ERR_01 +- SYSCTL_ERR_02 +- SYSCTL_ERR_03 +- SYSCTL_ERR_04 +- SYSCTL_ERR_05 +- SYSCTL_ERR_06 +- SYSOSC_ERR_01 +- SYSOSC_ERR_02 +- SYSOSC_ERR_04 +- SYSOSC_ERR_05 +- SYSOSC_ERR_06 +- SYSOSC_ERR_07 +- SYSPLL_ERR_01 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UART_ERR_01 +- UART_ERR_04 +- UART_ERR_05 +- UART_ERR_06 +- UART_ERR_07 +- UART_ERR_08 +- UART_ERR_10 +- UART_ERR_11 +- VREF_ERR_04 diff --git a/data/errata/mspm0g310x.yaml b/data/errata/mspm0g310x.yaml new file mode 100644 index 0000000..5a77972 --- /dev/null +++ b/data/errata/mspm0g310x.yaml @@ -0,0 +1,90 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ742. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- ADC_ERR_01 +- ADC_ERR_02 +- ADC_ERR_05 +- ADC_ERR_06 +- BSL_ERR_01 +- CLK_ERR_01 +- COMP_ERR_02 +- COMP_ERR_03 +- CPU_ERR_01 +- CPU_ERR_02 +- CPU_ERR_03 +- CPU_ERR_04 +- CRCP_ERR_01 +- CRC_ERR_01 +- DAC_ERR_01 +- DMA_ERR_01 +- FLASH_ERR_02 +- FLASH_ERR_04 +- FLASH_ERR_05 +- FLASH_ERR_06 +- FLASH_ERR_08 +- GPIO_ERR_01 +- GPIO_ERR_04 +- GPIO_ERR_06 +- I2C_ERR_01 +- I2C_ERR_02 +- I2C_ERR_03 +- I2C_ERR_04 +- I2C_ERR_05 +- I2C_ERR_06 +- I2C_ERR_07 +- I2C_ERR_08 +- I2C_ERR_09 +- I2C_ERR_10 +- I2C_ERR_13 +- I2C_ERR_15 +- MATHACL_ERR_01 +- MATHACL_ERR_02 +- PMCU_ERR_08 +- PMCU_ERR_10 +- PWREN_ERR_01 +- RST_ERR_01 +- RTC_ERR_01 +- SPI_ERR_01 +- SPI_ERR_02 +- SPI_ERR_03 +- SPI_ERR_04 +- SPI_ERR_05 +- SPI_ERR_06 +- SPI_ERR_07 +- SPI_ERR_10 +- SRAM_ERR_02 +- SYSCTL_ERR_02 +- SYSCTL_ERR_03 +- SYSCTL_ERR_04 +- SYSCTL_ERR_05 +- SYSCTL_ERR_06 +- SYSOSC_ERR_01 +- SYSOSC_ERR_02 +- SYSOSC_ERR_04 +- SYSOSC_ERR_05 +- SYSOSC_ERR_06 +- SYSPLL_ERR_01 +- TIMER_ERR_01 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UART_ERR_01 +- UART_ERR_02 +- UART_ERR_04 +- UART_ERR_05 +- UART_ERR_06 +- UART_ERR_07 +- UART_ERR_08 +- UART_ERR_09 +- UART_ERR_10 +- UART_ERR_11 +- VREF_ERR_01 +- VREF_ERR_02 +- VREF_ERR_04 +- WWDT_ERR_01 +- WWDT_ERR_02 diff --git a/data/errata/mspm0g350x.yaml b/data/errata/mspm0g350x.yaml new file mode 100644 index 0000000..5a77972 --- /dev/null +++ b/data/errata/mspm0g350x.yaml @@ -0,0 +1,90 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ742. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- ADC_ERR_01 +- ADC_ERR_02 +- ADC_ERR_05 +- ADC_ERR_06 +- BSL_ERR_01 +- CLK_ERR_01 +- COMP_ERR_02 +- COMP_ERR_03 +- CPU_ERR_01 +- CPU_ERR_02 +- CPU_ERR_03 +- CPU_ERR_04 +- CRCP_ERR_01 +- CRC_ERR_01 +- DAC_ERR_01 +- DMA_ERR_01 +- FLASH_ERR_02 +- FLASH_ERR_04 +- FLASH_ERR_05 +- FLASH_ERR_06 +- FLASH_ERR_08 +- GPIO_ERR_01 +- GPIO_ERR_04 +- GPIO_ERR_06 +- I2C_ERR_01 +- I2C_ERR_02 +- I2C_ERR_03 +- I2C_ERR_04 +- I2C_ERR_05 +- I2C_ERR_06 +- I2C_ERR_07 +- I2C_ERR_08 +- I2C_ERR_09 +- I2C_ERR_10 +- I2C_ERR_13 +- I2C_ERR_15 +- MATHACL_ERR_01 +- MATHACL_ERR_02 +- PMCU_ERR_08 +- PMCU_ERR_10 +- PWREN_ERR_01 +- RST_ERR_01 +- RTC_ERR_01 +- SPI_ERR_01 +- SPI_ERR_02 +- SPI_ERR_03 +- SPI_ERR_04 +- SPI_ERR_05 +- SPI_ERR_06 +- SPI_ERR_07 +- SPI_ERR_10 +- SRAM_ERR_02 +- SYSCTL_ERR_02 +- SYSCTL_ERR_03 +- SYSCTL_ERR_04 +- SYSCTL_ERR_05 +- SYSCTL_ERR_06 +- SYSOSC_ERR_01 +- SYSOSC_ERR_02 +- SYSOSC_ERR_04 +- SYSOSC_ERR_05 +- SYSOSC_ERR_06 +- SYSPLL_ERR_01 +- TIMER_ERR_01 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UART_ERR_01 +- UART_ERR_02 +- UART_ERR_04 +- UART_ERR_05 +- UART_ERR_06 +- UART_ERR_07 +- UART_ERR_08 +- UART_ERR_09 +- UART_ERR_10 +- UART_ERR_11 +- VREF_ERR_01 +- VREF_ERR_02 +- VREF_ERR_04 +- WWDT_ERR_01 +- WWDT_ERR_02 diff --git a/data/errata/mspm0g351x.yaml b/data/errata/mspm0g351x.yaml new file mode 100644 index 0000000..662a867 --- /dev/null +++ b/data/errata/mspm0g351x.yaml @@ -0,0 +1,78 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ758. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- ADC_ERR_06 +- ADC_ERR_10 +- AES_ERR_01 +- AES_ERR_02 +- CPU_ERR_02 +- CPU_ERR_03 +- CPU_ERR_04 +- DAC_ERR_01 +- DMA_ERR_02 +- FCC_ERR_01 +- FLASH_ERR_01 +- FLASH_ERR_03 +- FLASH_ERR_04 +- FLASH_ERR_05 +- FLASH_ERR_08 +- FLASH_ERR_09 +- GPIO_ERR_04 +- GPIO_ERR_06 +- GPIO_ERR_08 +- I2C_ERR_04 +- I2C_ERR_05 +- I2C_ERR_06 +- I2C_ERR_07 +- I2C_ERR_08 +- I2C_ERR_09 +- I2C_ERR_10 +- I2C_ERR_13 +- I2C_ERR_15 +- KEYSTORE_ERR_01 +- MATHACL_ERR_01 +- MATHACL_ERR_02 +- PMCU_ERR_09 +- PMCU_ERR_10 +- PMCU_ERR_11 +- PMCU_ERR_12 +- RST_ERR_01 +- RST_ERR_02 +- RTC_ERR_01 +- SPI_ERR_02 +- SPI_ERR_04 +- SPI_ERR_05 +- SPI_ERR_06 +- SPI_ERR_07 +- SPI_ERR_10 +- SRAM_ERR_03 +- SYSCTL_ERR_01 +- SYSCTL_ERR_02 +- SYSCTL_ERR_03 +- SYSCTL_ERR_04 +- SYSCTL_ERR_05 +- SYSCTL_ERR_06 +- SYSOSC_ERR_01 +- SYSOSC_ERR_02 +- SYSOSC_ERR_04 +- SYSOSC_ERR_05 +- SYSOSC_ERR_06 +- SYSOSC_ERR_07 +- SYSPLL_ERR_01 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UART_ERR_01 +- UART_ERR_04 +- UART_ERR_05 +- UART_ERR_06 +- UART_ERR_07 +- UART_ERR_08 +- UART_ERR_10 +- UART_ERR_11 +- VREF_ERR_04 diff --git a/data/errata/mspm0g518x.yaml b/data/errata/mspm0g518x.yaml new file mode 100644 index 0000000..6ffa5fc --- /dev/null +++ b/data/errata/mspm0g518x.yaml @@ -0,0 +1,46 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLVSJM6. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- AES_ERR_01 +- CPU_ERR_02 +- CPU_ERR_03 +- FLASH_ERR_03 +- FLASH_ERR_04 +- FLASH_ERR_05 +- FLASH_ERR_08 +- GPIO_ERR_05 +- GPIO_ERR_06 +- KEYSTORE_ERR_01 +- PMCU_ERR_13 +- RST_ERR_01 +- SYSCTL_ERR_01 +- SYSCTL_ERR_02 +- SYSCTL_ERR_03 +- SYSCTL_ERR_04 +- SYSOSC_ERR_01 +- SYSOSC_ERR_02 +- SYSPLL_ERR_01 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UNICOMMI2CC_ERR_01 +- UNICOMMI2CT_ERR_01 +- UNICOMMI2CT_ERR_02 +- UNICOMMI2CT_ERR_03 +- UNICOMMSPI_ERR_01 +- UNICOMMUART_ERR_01 +- UNICOMMUART_ERR_02 +- UNICOMMUART_ERR_03 +- UNICOMMUART_ERR_04 +- UNICOMMUART_ERR_05 +- UNICOMMUART_ERR_06 +- UNICOMMUART_ERR_07 +- UNICOMMUART_ERR_08 +- UNICOMMUART_ERR_09 +- UNICOMMUART_ERR_10 +- UNICOMMUART_ERR_11 diff --git a/data/errata/mspm0h321x.yaml b/data/errata/mspm0h321x.yaml new file mode 100644 index 0000000..7ba01a4 --- /dev/null +++ b/data/errata/mspm0h321x.yaml @@ -0,0 +1,49 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ763. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- CPU_ERR_02 +- CPU_ERR_03 +- FLASH_ERR_02 +- FLASH_ERR_04 +- FLASH_ERR_05 +- FLASH_ERR_08 +- GPIO_ERR_04 +- GPIO_ERR_08 +- I2C_ERR_04 +- I2C_ERR_05 +- I2C_ERR_06 +- I2C_ERR_07 +- I2C_ERR_08 +- I2C_ERR_09 +- I2C_ERR_10 +- I2C_ERR_13 +- LFXT_ERR_03 +- LFXT_ERR_04 +- PMCU_ERR_13 +- RST_ERR_01 +- SPI_ERR_04 +- SPI_ERR_05 +- SPI_ERR_06 +- SPI_ERR_07 +- SWD_ERR_01 +- SYSCTL_ERR_01 +- SYSCTL_ERR_02 +- SYSCTL_ERR_03 +- SYSOSC_ERR_02 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UART_ERR_01 +- UART_ERR_02 +- UART_ERR_04 +- UART_ERR_05 +- UART_ERR_06 +- UART_ERR_07 +- UART_ERR_08 +- UART_ERR_10 +- UART_ERR_11 diff --git a/data/errata/mspm0l110x.yaml b/data/errata/mspm0l110x.yaml new file mode 100644 index 0000000..51f614b --- /dev/null +++ b/data/errata/mspm0l110x.yaml @@ -0,0 +1,89 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ741. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- ADC_ERR_01 +- ADC_ERR_02 +- ADC_ERR_03 +- ADC_ERR_04 +- ADC_ERR_05 +- ADC_ERR_06 +- ADC_ERR_11 +- COMP_ERR_01 +- COMP_ERR_02 +- COMP_ERR_03 +- COMP_ERR_05 +- CPU_ERR_01 +- CPU_ERR_02 +- CPU_ERR_03 +- CPU_ERR_04 +- CRCP_ERR_01 +- CRC_ERR_01 +- FCC_ERR_01 +- FLASH_ERR_02 +- FLASH_ERR_03 +- FLASH_ERR_04 +- FLASH_ERR_05 +- FLASH_ERR_06 +- FLASH_ERR_08 +- GPIO_ERR_01 +- GPIO_ERR_02 +- GPIO_ERR_03 +- GPIO_ERR_04 +- GPIO_ERR_06 +- I2C_ERR_01 +- I2C_ERR_02 +- I2C_ERR_03 +- I2C_ERR_04 +- I2C_ERR_05 +- I2C_ERR_06 +- I2C_ERR_07 +- I2C_ERR_08 +- I2C_ERR_09 +- I2C_ERR_10 +- I2C_ERR_13 +- I2C_ERR_15 +- PMCU_ERR_01 +- PMCU_ERR_02 +- PMCU_ERR_03 +- PMCU_ERR_10 +- PMCU_ERR_13 +- PWREN_ERR_01 +- RST_ERR_01 +- SPI_ERR_01 +- SPI_ERR_03 +- SPI_ERR_04 +- SPI_ERR_05 +- SPI_ERR_06 +- SPI_ERR_07 +- SPI_ERR_08 +- SPI_ERR_09 +- SPI_ERR_10 +- SYSCTL_ERR_03 +- SYSCTL_ERR_05 +- SYSCTL_ERR_06 +- SYSCTL_ERR_11 +- SYSOSC_ERR_01 +- SYSOSC_ERR_02 +- SYSOSC_ERR_04 +- SYSOSC_ERR_05 +- SYSOSC_ERR_06 +- TIMER_ERR_01 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UART_ERR_01 +- UART_ERR_02 +- UART_ERR_04 +- UART_ERR_05 +- UART_ERR_06 +- UART_ERR_07 +- UART_ERR_08 +- UART_ERR_09 +- UART_ERR_10 +- UART_ERR_11 +- VREF_ERR_01 diff --git a/data/errata/mspm0l112x.yaml b/data/errata/mspm0l112x.yaml new file mode 100644 index 0000000..fc130bf --- /dev/null +++ b/data/errata/mspm0l112x.yaml @@ -0,0 +1,43 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ766. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- AES_ERR_01 +- CPU_ERR_02 +- CPU_ERR_03 +- FLASH_ERR_03 +- FLASH_ERR_04 +- FLASH_ERR_05 +- FLASH_ERR_08 +- KEYSTORE_ERR_01 +- PMCU_ERR_13 +- RST_ERR_01 +- SYSCTL_ERR_01 +- SYSCTL_ERR_02 +- SYSCTL_ERR_03 +- SYSCTL_ERR_04 +- SYSOSC_ERR_01 +- SYSOSC_ERR_02 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UNICOMMI2CC_ERR_01 +- UNICOMMI2CT_ERR_01 +- UNICOMMI2CT_ERR_02 +- UNICOMMI2CT_ERR_03 +- UNICOMMSPI_ERR_01 +- UNICOMMUART_ERR_01 +- UNICOMMUART_ERR_02 +- UNICOMMUART_ERR_03 +- UNICOMMUART_ERR_04 +- UNICOMMUART_ERR_05 +- UNICOMMUART_ERR_06 +- UNICOMMUART_ERR_07 +- UNICOMMUART_ERR_08 +- UNICOMMUART_ERR_09 +- UNICOMMUART_ERR_10 +- UNICOMMUART_ERR_11 diff --git a/data/errata/mspm0l122x.yaml b/data/errata/mspm0l122x.yaml new file mode 100644 index 0000000..f9835de --- /dev/null +++ b/data/errata/mspm0l122x.yaml @@ -0,0 +1,83 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ755. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- ADC_ERR_05 +- ADC_ERR_06 +- AES_ERR_01 +- COMP_ERR_03 +- COMP_ERR_04 +- CPU_ERR_01 +- CPU_ERR_02 +- CPU_ERR_03 +- CPU_ERR_04 +- CRCP_ERR_01 +- CRC_ERR_01 +- FLASH_ERR_04 +- FLASH_ERR_05 +- FLASH_ERR_08 +- FLASH_ERR_09 +- GPIO_ERR_03 +- GPIO_ERR_04 +- GPIO_ERR_06 +- I2C_ERR_03 +- I2C_ERR_04 +- I2C_ERR_05 +- I2C_ERR_06 +- I2C_ERR_07 +- I2C_ERR_08 +- I2C_ERR_09 +- I2C_ERR_10 +- I2C_ERR_13 +- I2C_ERR_15 +- KEYSTORE_ERR_01 +- LCD_ERR_01 +- LFSS_ERR_01 +- LFSS_ERR_02 +- LFSS_ERR_03 +- LFXT_ERR_01 +- LFXT_ERR_02 +- PMCU_ERR_08 +- PMCU_ERR_09 +- PMCU_ERR_10 +- PMCU_ERR_12 +- RST_ERR_01 +- RST_ERR_02 +- SPI_ERR_03 +- SPI_ERR_04 +- SPI_ERR_05 +- SPI_ERR_06 +- SPI_ERR_07 +- SPI_ERR_10 +- SRAM_ERR_01 +- SYSCTL_ERR_01 +- SYSCTL_ERR_02 +- SYSCTL_ERR_03 +- SYSCTL_ERR_04 +- SYSCTL_ERR_05 +- SYSCTL_ERR_06 +- SYSOSC_ERR_01 +- SYSOSC_ERR_02 +- SYSOSC_ERR_04 +- SYSOSC_ERR_05 +- SYSOSC_ERR_06 +- TAMPERIO_ERR_01 +- TIMER_ERR_01 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UART_ERR_01 +- UART_ERR_02 +- UART_ERR_03 +- UART_ERR_04 +- UART_ERR_05 +- UART_ERR_06 +- UART_ERR_07 +- UART_ERR_08 +- UART_ERR_09 +- UART_ERR_10 +- UART_ERR_11 diff --git a/data/errata/mspm0l130x.yaml b/data/errata/mspm0l130x.yaml new file mode 100644 index 0000000..51f614b --- /dev/null +++ b/data/errata/mspm0l130x.yaml @@ -0,0 +1,89 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ741. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- ADC_ERR_01 +- ADC_ERR_02 +- ADC_ERR_03 +- ADC_ERR_04 +- ADC_ERR_05 +- ADC_ERR_06 +- ADC_ERR_11 +- COMP_ERR_01 +- COMP_ERR_02 +- COMP_ERR_03 +- COMP_ERR_05 +- CPU_ERR_01 +- CPU_ERR_02 +- CPU_ERR_03 +- CPU_ERR_04 +- CRCP_ERR_01 +- CRC_ERR_01 +- FCC_ERR_01 +- FLASH_ERR_02 +- FLASH_ERR_03 +- FLASH_ERR_04 +- FLASH_ERR_05 +- FLASH_ERR_06 +- FLASH_ERR_08 +- GPIO_ERR_01 +- GPIO_ERR_02 +- GPIO_ERR_03 +- GPIO_ERR_04 +- GPIO_ERR_06 +- I2C_ERR_01 +- I2C_ERR_02 +- I2C_ERR_03 +- I2C_ERR_04 +- I2C_ERR_05 +- I2C_ERR_06 +- I2C_ERR_07 +- I2C_ERR_08 +- I2C_ERR_09 +- I2C_ERR_10 +- I2C_ERR_13 +- I2C_ERR_15 +- PMCU_ERR_01 +- PMCU_ERR_02 +- PMCU_ERR_03 +- PMCU_ERR_10 +- PMCU_ERR_13 +- PWREN_ERR_01 +- RST_ERR_01 +- SPI_ERR_01 +- SPI_ERR_03 +- SPI_ERR_04 +- SPI_ERR_05 +- SPI_ERR_06 +- SPI_ERR_07 +- SPI_ERR_08 +- SPI_ERR_09 +- SPI_ERR_10 +- SYSCTL_ERR_03 +- SYSCTL_ERR_05 +- SYSCTL_ERR_06 +- SYSCTL_ERR_11 +- SYSOSC_ERR_01 +- SYSOSC_ERR_02 +- SYSOSC_ERR_04 +- SYSOSC_ERR_05 +- SYSOSC_ERR_06 +- TIMER_ERR_01 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UART_ERR_01 +- UART_ERR_02 +- UART_ERR_04 +- UART_ERR_05 +- UART_ERR_06 +- UART_ERR_07 +- UART_ERR_08 +- UART_ERR_09 +- UART_ERR_10 +- UART_ERR_11 +- VREF_ERR_01 diff --git a/data/errata/mspm0l134x.yaml b/data/errata/mspm0l134x.yaml new file mode 100644 index 0000000..51f614b --- /dev/null +++ b/data/errata/mspm0l134x.yaml @@ -0,0 +1,89 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ741. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- ADC_ERR_01 +- ADC_ERR_02 +- ADC_ERR_03 +- ADC_ERR_04 +- ADC_ERR_05 +- ADC_ERR_06 +- ADC_ERR_11 +- COMP_ERR_01 +- COMP_ERR_02 +- COMP_ERR_03 +- COMP_ERR_05 +- CPU_ERR_01 +- CPU_ERR_02 +- CPU_ERR_03 +- CPU_ERR_04 +- CRCP_ERR_01 +- CRC_ERR_01 +- FCC_ERR_01 +- FLASH_ERR_02 +- FLASH_ERR_03 +- FLASH_ERR_04 +- FLASH_ERR_05 +- FLASH_ERR_06 +- FLASH_ERR_08 +- GPIO_ERR_01 +- GPIO_ERR_02 +- GPIO_ERR_03 +- GPIO_ERR_04 +- GPIO_ERR_06 +- I2C_ERR_01 +- I2C_ERR_02 +- I2C_ERR_03 +- I2C_ERR_04 +- I2C_ERR_05 +- I2C_ERR_06 +- I2C_ERR_07 +- I2C_ERR_08 +- I2C_ERR_09 +- I2C_ERR_10 +- I2C_ERR_13 +- I2C_ERR_15 +- PMCU_ERR_01 +- PMCU_ERR_02 +- PMCU_ERR_03 +- PMCU_ERR_10 +- PMCU_ERR_13 +- PWREN_ERR_01 +- RST_ERR_01 +- SPI_ERR_01 +- SPI_ERR_03 +- SPI_ERR_04 +- SPI_ERR_05 +- SPI_ERR_06 +- SPI_ERR_07 +- SPI_ERR_08 +- SPI_ERR_09 +- SPI_ERR_10 +- SYSCTL_ERR_03 +- SYSCTL_ERR_05 +- SYSCTL_ERR_06 +- SYSCTL_ERR_11 +- SYSOSC_ERR_01 +- SYSOSC_ERR_02 +- SYSOSC_ERR_04 +- SYSOSC_ERR_05 +- SYSOSC_ERR_06 +- TIMER_ERR_01 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UART_ERR_01 +- UART_ERR_02 +- UART_ERR_04 +- UART_ERR_05 +- UART_ERR_06 +- UART_ERR_07 +- UART_ERR_08 +- UART_ERR_09 +- UART_ERR_10 +- UART_ERR_11 +- VREF_ERR_01 diff --git a/data/errata/mspm0l211x.yaml b/data/errata/mspm0l211x.yaml new file mode 100644 index 0000000..fc130bf --- /dev/null +++ b/data/errata/mspm0l211x.yaml @@ -0,0 +1,43 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ766. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- AES_ERR_01 +- CPU_ERR_02 +- CPU_ERR_03 +- FLASH_ERR_03 +- FLASH_ERR_04 +- FLASH_ERR_05 +- FLASH_ERR_08 +- KEYSTORE_ERR_01 +- PMCU_ERR_13 +- RST_ERR_01 +- SYSCTL_ERR_01 +- SYSCTL_ERR_02 +- SYSCTL_ERR_03 +- SYSCTL_ERR_04 +- SYSOSC_ERR_01 +- SYSOSC_ERR_02 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UNICOMMI2CC_ERR_01 +- UNICOMMI2CT_ERR_01 +- UNICOMMI2CT_ERR_02 +- UNICOMMI2CT_ERR_03 +- UNICOMMSPI_ERR_01 +- UNICOMMUART_ERR_01 +- UNICOMMUART_ERR_02 +- UNICOMMUART_ERR_03 +- UNICOMMUART_ERR_04 +- UNICOMMUART_ERR_05 +- UNICOMMUART_ERR_06 +- UNICOMMUART_ERR_07 +- UNICOMMUART_ERR_08 +- UNICOMMUART_ERR_09 +- UNICOMMUART_ERR_10 +- UNICOMMUART_ERR_11 diff --git a/data/errata/mspm0l222x.yaml b/data/errata/mspm0l222x.yaml new file mode 100644 index 0000000..f9835de --- /dev/null +++ b/data/errata/mspm0l222x.yaml @@ -0,0 +1,83 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ755. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- ADC_ERR_05 +- ADC_ERR_06 +- AES_ERR_01 +- COMP_ERR_03 +- COMP_ERR_04 +- CPU_ERR_01 +- CPU_ERR_02 +- CPU_ERR_03 +- CPU_ERR_04 +- CRCP_ERR_01 +- CRC_ERR_01 +- FLASH_ERR_04 +- FLASH_ERR_05 +- FLASH_ERR_08 +- FLASH_ERR_09 +- GPIO_ERR_03 +- GPIO_ERR_04 +- GPIO_ERR_06 +- I2C_ERR_03 +- I2C_ERR_04 +- I2C_ERR_05 +- I2C_ERR_06 +- I2C_ERR_07 +- I2C_ERR_08 +- I2C_ERR_09 +- I2C_ERR_10 +- I2C_ERR_13 +- I2C_ERR_15 +- KEYSTORE_ERR_01 +- LCD_ERR_01 +- LFSS_ERR_01 +- LFSS_ERR_02 +- LFSS_ERR_03 +- LFXT_ERR_01 +- LFXT_ERR_02 +- PMCU_ERR_08 +- PMCU_ERR_09 +- PMCU_ERR_10 +- PMCU_ERR_12 +- RST_ERR_01 +- RST_ERR_02 +- SPI_ERR_03 +- SPI_ERR_04 +- SPI_ERR_05 +- SPI_ERR_06 +- SPI_ERR_07 +- SPI_ERR_10 +- SRAM_ERR_01 +- SYSCTL_ERR_01 +- SYSCTL_ERR_02 +- SYSCTL_ERR_03 +- SYSCTL_ERR_04 +- SYSCTL_ERR_05 +- SYSCTL_ERR_06 +- SYSOSC_ERR_01 +- SYSOSC_ERR_02 +- SYSOSC_ERR_04 +- SYSOSC_ERR_05 +- SYSOSC_ERR_06 +- TAMPERIO_ERR_01 +- TIMER_ERR_01 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UART_ERR_01 +- UART_ERR_02 +- UART_ERR_03 +- UART_ERR_04 +- UART_ERR_05 +- UART_ERR_06 +- UART_ERR_07 +- UART_ERR_08 +- UART_ERR_09 +- UART_ERR_10 +- UART_ERR_11 diff --git a/data/errata/msps003fx.yaml b/data/errata/msps003fx.yaml new file mode 100644 index 0000000..d4e55b7 --- /dev/null +++ b/data/errata/msps003fx.yaml @@ -0,0 +1,64 @@ +# Errata which apply to this family, by TI's identifier. +# +# GENERATED by `tools/errata.py --write` from section 1, "Functional Advisories", of SLAZ753. +# Re-run the tool rather than editing by hand. +# +# An erratum is listed when any silicon revision is affected, since a driver built for a part has to +# run on whichever revision it meets. + +- ADC_ERR_05 +- ADC_ERR_09 +- CLK_ERR_02 +- CPU_ERR_01 +- CPU_ERR_02 +- CPU_ERR_03 +- CPU_ERR_04 +- CRCP_ERR_01 +- CRC_ERR_01 +- FCC_ERR_01 +- FLASH_ERR_03 +- FLASH_ERR_05 +- FLASH_ERR_06 +- FLASH_ERR_08 +- GPIO_ERR_03 +- GPIO_ERR_04 +- GPIO_ERR_09 +- I2C_ERR_03 +- I2C_ERR_04 +- I2C_ERR_05 +- I2C_ERR_06 +- I2C_ERR_07 +- I2C_ERR_08 +- I2C_ERR_09 +- I2C_ERR_10 +- I2C_ERR_13 +- I2C_ERR_15 +- RST_ERR_01 +- SPI_ERR_03 +- SPI_ERR_04 +- SPI_ERR_05 +- SPI_ERR_06 +- SPI_ERR_07 +- SPI_ERR_10 +- SYSCTL_ERR_03 +- SYSCTL_ERR_05 +- SYSCTL_ERR_06 +- SYSCTL_ERR_07 +- SYSOSC_ERR_02 +- SYSOSC_ERR_04 +- SYSOSC_ERR_05 +- SYSOSC_ERR_06 +- TIMER_ERR_01 +- TIMER_ERR_04 +- TIMER_ERR_06 +- TIMER_ERR_07 +- UART_ERR_01 +- UART_ERR_02 +- UART_ERR_04 +- UART_ERR_05 +- UART_ERR_06 +- UART_ERR_07 +- UART_ERR_08 +- UART_ERR_09 +- UART_ERR_10 +- UART_ERR_11 From aa2fc3b39e0c6bfea05168a170b6f9df485e1bdd Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Tue, 4 Aug 2026 21:07:36 +0100 Subject: [PATCH 028/134] Add low-power, clock and timer metadata Fields the embassy-mspm0 HAL was hardcoding, inferring from an unrelated value, or keeping as a hand-written table. Two of these cannot be keyed on Peripheral::version by a consumer, which is why they are chip-level: mspm0c110x and mspm0c1105_c1106 differ in clock tree while sharing a SYSCTL block, as do mspm0l112x and mspm0l211x in STOP1. clock_tree is read from sysconfig rather than curated, apart from the two flags and the HFCLK range that file cannot answer. hfclk_hz is deliberately not the SYSPLL reference range: those coincide only on the G families, and HFCLK stops at 32MHz elsewhere. adc_memctl and adc_vrsel move onto the peripheral, since they describe an ADC instance. Timer channel counts follow the datasheet's naming rather than the HAL's, whose "internal" count matched neither of TI's two columns. --- data/parts.yaml | 182 ++++++++++++++++++++--- mspm0-data-gen/src/clock_tree.rs | 129 ++++++++-------- mspm0-data-gen/src/errata.rs | 34 +++++ mspm0-data-gen/src/generate.rs | 155 ++++++++++++++------ mspm0-data-gen/src/main.rs | 12 +- mspm0-data-gen/src/parts.rs | 63 ++++++++ mspm0-data-gen/src/timers.rs | 37 +++++ mspm0-data-gen/src/verify.rs | 218 +++++++++++++++++++++++++++ mspm0-data-gen/src/wakeup.rs | 28 ++++ mspm0-data-types/src/lib.rs | 235 +++++++++++++++++++++++++++++- mspm0-metapac-gen/res/metadata.rs | 232 ++++++++++++++++++++++++++++- mspm0-metapac-gen/src/main.rs | 62 +++++++- mspm0-metapac-gen/src/metadata.rs | 51 +++++++ 13 files changed, 1294 insertions(+), 144 deletions(-) create mode 100644 mspm0-data-gen/src/errata.rs create mode 100644 mspm0-data-gen/src/timers.rs create mode 100644 mspm0-data-gen/src/wakeup.rs diff --git a/data/parts.yaml b/data/parts.yaml index 703a8c7..e65ecb1 100644 --- a/data/parts.yaml +++ b/data/parts.yaml @@ -16,9 +16,16 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau893 errata_url: https://www.ti.com/lit/pdf/slaz753 adc_vrsel: VDD_INTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0C1104 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0C1104 datasheet. max_mclk_hz: 24000000 max_ulpclk_hz: 24000000 + sysosc_base_hz: 24000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000] + adc_clock_hz: { min: 12000000, max: 24000000 } # fADCCLK + clock_tree: + ulpclk_div: false + stop1: false standby1_timers: - TIMA0 - TIMG8 @@ -52,9 +59,16 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau893 errata_url: https://www.ti.com/lit/pdf/slaz753 adc_vrsel: VDD_INTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0C1104 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0C1104 datasheet. max_mclk_hz: 24000000 max_ulpclk_hz: 24000000 + sysosc_base_hz: 24000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000] + adc_clock_hz: { min: 12000000, max: 24000000 } # fADCCLK + clock_tree: + ulpclk_div: false + stop1: false standby1_timers: - TIMA0 - TIMG8 @@ -94,9 +108,17 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau893 errata_url: https://www.ti.com/lit/pdf/slaz761 adc_vrsel: VDD_INTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0C1106 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0C1106 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + sysosc_base_hz: 32000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000, 32000000] + adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + clock_tree: + hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN + ulpclk_div: false + stop1: false standby1_timers: - TIMG8 - TIMG14 @@ -147,9 +169,17 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau846 errata_url: https://www.ti.com/lit/pdf/slaz742 adc_vrsel: VDD_INTREF_EXTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0G1107 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0G1107 datasheet. max_mclk_hz: 80000000 max_ulpclk_hz: 40000000 + sysosc_base_hz: 32000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000, 48000000, 80000000] + adc_clock_hz: { min: 4000000, max: 48000000 } # fADCCLK + clock_tree: + hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN + ulpclk_div: true + stop1: true standby1_timers: - TIMG0 - TIMG8 @@ -208,9 +238,18 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau846 errata_url: https://www.ti.com/lit/pdf/slaz742 adc_vrsel: VDD_INTREF_EXTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0G1507 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0G1507 datasheet. max_mclk_hz: 80000000 max_ulpclk_hz: 40000000 + sysosc_base_hz: 32000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000, 48000000, 80000000] + adc_clock_hz: { min: 4000000, max: 48000000 } # fADCCLK + trng_clock_hz: { min: 9500000, max: 25000000 } # TRNGCLKF + clock_tree: + hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN + ulpclk_div: true + stop1: true standby1_timers: - TIMG0 - TIMG8 @@ -269,9 +308,18 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau846 errata_url: https://www.ti.com/lit/pdf/slaz758 adc_vrsel: VDD_INTREF_EXTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0G1519 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0G1519 datasheet. max_mclk_hz: 80000000 max_ulpclk_hz: 40000000 + sysosc_base_hz: 32000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000, 48000000, 80000000] + adc_clock_hz: { min: 4000000, max: 48000000 } # fADCCLK + trng_clock_hz: { min: 9500000, max: 25000000 } # TRNGCLKF + clock_tree: + hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN + ulpclk_div: true + stop1: true standby1_timers: - TIMG0 - TIMG8 @@ -343,9 +391,18 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau846 errata_url: https://www.ti.com/lit/pdf/slaz742 adc_vrsel: VDD_INTREF_EXTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0G3107 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0G3107 datasheet. max_mclk_hz: 80000000 max_ulpclk_hz: 40000000 + sysosc_base_hz: 32000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000, 48000000, 80000000] + adc_clock_hz: { min: 4000000, max: 48000000 } # fADCCLK + trng_clock_hz: { min: 9500000, max: 25000000 } # TRNGCLKF + clock_tree: + hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN + ulpclk_div: true + stop1: true standby1_timers: - TIMG0 - TIMG8 @@ -394,9 +451,18 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau846 errata_url: https://www.ti.com/lit/pdf/slaz742 adc_vrsel: VDD_INTREF_EXTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0G3507 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0G3507 datasheet. max_mclk_hz: 80000000 max_ulpclk_hz: 40000000 + sysosc_base_hz: 32000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000, 48000000, 80000000] + adc_clock_hz: { min: 4000000, max: 48000000 } # fADCCLK + trng_clock_hz: { min: 9500000, max: 25000000 } # TRNGCLKF + clock_tree: + hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN + ulpclk_div: true + stop1: true standby1_timers: - TIMG0 - TIMG8 @@ -451,9 +517,18 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau846 errata_url: https://www.ti.com/lit/pdf/slaz758 adc_vrsel: VDD_INTREF_EXTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0G3519 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0G3519 datasheet. max_mclk_hz: 80000000 max_ulpclk_hz: 40000000 + sysosc_base_hz: 32000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000, 48000000, 80000000] + adc_clock_hz: { min: 4000000, max: 48000000 } # fADCCLK + trng_clock_hz: { min: 9500000, max: 25000000 } # TRNGCLKF + clock_tree: + hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN + ulpclk_div: true + stop1: true standby1_timers: - TIMG0 - TIMG8 @@ -525,9 +600,17 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau846 errata_url: https://www.ti.com/lit/pdf/slvsjm6 adc_vrsel: VDD_INTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0G5187 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0G5187 datasheet. max_mclk_hz: 80000000 max_ulpclk_hz: 40000000 + sysosc_base_hz: 32000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000, 48000000, 80000000] + adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + clock_tree: + hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN + ulpclk_div: true + stop1: true standby1_timers: - TIMG0 - TIMB0 @@ -556,9 +639,17 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau923 errata_url: https://www.ti.com/lit/pdf/slaz763 adc_vrsel: VDD_INTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0H3216 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0H3216 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + sysosc_base_hz: 32000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000, 32000000] + adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + clock_tree: + hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN + ulpclk_div: false + stop1: false standby1_timers: - TIMG14 part_numbers: @@ -590,9 +681,16 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau847 errata_url: https://www.ti.com/lit/pdf/slaz741 adc_vrsel: VDD_INTREF_EXTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0L1106 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0L1106 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + sysosc_base_hz: 32000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000, 32000000] + adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + clock_tree: + ulpclk_div: false + stop1: true standby1_timers: - TIMG0 - TIMG1 @@ -634,9 +732,17 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau847 errata_url: https://www.ti.com/lit/pdf/slaz766 adc_vrsel: VDD_INTREF_EXTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0L2117 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0L2117 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + sysosc_base_hz: 32000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000, 32000000] + adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + clock_tree: + hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN + ulpclk_div: false + stop1: false standby1_timers: - TIMG14 - TIMB0 @@ -689,9 +795,18 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau847 errata_url: https://www.ti.com/lit/pdf/slaz755 adc_vrsel: VDD_INTREF_EXTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0L1228 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0L1228 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + sysosc_base_hz: 32000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000, 32000000] + adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + trng_clock_hz: { min: 9500000, max: 25000000 } # TRNGCLKF + clock_tree: + hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN + ulpclk_div: false + stop1: true standby1_timers: - TIMA0 - TIMG0 @@ -737,9 +852,16 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau847 errata_url: https://www.ti.com/lit/pdf/slaz741 adc_vrsel: VDD_INTREF_EXTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0L1306 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0L1306 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + sysosc_base_hz: 32000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000, 32000000] + adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + clock_tree: + ulpclk_div: false + stop1: true standby1_timers: - TIMG0 - TIMG1 @@ -807,9 +929,16 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau847 errata_url: https://www.ti.com/lit/pdf/slaz741 adc_vrsel: VDD_INTREF_EXTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0L1346 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0L1346 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + sysosc_base_hz: 32000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000, 32000000] + adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + clock_tree: + ulpclk_div: false + stop1: true standby1_timers: - TIMG0 - TIMG1 @@ -863,9 +992,17 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau847 errata_url: https://www.ti.com/lit/pdf/slaz766 adc_vrsel: VDD_INTREF_EXTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0L2117 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0L2117 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + sysosc_base_hz: 32000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000, 32000000] + adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + clock_tree: + hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN + ulpclk_div: false + stop1: false standby1_timers: - TIMG14 - TIMB0 @@ -918,9 +1055,18 @@ families: reference_manual_url: https://www.ti.com/lit/pdf/slau847 errata_url: https://www.ti.com/lit/pdf/slaz755 adc_vrsel: VDD_INTREF_EXTREF - # Clock ceilings and STANDBY1 timers come from the MSPM0L2228 datasheet. + # Every frequency here, and the STANDBY1 timers, come from the MSPM0L2228 datasheet. max_mclk_hz: 32000000 max_ulpclk_hz: 32000000 + sysosc_base_hz: 32000000 + # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. + flash_wait_hz: [24000000, 32000000] + adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + trng_clock_hz: { min: 9500000, max: 25000000 } # TRNGCLKF + clock_tree: + hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN + ulpclk_div: false + stop1: true standby1_timers: - TIMA0 - TIMG0 diff --git a/mspm0-data-gen/src/clock_tree.rs b/mspm0-data-gen/src/clock_tree.rs index 2e708d5..a8254d3 100644 --- a/mspm0-data-gen/src/clock_tree.rs +++ b/mspm0-data-gen/src/clock_tree.rs @@ -1,97 +1,94 @@ -// probably look at ipInstances -// type: -// - Mux - should be configurable -// - Divider - specifies the allowed values +//! Reads sysconfig's `clocktree.json`, which describes the clock tree a family actually has. +//! +//! This is what tells the two C-series families apart: they share the `sysctl_c110x` register block, +//! but only mspm0c1105_c1106 has a crystal driver. So neither the register YAMLs nor +//! `Peripheral::version` can answer these, and `sysctl_c110x.yaml` is in any case missing fields its +//! SVD has. +//! +//! Only the nodes whose presence is a real per-device capability are read. `UDIV` is not: the file +//! lists it for families whose SYSCTL has no such field and whose ULPCLK ceiling equals their MCLK +//! ceiling, so it is curated in `parts.yaml` instead. use std::{collections::BTreeMap, fs, path::Path}; +use mspm0_data_types::ClockTree; use serde::Deserialize; -use serde_json::Value; -#[derive(Debug, Deserialize)] -pub struct ClockTree { - #[serde(rename(deserialize = "ipInstances"))] - pub ip_instances: Vec<IpInstance>, +use crate::parts::PartFamily; + +/// The node which is present exactly when the family has a high frequency crystal driver. +const HFXT: &str = "HFXT"; + +/// The node for the external digital HFCLK input. +const HFCLK_IN: &str = "HFCLKEXT"; + +/// The node which is present exactly when the family has a low frequency crystal driver. +const LFXT: &str = "LFXT"; + +/// The mux which selects the external digital LFCLK input. +const LFCLK_IN: &str = "EXLFMUX"; + +/// The mux which selects between HFCLK and the SYSPLL, and so is present only with a SYSPLL. +const SYSPLL: &str = "SYSPLLMUX"; + +#[derive(Debug)] +pub struct ClockTrees { + /// Keyed by family name, lowercase (e.g. `mspm0g350x`). + pub files: BTreeMap<String, ClockTreeFile>, } -impl ClockTree { - pub fn read_clock_trees(data_sources: &Path) -> anyhow::Result<BTreeMap<String, Self>> { - let mut clock_trees = BTreeMap::new(); +impl ClockTrees { + pub fn parse(data_sources: &Path) -> anyhow::Result<Self> { + let mut files = BTreeMap::new(); let sysconfigs = data_sources.join("sysconfig"); for path in glob::glob(&format!("{}/**/clocktree.json", sysconfigs.display())) .unwrap() .flatten() { + // The file is named after its directory rather than the device. let Some(name) = path.iter().nth_back(1) else { continue; }; let name = name.to_string_lossy().to_lowercase(); - let content = fs::read_to_string(path)?; - let clock_tree = serde_json::from_str::<ClockTree>(&content)?; - - if name.contains("c110x") { - for entry in clock_tree.ip_instances.iter() { - match entry.ty.as_str() { - "Divider" | "Mux" => { - // dbg!(&entry); - } - _ => {} - } - } - } - - clock_trees.insert(name, clock_tree); + let content = fs::read_to_string(&path)?; + + files.insert(name, serde_json::from_str::<ClockTreeFile>(&content)?); } - Ok(clock_trees) + Ok(Self { files }) } } #[derive(Debug, Deserialize)] -pub struct IpInstance { - pub name: String, - - #[serde(rename(deserialize = "displayName"))] - pub display_name: Option<String>, - - #[serde(rename(deserialize = "type"))] - pub ty: String, - - #[serde(flatten)] - pub mode: Option<Mode>, - - #[serde(rename(deserialize = "inPins"))] - pub in_pins: Vec<Pin>, - - #[serde(rename(deserialize = "outPins"))] - pub out_pins: Vec<Pin>, - - // TODO: divideValues (optional) - #[serde(rename(deserialize = "divideValues"))] - pub divide_values: Option<Vec<u32>>, - - /// The meaning of the reset value depends on the type of the instance. - #[serde(rename(deserialize = "resetValue"))] - pub reset_value: Option<Value>, - - pub range: Option<[Value; 2]>, +pub struct ClockTreeFile { + #[serde(rename(deserialize = "ipInstances"))] + pub ip_instances: Vec<IpInstance>, } -#[derive(Debug, Deserialize)] -pub enum Mode { - #[serde(rename(deserialize = "enabled"))] - Enabled(bool), - - #[serde(rename(deserialize = "fixed"))] - Fixed(bool), +impl ClockTreeFile { + /// The clock tree of a family, combining what this file describes with the curated remainder. + pub fn clock_tree(&self, family: &PartFamily) -> ClockTree { + let has = |name: &str| self.ip_instances.iter().any(|node| node.name == name); + + ClockTree { + hfxt: has(HFXT), + hfclk_in: has(HFCLK_IN), + hfclk_hz: family.clock_tree.hfclk_hz.map(Into::into), + lfxt: has(LFXT), + lfclk_in: has(LFCLK_IN), + syspll: has(SYSPLL), + ulpclk_div: family.clock_tree.ulpclk_div, + stop1: family.clock_tree.stop1, + } + } } +/// One node of the tree. +/// +/// Only the name is read: every fact taken from this file is the presence of a node. #[derive(Debug, Deserialize)] -pub struct Pin { +pub struct IpInstance { pub name: String, - - #[serde(rename(deserialize = "displayName"))] - pub display_name: Option<String>, } diff --git a/mspm0-data-gen/src/errata.rs b/mspm0-data-gen/src/errata.rs new file mode 100644 index 0000000..fbeacff --- /dev/null +++ b/mspm0-data-gen/src/errata.rs @@ -0,0 +1,34 @@ +//! Reads `data/errata/<family>.yaml`, which `tools/errata.py` extracts from the errata sheets. +//! +//! Which errata apply is not derivable from anything else in the sources, and it is the one kind of +//! data whose absence is silent: a missing workaround leaves a device that mostly works. + +use std::{collections::BTreeMap, fs}; + +use anyhow::Context; +use serde::Deserialize; + +/// The errata of one family, by TI's identifier. +#[derive(Debug, Deserialize)] +#[serde(transparent)] +pub struct Errata { + pub errata: Vec<String>, +} + +impl Errata { + /// Read every `data/errata/<family>.yaml`, keyed by family name. + pub fn parse() -> anyhow::Result<BTreeMap<String, Self>> { + let mut families = BTreeMap::new(); + + for path in glob::glob("data/errata/*.yaml").unwrap().flatten() { + let family = path.file_stem().unwrap().to_string_lossy().to_string(); + let content = fs::read_to_string(&path)?; + let errata = serde_yaml::from_str::<Self>(&content) + .context(format!("Error reading errata for {family}"))?; + + families.insert(family, errata); + } + + Ok(families) + } +} diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index ce878b5..ea4557e 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -8,12 +8,14 @@ use std::{ use anyhow::{anyhow, bail, ensure, Context}; use mspm0_data_types::{ - Chip, DmaChannel, Interrupt, Memory, MemoryKind, Package, PackagePin, Peripheral, - PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, + Adc, Chip, DmaChannel, Interrupt, Memory, MemoryKind, Package, PackagePin, Peripheral, + PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, WakeTimes, }; use regex::Regex; use crate::{ + clock_tree::{ClockTreeFile, ClockTrees}, + errata::Errata, header::{Header, Headers}, int_group::Groups, operating_modes::OperatingModes, @@ -21,6 +23,7 @@ use crate::{ perimap::PERIMAP, svd::{Svd, Svds}, sysconfig::{self, PartPeripheralWrapper, Sysconfig, SysconfigFile}, + timers::Timers, verify, }; @@ -31,6 +34,10 @@ pub fn generate( svds: &Svds, operating_modes: &BTreeMap<String, OperatingModes>, int_groups: &BTreeMap<String, Groups>, + timers: &BTreeMap<String, Timers>, + clock_trees: &ClockTrees, + errata: &BTreeMap<String, Errata>, + wake: &BTreeMap<String, WakeTimes>, ) -> anyhow::Result<()> { fs::create_dir_all("./build/data/").unwrap(); @@ -61,6 +68,10 @@ pub fn generate( svd, operating_modes.get(&family.family), int_groups, + timers.get(&family.family), + clock_trees.files.get(&family.family), + errata.get(&family.family), + wake.get(&family.family).copied(), ) .context(format!("Error when generating family: {}", family.family))?; } @@ -75,6 +86,10 @@ fn generate_family( svd: Option<&Svd>, operating_modes: Option<&OperatingModes>, int_groups: &BTreeMap<String, Groups>, + timers: Option<&Timers>, + clock_tree: Option<&ClockTreeFile>, + errata: Option<&Errata>, + wake: Option<WakeTimes>, ) -> anyhow::Result<()> { // Data shared across all chips in a family. let packages = get_packages(&family.family, sysconfig)?; @@ -83,14 +98,19 @@ fn generate_family( let mut peripherals = generate_peripherals2(&family.family, header, sysconfig)?; let interrupts = generate_irqs(&family.family, header, int_groups)?; let dma_channels = generate_dma_channels(&family.family, sysconfig)?; - let adc_memctl = generate_adc_memctl_dim(&family.family, sysconfig)?; let backup_domain = has_backup_domain(&family.family, sysconfig, &peripherals)?; + let clock_tree = clock_tree + .context(format!("{}: no clocktree.json", family.family))? + .clock_tree(family); // Low power facts which are easier to attach once every peripheral is known. apply_peripheral_interrupts(&mut peripherals, &interrupts); apply_block_async(&mut peripherals, svd); apply_operating_modes(operating_modes, &mut peripherals); apply_standby1_timers(family, &mut peripherals)?; + apply_timers(timers, &mut peripherals); + apply_clock_ranges(family, &mut peripherals); + apply_adc(family, sysconfig, &mut peripherals)?; for part_number in family.part_numbers.iter() { // Filter for package types available on the part number. @@ -132,12 +152,15 @@ fn generate_family( peripherals: peripherals.clone(), interrupts: interrupts.clone(), dma_channels: dma_channels.clone(), - adc_memctl, - adc_vrsel: adc_vrsel_mapping(&family.adc_vrsel)?, nvic_priority_bits: header.nvic_priority_bits, max_mclk_hz: family.max_mclk_hz, max_ulpclk_hz: family.max_ulpclk_hz, + sysosc_base_hz: family.sysosc_base_hz, + flash_wait_hz: family.flash_wait_hz.clone(), backup_domain, + clock_tree, + errata: errata.map(|e| e.errata.clone()).unwrap_or_default(), + wake_ns: wake.unwrap_or_default(), }; for err in verify::verify(&chip, &part_number.name) { @@ -317,6 +340,9 @@ fn generate_peripherals2( retained_through: None, usable_through: None, clocked_in_standby1: None, + timer: None, + clock_range_hz: None, + adc: None, }; // Lookup the pins @@ -570,6 +596,9 @@ fn generate_missing( retained_through: None, usable_through: None, clocked_in_standby1: None, + timer: None, + clock_range_hz: None, + adc: None, }, ); @@ -598,6 +627,9 @@ fn generate_missing( retained_through: None, usable_through: None, clocked_in_standby1: None, + timer: None, + clock_range_hz: None, + adc: None, }, ); @@ -634,6 +666,9 @@ fn generate_missing( retained_through: None, usable_through: None, clocked_in_standby1: None, + timer: None, + clock_range_hz: None, + adc: None, }); let pin = device_pin @@ -894,56 +929,53 @@ fn generate_dma_channels( Ok(channels) } -fn generate_adc_memctl_dim(chip_name: &str, sysconfig: &SysconfigFile) -> anyhow::Result<u8> { - // We parse the SYS_ADC_MEMCTL_DIM attribute in the ADC peripherals. - // Because we use the same adc pac for all ADC instances, we check if all existing MEMCTL_DIM - // attributes are equal. - let mut result = None; +/// Record the facts about each ADC instance which the shared `adc_v1` register block cannot carry. +/// +/// `MEMCTL` is per instance in sysconfig, so it is read per instance even though no part has two ADCs +/// which disagree. `VRSEL` is stated once per family. +fn apply_adc( + family: &PartFamily, + sysconfig: &SysconfigFile, + peripherals: &mut BTreeMap<String, Peripheral>, +) -> anyhow::Result<()> { + let chip_name = &family.family; + let vrsel = adc_vrsel_mapping(&family.adc_vrsel)?; + let mut memctl = BTreeMap::new(); for peripheral in sysconfig .peripherals .values() .filter(|p| p.name.starts_with("ADC")) { - let name = &peripheral.name; - // make names consistent sometimes - let name = maybe_rename(name); + let name = maybe_rename(&peripheral.name); - let raw_memctl_dim = &peripheral + let raw = peripheral .attributes .get("SYS_ADC_MEMCTL_DIM") - .unwrap_or_else(|| { - panic!( - "SYS_ADC_MEMCTL_DIM should exist for {} in {}", - name, chip_name - ) - }); - let memctl_error = format!( - "SYS_ADC_MEMCTL_DIM: {} should be an u32 as string for {} in {}", - raw_memctl_dim, name, chip_name - ); - let memctl_dim = raw_memctl_dim - .as_str() - .expect(memctl_error.as_str()) - .parse::<u8>() - .expect(memctl_error.as_str()); - match result { - Some(m) => ensure!( - m == memctl_dim, - "{}: Found unequal memctl dim attributes {}, {}", - chip_name, - m, - memctl_dim - ), - None => result = Some(memctl_dim), - }; + .context(format!("{chip_name}: {name} has no SYS_ADC_MEMCTL_DIM"))?; + let raw = raw.as_str().context(format!( + "{chip_name}: {name} SYS_ADC_MEMCTL_DIM is not a string value" + ))?; + let dim = raw.parse::<u8>().context(format!( + "{chip_name}: {name} SYS_ADC_MEMCTL_DIM `{raw}` is not a number" + ))?; + + memctl.insert(name, dim); } - ensure!( - result.is_some(), - "{}: Unable to find any SYS_ADC_MEMCTL_DIM", - chip_name - ); - Ok(result.unwrap()) + + for (name, peripheral) in peripherals.iter_mut() { + if peripheral.ty != PeripheralType::Adc { + continue; + } + + let memctl = *memctl + .get(name) + .context(format!("{chip_name}: {name} has no MEMCTL count"))?; + + peripheral.adc = Some(Adc { memctl, vrsel }); + } + + Ok(()) } fn adc_vrsel_mapping(vrsel: &String) -> anyhow::Result<u8> { @@ -1111,6 +1143,39 @@ fn apply_operating_modes( } } +/// Record the input clock range of the peripherals whose datasheet specifies one. +/// +/// The ADC and the TRNG are the only two so far. Both are stated once per datasheet, so they apply to +/// every instance of the family: the parts which have two ADCs give one `fADCCLK` covering both. +fn apply_clock_ranges(family: &PartFamily, peripherals: &mut BTreeMap<String, Peripheral>) { + for peripheral in peripherals.values_mut() { + peripheral.clock_range_hz = match peripheral.ty { + PeripheralType::Adc => Some(family.adc_clock_hz.into()), + PeripheralType::Trng => family.trng_clock_hz.map(Into::into), + _ => None, + }; + } +} + +/// Record what each timer instance can do. +/// +/// Matched by instance name, which is what the datasheet table names its rows. That the same name +/// can mean different capabilities on another family is exactly why this is read per family rather +/// than from one table. +fn apply_timers(timers: Option<&Timers>, peripherals: &mut BTreeMap<String, Peripheral>) { + let Some(timers) = timers else { + return; + }; + + for (name, peripheral) in peripherals.iter_mut() { + if peripheral.ty != PeripheralType::Tim { + continue; + } + + peripheral.timer = timers.timers.get(name).copied(); + } +} + /// Record which timers keep receiving a clock in STANDBY1. fn apply_standby1_timers( family: &PartFamily, diff --git a/mspm0-data-gen/src/main.rs b/mspm0-data-gen/src/main.rs index 77a1709..a4877d0 100644 --- a/mspm0-data-gen/src/main.rs +++ b/mspm0-data-gen/src/main.rs @@ -1,4 +1,5 @@ mod clock_tree; +mod errata; mod generate; mod header; mod int_group; @@ -7,8 +8,10 @@ mod parts; mod perimap; mod svd; mod sysconfig; +mod timers; mod util; mod verify; +mod wakeup; use std::{path::PathBuf, time::Instant}; @@ -60,7 +63,7 @@ fn main() -> anyhow::Result<()> { stopwatch.section("Sysconfig metadata"); let sysconfig = sysconfig::Sysconfig::parse(&data_sources)?; - let _clock_trees = clock_tree::ClockTree::read_clock_trees(&data_sources)?; + let clock_trees = clock_tree::ClockTrees::parse(&data_sources)?; stopwatch.section("Parsing SVDs"); @@ -70,6 +73,9 @@ fn main() -> anyhow::Result<()> { let int_groups = int_group::Groups::parse()?; let operating_modes = operating_modes::OperatingModes::parse()?; + let timers = timers::Timers::parse()?; + let errata = errata::Errata::parse()?; + let wake = wakeup::parse()?; let parts = parts::PartsFile::read()?; // TODO: Expanded family names (ex. C110X -> C1103 & C1104) @@ -82,6 +88,10 @@ fn main() -> anyhow::Result<()> { &svds, &operating_modes, &int_groups, + &timers, + &clock_trees, + &errata, + &wake, )?; stopwatch.stop(); diff --git a/mspm0-data-gen/src/parts.rs b/mspm0-data-gen/src/parts.rs index 3f0ea21..a971ef7 100644 --- a/mspm0-data-gen/src/parts.rs +++ b/mspm0-data-gen/src/parts.rs @@ -40,6 +40,24 @@ pub struct PartFamily { /// Maximum frequency of ULPCLK, in Hz. pub max_ulpclk_hz: u32, + /// Frequency SYSOSC runs at in its factory trimmed base mode, in Hz. + pub sysosc_base_hz: u32, + + /// MCLK ceiling, in Hz, for each `MCLKCFG.FLASHWAIT` setting, starting at zero wait states. + pub flash_wait_hz: Vec<u32>, + + /// The range `fADCCLK` must stay within, which applies to every ADC instance of the family. + pub adc_clock_hz: ClockRange, + + /// The range `TRNGCLKF` must stay within. + /// + /// Absent for the families with no TRNG. + #[serde(default)] + pub trng_clock_hz: Option<ClockRange>, + + /// The parts of the clock tree with no machine-readable source. + pub clock_tree: ClockTreeSpec, + /// Timers which keep receiving ULPCLK or LFCLK in STANDBY1. /// /// Only the timers named here can wake the core from STANDBY1. @@ -49,6 +67,51 @@ pub struct PartFamily { pub part_numbers: Vec<PartNumber>, } +/// The clock tree facts which have to be curated. +/// +/// The crystal drivers, the digital clock inputs and the SYSPLL are not here: sysconfig's +/// `clocktree.json` has a node for each of those exactly when the family has one. These two do not +/// follow from it, and cannot be taken from the register YAMLs either, since two families sharing one +/// SYSCTL version can differ. +#[derive(Debug, Deserialize)] +pub struct ClockTreeSpec { + /// The range HFCLK must stay within, whether it comes from the crystal or the digital input. + /// + /// Absent where the datasheet gives no figure, which is the families with no HFCLK path and + /// MSPM0C1103/C1104, which has an `HFCLKIN` pin but no `fHFIN` row. + #[serde(default)] + pub hfclk_hz: Option<ClockRange>, + + /// Whether the device has `MCLKCFG.UDIV`. + /// + /// Cross-checked in `verify` against the clock ceilings: a device with a divider is one whose + /// ULPCLK ceiling is below its MCLK ceiling. + pub ulpclk_div: bool, + + /// Whether the device has the STOP1 sub-mode, from the presence of a `SYSOSCCFG.FREQ=01` + /// operating point. + pub stop1: bool, +} + +/// An inclusive frequency range as `parts.yaml` writes it, in Hz. +/// +/// Spelled `min`/`max` there rather than `min_hz`/`max_hz`, since the key it sits under already says +/// the unit. +#[derive(Debug, Clone, Copy, Deserialize)] +pub struct ClockRange { + pub min: u32, + pub max: u32, +} + +impl From<ClockRange> for mspm0_data_types::ClockRange { + fn from(range: ClockRange) -> Self { + Self { + min_hz: range.min, + max_hz: range.max, + } + } +} + #[derive(Debug, Deserialize)] pub struct PartNumber { /// The part number. diff --git a/mspm0-data-gen/src/timers.rs b/mspm0-data-gen/src/timers.rs new file mode 100644 index 0000000..55e70a5 --- /dev/null +++ b/mspm0-data-gen/src/timers.rs @@ -0,0 +1,37 @@ +//! Reads `data/timers/<family>.yaml`, which `tools/timers.py` extracts from the datasheets. +//! +//! Every timer instance shares the `tim_v1` register block, so nothing in `data/registers` or the +//! SVDs distinguishes a bare `TIMB0` from a `TIMA0` with deadband insertion and a fault handler. +//! The datasheet's TIMx configuration table is the only per-device source which does; see the +//! tool's module docs for why the SVDs and the TRM are not used. + +use std::{collections::BTreeMap, fs}; + +use anyhow::Context; +use mspm0_data_types::Timer; +use serde::Deserialize; + +/// Timers of one family, keyed by instance name. +#[derive(Debug, Deserialize)] +#[serde(transparent)] +pub struct Timers { + pub timers: BTreeMap<String, Timer>, +} + +impl Timers { + /// Read every `data/timers/<family>.yaml`, keyed by family name. + pub fn parse() -> anyhow::Result<BTreeMap<String, Self>> { + let mut families = BTreeMap::new(); + + for path in glob::glob("data/timers/*.yaml").unwrap().flatten() { + let family = path.file_stem().unwrap().to_string_lossy().to_string(); + let content = fs::read_to_string(&path)?; + let timers = serde_yaml::from_str::<Self>(&content) + .context(format!("Error reading timers for {family}"))?; + + families.insert(family, timers); + } + + Ok(families) + } +} diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 1a06f21..65e4e21 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -15,6 +15,11 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { // Low power data which is only as complete as the data sources block_async_known, standby1_timer_exists, + timer_capabilities_known, + errata_known, + wake_times_ordered, + // Frequencies transcribed from the datasheets + clock_frequencies_consistent, retention_known, wakeup_pins_known, // Power domains @@ -129,6 +134,219 @@ fn wakeup_pins_known(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// Verify the frequencies which have to agree with each other. +/// +/// These are transcribed per family from the datasheet, so a typo is the likely failure and it would +/// otherwise be silent. +fn clock_frequencies_consistent(chip: &Chip, name: &str) -> anyhow::Result<()> { + if chip.max_ulpclk_hz > chip.max_mclk_hz { + bail!( + "{name}: max_ulpclk_hz {} is above max_mclk_hz {}", + chip.max_ulpclk_hz, + chip.max_mclk_hz + ); + } + + // The only reason to have a MCLK to ULPCLK divider is a ULPCLK which cannot run as fast as MCLK, + // so the curated flag and the two ceilings have to say the same thing. + let ulpclk_slower = chip.max_ulpclk_hz < chip.max_mclk_hz; + if chip.clock_tree.ulpclk_div != ulpclk_slower { + bail!( + "{name}: clock_tree.ulpclk_div is {} but ULPCLK {} runs slower than MCLK {}", + chip.clock_tree.ulpclk_div, + if ulpclk_slower { "does" } else { "does not" }, + chip.max_mclk_hz + ); + } + + // A range for a clock the device cannot source is a transcription slip. The converse is fine: + // mspm0c110x has an HFCLKIN pin whose frequency its datasheet never specifies. + if let Some(hfclk) = chip.clock_tree.hfclk_hz { + if !chip.clock_tree.hfxt && !chip.clock_tree.hfclk_in { + bail!( + "{name}: clock_tree.hfclk_hz is {}..{} but the device has neither HFXT nor an HFCLK input", + hfclk.min_hz, + hfclk.max_hz + ); + } + + // HFCLK feeds HSCLK, which feeds MCLK, so a range above the MCLK ceiling is unusable at its + // top end and most likely a wrong row. + if hfclk.max_hz > chip.max_mclk_hz { + bail!( + "{name}: clock_tree.hfclk_hz reaches {} but max_mclk_hz is {}", + hfclk.max_hz, + chip.max_mclk_hz + ); + } + } + + // SYSOSC is one of MCLK's sources, so the tree cannot be built if it overshoots the ceiling. + if chip.sysosc_base_hz > chip.max_mclk_hz { + bail!( + "{name}: sysosc_base_hz {} is above max_mclk_hz {}", + chip.sysosc_base_hz, + chip.max_mclk_hz + ); + } + + if chip.flash_wait_hz.is_empty() { + bail!("{name}: flash_wait_hz has no zero wait state ceiling"); + } + + if chip.flash_wait_hz.windows(2).any(|pair| pair[0] >= pair[1]) { + bail!( + "{name}: flash_wait_hz {:?} is not ascending", + chip.flash_wait_hz + ); + } + + // The deepest band has to reach the ceiling, or MCLK could be configured at a rate no number of + // wait states covers. + let deepest = *chip.flash_wait_hz.last().unwrap(); + if deepest != chip.max_mclk_hz { + bail!( + "{name}: deepest flash_wait_hz band is {deepest} but max_mclk_hz is {}", + chip.max_mclk_hz + ); + } + + for peripheral in chip.peripherals.values() { + // The datasheets specify an input clock range for both of these, so a missing one is a + // missing `adc_clock_hz` or `trng_clock_hz` in parts.yaml rather than a silent peripheral. + let specified = matches!(peripheral.ty, PeripheralType::Adc | PeripheralType::Trng); + + let Some(range) = peripheral.clock_range_hz else { + if specified { + bail!( + "{name}, {}: the datasheet specifies an input clock range for this peripheral \ + but parts.yaml has none for family {}", + peripheral.name, + chip.family + ); + } + continue; + }; + + if range.min_hz > range.max_hz { + bail!( + "{name}, {}: clock range {}..{} is inverted", + peripheral.name, + range.min_hz, + range.max_hz + ); + } + } + + Ok(()) +} + +/// Report chips with no errata data, and check the identifiers are well formed and sorted. +/// +/// Empty is a claim that no functional advisory applies, which no MSPM0 device makes, so it means the +/// sheet has not been read rather than that the device is clean. Sorted is what lets a consumer look +/// an erratum up by binary search. +fn errata_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + static PATTERN: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^[A-Z0-9]+_ERR_\d+$").unwrap()); + + if chip.errata.is_empty() { + bail!( + "{name}: data/errata/{}.yaml is missing or empty", + chip.family + ); + } + + for erratum in chip.errata.iter() { + if !PATTERN.is_match(erratum) { + bail!("{name}: `{erratum}` is not an errata identifier"); + } + } + + if !chip.errata.is_sorted() { + bail!("{name}: errata are not sorted"); + } + + Ok(()) +} + +/// Verify the wake-up times, which are transcribed per family from the datasheet. +/// +/// A deeper mode costs more to leave than a shallower one, so an inversion is a misread row rather +/// than a real measurement. Only the pairs the datasheets actually state are compared: a mode with no +/// figure is skipped rather than assumed. +fn wake_times_ordered(chip: &Chip, name: &str) -> anyhow::Result<()> { + let wake = &chip.wake_ns; + + if wake == &Default::default() { + bail!( + "{name}: data/wakeup/{}.yaml is missing or empty", + chip.family + ); + } + + // Shallowest to deepest, with two deliberate omissions. + // + // STOP2 is not in the chain: it disables SYSOSC where STOP1 keeps it at 4MHz, and leaving it can + // be *cheaper* than leaving STOP1 rather than dearer. mspm0l130x is 13.0us from STOP2 against + // 14.0us from STOP1, and mspm0g350x 12.9us against 13.5us, so an ordering check would misfire. + // + // SHUTDOWN is not either: it is a boot rather than a wake, which is why its figure is an order of + // magnitude larger. It is compared against STANDBY0 separately below. + let ordered = [ + ("sleep0", wake.sleep0), + ("sleep1", wake.sleep1), + ("sleep2", wake.sleep2), + ("stop0", wake.stop0), + ("stop1", wake.stop1), + ("standby0", wake.standby0), + ("standby1", wake.standby1), + ]; + + let stated = ordered + .iter() + .filter_map(|(mode, ns)| ns.map(|ns| (mode, ns))) + .collect::<Vec<_>>(); + + for pair in stated.windows(2) { + let ((shallow, shallow_ns), (deep, deep_ns)) = (pair[0], pair[1]); + if shallow_ns > deep_ns { + bail!( + "{name}: waking from {shallow} costs {shallow_ns}ns but from the deeper {deep} only \ + {deep_ns}ns" + ); + } + } + + if let (Some(standby), Some(shutdown)) = (wake.standby0, wake.shutdown) { + if shutdown < standby { + bail!("{name}: booting from SHUTDOWN ({shutdown}ns) is faster than waking from STANDBY0 ({standby}ns)"); + } + } + + Ok(()) +} + +/// Report timers whose capabilities are unknown, which is a missing row in `data/timers`. +fn timer_capabilities_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + let unknown = chip + .peripherals + .values() + .filter(|peripheral| peripheral.ty == PeripheralType::Tim) + .filter(|peripheral| peripheral.timer.is_none()) + .map(|peripheral| peripheral.name.as_str()) + .collect::<Vec<_>>(); + + if !unknown.is_empty() { + bail!( + "{name}: timers missing from data/timers/{}.yaml: {}", + chip.family, + unknown.join(", ") + ); + } + + Ok(()) +} + /// Verify that every timer knows whether it is clocked in STANDBY1, and that at least one is. fn standby1_timer_exists(chip: &Chip, name: &str) -> anyhow::Result<()> { let timers = chip diff --git a/mspm0-data-gen/src/wakeup.rs b/mspm0-data-gen/src/wakeup.rs new file mode 100644 index 0000000..924bc2e --- /dev/null +++ b/mspm0-data-gen/src/wakeup.rs @@ -0,0 +1,28 @@ +//! Reads `data/wakeup/<family>.yaml`, which `tools/wakeup.py` extracts from the datasheets. +//! +//! How long a device takes to reach RUN from each sleep mode has no machine-readable source, and it is +//! what decides whether a sleep is worth entering before the next deadline. +//! +//! Unlike the other `data/` readers there is no intermediate type: the file's keys are exactly +//! [`WakeTimes`]' fields, so it deserializes straight into the shape the JSON carries. + +use std::{collections::BTreeMap, fs}; + +use anyhow::Context; +use mspm0_data_types::WakeTimes; + +/// Read every `data/wakeup/<family>.yaml`, keyed by family name. +pub fn parse() -> anyhow::Result<BTreeMap<String, WakeTimes>> { + let mut families = BTreeMap::new(); + + for path in glob::glob("data/wakeup/*.yaml").unwrap().flatten() { + let family = path.file_stem().unwrap().to_string_lossy().to_string(); + let content = fs::read_to_string(&path)?; + let wake = serde_yaml::from_str::<WakeTimes>(&content) + .context(format!("Error reading wake-up times for {family}"))?; + + families.insert(family, wake); + } + + Ok(families) +} diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index c2cb062..02f73c9 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -53,14 +53,6 @@ pub struct Chip { /// DMA channels available on the chip. pub dma_channels: BTreeMap<u32, DmaChannel>, - /// Number configurable channels (MEMCTL) in the ADC peripheral. - pub adc_memctl: u8, - - /// Number of options for VRSEL of the ADC peripheral. - /// - /// This is requried because we use a single adc_v1 pac for all chips. - pub adc_vrsel: u8, - /// Number of bits used by the NVIC for interrupt priority levels. pub nvic_priority_bits: u8, @@ -75,11 +67,141 @@ pub struct Chip { /// entering STOP throttles ULPCLK to 4MHz and STANDBY to 32kHz on every device. pub max_ulpclk_hz: u32, + /// Frequency SYSOSC runs at in its factory trimmed base mode (`SYSOSCCFG.FREQ = 0`), in Hz. + /// + /// This is the rate the device boots at, and the rate SYSOSC returns to when a peripheral raises + /// an asynchronous fast clock request. It does not follow [`Chip::max_mclk_hz`]: the two happen + /// to agree on the one part where the base rate is not 32MHz. + /// + /// The fixed low-power operating point (`SYSOSCCFG.FREQ = 1`) is not described separately: it is + /// 4MHz on every device whose datasheet specifies one, and whether it exists at all is what + /// [`ClockTree::stop1`] says. + pub sysosc_base_hz: u32, + + /// MCLK ceiling, in Hz, for each `MCLKCFG.FLASHWAIT` setting, starting at zero wait states. + /// + /// `[24_000_000, 48_000_000, 80_000_000]` means zero wait states up to 24MHz, one up to 48MHz + /// and two up to 80MHz. A single entry means the device's MCLK ceiling is within the zero wait + /// state band, so software never has a reason to raise `FLASHWAIT`. + /// + /// SYSCTL manages wait states on its own unless MCLK is sourced from a high speed clock, which + /// is the case where a consumer has to program them. + pub flash_wait_hz: Vec<u32>, + /// Whether the chip has an independent `VBAT` supply and therefore a real backup power domain /// (PDB). /// /// Peripherals in the backup power domain are the only wake sources which survive SHUTDOWN. pub backup_domain: bool, + + /// Which clock sources and dividers this device's SYSCTL provides. + pub clock_tree: ClockTree, + + /// Errata which apply to this device, by TI's identifier (`GPIO_ERR_01`, `UART_ERR_03`, ...). + /// + /// The functional advisories of the family's errata sheet, meaning the ones TI describes as + /// affecting "the device's operation, function, or parametrics". The preprogrammed-software, + /// debug-only and fixed-by-compiler advisories are not here. + /// + /// An erratum is listed when any silicon revision is affected, since a consumer built for a part + /// has to run on whichever revision it meets. + pub errata: Vec<String>, + + /// How long this device takes to reach RUN from each sleep mode. + pub wake_ns: WakeTimes, +} + +/// Time to reach RUN from each sleep mode, in nanoseconds. +/// +/// This is what decides whether a sleep is worth entering: a mode whose wake-up costs more than the +/// time left before the next deadline is not usable for that deadline. +/// +/// The sub-modes are named as the datasheet's wake-up timing table names them, which is also how +/// `SYSCTL` describes them: STOP0/1/2 and STANDBY0/1 are selected by different register fields and +/// have measurably different costs. +/// +/// Every figure is **typical, not a guaranteed ceiling**. The datasheets give one unqualified number +/// per mode, in a cell spanning their MIN, TYP and MAX columns, so there is no worst case to report. +/// A consumer needing a margin has to add its own. +/// +/// `None` means the datasheet has no figure: either the device does not have that mode, or the figure +/// is given in CPU cycles rather than a time, which is how several state SLEEP0. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct WakeTimes { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sleep0: Option<u32>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sleep1: Option<u32>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sleep2: Option<u32>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub stop0: Option<u32>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub stop1: Option<u32>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub stop2: Option<u32>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub standby0: Option<u32>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub standby1: Option<u32>, + + /// SHUTDOWN is a reset rather than a wake, so this is a boot time. Where the datasheet gives it + /// for fast boot both enabled and disabled, the slower figure is the one recorded. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub shutdown: Option<u32>, +} + +/// The clock sources and dividers a device provides. +/// +/// These are presence questions with a definite answer, so they are `bool` rather than +/// `Option<bool>`. +/// +/// Do not expect a consumer to be able to derive these from the SYSCTL version. mspm0c110x and +/// mspm0c1105_c1106 share `sysctl_c110x` but only the latter has a high frequency crystal driver, and +/// mspm0l112x and mspm0l211x share `sysctl_l122x_l222x` but have no STOP1 where mspm0l122x does. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct ClockTree { + /// Has a high frequency crystal driver (`HSCLKEN.HFXTEN`, `HFXIN`/`HFXOUT` pins). + pub hfxt: bool, + + /// Has an external digital HFCLK input (`HSCLKEN.USEEXTHFCLK`, `HFCLKIN` pin). + /// + /// Separate from [`ClockTree::hfxt`]: mspm0c110x accepts a digital HFCLK but has no crystal + /// driver. + pub hfclk_in: bool, + + /// The range HFCLK must stay within, from the datasheet's `fHFXT` and `fHFIN`. + /// + /// Both paths share one range on every device which specifies them, so this covers a crystal and + /// a digital input alike. It is **not** the SYSPLL reference range: `fSYSPLLREF` is 4-48MHz on + /// every device with a SYSPLL, which HFCLK is only on the G families. + /// + /// `None` where the datasheet gives no figure: the families with no HFCLK path, and + /// mspm0c110x, which has an `HFCLKIN` pin but no `fHFIN` row. + #[serde(skip_serializing_if = "Option::is_none")] + pub hfclk_hz: Option<ClockRange>, + + /// Has a low frequency crystal driver (`LFXTCTL.SETUSELFXT`, `LFXIN`/`LFXOUT` pins). + pub lfxt: bool, + + /// Has an external digital LFCLK input (`EXLFCTL.SETUSEEXLF`, `LFCLKIN` pin). + pub lfclk_in: bool, + + /// Has a SYSPLL, and therefore an `HSCLKCFG.HSCLKSEL` mux. + /// + /// Without it HSCLK is HFCLK and there is no mux field to program. + pub syspll: bool, + + /// Has `MCLKCFG.UDIV`, the MCLK to ULPCLK divider. + /// + /// Only the devices whose ULPCLK ceiling is below their MCLK ceiling have one. + pub ulpclk_div: bool, + + /// Has the STOP1 sub-mode, in which SYSOSC drops to 4MHz rather than stopping + /// (`SYSOSCCFG.USE4MHZSTOP`). + /// + /// Where this is `false` the device's STOP mode is only STOP0 and STOP2. + pub stop1: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -323,6 +445,103 @@ pub struct Peripheral { /// not timers. #[serde(skip_serializing_if = "Option::is_none")] pub clocked_in_standby1: Option<bool>, + + /// What this timer instance can do. + /// + /// `None` for peripherals which are not timers, and for timers of a family which publishes no + /// SVD and has no entry in `data/timers.yaml`. + #[serde(skip_serializing_if = "Option::is_none")] + pub timer: Option<Timer>, + + /// The range this peripheral's functional clock input must stay within. + /// + /// For the ADC this is `fADCCLK`, the rate of the source selected by `CLKCFG.SAMPCLK` before + /// `CTL0.SCLKDIV` divides it down. For the TRNG it is `TRNGCLKF`, the rate reaching the module + /// after `CLKDIV.RATIO`. + /// + /// `None` where the datasheet specifies no such range for the peripheral, which is every + /// peripheral other than those two so far. + #[serde(skip_serializing_if = "Option::is_none")] + pub clock_range_hz: Option<ClockRange>, + + /// What this ADC instance provides. + /// + /// `None` for peripherals which are not ADCs. + #[serde(skip_serializing_if = "Option::is_none")] + pub adc: Option<Adc>, +} + +/// The parts of one ADC instance which the single `adc_v1` register block does not describe. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Adc { + /// Number of configurable conversion channels (`MEMCTL`). + pub memctl: u8, + + /// Number of options `CTL2.VRSEL` accepts. + pub vrsel: u8, +} + +/// An inclusive frequency range, in Hz. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct ClockRange { + pub min_hz: u32, + pub max_hz: u32, +} + +/// The capabilities of one timer instance. +/// +/// Every MSPM0 timer shares the `tim_v1` register block, so the generated PAC says nothing about +/// which of these an instance actually implements. Instances differ widely: `TIMA0` has deadband +/// insertion and a fault handler, `TIMG12` is 32-bit with no prescaler, and `TIMB0` is a bare +/// counter with no capture/compare at all. +/// +/// Capability does not follow the instance name, so a consumer must not key a table on it: `TIMG2` +/// has two capture/compare channels on mspm0l110x, and sysconfig gives the mspm0l112x one the same +/// `SYS_FLAVOR` as `TIMA0`. +/// +/// Read from the datasheet's TIMx configuration table by `tools/timers.py`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Timer { + /// Counter width in bits, either 16 or 32. + pub bits: u8, + + /// Whether the instance has the 8-bit prescaler. + pub prescaler: bool, + + /// Whether the instance has the repeat counter. + pub repeat_counter: bool, + + /// Capture/compare channels which have a `CCPx` output. + /// + /// Zero for the basic timers (`TIMBx`), which cannot capture or compare at all. The datasheet + /// calls these the external channels; the compare-only channels behind them (`CC_45`, on the + /// advanced timers only) are not described here, since only the L-series datasheets say how + /// many there are. + pub ccp_channels: u8, + + /// PWM outputs the instance drives, counting a channel's complementary output separately. + /// + /// Twice [`Timer::ccp_channels`] on the instances with deadband insertion, since those pair + /// each channel with a `CCPx_CMPL` output, and equal to it on the rest. + pub external_pwm_channels: u8, + + /// Whether the instance has the phase load register. + pub phase_load: bool, + + /// Whether the load register is shadowed. + pub shadow_load: bool, + + /// Whether the capture/compare registers are shadowed. + pub shadow_ccs: bool, + + /// Whether the instance has deadband insertion. + pub deadband: bool, + + /// Whether the instance has a fault handler. + pub fault_handler: bool, + + /// Whether the instance can decode quadrature and Hall inputs. + pub qei_hall: bool, } /// An operating mode, ordered from shallowest to deepest. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 08a3675..27af2fa 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -9,8 +9,6 @@ pub struct Metadata { pub interrupts: &'static [Interrupt], pub interrupt_groups: &'static [InterruptGroup], pub dma_channels: &'static [DmaChannel], - pub adc_memctl: u8, - pub adc_vrsel: u8, /// Maximum frequency of MCLK, in Hz. /// @@ -23,11 +21,140 @@ pub struct Metadata { /// throttles ULPCLK to 4MHz and STANDBY to 32kHz on every device. pub max_ulpclk_hz: u32, + /// Frequency SYSOSC runs at in its factory trimmed base mode (`SYSOSCCFG.FREQ = 0`), in Hz. + /// + /// This is the rate the device boots at, and the rate SYSOSC returns to when a peripheral raises + /// an asynchronous fast clock request. Do not infer it from `max_mclk_hz`: the two happen to + /// agree on the one part whose base rate is not 32MHz, and nothing makes them agree in general. + /// + /// The fixed low-power operating point (`SYSOSCCFG.FREQ = 1`) is not described separately: it is + /// 4MHz on every device whose datasheet specifies one, and whether it exists at all is what + /// `clock_tree.stop1` says. + pub sysosc_base_hz: u32, + + /// MCLK ceiling, in Hz, for each `MCLKCFG.FLASHWAIT` setting, starting at zero wait states. + /// + /// `[24_000_000, 48_000_000, 80_000_000]` means zero wait states up to 24MHz, one up to 48MHz + /// and two up to 80MHz. A single entry means this device's MCLK ceiling is inside the zero wait + /// state band, so software never has a reason to raise `FLASHWAIT`. + /// + /// SYSCTL manages wait states on its own unless MCLK is sourced from a high speed clock, which + /// is the case where they have to be programmed. + pub flash_wait_hz: &'static [u32], + /// Whether the chip has an independent `VBAT` supply and therefore a real backup power domain /// (PDB). /// /// Peripherals in the backup power domain are the only wake sources which survive SHUTDOWN. pub backup_domain: bool, + + /// Which clock sources and dividers this device's SYSCTL provides. + pub clock_tree: ClockTree, + + /// Errata which apply to this device, by TI's identifier (`GPIO_ERR_01`, `UART_ERR_03`, ...), + /// sorted. + /// + /// These are the functional advisories of the device's errata sheet, the ones TI describes as + /// affecting "the device's operation, function, or parametrics". The preprogrammed-software, + /// debug-only and fixed-by-compiler advisories are not listed. + /// + /// An erratum is listed when any silicon revision is affected, since a driver built for a part has + /// to run on whichever revision it meets. It is not a claim that a workaround is needed in every + /// case -- read the advisory. + pub errata: &'static [&'static str], + + /// How long this device takes to reach RUN from each sleep mode. + pub wake_ns: WakeTimes, +} + +/// Time to reach RUN from each sleep mode, in nanoseconds. +/// +/// This is what decides whether a sleep is worth entering: a mode whose wake-up costs more than the +/// time left before the next deadline is not usable for that deadline. +/// +/// The sub-modes are named as SYSCTL describes them, since STOP0/1/2 and STANDBY0/1 are selected by +/// different register fields and have measurably different costs. +/// +/// Every figure is **typical, not a guaranteed ceiling**. The datasheets give one unqualified number +/// per mode, in a cell spanning their MIN, TYP and MAX columns, so there is no worst case to report and +/// a consumer needing a margin has to add its own. +/// +/// `None` means the datasheet has no figure: either the device does not have that mode, or the figure +/// is given in CPU cycles rather than a time, which is how several state SLEEP0. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct WakeTimes { + pub sleep0: Option<u32>, + pub sleep1: Option<u32>, + pub sleep2: Option<u32>, + pub stop0: Option<u32>, + pub stop1: Option<u32>, + pub stop2: Option<u32>, + pub standby0: Option<u32>, + pub standby1: Option<u32>, + + /// SHUTDOWN is a reset rather than a wake, so this is a boot time. Where the datasheet gives it + /// for fast boot both enabled and disabled, the slower figure is the one recorded. + pub shutdown: Option<u32>, +} + +impl Metadata { + /// Whether the named erratum applies to this device. + pub fn has_erratum(&self, erratum: &str) -> bool { + self.errata.binary_search(&erratum).is_ok() + } +} + +/// The clock sources and dividers a device provides. +/// +/// These are presence questions with a definite answer, so they are `bool` rather than +/// `Option<bool>`. +/// +/// Do not derive these from the SYSCTL version: mspm0c110x and mspm0c1105_c1106 share +/// `sysctl_c110x` but only the latter has a high frequency crystal driver, and mspm0l112x and +/// mspm0l211x share `sysctl_l122x_l222x` but have no STOP1 where mspm0l122x does. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct ClockTree { + /// Has a high frequency crystal driver (`HSCLKEN.HFXTEN`, `HFXIN`/`HFXOUT` pins). + pub hfxt: bool, + + /// Has an external digital HFCLK input (`HSCLKEN.USEEXTHFCLK`, `HFCLKIN` pin). + /// + /// Separate from `hfxt`: mspm0c110x accepts a digital HFCLK but has no crystal driver. + pub hfclk_in: bool, + + /// The range HFCLK must stay within, from the datasheet's `fHFXT` and `fHFIN`. + /// + /// Both paths share one range on every device which specifies them, so this covers a crystal and + /// a digital input alike. **It is not the SYSPLL reference range**: `fSYSPLLREF` is 4-48MHz on + /// every device that has a SYSPLL, but HFCLK only reaches 48MHz on the G families and stops at + /// 32MHz on the rest, so one constant cannot serve both checks. + /// + /// `None` where the datasheet gives no figure: the families with no HFCLK path, and mspm0c110x, + /// which has an `HFCLKIN` pin but no `fHFIN` row. + pub hfclk_hz: Option<ClockRange>, + + /// Has a low frequency crystal driver (`LFXTCTL.SETUSELFXT`, `LFXIN`/`LFXOUT` pins). + pub lfxt: bool, + + /// Has an external digital LFCLK input (`EXLFCTL.SETUSEEXLF`, `LFCLKIN` pin). + pub lfclk_in: bool, + + /// Has a SYSPLL, and therefore an `HSCLKCFG.HSCLKSEL` mux. + /// + /// Without it HSCLK is HFCLK and there is no mux field to program. + pub syspll: bool, + + /// Has `MCLKCFG.UDIV`, the MCLK to ULPCLK divider. + /// + /// Only the devices whose `max_ulpclk_hz` is below their `max_mclk_hz` have one. + pub ulpclk_div: bool, + + /// Has the STOP1 sub-mode, in which SYSOSC drops to 4MHz rather than stopping + /// (`SYSOSCCFG.USE4MHZSTOP`). + /// + /// Where this is `false` the device's STOP mode is only STOP0 and STOP2, and a consumer with a + /// STOP1 of its own has to round it to STOP0 rather than weakening the guard. + pub stop1: bool, } #[derive(Debug, Eq, PartialEq, Clone, Copy)] @@ -79,6 +206,107 @@ pub struct Peripheral { /// only timers which can wake the core from the deepest sleep. `None` for peripherals which are /// not timers. pub clocked_in_standby1: Option<bool>, + + /// What this timer instance can do. + /// + /// `None` for peripherals which are not timers. + pub timer: Option<Timer>, + + /// The range this peripheral's functional clock input must stay within. + /// + /// For the ADC this is `fADCCLK`, the rate of the source selected by `CLKCFG.SAMPCLK` before + /// `CTL0.SCLKDIV` divides it down โ€” not the sampling clock itself. For the TRNG it is + /// `TRNGCLKF`, the rate reaching the module after `CLKDIV.RATIO`. + /// + /// It does not follow anything else here. Two parts can share `max_mclk_hz` and a SYSCTL version + /// and still differ, and the minimum is not always 4MHz. + /// + /// `None` where the datasheet specifies no such range, which is every peripheral other than the + /// ADC and the TRNG so far. + pub clock_range_hz: Option<ClockRange>, + + /// What this ADC instance provides. + /// + /// `None` for peripherals which are not ADCs. + pub adc: Option<Adc>, +} + +/// An inclusive frequency range, in Hz. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct ClockRange { + pub min_hz: u32, + pub max_hz: u32, +} + +impl ClockRange { + /// Whether `hz` is within the range. + pub const fn contains(&self, hz: u32) -> bool { + self.min_hz <= hz && hz <= self.max_hz + } +} + +/// The parts of one ADC instance which the single `adc_v1` register block does not describe. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct Adc { + /// Number of configurable conversion channels (`MEMCTL`). + pub memctl: u8, + + /// Number of options `CTL2.VRSEL` accepts. + pub vrsel: u8, +} + +/// The capabilities of one timer instance. +/// +/// Every MSPM0 timer shares the `tim_v1` register block, so the `pac` module says nothing about +/// which of these an instance actually implements. Instances differ widely: `TIMA0` has deadband +/// insertion and a fault handler, `TIMG12` is 32-bit with no prescaler, and `TIMB0` is a bare +/// counter with no capture/compare at all. +/// +/// Capability does not follow the instance name, so do not key a table on it. `TIMG2` has two +/// capture/compare channels on mspm0l110x, and TI's own metadata gives the mspm0l112x one the same +/// feature tier as `TIMA0`. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct Timer { + /// Counter width in bits, either 16 or 32. + pub bits: u8, + + /// Whether the instance has the 8-bit prescaler. + pub prescaler: bool, + + /// Whether the instance has the repeat counter. + pub repeat_counter: bool, + + /// Capture/compare channels which have a `CCPx` output. + /// + /// Zero for the basic timers (`TIMBx`), which cannot capture or compare at all. The datasheet + /// calls these the external channels; the compare-only channels behind them (`CC_45`, on the + /// advanced timers only) are not described here, since only the L-series datasheets say how + /// many there are. + pub ccp_channels: u8, + + /// PWM outputs the instance drives, counting a channel's complementary output separately. + /// + /// Twice `ccp_channels` on the instances with deadband insertion, since those pair each channel + /// with a `CCPx_CMPL` output, and equal to it on the rest. + pub external_pwm_channels: u8, + + /// Whether the instance has the phase load register. + pub phase_load: bool, + + /// Whether the load register is shadowed. + pub shadow_load: bool, + + /// Whether the capture/compare registers are shadowed. + pub shadow_ccs: bool, + + /// Whether the instance has deadband insertion. + pub deadband: bool, + + /// Whether the instance has a fault handler. + pub fault_handler: bool, + + /// Whether the instance can decode quadrature and Hall inputs. + pub qei_hall: bool, } /// An operating mode, ordered from shallowest to deepest. diff --git a/mspm0-metapac-gen/src/main.rs b/mspm0-metapac-gen/src/main.rs index 916e426..805da40 100644 --- a/mspm0-metapac-gen/src/main.rs +++ b/mspm0-metapac-gen/src/main.rs @@ -148,12 +148,63 @@ fn generate_chip_metadata( }); let family = &chip.family; - let adc_memctl = &chip.adc_memctl; - let adc_vrsel = &chip.adc_vrsel; let nvic_priority_bits = &chip.nvic_priority_bits; let max_mclk_hz = &chip.max_mclk_hz; let max_ulpclk_hz = &chip.max_ulpclk_hz; + let sysosc_base_hz = &chip.sysosc_base_hz; + let flash_wait_hz = chip + .flash_wait_hz + .iter() + .map(|hz| hz.to_string()) + .collect::<Vec<_>>() + .join(", "); let backup_domain = &chip.backup_domain; + let tree = &chip.clock_tree; + let hfclk_hz = match tree.hfclk_hz { + Some(range) => format!( + "Some(ClockRange {{ min_hz: {}, max_hz: {} }})", + range.min_hz, range.max_hz + ), + None => "None".to_string(), + }; + let clock_tree = format!( + "ClockTree {{ hfxt: {}, hfclk_in: {}, hfclk_hz: {}, lfxt: {}, lfclk_in: {}, syspll: {}, \ + ulpclk_div: {}, stop1: {} }}", + tree.hfxt, + tree.hfclk_in, + hfclk_hz, + tree.lfxt, + tree.lfclk_in, + tree.syspll, + tree.ulpclk_div, + tree.stop1, + ); + + let errata = chip + .errata + .iter() + .map(|erratum| format!("{erratum:?}")) + .collect::<Vec<_>>() + .join(", "); + + let wake = &chip.wake_ns; + let ns = |mode: Option<u32>| match mode { + Some(ns) => format!("Some({ns})"), + None => "None".to_string(), + }; + let wake_ns = format!( + "WakeTimes {{ sleep0: {}, sleep1: {}, sleep2: {}, stop0: {}, stop1: {}, stop2: {}, \ + standby0: {}, standby1: {}, shutdown: {} }}", + ns(wake.sleep0), + ns(wake.sleep1), + ns(wake.sleep2), + ns(wake.stop0), + ns(wake.stop1), + ns(wake.stop2), + ns(wake.standby0), + ns(wake.standby1), + ns(wake.shutdown), + ); // Memory is the one part of the metadata which varies by part number rather than by family let memory = metadata::memory(chip); @@ -175,11 +226,14 @@ fn generate_chip_metadata( interrupts: INTERRUPTS, interrupt_groups: INTERRUPT_GROUPS, dma_channels: DMA_CHANNELS, - adc_memctl: {adc_memctl}, - adc_vrsel: {adc_vrsel}, max_mclk_hz: {max_mclk_hz}, max_ulpclk_hz: {max_ulpclk_hz}, + sysosc_base_hz: {sysosc_base_hz}, + flash_wait_hz: &[{flash_wait_hz}], backup_domain: {backup_domain}, + clock_tree: {clock_tree}, + errata: &[{errata}], + wake_ns: {wake_ns}, }}; ", ) diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 5cc3d2a..95e2d75 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -304,6 +304,54 @@ fn generate_peripheral( None => quote! { None }, }; + let timer = match &peripheral.timer { + Some(timer) => { + let bits = Literal::u8_unsuffixed(timer.bits); + let ccp_channels = Literal::u8_unsuffixed(timer.ccp_channels); + let external_pwm_channels = Literal::u8_unsuffixed(timer.external_pwm_channels); + let (prescaler, repeat_counter) = (timer.prescaler, timer.repeat_counter); + let (phase_load, shadow_load, shadow_ccs) = + (timer.phase_load, timer.shadow_load, timer.shadow_ccs); + let (deadband, fault_handler, qei_hall) = + (timer.deadband, timer.fault_handler, timer.qei_hall); + + quote! { + Some(Timer { + bits: #bits, + prescaler: #prescaler, + repeat_counter: #repeat_counter, + ccp_channels: #ccp_channels, + external_pwm_channels: #external_pwm_channels, + phase_load: #phase_load, + shadow_load: #shadow_load, + shadow_ccs: #shadow_ccs, + deadband: #deadband, + fault_handler: #fault_handler, + qei_hall: #qei_hall, + }) + } + } + None => quote! { None }, + }; + + let clock_range_hz = match peripheral.clock_range_hz { + Some(range) => { + let min_hz = Literal::u32_unsuffixed(range.min_hz); + let max_hz = Literal::u32_unsuffixed(range.max_hz); + quote! { Some(ClockRange { min_hz: #min_hz, max_hz: #max_hz }) } + } + None => quote! { None }, + }; + + let adc = match peripheral.adc { + Some(adc) => { + let memctl = Literal::u8_unsuffixed(adc.memctl); + let vrsel = Literal::u8_unsuffixed(adc.vrsel); + quote! { Some(Adc { memctl: #memctl, vrsel: #vrsel }) } + } + None => quote! { None }, + }; + Some(quote! { Peripheral { name: #name, @@ -317,6 +365,9 @@ fn generate_peripheral( retained_through: #retained_through, usable_through: #usable_through, clocked_in_standby1: #clocked_in_standby1, + timer: #timer, + clock_range_hz: #clock_range_hz, + adc: #adc, } }) } From 623c141b4b7a61ab8be231c00a7131bc2bc3b90c Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Tue, 4 Aug 2026 21:07:36 +0100 Subject: [PATCH 029/134] Document the new data sources --- README.md | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7957a32..a35594e 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,11 @@ These are the data sources currently used. * Peripheral PF (pin function) mappings. * Peripheral pin names. * Which pins have wakeup logic (`io_wakeup`). + * Number of ADC conversion channels (`SYS_ADC_MEMCTL_DIM`). +* SysConfig `clocktree.json` + * Which crystal drivers, external clock inputs and SYSPLL a family has. This is not derivable from + the SYSCTL register block: MSPM0C110x and MSPM0C1105/C1106 share one, but only the latter has a + crystal driver. * mspm0-sdk header files * Interrupt number, name * Peripheral addresses @@ -47,24 +52,40 @@ These are the data sources currently used. * Register blocks * Which peripheral instances have a `CLKCFG.BLOCKASYNC` bit. TI does not publish an SVD for every family, so this is optional per family. -* Device datasheets, from the "Supported Functionality by Operating Mode" table and its footnotes +* Device datasheets, read by the scripts in [`tools/`](./tools) * How deep a sleep each PD1 peripheral is retained through, and how deep each peripheral - stays usable ([`data/operating_modes/`](./data/operating_modes)) + stays usable, from the "Supported Functionality by Operating Mode" table + ([`data/operating_modes/`](./data/operating_modes)) + * What each timer instance can do, from the TIMx configuration table + ([`data/timers/`](./data/timers)) + * How long the device takes to reach RUN from each sleep mode, from the wake-up timing table + ([`data/wakeup/`](./data/wakeup)) * Which timers stay clocked in STANDBY1 (`standby1_timers` in [`parts.yaml`](./data/parts.yaml)) - * MCLK and ULPCLK ceilings + * MCLK and ULPCLK ceilings, the SYSOSC base frequency, the flash wait-state bands, `fADCCLK` and + `TRNGCLKF` (all in [`parts.yaml`](./data/parts.yaml)) +* Device errata sheets + * Which functional advisories apply ([`data/errata/`](./data/errata)) * Manually entered * IIDX values for interrupts within a `INT_GROUP` + * Whether the device has `MCLKCFG.UDIV` and the STOP1 sub-mode (`clock_tree` in + [`parts.yaml`](./data/parts.yaml)) + +Run `./d download-docs` to fetch the datasheets, errata sheets and reference manuals into `./files/`; +the `tools/` scripts read them from there. # Adding a new chip 1. Update the data sources to include the new chip. You will need to get the SVD and sysconfig metadata. 2. Add the new chip family and part numbers to [`parts.yaml`](./data/parts.yaml). Besides the part - numbers and memory this needs the clock ceilings and `standby1_timers` from the datasheet. + numbers and memory this needs every frequency, the `clock_tree` entries and `standby1_timers`, + all from the datasheet. 3. If needed, add any chip specific register blocks like `sysctl`. 4. Check the peripheral mapping in [`perimap.rs`](./mspm0-data-gen/src/perimap.rs) to use the correct peripherals. -5. Generate `data/operating_modes/<family>.yaml` with - [`tools/operating_modes.py`](./tools/operating_modes.py), which reads the datasheet's per-mode - table. +5. Fetch the documents with `./d download-docs`, then regenerate the extracted data: + `tools/operating_modes.py`, `tools/timers.py`, `tools/wakeup.py` and `tools/errata.py`, each with + `--write files`. +6. Run `./d gen` and read its output. `verify.rs` reports every per-chip gap it can detect, including + a family with no timer, errata or operating-mode data. # Adding support for a new peripheral From 3e43c6c365ade608a1cf8f79b70335cc1f571678 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Tue, 4 Aug 2026 21:29:32 +0100 Subject: [PATCH 030/134] Cross-check timer channel counts against sysconfig Sysconfig counts capture/compare channels too, and agrees with the datasheet on all 98 instances which have both, so a disagreement means the table was misread. This would have caught the MSPM0L2117 column shift. Only the count is checked. SYS_FLAVOR cannot stand in for the rest: flavorC with two channels covers TIMG6 both with and without shadow load, since the later G families drop it where the earlier ones keep it. --- mspm0-data-gen/src/generate.rs | 48 +++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index ea4557e..1a4d070 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -108,7 +108,7 @@ fn generate_family( apply_block_async(&mut peripherals, svd); apply_operating_modes(operating_modes, &mut peripherals); apply_standby1_timers(family, &mut peripherals)?; - apply_timers(timers, &mut peripherals); + apply_timers(family, sysconfig, timers, &mut peripherals)?; apply_clock_ranges(family, &mut peripherals); apply_adc(family, sysconfig, &mut peripherals)?; @@ -1162,18 +1162,58 @@ fn apply_clock_ranges(family: &PartFamily, peripherals: &mut BTreeMap<String, Pe /// Matched by instance name, which is what the datasheet table names its rows. That the same name /// can mean different capabilities on another family is exactly why this is read per family rather /// than from one table. -fn apply_timers(timers: Option<&Timers>, peripherals: &mut BTreeMap<String, Peripheral>) { +fn apply_timers( + family: &PartFamily, + sysconfig: &SysconfigFile, + timers: Option<&Timers>, + peripherals: &mut BTreeMap<String, Peripheral>, +) -> anyhow::Result<()> { let Some(timers) = timers else { - return; + return Ok(()); }; + // Sysconfig's own count of capture/compare channels, where it has one. It agrees with the + // datasheet on every instance which has both, so a disagreement means the table was misread + // rather than that the two sources describe different things. + // + // Only the count is cross-checked. `SYS_FLAVOR` cannot stand in for the rest: `flavorC` with two + // channels covers `TIMG6` both with and without shadow load, since mspm0g151x, mspm0g351x and + // mspm0g518x drop it where the earlier G families keep it. + let mut channels = BTreeMap::new(); + for peripheral in sysconfig.peripherals.values() { + let Some(count) = peripheral.attributes.get("SYS_NUM_CC") else { + continue; + }; + let Some(count) = count.as_str().and_then(|count| count.parse::<u8>().ok()) else { + bail!( + "{}: {} SYS_NUM_CC is not a number: {count}", + family.family, + peripheral.name + ); + }; + + channels.insert(maybe_rename(&peripheral.name), count); + } + for (name, peripheral) in peripherals.iter_mut() { if peripheral.ty != PeripheralType::Tim { continue; } - peripheral.timer = timers.timers.get(name).copied(); + let timer = timers.timers.get(name).copied(); + peripheral.timer = timer; + + if let (Some(timer), Some(&count)) = (timer, channels.get(name)) { + ensure!( + timer.ccp_channels == count, + "{}, {name}: data/timers says {} capture/compare channels but sysconfig says {count}", + family.family, + timer.ccp_channels + ); + } } + + Ok(()) } /// Record which timers keep receiving a clock in STANDBY1. From 5901d4b7f522d8363c25d135785f15a7e38d3e9b Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Tue, 4 Aug 2026 22:00:39 +0100 Subject: [PATCH 031/134] Let a peripheral have more than one interrupt The MSPM33 parts route a peripheral's interrupt outputs to several NVIC lines: on MSPM33C321x the HSADC has five, and the timers and UNICOMM instances have two to four. Note this is the opposite multiplicity to an INT_GROUP, so the two have to coexist rather than one subsuming the other. Also report NVIC interrupts no peripheral claims, which is how a naming convention this does not recognise would otherwise go unnoticed. The generic event subscriber ports are exempt, having no peripheral to claim them. --- mspm0-data-gen/src/generate.rs | 56 ++++++++++++++++--------------- mspm0-data-gen/src/verify.rs | 41 ++++++++++++++++++++++ mspm0-data-types/src/lib.rs | 11 ++++-- mspm0-metapac-gen/res/metadata.rs | 9 +++-- mspm0-metapac-gen/src/metadata.rs | 37 ++++++++++---------- 5 files changed, 102 insertions(+), 52 deletions(-) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 1a4d070..4bb4e5f 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -335,7 +335,7 @@ fn generate_peripherals2( pins: vec![], sys_fentries, // Filled in by the `apply_*` passes once every peripheral is known. - interrupt: None, + interrupts: Vec::new(), block_async: None, retained_through: None, usable_through: None, @@ -591,7 +591,7 @@ fn generate_missing( power_domain: PowerDomain::Pd1, pins: vec![], sys_fentries: None, - interrupt: None, + interrupts: Vec::new(), block_async: None, retained_through: None, usable_through: None, @@ -622,7 +622,7 @@ fn generate_missing( power_domain: PowerDomain::Pd0, pins: vec![], sys_fentries: None, - interrupt: None, + interrupts: Vec::new(), block_async: None, retained_through: None, usable_through: None, @@ -661,7 +661,7 @@ fn generate_missing( power_domain: PowerDomain::Pd0, pins: vec![], sys_fentries: None, - interrupt: None, + interrupts: Vec::new(), block_async: None, retained_through: None, usable_through: None, @@ -1076,36 +1076,38 @@ fn has_backup_domain( Ok(vbat) } -/// Attach each peripheral to the interrupt it raises. +/// Attach each peripheral to the interrupts it raises. /// -/// A peripheral either owns an NVIC interrupt of its own or sits inside an `INT_GROUP` and shares -/// the group's interrupt, distinguished by an `IIDX` value. Both are matched by name, which is the -/// only thing tying them together in the vendor data. +/// A peripheral either owns NVIC interrupts of its own or sits inside an `INT_GROUP` and shares the +/// group's interrupt, distinguished by an `IIDX` value. Both are matched by name, which is the only +/// thing tying them together in the vendor data. +/// +/// Every match is collected rather than the first. No MSPM0 peripheral has more than one, but the +/// MSPM33 parts route a peripheral's interrupt outputs to several NVIC lines. fn apply_peripheral_interrupts( peripherals: &mut BTreeMap<String, Peripheral>, interrupts: &BTreeMap<i32, Interrupt>, ) { for (name, peripheral) in peripherals.iter_mut() { - peripheral.interrupt = interrupts - .values() - .find_map(|interrupt| { - (&interrupt.name == name).then(|| PeripheralInterrupt { - name: interrupt.name.clone(), - num: interrupt.num, - group_iidx: None, - }) + let own = interrupts.values().filter_map(|interrupt| { + (&interrupt.name == name).then(|| PeripheralInterrupt { + name: interrupt.name.clone(), + num: interrupt.num, + group_iidx: None, }) - .or_else(|| { - interrupts.values().find_map(|interrupt| { - let (&iidx, _) = interrupt.group.iter().find(|(_, member)| *member == name)?; - - Some(PeripheralInterrupt { - name: interrupt.name.clone(), - num: interrupt.num, - group_iidx: Some(iidx), - }) - }) - }); + }); + + let shared = interrupts.values().filter_map(|interrupt| { + let (&iidx, _) = interrupt.group.iter().find(|(_, member)| *member == name)?; + + Some(PeripheralInterrupt { + name: interrupt.name.clone(), + num: interrupt.num, + group_iidx: Some(iidx), + }) + }); + + peripheral.interrupts = own.chain(shared).collect(); } } diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 65e4e21..0691c83 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -17,6 +17,7 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { standby1_timer_exists, timer_capabilities_known, errata_known, + interrupts_claimed, wake_times_ordered, // Frequencies transcribed from the datasheets clock_frequencies_consistent, @@ -241,6 +242,46 @@ fn clock_frequencies_consistent(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// Report NVIC interrupts which no peripheral claims. +/// +/// `apply_peripheral_interrupts` ties the two together by name, so a naming convention it does not +/// recognise leaves the peripheral with an empty list and nothing to say so. That is what the MSPM33 +/// parts would do here: their headers name the HSADC's five lines `ADC0_INT_PUB1` through +/// `ADC0_EVT_INT_PUB1`, none of which equals the peripheral's own name. +fn interrupts_claimed(chip: &Chip, name: &str) -> anyhow::Result<()> { + /// Interrupts which belong to no peripheral, so nothing can claim them. + /// + /// The generic event subscriber ports are part of the event fabric rather than a peripheral, and + /// sysconfig lists no peripheral for them. Only mspm0h321x has them. + static NO_PERIPHERAL: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^GENSUB\d+$").unwrap()); + + let claimed = chip + .peripherals + .values() + .flat_map(|peripheral| peripheral.interrupts.iter()) + .map(|interrupt| interrupt.num) + .collect::<HashSet<_>>(); + + let unclaimed = chip + .interrupts + .values() + // Interrupts handled by cortex-m rather than by a peripheral of ours. + .filter(|interrupt| interrupt.num >= 0) + .filter(|interrupt| !claimed.contains(&interrupt.num)) + .filter(|interrupt| !NO_PERIPHERAL.is_match(&interrupt.name)) + .map(|interrupt| format!("{} ({})", interrupt.name, interrupt.num)) + .collect::<Vec<_>>(); + + if !unclaimed.is_empty() { + bail!( + "{name}: no peripheral claims these interrupts, so the names did not match: {}", + unclaimed.join(", ") + ); + } + + Ok(()) +} + /// Report chips with no errata data, and check the identifiers are well formed and sorted. /// /// Empty is a claim that no functional advisory applies, which no MSPM0 device makes, so it means the diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 02f73c9..c0fa8ca 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -407,9 +407,14 @@ pub struct Peripheral { #[serde(skip_serializing_if = "Option::is_none")] pub sys_fentries: Option<usize>, - /// The interrupt raised by this peripheral. - #[serde(skip_serializing_if = "Option::is_none")] - pub interrupt: Option<PeripheralInterrupt>, + /// The interrupts raised by this peripheral. + /// + /// Usually one, and empty for a peripheral which raises none. A peripheral can have several: the + /// MSPM33 parts route a peripheral's interrupt outputs to more than one NVIC line, so the HSADC + /// has five. Note this is the opposite multiplicity to an `INT_GROUP`, where several peripherals + /// share one line and are told apart by [`PeripheralInterrupt::group_iidx`]; the two can coexist. + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub interrupts: Vec<PeripheralInterrupt>, /// Whether this peripheral instance has its own `CLKCFG.BLOCKASYNC` bit, masking the asynchronous /// fast clock request it raises. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 27af2fa..62cc27c 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -166,8 +166,13 @@ pub struct Peripheral { pub power_domain: PowerDomain, pub sys_fentries: Option<usize>, - /// The interrupt raised by this peripheral, if it has one. - pub interrupt: Option<PeripheralInterrupt>, + /// The interrupts raised by this peripheral. + /// + /// Usually one, and empty for a peripheral which raises none. A peripheral can have several: the + /// MSPM33 parts route a peripheral's interrupt outputs to more than one NVIC line, so the HSADC + /// has five. Note this is the opposite multiplicity to an `INT_GROUP`, where several peripherals + /// share one line and are told apart by `PeripheralInterrupt::group_iidx`; the two can coexist. + pub interrupts: &'static [PeripheralInterrupt], /// Whether this peripheral instance has its own `CLKCFG.BLOCKASYNC` bit. /// diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 95e2d75..2bda1bd 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -255,28 +255,25 @@ fn generate_peripheral( None => quote! { None }, }; - let interrupt = match &peripheral.interrupt { - Some(interrupt) => { - let name = &interrupt.name; - let number = Literal::u32_unsuffixed(interrupt.num as u32); - let group_iidx = match interrupt.group_iidx { - Some(iidx) => { - let iidx = Literal::u32_unsuffixed(iidx); - quote! { Some(#iidx) } - } - None => quote! { None }, - }; + let interrupts = peripheral.interrupts.iter().map(|interrupt| { + let name = &interrupt.name; + let number = Literal::u32_unsuffixed(interrupt.num as u32); + let group_iidx = match interrupt.group_iidx { + Some(iidx) => { + let iidx = Literal::u32_unsuffixed(iidx); + quote! { Some(#iidx) } + } + None => quote! { None }, + }; - quote! { - Some(PeripheralInterrupt { - name: #name, - number: #number, - group_iidx: #group_iidx, - }) + quote! { + PeripheralInterrupt { + name: #name, + number: #number, + group_iidx: #group_iidx, } } - None => quote! { None }, - }; + }); let block_async = match peripheral.block_async { Some(block_async) => quote! { Some(#block_async) }, @@ -360,7 +357,7 @@ fn generate_peripheral( pins: &[#(#pins),*], power_domain: #power_domain, sys_fentries: #sys_fentries, - interrupt: #interrupt, + interrupts: &[#(#interrupts),*], block_async: #block_async, retained_through: #retained_through, usable_through: #usable_through, From 305bb5270bea65147c34d581146690907163c2f5 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Tue, 4 Aug 2026 23:33:54 +0100 Subject: [PATCH 032/134] Make the SYSCTL blocks agree on how shared fields are typed Five fields were an enum in some SYSCTL versions and bare in others, so a single write did not compile across blocks. embassy-mspm0 carries an `mspm0_hfxttime_u8` cfg that exists only to pick a spelling. HFXTTIME loses its enum everywhere rather than gaining one on sysctl_c1105_c1106. It is a non-exhaustive 8-bit newtype naming only the numeric endpoints, which is what the README calls a useless enum, and the one consumer writes a computed value. The endpoints move into the field description, so `set_hfxttime(units)` now compiles on every block. HSCLKSEL goes the other way. TI documents only `1h = HFCLK` on the parts with no SYSPLL, so h321x and l122x_l222x had dropped the enum, and c1105_c1106 spelled value 1 `HFCLK` where the g blocks say `HFCLKCLK`. All six now carry the same enum, with value 0 documented as the SYSPLL position the device cannot use. sysctl_c1105_c1106's CURHSCLKSEL gets its own enum instead of borrowing HSCLKSEL's, matching the other blocks and SLAU893 2.7, which does encode both values for the status field. CURHSCLKSEL on h321x and HFCLK4MFPCLKDIV and BORCURTHRESHOLD on both C blocks were the same shape and are now enums too. Their encodings are identical in every SVD which has the field and in SLAU893 2.6 and 2.7. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --- data/registers/sysctl_c1105_c1106.yaml | 92 +++++++++++++++++-- data/registers/sysctl_c110x.yaml | 71 +++++++++++++- .../sysctl_g350x_g310x_g150x_g110x.yaml | 12 +-- data/registers/sysctl_g351x_g151x.yaml | 12 +-- data/registers/sysctl_h321x.yaml | 34 ++++--- data/registers/sysctl_l122x_l222x.yaml | 24 ++--- 6 files changed, 191 insertions(+), 54 deletions(-) diff --git a/data/registers/sysctl_c1105_c1106.yaml b/data/registers/sysctl_c1105_c1106.yaml index 8cf7916..917b7d4 100644 --- a/data/registers/sysctl_c1105_c1106.yaml +++ b/data/registers/sysctl_c1105_c1106.yaml @@ -224,7 +224,7 @@ fieldset/CLKSTATUS: description: Current HSCLK source, matching HSCLKCFG.HSCLKSEL. bit_offset: 16 bit_size: 1 - enum: HSCLKSEL + enum: CURHSCLKSEL - name: CURMCLKSEL description: 'MCLK Clock Source 0: NOT LFCLK, 1:LFCLK.' bit_offset: 17 @@ -318,9 +318,10 @@ fieldset/GENCLKCFG: bit_size: 1 enum: MFPCLKSRC - name: HFCLK4MFPCLKDIV - description: HFCLK Divider 1..16. + description: Divider applied to HFCLK when HFCLK is used as the MFPCLK source. bit_offset: 12 bit_size: 4 + enum: HFCLK4MFPCLKDIV - name: FCCSELCLK bit_offset: 16 bit_size: 4 @@ -359,7 +360,7 @@ fieldset/HFCLKCLKCFG: description: HFCLK Clock Configuration. fields: - name: HFXTTIME - description: HFXT startup time in 64us resolution. + description: HFXT startup time in 64us resolution, from 0 (minimum startup time, approximately zero) to 255 (maximum, approximately 16.32ms). If the HFCLK startup monitor is enabled (HFCLKFLTCHK), HFXT is checked after this time expires. bit_offset: 0 bit_size: 8 - name: HFXTRSEL @@ -375,7 +376,7 @@ fieldset/HSCLKCFG: description: High Speed Clock Source Selection. fields: - name: HSCLKSEL - description: Selects the HSCLK source. + description: Selects the HSCLK source. This device has no SYSPLL, so HFCLKCLK is the only source it can select, and software must write it - the reset value leaves HSCLK unsourced. bit_offset: 0 bit_size: 1 enum: HSCLKSEL @@ -605,8 +606,10 @@ fieldset/SYSOSCFCLCTL: fieldset/SYSSTATUS: fields: - name: BORCURTHRESHOLD + description: Indicates the active brown-out reset supply monitor configuration. bit_offset: 2 bit_size: 2 + enum: BORCURTHRESHOLD - name: BORLVL bit_offset: 4 bit_size: 1 @@ -675,6 +678,30 @@ enum/BORCLRCMD_KEY: variants: - name: KEY value: 199 +enum/BORCURTHRESHOLD: + bit_size: 2 + variants: + - name: BORMIN + description: Default minimum threshold. A BOR0- violation triggers a BOR. + value: 0 + - name: BORLEVEL1 + description: A BOR1- violation generates a BORLVL interrupt. + value: 1 + - name: BORLEVEL2 + description: A BOR2- violation generates a BORLVL interrupt. + value: 2 + - name: BORLEVEL3 + description: A BOR3- violation generates a BORLVL interrupt. + value: 3 +enum/CURHSCLKSEL: + bit_size: 1 + variants: + - name: SYSPLL + description: HSCLK is currently sourced from the SYSPLL. + value: 0 + - name: HFCLK + description: HSCLK is currently sourced from the HFCLK. + value: 1 enum/DSLEEP: bit_size: 2 variants: @@ -775,6 +802,57 @@ enum/FLASHWAIT: - name: WAIT2 description: 2 flash wait states are applied. value: 2 +enum/HFCLK4MFPCLKDIV: + bit_size: 4 + variants: + - name: DIV1 + description: HFCLK is not divided before being used for MFPCLK. + value: 0 + - name: DIV2 + description: HFCLK is divided by 2 before being used for MFPCLK. + value: 1 + - name: DIV3 + description: HFCLK is divided by 3 before being used for MFPCLK. + value: 2 + - name: DIV4 + description: HFCLK is divided by 4 before being used for MFPCLK. + value: 3 + - name: DIV5 + description: HFCLK is divided by 5 before being used for MFPCLK. + value: 4 + - name: DIV6 + description: HFCLK is divided by 6 before being used for MFPCLK. + value: 5 + - name: DIV7 + description: HFCLK is divided by 7 before being used for MFPCLK. + value: 6 + - name: DIV8 + description: HFCLK is divided by 8 before being used for MFPCLK. + value: 7 + - name: DIV9 + description: HFCLK is divided by 9 before being used for MFPCLK. + value: 8 + - name: DIV10 + description: HFCLK is divided by 10 before being used for MFPCLK. + value: 9 + - name: DIV11 + description: HFCLK is divided by 11 before being used for MFPCLK. + value: 10 + - name: DIV12 + description: HFCLK is divided by 12 before being used for MFPCLK. + value: 11 + - name: DIV13 + description: HFCLK is divided by 13 before being used for MFPCLK. + value: 12 + - name: DIV14 + description: HFCLK is divided by 14 before being used for MFPCLK. + value: 13 + - name: DIV15 + description: HFCLK is divided by 15 before being used for MFPCLK. + value: 14 + - name: DIV16 + description: HFCLK is divided by 16 before being used for MFPCLK. + value: 15 enum/HFXTRSEL: bit_size: 2 variants: @@ -801,10 +879,10 @@ enum/HSCLKSEL: bit_size: 1 variants: - name: SYSPLL - description: HSCLK is sourced from the SYSPLL. + description: The SYSPLL position of the mux, and the reset value. This device has no SYSPLL, so HSCLK has no source while this is selected. value: 0 - - name: HFCLK - description: HSCLK is sourced from HFCLK. + - name: HFCLKCLK + description: HSCLK is sourced from the HFCLK. value: 1 enum/ID: bit_size: 5 diff --git a/data/registers/sysctl_c110x.yaml b/data/registers/sysctl_c110x.yaml index 6a7679f..f315179 100644 --- a/data/registers/sysctl_c110x.yaml +++ b/data/registers/sysctl_c110x.yaml @@ -272,9 +272,10 @@ fieldset/GENCLKCFG: bit_size: 1 enum: MFPCLKSRC - name: HFCLK4MFPCLKDIV - description: HFCLK Divider 1..16. + description: Divider applied to HFCLK when HFCLK is used as the MFPCLK source. bit_offset: 12 bit_size: 4 + enum: HFCLK4MFPCLKDIV - name: FCCSELCLK bit_offset: 16 bit_size: 4 @@ -475,8 +476,10 @@ fieldset/SYSOSCFCLCTL: fieldset/SYSSTATUS: fields: - name: BORCURTHRESHOLD + description: Indicates the active brown-out reset supply monitor configuration. bit_offset: 2 bit_size: 2 + enum: BORCURTHRESHOLD - name: BORLVL bit_offset: 4 bit_size: 1 @@ -545,6 +548,21 @@ enum/BORCLRCMD_KEY: variants: - name: KEY value: 199 +enum/BORCURTHRESHOLD: + bit_size: 2 + variants: + - name: BORMIN + description: Default minimum threshold. A BOR0- violation triggers a BOR. + value: 0 + - name: BORLEVEL1 + description: A BOR1- violation generates a BORLVL interrupt. + value: 1 + - name: BORLEVEL2 + description: A BOR2- violation generates a BORLVL interrupt. + value: 2 + - name: BORLEVEL3 + description: A BOR3- violation generates a BORLVL interrupt. + value: 3 enum/DSLEEP: bit_size: 2 variants: @@ -633,6 +651,57 @@ enum/FCCTRIGSRC: value: 0 - name: LFCLK value: 1 +enum/HFCLK4MFPCLKDIV: + bit_size: 4 + variants: + - name: DIV1 + description: HFCLK is not divided before being used for MFPCLK. + value: 0 + - name: DIV2 + description: HFCLK is divided by 2 before being used for MFPCLK. + value: 1 + - name: DIV3 + description: HFCLK is divided by 3 before being used for MFPCLK. + value: 2 + - name: DIV4 + description: HFCLK is divided by 4 before being used for MFPCLK. + value: 3 + - name: DIV5 + description: HFCLK is divided by 5 before being used for MFPCLK. + value: 4 + - name: DIV6 + description: HFCLK is divided by 6 before being used for MFPCLK. + value: 5 + - name: DIV7 + description: HFCLK is divided by 7 before being used for MFPCLK. + value: 6 + - name: DIV8 + description: HFCLK is divided by 8 before being used for MFPCLK. + value: 7 + - name: DIV9 + description: HFCLK is divided by 9 before being used for MFPCLK. + value: 8 + - name: DIV10 + description: HFCLK is divided by 10 before being used for MFPCLK. + value: 9 + - name: DIV11 + description: HFCLK is divided by 11 before being used for MFPCLK. + value: 10 + - name: DIV12 + description: HFCLK is divided by 12 before being used for MFPCLK. + value: 11 + - name: DIV13 + description: HFCLK is divided by 13 before being used for MFPCLK. + value: 12 + - name: DIV14 + description: HFCLK is divided by 14 before being used for MFPCLK. + value: 13 + - name: DIV15 + description: HFCLK is divided by 15 before being used for MFPCLK. + value: 14 + - name: DIV16 + description: HFCLK is divided by 16 before being used for MFPCLK. + value: 15 enum/HSCLKMUX: bit_size: 1 variants: diff --git a/data/registers/sysctl_g350x_g310x_g150x_g110x.yaml b/data/registers/sysctl_g350x_g310x_g150x_g110x.yaml index 6b96212..c12176d 100644 --- a/data/registers/sysctl_g350x_g310x_g150x_g110x.yaml +++ b/data/registers/sysctl_g350x_g310x_g150x_g110x.yaml @@ -427,10 +427,9 @@ fieldset/HFCLKCLKCFG: description: High-frequency clock (HFCLK) configuration. fields: - name: HFXTTIME - description: HFXTTIME specifies the HFXT startup time in 64us resolution. If the HFCLK startup monitor is enabled (HFCLKFLTCHK), HFXT will be checked after this time expires. + description: HFXT startup time in 64us resolution, from 0 (minimum startup time, approximately zero) to 255 (maximum, approximately 16.32ms). If the HFCLK startup monitor is enabled (HFCLKFLTCHK), HFXT is checked after this time expires. bit_offset: 0 bit_size: 8 - enum: HFXTTIME - name: HFXTRSEL description: HFXT Range Select. bit_offset: 12 @@ -1219,15 +1218,6 @@ enum/HFXTRSEL: - name: RANGE32TO48 description: 32MHz < HFXT frequency <= 48MHz. value: 3 -enum/HFXTTIME: - bit_size: 8 - variants: - - name: MINSTARTTIME - description: Minimum startup time (approximatly zero). - value: 0 - - name: MAXSTARTTIME - description: Maximum startup time (approximatly 16.32ms). - value: 255 enum/HSCLKMUX: bit_size: 1 variants: diff --git a/data/registers/sysctl_g351x_g151x.yaml b/data/registers/sysctl_g351x_g151x.yaml index 845bec4..0437540 100644 --- a/data/registers/sysctl_g351x_g151x.yaml +++ b/data/registers/sysctl_g351x_g151x.yaml @@ -539,10 +539,9 @@ fieldset/HFCLKCLKCFG: description: High-frequency clock (HFCLK) configuration. fields: - name: HFXTTIME - description: HFXTTIME specifies the HFXT startup time in 64us resolution. If the HFCLK startup monitor is enabled (HFCLKFLTCHK), HFXT will be checked after this time expires. + description: HFXT startup time in 64us resolution, from 0 (minimum startup time, approximately zero) to 255 (maximum, approximately 16.32ms). If the HFCLK startup monitor is enabled (HFCLKFLTCHK), HFXT is checked after this time expires. bit_offset: 0 bit_size: 8 - enum: HFXTTIME - name: HFXTRSEL description: HFXT Range Select. bit_offset: 12 @@ -1333,15 +1332,6 @@ enum/HFXTRSEL: - name: RANGE32TO48 description: 32MHz < HFXT frequency <= 48MHz. value: 3 -enum/HFXTTIME: - bit_size: 8 - variants: - - name: MINSTARTTIME - description: Minimum startup time (approximatly zero). - value: 0 - - name: MAXSTARTTIME - description: Maximum startup time (approximatly 16.32ms). - value: 255 enum/HSCLKMUX: bit_size: 1 variants: diff --git a/data/registers/sysctl_h321x.yaml b/data/registers/sysctl_h321x.yaml index eb38132..bea134e 100644 --- a/data/registers/sysctl_h321x.yaml +++ b/data/registers/sysctl_h321x.yaml @@ -239,6 +239,7 @@ fieldset/CLKSTATUS: description: CURHSCLKSEL indicates the current clock source for HSCLK. bit_offset: 16 bit_size: 1 + enum: CURHSCLKSEL - name: CURMCLKSEL description: CURMCLKSEL indicates if MCLK is currently sourced from LFCLK. bit_offset: 17 @@ -381,10 +382,9 @@ fieldset/HFCLKCLKCFG: description: High-frequency clock (HFCLK) configuration. fields: - name: HFXTTIME - description: HFXTTIME specifies the HFXT startup time in 64us resolution. If the HFCLK startup monitor is enabled (HFCLKFLTCHK), HFXT will be checked after this time expires. + description: HFXT startup time in 64us resolution, from 0 (minimum startup time, approximately zero) to 255 (maximum, approximately 16.32ms). If the HFCLK startup monitor is enabled (HFCLKFLTCHK), HFXT is checked after this time expires. bit_offset: 0 bit_size: 8 - enum: HFXTTIME - name: HFXTRSEL description: HFXT Range Select. bit_offset: 12 @@ -398,9 +398,10 @@ fieldset/HSCLKCFG: description: High-speed clock (HSCLK) source selection. fields: - name: HSCLKSEL - description: HSCLKSEL selects the HSCLK source (SYSPLL or HFCLK). + description: Selects the HSCLK source. This device has no SYSPLL, so HFCLKCLK is the only source it can select, and software must write it - the reset value leaves HSCLK unsourced. bit_offset: 0 bit_size: 1 + enum: HSCLKSEL fieldset/HSCLKEN: description: High-speed clock (HSCLK) source enable/disable. fields: @@ -718,6 +719,15 @@ enum/BORCURTHRESHOLD: - name: BORLEVEL3 description: A BOR3- violation generates a BORLVL interrupt. value: 3 +enum/CURHSCLKSEL: + bit_size: 1 + variants: + - name: SYSPLL + description: HSCLK is currently sourced from the SYSPLL. + value: 0 + - name: HFCLK + description: HSCLK is currently sourced from the HFCLK. + value: 1 enum/DSLEEP: bit_size: 2 variants: @@ -904,15 +914,6 @@ enum/HFXTRSEL: - name: RANGE32TO48 description: 32MHz < HFXT frequency <= 48MHz. value: 3 -enum/HFXTTIME: - bit_size: 8 - variants: - - name: MINSTARTTIME - description: Minimum startup time (approximatly zero). - value: 0 - - name: MAXSTARTTIME - description: Maximum startup time (approximatly 16.32ms). - value: 255 enum/HSCLKMUX: bit_size: 1 variants: @@ -922,6 +923,15 @@ enum/HSCLKMUX: - name: HSCLK description: MCLK is sourced from HSCLK. value: 1 +enum/HSCLKSEL: + bit_size: 1 + variants: + - name: SYSPLL + description: The SYSPLL position of the mux, and the reset value. This device has no SYSPLL, so HSCLK has no source while this is selected. + value: 0 + - name: HFCLKCLK + description: HSCLK is sourced from the HFCLK. + value: 1 enum/ID: bit_size: 5 variants: diff --git a/data/registers/sysctl_l122x_l222x.yaml b/data/registers/sysctl_l122x_l222x.yaml index 7c7b865..e632615 100644 --- a/data/registers/sysctl_l122x_l222x.yaml +++ b/data/registers/sysctl_l122x_l222x.yaml @@ -487,10 +487,9 @@ fieldset/HFCLKCLKCFG: description: High-frequency clock (HFCLK) configuration. fields: - name: HFXTTIME - description: HFXTTIME specifies the HFXT startup time in 64us resolution. If the HFCLK startup monitor is enabled (HFCLKFLTCHK), HFXT will be checked after this time expires. + description: HFXT startup time in 64us resolution, from 0 (minimum startup time, approximately zero) to 255 (maximum, approximately 16.32ms). If the HFCLK startup monitor is enabled (HFCLKFLTCHK), HFXT is checked after this time expires. bit_offset: 0 bit_size: 8 - enum: HFXTTIME - name: HFXTRSEL description: HFXT Range Select. bit_offset: 12 @@ -504,9 +503,10 @@ fieldset/HSCLKCFG: description: High-speed clock (HSCLK) source selection. fields: - name: HSCLKSEL - description: HSCLKSEL selects the HSCLK source (SYSPLL or HFCLK). + description: Selects the HSCLK source. This device has no SYSPLL, so HFCLKCLK is the only source it can select, and software must write it - the reset value leaves HSCLK unsourced. bit_offset: 0 bit_size: 1 + enum: HSCLKSEL fieldset/HSCLKEN: description: High-speed clock (HSCLK) source enable/disable. fields: @@ -1140,15 +1140,6 @@ enum/HFXTRSEL: - name: RANGE32TO48 description: 32MHz < HFXT frequency <= 48MHz. value: 3 -enum/HFXTTIME: - bit_size: 8 - variants: - - name: MINSTARTTIME - description: Minimum startup time (approximatly zero). - value: 0 - - name: MAXSTARTTIME - description: Maximum startup time (approximatly 16.32ms). - value: 255 enum/HSCLKMUX: bit_size: 1 variants: @@ -1158,6 +1149,15 @@ enum/HSCLKMUX: - name: HSCLK description: MCLK is sourced from HSCLK. value: 1 +enum/HSCLKSEL: + bit_size: 1 + variants: + - name: SYSPLL + description: The SYSPLL position of the mux, and the reset value. This device has no SYSPLL, so HSCLK has no source while this is selected. + value: 0 + - name: HFCLKCLK + description: HSCLK is sourced from the HFCLK. + value: 1 enum/ID: bit_size: 5 variants: From 95936d19113d806cf82858bd516f189062b7240b Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Tue, 4 Aug 2026 23:34:47 +0100 Subject: [PATCH 033/134] Stop claiming a device without a SYSPLL has no HSCLK mux ClockTree::syspll's doc said that without a SYSPLL "HSCLK is HFCLK and there is no mux field to program". Three of the four blocks with `syspll: false` do have HSCLKCFG.HSCLKSEL: c1105_c1106, h321x and l122x_l222x. Only c110x and l110x_l130x_l134x lack one, and SLAU893 2.6 confirms c110x has no HSCLKCFG register at all, whatever MSPM0C110X.svd says. The stronger half of the correction is that leaving the field alone is not correct either. It resets to 0, the SYSPLL position, and all three TRMs say to set the bit before MCLK can run from HSCLK, so a consumer which skips the write on those parts leaves HSCLK unsourced. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --- mspm0-data-types/src/lib.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index c0fa8ca..c4071ce 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -187,9 +187,14 @@ pub struct ClockTree { /// Has an external digital LFCLK input (`EXLFCTL.SETUSEEXLF`, `LFCLKIN` pin). pub lfclk_in: bool, - /// Has a SYSPLL, and therefore an `HSCLKCFG.HSCLKSEL` mux. - /// - /// Without it HSCLK is HFCLK and there is no mux field to program. + /// Has a SYSPLL, and therefore something other than HFCLK to select as HSCLK. + /// + /// Where this is `false` there is nothing to choose between, but that is **not** the same as + /// there being no `HSCLKCFG.HSCLKSEL` field: only mspm0c110x and the l110x/l130x/l134x families + /// lack one. The rest still have the field, still reset it to the SYSPLL position they cannot + /// use, and still require software to write `HFCLKCLK` before MCLK can run from HSCLK - the + /// TRMs say to set the bit (SLAU893 2.7, SLAU923, SLAU847), and TI's own SVDs document no + /// encoding for the reset value on those parts. pub syspll: bool, /// Has `MCLKCFG.UDIV`, the MCLK to ULPCLK divider. From c69fa4766cdad0a227a4ccd83885c873bf5f1769 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 18:46:43 +0100 Subject: [PATCH 034/134] Take NVIC_PRIO_BITS from the chip It was a hardcoded u32 literal carrying a FIXME, while the parsed value has been on Chip and in Metadata all along. Every device reads 2, so only the type changes: cortex-m, svd2rust and RTIC's generated code all want a u8, and consumers had to cast. --- mspm0-metapac-gen/src/interrupt.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mspm0-metapac-gen/src/interrupt.rs b/mspm0-metapac-gen/src/interrupt.rs index 92924e3..73cf8a4 100644 --- a/mspm0-metapac-gen/src/interrupt.rs +++ b/mspm0-metapac-gen/src/interrupt.rs @@ -9,11 +9,11 @@ pub fn generate(chip: &Chip) -> TokenStream { let interrupt_enum = interrupt_enum(chip); let group_enums = group_enums(chip); let rt_vectors = rt_vectors(chip); + let nvic_priority_bits = Literal::u8_unsuffixed(chip.nvic_priority_bits); quote! { - // FIXME define this from metadata #[cfg(feature = "rt")] - pub const NVIC_PRIO_BITS: u32 = 2; + pub const NVIC_PRIO_BITS: u8 = #nvic_priority_bits; #interrupt_enum #group_enums #rt_vectors From 147b442ec0da748bfbad3de62483cc62d27af4d1 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 18:47:41 +0100 Subject: [PATCH 035/134] Generate the VREF register block 216 chips declared `pub const VREF: () = ();` for a peripheral they physically have. Nothing could start the reference, choose 1.4V over 2.5V, or read its ready bit, so the ADC's internal reference had no honest handling: the TRM is explicit that VREF's ready response does not gate the ADC's sample phase. One version covers the portfolio. SLAU846 23.3, SLAU847 19.3, SLAU893 13.3 and SLAU923 11.3 describe the same eight registers with the same fields, while the SVDs split into four variants which disagree with each other, so the YAML follows the TRMs. CTL0 keeps the three reference buffer enables and CTL1 a single READY, and the DESC register, CTL0.SPARE and CTL1.VREFLOSEL of the L110x/L130x/L134x SVD are dropped as no TRM has them. That SVD also names CTL0 bits 1 to 3 ENABLEBIAS and IBPROG, with TI-internal codenames in the descriptions. PWREN.KEY and RSTCTL.KEY are restored from the TRM. The SVD omits both, and a write to either register does nothing without the key. --- data/registers/vref_v1.yaml | 155 ++++++++++++++++++++++++++++ mspm0-data-gen/src/perimap.rs | 5 + mspm0-metapac-gen/src/peripheral.rs | 1 + 3 files changed, 161 insertions(+) create mode 100644 data/registers/vref_v1.yaml diff --git a/data/registers/vref_v1.yaml b/data/registers/vref_v1.yaml new file mode 100644 index 0000000..1b53359 --- /dev/null +++ b/data/registers/vref_v1.yaml @@ -0,0 +1,155 @@ +block/GPRCM: + items: + - name: PWREN + description: Power enable. + byte_offset: 0 + fieldset: PWREN + - name: RSTCTL + description: Reset Control. + byte_offset: 4 + access: Write + fieldset: RSTCTL + - name: STAT + description: Status Register. + byte_offset: 20 + access: Read + fieldset: STAT +block/VREF: + description: PERIPHERALREGION. + items: + - name: GPRCM + byte_offset: 2048 + block: GPRCM + - name: CLKDIV + description: Clock Divider. + byte_offset: 4096 + fieldset: CLKDIV + - name: CLKSEL + description: Clock Selection. + byte_offset: 4104 + fieldset: CLKSEL + - name: CTL0 + description: Control 0. + byte_offset: 4352 + fieldset: CTL0 + - name: CTL1 + description: Control 1. + byte_offset: 4356 + fieldset: CTL1 + - name: CTL2 + description: Control 2. + byte_offset: 4360 + fieldset: CTL2 +fieldset/CLKDIV: + description: Clock Divider. + fields: + - name: RATIO + description: Selects divide ratio of module clock to be used in sample and hold logic. + bit_offset: 0 + bit_size: 3 +fieldset/CLKSEL: + description: Clock Selection. + fields: + - name: LFCLK_SEL + description: Selects LFCLK as clock source if enabled. + bit_offset: 1 + bit_size: 1 + - name: MFCLK_SEL + description: Selects MFCLK as clock source if enabled. + bit_offset: 2 + bit_size: 1 + - name: BUSCLK_SEL + description: Selects BUSCLK as clock source if enabled. + bit_offset: 3 + bit_size: 1 +fieldset/CTL0: + description: Control 0. + fields: + - name: ENABLE + description: Enables the corresponding VREF reference buffer. A device may implement fewer buffers than the three the register can address; the device datasheet says how many. + bit_offset: 0 + bit_size: 1 + array: + len: 3 + stride: 1 + - name: BUFCONFIG + description: Selects the output voltage of the reference buffers. Changing from 2.5V to 1.4V has a very slow slew rate and READY stays set throughout, so that transition has to be timed by the caller. + bit_offset: 7 + bit_size: 1 + enum: BUFCONFIG + - name: SHMODE + description: This bit enable sample and hold mode. + bit_offset: 8 + bit_size: 1 +fieldset/CTL1: + description: Control 1. + fields: + - name: READY + description: VREF output is ready. Once set, it stays set when VREF is disabled and later re-enabled. On a device with more than one reference buffer it only applies to the first one enabled. + bit_offset: 0 + bit_size: 1 +fieldset/CTL2: + description: Control 2. + fields: + - name: SHCYCLE + description: Sample and Hold cycle count Total cycles of module clock for sample and hold phase when VREF is working in sample and hold mode in STANDBY to save power. This field should be greater than HCYCLE field. The difference between this field and HCYCLE gives the number of cycles of sample phase. Please refer VREF section of datasheet for recommended values of sample and hold times. + bit_offset: 0 + bit_size: 16 + - name: HCYCLE + description: Hold cycle count Total cycles of module clock for hold phase when VREF is working in sample and hold mode in STANDBY to save power. Please refer VREF section of datasheet for recommended values of sample and hold times. + bit_offset: 16 + bit_size: 16 +fieldset/PWREN: + description: Power enable. + fields: + - name: ENABLE + description: Enable the power. + bit_offset: 0 + bit_size: 1 + - name: KEY + description: KEY to allow Power State Change. + bit_offset: 24 + bit_size: 8 + enum: PWREN_KEY +fieldset/RSTCTL: + description: Reset Control. + fields: + - name: RESETASSERT + description: Assert reset to the peripheral. + bit_offset: 0 + bit_size: 1 + - name: RESETSTKYCLR + description: Clear the RESETSTKY bit in the STAT register. + bit_offset: 1 + bit_size: 1 + - name: KEY + description: Unlock key. + bit_offset: 24 + bit_size: 8 + enum: RESET_KEY +fieldset/STAT: + description: Status Register. + fields: + - name: RESETSTKY + description: This bit indicates, if the peripheral was reset, since this bit was cleared by RESETSTKYCLR in the RSTCTL register. + bit_offset: 16 + bit_size: 1 +enum/BUFCONFIG: + bit_size: 1 + variants: + - name: OUTPUT2P5V + description: Configure Output Buffer to 2.5v. + value: 0 + - name: OUTPUT1P4V + description: Configure Output Buffer to 1.4v. + value: 1 +enum/PWREN_KEY: + bit_size: 8 + variants: + - name: KEY + value: 38 +enum/RESET_KEY: + bit_size: 8 + variants: + - name: KEY + value: 177 diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index e120f52..a52caed 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -16,6 +16,11 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ (".*:flashctl", "v1"), (".*:trng", "v1"), (".*:canfd", "v1"), + // SLAU846 ยง23.3, SLAU847 ยง19.3, SLAU893 ยง13.3 and SLAU923 ยง11.3 describe the same eight + // registers with the same fields, so every family shares one version. The SVDs disagree with + // the TRMs and with each other about CTL0 bits 1 and 2, which the YAML resolves in the TRMs' + // favour. + (".*:vref", "v1"), // The RTC comes in variants which are not distinguished by a version number. // SysConfig calls the standalone pre-LFSS peripheral the "legacy" RTC; the variants inside the // LFSS are RTC_A when the LFSS has an independent VBAT supply and RTC_B when it is powered from diff --git a/mspm0-metapac-gen/src/peripheral.rs b/mspm0-metapac-gen/src/peripheral.rs index 0b583e8..c0ceec1 100644 --- a/mspm0-metapac-gen/src/peripheral.rs +++ b/mspm0-metapac-gen/src/peripheral.rs @@ -24,6 +24,7 @@ const GENERATE_PERIPHERALS: &[PeripheralType] = &[ PeripheralType::Tim, PeripheralType::Trng, PeripheralType::Uart, + PeripheralType::Vref, PeripheralType::Wwdt, ]; From 0987114d8212314f691b729dc1e09f53769fbbdc Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 18:48:01 +0100 Subject: [PATCH 036/134] Give TIMB its own register block TIMB is a basic timer. Its CTL0/LD/CNT sit at 0x1100 where the general-purpose block has CCPD/ODIS/CCLKCTL, and it has neither CLKDIV/CLKSEL nor a COUNTERREGS group, but it was mapped to tim_v1, so every register access on mspm0g518x, mspm0l112x and mspm0l211x landed on the wrong field. The perimap key is the peripheral type and TIMB shares PeripheralType::Tim with TIMA and TIMG, so instances named TIMB now key on `timb` instead. The type itself is unchanged, which keeps Peripheral::timer and the timer capability data working for them. The metapac emitted one module per type and deduped the instances away, which would have given two `pub mod tim`. Modules are now emitted per type and version, with VARIANT_MODULES naming the odd one out so `tim` still means the general-purpose block on every chip and TIMB adds `timb`. A collision with no entry panics rather than generating a module twice. The counter array follows the TRM and TI's own header rather than the SVD, which declares four counters twelve bytes apart. SLAU846 35.4.20 and SLAU847 29.4.20 both give `1100h + (j * 100h)`, and hw_btimer.h declares CTRREGS[2] with a 256 byte stride, which is how dl_timerb.h indexes it. --- README.md | 7 + data/registers/tim_btimer.yaml | 382 ++++++++++++++++++++++++++++ mspm0-data-gen/src/generate.rs | 10 +- mspm0-data-gen/src/perimap.rs | 4 + mspm0-metapac-gen/src/peripheral.rs | 120 ++++++--- 5 files changed, 482 insertions(+), 41 deletions(-) create mode 100644 data/registers/tim_btimer.yaml diff --git a/README.md b/README.md index a35594e..d48b2d9 100644 --- a/README.md +++ b/README.md @@ -146,3 +146,10 @@ When parsing a chip, for each peripheral a "key" string is constructed using thi ("mspm0c110x:sysctl", ("sysctl", "c110x")), ("mspm0g..0x:sysctl", ("sysctl", "g350x_g310x_g150x_g110x")), ``` + +`PERIPHERAL_NAME` is the peripheral type, so every instance of a type on a chip gets the same +version. Where one type covers instances with different register blocks the key has to say which, +as `timb` does for the basic timers alongside `tim` โ€” see `get_peripheral_type_version`. Such a +version also needs an entry in `VARIANT_MODULES` in +[`peripheral.rs`](./mspm0-metapac-gen/src/peripheral.rs) to give it a module name of its own, +otherwise the metapac generator panics on the two blocks colliding. diff --git a/data/registers/tim_btimer.yaml b/data/registers/tim_btimer.yaml new file mode 100644 index 0000000..a46c14c --- /dev/null +++ b/data/registers/tim_btimer.yaml @@ -0,0 +1,382 @@ +block/CPU_INT: + items: + - name: IIDX + description: Interrupt index. + byte_offset: 0 + access: Read + fieldset: IIDX + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: INT + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: INT + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: INT + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: INT + - name: ICLR + description: Interrupt clear. + byte_offset: 40 + access: Write + fieldset: INT +block/CTRREGS: + items: + - name: CTL0 + description: CTL0 register. + byte_offset: 0 + fieldset: CTL0 + - name: LD + description: Counter period register. + byte_offset: 4 + fieldset: LD + - name: CNT + description: Counter register. + byte_offset: 8 + fieldset: CNT +block/GEN_EVENT: + items: + - name: IIDX + description: Interrupt index. + byte_offset: 0 + access: Read + fieldset: IIDX + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: INT + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: INT + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: INT + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: INT + - name: ICLR + description: Interrupt clear. + byte_offset: 40 + access: Write + fieldset: INT +block/GPRCM: + items: + - name: PWREN + description: Power enable. + byte_offset: 0 + fieldset: PWREN + - name: RSTCTL + description: Reset Control. + byte_offset: 4 + access: Write + fieldset: RSTCTL + - name: STAT + description: Status Register. + byte_offset: 20 + access: Read + fieldset: STAT +block/TIM: + description: PERIPHERALREGION. + items: + - name: FSUB + description: Subscriber Port 0. + byte_offset: 1024 + fieldset: FPORT + - name: FPUB + description: Publisher Port 0. + byte_offset: 1092 + fieldset: FPORT + - name: GPRCM + array: + len: 1 + stride: 24 + byte_offset: 2048 + block: GPRCM + - name: PDBGCTL + description: Peripheral Debug Control. + byte_offset: 4120 + fieldset: PDBGCTL + - name: CPU_INT + array: + len: 1 + stride: 44 + byte_offset: 4128 + block: CPU_INT + - name: GEN_EVENT + array: + len: 1 + stride: 44 + byte_offset: 4176 + block: GEN_EVENT + - name: EVT_MODE + description: Event Mode. + byte_offset: 4320 + access: Read + fieldset: EVT_MODE + - name: CTRREGS + description: One independent counter. Every device with a TIMB implements two, which is what TI's own `BTIMER_Regs` declares; the register array itself runs to eight. + array: + len: 2 + stride: 256 + byte_offset: 4352 + block: CTRREGS +fieldset/CNT: + description: Counter register. + fields: + - name: VALUE + description: Counter Value. + bit_offset: 0 + bit_size: 16 +fieldset/CTL0: + description: CTL0 register. + fields: + - name: EN + description: Counter Enable. + bit_offset: 0 + bit_size: 1 + - name: STARTSEL + description: Selects the source used to start the counter. + bit_offset: 4 + bit_size: 4 + - name: STOPSEL + description: Selects the source used to stop the counter. + bit_offset: 8 + bit_size: 4 + - name: RESETSEL + description: Selects the source used to reset the counter. + bit_offset: 12 + bit_size: 4 + - name: CLKSEL + description: Clock sources. + bit_offset: 16 + bit_size: 4 +fieldset/EVT_MODE: + description: Event Mode. + fields: + - name: CPU_INT_CFG + description: Event line mode select for event corresponding to CPU_INT. + bit_offset: 0 + bit_size: 2 + enum: EVT_CFG + - name: GEN_EVENT_CFG + description: Event line mode select for event corresponding to GEN_EVENT. + bit_offset: 2 + bit_size: 2 + enum: EVT_CFG +fieldset/FPORT: + description: Publisher or subscriber port. + fields: + - name: CHANID + description: 0 = disconnected. 1-15 = connected to channelID = CHANID. + bit_offset: 0 + bit_size: 4 +fieldset/IIDX: + description: Interrupt index. + fields: + - name: STAT + description: Interrupt index status. + bit_offset: 0 + bit_size: 10 + enum: IIDX_STAT +fieldset/INT: + description: Interrupt mask, status, set or clear. + fields: + - name: CNTOVF + description: Counter period match. + bit_offset: 0 + bit_size: 1 + array: + len: 8 + stride: 3 + - name: CNTSTRT + description: Counter start. + bit_offset: 1 + bit_size: 1 + array: + len: 8 + stride: 3 + - name: CNTSTOP + description: Counter stop. + bit_offset: 2 + bit_size: 1 + array: + len: 8 + stride: 3 +fieldset/LD: + description: Counter period register. + fields: + - name: VAL + description: Period value. + bit_offset: 0 + bit_size: 16 +fieldset/PDBGCTL: + description: Peripheral Debug Control. + fields: + - name: FREE + description: Free run control. + bit_offset: 0 + bit_size: 1 + - name: SOFT + description: Soft halt boundary control. This function is only available, if [FREE] is set to 'STOP'. + bit_offset: 1 + bit_size: 1 + enum: SOFT +fieldset/PWREN: + description: Power enable. + fields: + - name: ENABLE + description: Enable the power. + bit_offset: 0 + bit_size: 1 + - name: KEY + description: KEY to allow Power State Change 26h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: PWREN_KEY +fieldset/RSTCTL: + description: Reset Control. + fields: + - name: RESETASSERT + description: Assert reset to the peripheral. + bit_offset: 0 + bit_size: 1 + - name: RESETSTKYCLR + description: Clear the RESETSTKY bit in the STAT register. + bit_offset: 1 + bit_size: 1 + - name: KEY + description: Unlock key B1h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: RESET_KEY +fieldset/STAT: + description: Status Register. + fields: + - name: RESETSTKY + description: This bit indicates, if the peripheral was reset, since this bit was cleared by RESETSTKYCLR in the RSTCTL register. + bit_offset: 16 + bit_size: 1 +enum/EVT_CFG: + bit_size: 2 + variants: + - name: DISABLE + description: The interrupt or event line is disabled. + value: 0 + - name: SOFTWARE + description: The interrupt or event line is in software mode. Software must clear the RIS. + value: 1 + - name: HARDWARE + description: The interrupt or event line is in hardware mode. The hardware (another module) clears automatically the associated RIS flag. + value: 2 +enum/IIDX_STAT: + bit_size: 10 + variants: + - name: NO_INTR + description: No bit is set means there is no pending interrupt request. + value: 0 + - name: CNT0OVF + description: Counter0 period match interrupt. + value: 1 + - name: CNT0STRT + description: Counter 0 start. + value: 2 + - name: CNT0STOP + description: Counter0 stop. + value: 3 + - name: CNT1OVF + description: Counter1 period match interrupt. + value: 4 + - name: CNT1STRT + description: Counter 1 start. + value: 5 + - name: CNT1STOP + description: Counter1 stop. + value: 6 + - name: CNT2OVF + description: Counter2 period match interrupt. + value: 7 + - name: CNT2STRT + description: Counter 2 start. + value: 8 + - name: CNT2STOP + description: Counter2 stop. + value: 9 + - name: CNT3OVF + description: Counter3 period match interrupt. + value: 10 + - name: CNT3STRT + description: Counter 3 start. + value: 11 + - name: CNT3STOP + description: Counter3 stop. + value: 12 + - name: CNT4OVF + description: Counter4 period match interrupt. + value: 13 + - name: CNT4STRT + description: Counter 4 start. + value: 14 + - name: CNT4STOP + description: Counter4 stop. + value: 15 + - name: CNT5OVF + description: Counter5 period match interrupt. + value: 16 + - name: CNT5STRT + description: Counter 5 start. + value: 17 + - name: CNT5STOP + description: Counter5 stop. + value: 18 + - name: CNT6OVF + description: Counter6 period match interrupt. + value: 19 + - name: CNT6STRT + description: Counter 6 start. + value: 20 + - name: CNT6STOP + description: Counter6 stop. + value: 21 + - name: CNT7OVF + description: Counter7 period match interrupt. + value: 22 + - name: CNT7STRT + description: Counter 7 start. + value: 23 + - name: CNT7STOP + description: Counter7 stop. + value: 24 +enum/PWREN_KEY: + bit_size: 8 + variants: + - name: KEY + value: 38 +enum/RESET_KEY: + bit_size: 8 + variants: + - name: KEY + value: 177 +enum/SOFT: + bit_size: 1 + variants: + - name: IMMEDIATE + description: The peripheral will halt immediately, even if the resultant state will result in corruption if the system is restarted. + value: 0 + - name: DELAYED + description: The peripheral blocks the debug freeze until it has reached a boundary where it can resume without corruption. + value: 1 diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 4bb4e5f..60e9a68 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -783,8 +783,16 @@ fn get_peripheral_type_version(chip_name: &str, name: &str) -> (PeripheralType, } else { PeripheralType::Unknown }; + + // TIMB is a basic timer and has its own register block, so the key names the instance kind + // rather than the peripheral type. TIMA and TIMG share one, and both keep the plain `tim` key. + let key = if ty == PeripheralType::Tim && name.starts_with("TIMB") { + "timb" + } else { + &ty.to_string() + }; let version = PERIMAP - .get(&format!("{}:{}", chip_name, ty)) + .get(&format!("{}:{}", chip_name, key)) .map(|s| s.to_string()); (ty, version) diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index a52caed..5558c12 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -10,6 +10,10 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ (".*:iomux", "v1"), (".*:mathacl", "v1"), (".*:opa", "v1"), + // A TIMB instance is a basic timer: its counters live at 0x1100 where the general-purpose + // block has CCPD/ODIS/CCLKCTL, and it has neither CLKDIV/CLKSEL nor a COUNTERREGS group. + // Keyed on the instance kind, not the peripheral type โ€” see `get_peripheral_type_version`. + (".*:timb", "btimer"), (".*:tim", "v1"), (".*:adc", "v1"), (".*:wwdt", "v1"), diff --git a/mspm0-metapac-gen/src/peripheral.rs b/mspm0-metapac-gen/src/peripheral.rs index c0ceec1..4aeb9f0 100644 --- a/mspm0-metapac-gen/src/peripheral.rs +++ b/mspm0-metapac-gen/src/peripheral.rs @@ -28,6 +28,15 @@ const GENERATE_PERIPHERALS: &[PeripheralType] = &[ PeripheralType::Wwdt, ]; +/// Register block versions which do not get the module named after their peripheral type. +/// +/// One entry per version that has to coexist with another version of the same type on one chip. +/// TIMB is a basic timer: it has its own register block but sits alongside the general-purpose +/// TIMA and TIMG instances, which keep `tim`. +/// +/// Peripheral type, version, module name. +const VARIANT_MODULES: &[(&str, &str, &str)] = &[("tim", "btimer", "timb")]; + pub fn generate(chip: &Chip, all_versions: &mut BTreeMap<String, BTreeSet<String>>) -> TokenStream { let peripheral_imports = generate_peripheral_imports(chip, all_versions); let peripheral_consts = generate_peripheral_consts(chip); @@ -38,52 +47,81 @@ pub fn generate(chip: &Chip, all_versions: &mut BTreeMap<String, BTreeSet<String } } -fn generate_peripheral_imports( - chip: &Chip, - all_versions: &mut BTreeMap<String, BTreeSet<String>>, -) -> TokenStream { - // Sort the peripherals by type for generation. - let mut peripheral_types = chip.peripherals.iter().collect::<Vec<_>>(); - peripheral_types.sort_by_key(|(a, _)| *a); - peripheral_types.dedup_by_key(|(_, v)| v.ty); +/// The register block versions this chip needs, per peripheral type. +/// +/// A type can need more than one: TIMB is a basic timer and does not share the general-purpose +/// block that TIMA and TIMG use, so a chip with both needs two timer blocks generated. +fn chip_versions(chip: &Chip) -> BTreeMap<String, BTreeSet<String>> { + let mut versions = BTreeMap::<String, BTreeSet<String>>::new(); + + for peripheral in chip.peripherals.values() { + if !GENERATE_PERIPHERALS.contains(&peripheral.ty) { + continue; + } + + if let Some(version) = &peripheral.version { + versions + .entry(peripheral.ty.to_string()) + .or_default() + .insert(version.clone()); + } + } + + versions +} - peripheral_types +/// Name of the module a register block is imported as. +/// +/// The module is normally named after the peripheral type, since a chip has one register block per +/// type. `VARIANT_MODULES` names the versions that are the exception, so that the type keeps its +/// own module name on every chip and a chip which also has the variant just gains a second module. +fn module_name<'a>(ty: &'a str, version: &str) -> &'a str { + VARIANT_MODULES .iter() - .map(|(name, peripheral)| generate_import(chip, name, peripheral, all_versions)) - .collect() + .find(|(variant_ty, variant_version, _)| *variant_ty == ty && *variant_version == version) + .map(|(_, _, module)| *module) + .unwrap_or(ty) } -fn generate_import( - _chip: &Chip, - _name: &str, - peripheral: &Peripheral, +fn generate_peripheral_imports( + chip: &Chip, all_versions: &mut BTreeMap<String, BTreeSet<String>>, ) -> TokenStream { - if !GENERATE_PERIPHERALS - .iter() - .any(|ty_generate| &peripheral.ty == ty_generate) - { - return TokenStream::default(); - } + let mut modules = BTreeMap::<String, (String, String)>::new(); - let name = Ident::new( - &format!("{}", peripheral.ty).to_lowercase(), - Span::call_site(), - ); - - if let Some(version) = peripheral.version.clone() { + for (ty, versions) in chip_versions(chip) { all_versions - .entry(name.to_string()) + .entry(ty.clone()) .or_default() - .insert(version.clone()); - let path = format!("../../peripherals/{}_{}.rs", name, version); - quote! { - #[path = #path] - pub mod #name; + .extend(versions.iter().cloned()); + + for version in versions { + let module = module_name(&ty, &version).to_owned(); + + if let Some((_, other)) = modules.insert(module.clone(), (ty.clone(), version.clone())) + { + // Two register blocks cannot share a module. Whichever version is the odd one out + // needs an entry in `VARIANT_MODULES`. + panic!( + "{}: {ty} versions {other} and {version} both want the module {module}", + chip.name + ); + } } - } else { - TokenStream::default() } + + modules + .iter() + .map(|(module, (ty, version))| { + let module = Ident::new(module, Span::call_site()); + let path = format!("../../peripherals/{ty}_{version}.rs"); + + quote! { + #[path = #path] + pub mod #module; + } + }) + .collect() } fn generate_peripheral_consts(chip: &Chip) -> TokenStream { @@ -109,11 +147,13 @@ fn generate_const(peripheral: &Peripheral) -> TokenStream { } let address = Literal::u32_unsuffixed(address); - let module = Ident::new(&format!("{}", peripheral.ty), Span::call_site()); - let ty = Ident::new( - &format!("{}", peripheral.ty).to_pascal_case(), - Span::call_site(), - ); + let ty_name = peripheral.ty.to_string(); + let module = peripheral + .version + .as_deref() + .map_or(ty_name.as_str(), |version| module_name(&ty_name, version)); + let module = Ident::new(module, Span::call_site()); + let ty = Ident::new(&ty_name.to_pascal_case(), Span::call_site()); quote! { pub const #name: #module::#ty = unsafe { #module::#ty::from_ptr(#address as *mut _) }; From a3d9e10ad8465584766d7eb388e5dd762c427939 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 20:52:30 +0100 Subject: [PATCH 037/134] Widen the basic timer's counter array to eight tim_btimer took its length from TI's hw_btimer.h, which declares CTRREGS[2]. That is the count on the L-series parts and on no other: the G-series ones have four, and MSPM0G518X.svd says so too, whatever its stride claims. SLAU846 35.4.20 and SLAU847 29.4.20 both give the array as 1100h + (j * 100h) for j = 0h to 7h, and the interrupt registers carry bits for eight counters. A block shared by every device has to offer all of them; which ones exist is a per-instance fact and does not belong here. --- data/registers/tim_btimer.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/registers/tim_btimer.yaml b/data/registers/tim_btimer.yaml index a46c14c..e65600c 100644 --- a/data/registers/tim_btimer.yaml +++ b/data/registers/tim_btimer.yaml @@ -129,9 +129,9 @@ block/TIM: access: Read fieldset: EVT_MODE - name: CTRREGS - description: One independent counter. Every device with a TIMB implements two, which is what TI's own `BTIMER_Regs` declares; the register array itself runs to eight. + description: One independent counter. The array is the eight the TRM documents and the interrupt registers have bits for; how many a device implements is `Timer::counters`, which is 4 on the G-series instances and 2 on the L-series ones. array: - len: 2 + len: 8 stride: 256 byte_offset: 4352 block: CTRREGS From 609ac0c1566a93a326a626949e690a7697d4b338 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 20:52:43 +0100 Subject: [PATCH 038/134] Say how many counters a timer instance has A basic timer is a counter array, and the count differs between devices which share the register block: four on the G-series TIMBx, two on the L-series. Nothing said which, so a consumer had only the block's eight to bound an index by. The datasheets cannot answer it. Both state it as a feature of TIMBx in general, in identical words on the G-series parts which have four and the L-series ones which have two, and the TIMx configuration table that tools/timers.py reads has no counter column. Sysconfig's SYS_NUM_COUNTERS is per instance and does distinguish them, so apply_timers takes it from there, the way it already takes SYS_NUM_CC. Every other timer has one counter and no attribute. verify.rs rejects a count the instance's own register block cannot reach. --- README.md | 2 ++ mspm0-data-gen/src/generate.rs | 28 ++++++++++++++++++++++++-- mspm0-data-gen/src/verify.rs | 33 +++++++++++++++++++++++++++++++ mspm0-data-types/src/lib.rs | 18 +++++++++++++++++ mspm0-metapac-gen/res/metadata.rs | 8 ++++++++ mspm0-metapac-gen/src/metadata.rs | 2 ++ 6 files changed, 89 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d48b2d9..11d35f8 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ These are the data sources currently used. * Peripheral pin names. * Which pins have wakeup logic (`io_wakeup`). * Number of ADC conversion channels (`SYS_ADC_MEMCTL_DIM`). + * How many counters a basic timer instance has (`SYS_NUM_COUNTERS`), and a cross-check of the + datasheet's capture/compare channel counts (`SYS_NUM_CC`). * SysConfig `clocktree.json` * Which crystal drivers, external clock inputs and SYSPLL a family has. This is not derivable from the SYSCTL register block: MSPM0C110x and MSPM0C1105/C1106 share one, but only the latter has a diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 60e9a68..71ff167 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -9,7 +9,7 @@ use std::{ use anyhow::{anyhow, bail, ensure, Context}; use mspm0_data_types::{ Adc, Chip, DmaChannel, Interrupt, Memory, MemoryKind, Package, PackagePin, Peripheral, - PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, WakeTimes, + PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, Timer, WakeTimes, }; use regex::Regex; @@ -1205,12 +1205,36 @@ fn apply_timers( channels.insert(maybe_rename(&peripheral.name), count); } + // How many of the counter array's counters an instance implements. Only the basic timers have + // more than one, and sysconfig is the only per-instance source: the datasheets state it as a + // feature of TIMBx in general, in the same words on the G-series parts which have four and the + // L-series ones which have two. + let mut counters = BTreeMap::new(); + for peripheral in sysconfig.peripherals.values() { + let Some(count) = peripheral.attributes.get("SYS_NUM_COUNTERS") else { + continue; + }; + let Some(count) = count.as_str().and_then(|count| count.parse::<u8>().ok()) else { + bail!( + "{}: {} SYS_NUM_COUNTERS is not a number: {count}", + family.family, + peripheral.name + ); + }; + + counters.insert(maybe_rename(&peripheral.name), count); + } + for (name, peripheral) in peripherals.iter_mut() { if peripheral.ty != PeripheralType::Tim { continue; } - let timer = timers.timers.get(name).copied(); + let timer = timers.timers.get(name).copied().map(|timer| Timer { + // Absent means the instance is not a counter array, so it has the one counter. + counters: counters.get(name).copied().unwrap_or(1), + ..timer + }); peripheral.timer = timer; if let (Some(timer), Some(&count)) = (timer, channels.get(name)) { diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 0691c83..6989b84 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -16,6 +16,7 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { block_async_known, standby1_timer_exists, timer_capabilities_known, + timer_counters_addressable, errata_known, interrupts_claimed, wake_times_ordered, @@ -388,6 +389,38 @@ fn timer_capabilities_known(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// Verify that every counter an instance claims is one its register block can reach. +/// +/// `tim_v1` has a single counter and `tim_btimer` an array of the eight the TRM documents, so a +/// count outside those is either a sysconfig attribute nobody expected or a timer mapped to the +/// wrong block. +fn timer_counters_addressable(chip: &Chip, name: &str) -> anyhow::Result<()> { + for peripheral in chip.peripherals.values() { + let Some(timer) = peripheral.timer else { + continue; + }; + + let addressable = match peripheral.version.as_deref() { + Some("btimer") => 8, + _ => 1, + }; + + if timer.counters == 0 || timer.counters > addressable { + bail!( + "{name}: {} has {} counters, but {} addresses {addressable}", + peripheral.name, + timer.counters, + peripheral + .version + .as_deref() + .unwrap_or("no register block for it") + ); + } + } + + Ok(()) +} + /// Verify that every timer knows whether it is clocked in STANDBY1, and that at least one is. fn standby1_timer_exists(chip: &Chip, name: &str) -> anyhow::Result<()> { let timers = chip diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index c4071ce..30f6320 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -515,6 +515,19 @@ pub struct Timer { /// Counter width in bits, either 16 or 32. pub bits: u8, + /// Independent counters the instance implements. + /// + /// One on every general-purpose and advanced timer. The basic timers are a counter array and + /// are the only instances where this is not 1: four counters on the G-series `TIMBx`, two on + /// the L-series ones. The `tim_btimer` register block addresses the eight the TRM documents, so + /// this is what says which of those exist. + /// + /// Unlike the rest of this struct it does not come from `data/timers`: the datasheets state it + /// only as a feature of `TIMBx` in general, in wording which is identical on devices that + /// disagree. It is sysconfig's `SYS_NUM_COUNTERS`, which is per instance. + #[serde(default = "one")] + pub counters: u8, + /// Whether the instance has the 8-bit prescaler. pub prescaler: bool, @@ -554,6 +567,11 @@ pub struct Timer { pub qei_hall: bool, } +/// Default for [`Timer::counters`], which `data/timers` does not carry. +fn one() -> u8 { + 1 +} + /// An operating mode, ordered from shallowest to deepest. /// /// Ordering is meaningful and is what makes retention comparable: a peripheral retained through diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 62cc27c..f1bc26e 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -275,6 +275,14 @@ pub struct Timer { /// Counter width in bits, either 16 or 32. pub bits: u8, + /// Independent counters the instance implements. + /// + /// One on every general-purpose and advanced timer. The basic timers are a counter array and + /// are the only instances where this is not 1: four counters on the G-series `TIMBx`, two on + /// the L-series ones. The `timb` register block addresses the eight the TRM documents, so this + /// is what says which of those exist. + pub counters: u8, + /// Whether the instance has the 8-bit prescaler. pub prescaler: bool, diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 2bda1bd..5e00a86 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -304,6 +304,7 @@ fn generate_peripheral( let timer = match &peripheral.timer { Some(timer) => { let bits = Literal::u8_unsuffixed(timer.bits); + let counters = Literal::u8_unsuffixed(timer.counters); let ccp_channels = Literal::u8_unsuffixed(timer.ccp_channels); let external_pwm_channels = Literal::u8_unsuffixed(timer.external_pwm_channels); let (prescaler, repeat_counter) = (timer.prescaler, timer.repeat_counter); @@ -315,6 +316,7 @@ fn generate_peripheral( quote! { Some(Timer { bits: #bits, + counters: #counters, prescaler: #prescaler, repeat_counter: #repeat_counter, ccp_channels: #ccp_channels, From d2c35d7da394d58a3262c0daf888425145f90ff0 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 21:45:59 +0100 Subject: [PATCH 039/134] Add the UNICOMM wrapper and its per-instance modes 47 chips declared `pub const UC4: () = ();` for what is their only serial peripheral. The wrapper is the part every instance has whatever mode it is in: GPRCM, then IPMODE, BASEADDR and RANGE. The SVDs have neither BASEADDR nor RANGE, so those come from hw_unicomm.h, and the three GPRCM keys from the TRM as in the other blocks here. Which register maps an instance implements does not follow its name, and none has all four: on mspm0g518x UC0 is a UART or either half of an I2C but never an SPI, UC2 is an SPI alone, and UC3 is a UART or an SPI. The device header's instance table is the only source which says, populating one register pointer per mode the instance has, and it is now Peripheral::unicomm. Its fixedMode flag is not carried, being true exactly when one pointer is populated. The mode maps themselves come separately. Each sits at an offset below the instance address which is fixed by the IP, so apply_unicomm holds every base a header states literally to the offset consumers are told to use, rather than trusting the constant. --- data/registers/unicomm_v1.yaml | 140 ++++++++++++++++++++++++++++ mspm0-data-gen/src/generate.rs | 63 +++++++++++++ mspm0-data-gen/src/header.rs | 46 +++++++++ mspm0-data-gen/src/perimap.rs | 3 + mspm0-data-types/src/lib.rs | 34 +++++++ mspm0-metapac-gen/res/metadata.rs | 30 ++++++ mspm0-metapac-gen/src/metadata.rs | 17 ++++ mspm0-metapac-gen/src/peripheral.rs | 1 + 8 files changed, 334 insertions(+) create mode 100644 data/registers/unicomm_v1.yaml diff --git a/data/registers/unicomm_v1.yaml b/data/registers/unicomm_v1.yaml new file mode 100644 index 0000000..6cd9fdb --- /dev/null +++ b/data/registers/unicomm_v1.yaml @@ -0,0 +1,140 @@ +block/GPRCM: + items: + - name: PWREN + description: Power enable. + byte_offset: 0 + fieldset: PWREN + - name: RSTCTL + description: Reset Control. + byte_offset: 4 + access: Write + fieldset: RSTCTL + - name: CLKCFG + description: Peripheral Clock Configuration Register. + byte_offset: 8 + fieldset: CLKCFG + - name: STAT + description: Status Register. + byte_offset: 20 + access: Read + fieldset: STAT +block/UNICOMM: + description: PERIPHERALREGION. + items: + - name: GPRCM + array: + len: 1 + stride: 24 + byte_offset: 2048 + block: GPRCM + - name: IPMODE + description: Mode Selection Register. + byte_offset: 4352 + fieldset: IPMODE + - name: BASEADDR + description: Base address of the SRAM range DMA may read and write, used in security configuration. + byte_offset: 4356 + fieldset: BASEADDR + - name: RANGE + description: Size of the SRAM range DMA may read and write, used in security configuration. + byte_offset: 4360 + fieldset: RANGE +fieldset/BASEADDR: + description: Base address of the SRAM range DMA may read and write. + fields: + - name: ADDR + description: Base address, in units of 64 bytes. + bit_offset: 6 + bit_size: 25 +fieldset/CLKCFG: + description: Peripheral Clock Configuration Register. + fields: + - name: BLOCKASYNC + description: Async Clock Request is blocked from starting SYSOSC or forcing bus clock to 32MHz. + bit_offset: 8 + bit_size: 1 + - name: KEY + description: KEY to Allow State Change A9h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: CLKCFG_KEY +fieldset/IPMODE: + description: Mode Selection Register. + fields: + - name: SELECT + description: Which of the instance's register maps is active. Only the modes `Peripheral::unicomm` reports are implemented; an instance which implements one has no choice to make and ignores this register. + bit_offset: 0 + bit_size: 2 + enum: SELECT +fieldset/PWREN: + description: Power enable. + fields: + - name: ENABLE + description: Enable the power. + bit_offset: 0 + bit_size: 1 + - name: KEY + description: KEY to allow Power State Change 26h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: PWREN_KEY +fieldset/RANGE: + description: Size of the SRAM range DMA may read and write. + fields: + - name: RANGE + description: Range in bytes. + bit_offset: 0 + bit_size: 20 +fieldset/RSTCTL: + description: Reset Control. + fields: + - name: RESETASSERT + description: Assert reset to the peripheral. + bit_offset: 0 + bit_size: 1 + - name: RESETSTKYCLR + description: Clear the RESETSTKY bit in the STAT register. + bit_offset: 1 + bit_size: 1 + - name: KEY + description: Unlock key B1h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: RESET_KEY +fieldset/STAT: + description: Status Register. + fields: + - name: RESETSTKY + description: This bit indicates, if the peripheral was reset, since this bit was cleared by RESETSTKYCLR in the RSTCTL register. + bit_offset: 16 + bit_size: 1 +enum/CLKCFG_KEY: + bit_size: 8 + variants: + - name: KEY + value: 169 +enum/PWREN_KEY: + bit_size: 8 + variants: + - name: KEY + value: 38 +enum/RESET_KEY: + bit_size: 8 + variants: + - name: KEY + value: 177 +enum/SELECT: + bit_size: 2 + variants: + - name: UART + description: The UART register map is active. + value: 0 + - name: SPI + description: The SPI register map is active. + value: 1 + - name: I2C_CONTROLLER + description: The I2C controller register map is active. + value: 2 + - name: I2C_TARGET + description: The I2C target register map is active. TI's headers call this the peripheral mode. + value: 3 diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 71ff167..45281be 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -111,6 +111,7 @@ fn generate_family( apply_timers(family, sysconfig, timers, &mut peripherals)?; apply_clock_ranges(family, &mut peripherals); apply_adc(family, sysconfig, &mut peripherals)?; + apply_unicomm(family, header, &mut peripherals)?; for part_number in family.part_numbers.iter() { // Filter for package types available on the part number. @@ -343,6 +344,7 @@ fn generate_peripherals2( timer: None, clock_range_hz: None, adc: None, + unicomm: None, }; // Lookup the pins @@ -599,6 +601,7 @@ fn generate_missing( timer: None, clock_range_hz: None, adc: None, + unicomm: None, }, ); @@ -630,6 +633,7 @@ fn generate_missing( timer: None, clock_range_hz: None, adc: None, + unicomm: None, }, ); @@ -669,6 +673,7 @@ fn generate_missing( timer: None, clock_range_hz: None, adc: None, + unicomm: None, }); let pin = device_pin @@ -941,6 +946,64 @@ fn generate_dma_channels( /// /// `MEMCTL` is per instance in sysconfig, so it is read per instance even though no part has two ADCs /// which disagree. `VRSEL` is stated once per family. +/// Offset of each UNICOMM register map below the instance's own address. +/// +/// Fixed by the IP: `mspm0g518x.h` computes them with `UC_UART_BASE(UC0_BASE)` and friends over +/// `UC_*_OFFSET`, and the L-series headers write every base out literally. `apply_unicomm` checks +/// each literal against these. +const UNICOMM_MODE_OFFSETS: &[(&str, u32)] = &[ + ("UART", 0x80000), + ("I2CC", 0x60000), + ("I2CT", 0x40000), + ("SPI", 0x20000), +]; + +/// Record which register maps each UNICOMM instance implements. +fn apply_unicomm( + family: &PartFamily, + header: &Header, + peripherals: &mut BTreeMap<String, Peripheral>, +) -> anyhow::Result<()> { + for (name, peripheral) in peripherals.iter_mut() { + if peripheral.ty != PeripheralType::Unicomm { + continue; + } + + let modes = header.unicomm_modes.get(name).context(format!( + "{}: {name} is not in the header's UNICOMM instance table", + family.family + ))?; + + ensure!( + modes.uart || modes.i2c_controller || modes.i2c_target || modes.spi, + "{}: {name} implements no UNICOMM register map", + family.family + ); + + // Where the header states a map's address rather than computing it, hold it to the offset + // a consumer is told to use. A part which moved one would otherwise be silently wrong. + if let Some(address) = peripheral.address { + for (mode, offset) in UNICOMM_MODE_OFFSETS { + let Some(&stated) = header.peripheral_addresses.get(&format!("{name}_{mode}")) + else { + continue; + }; + + ensure!( + stated == address - offset, + "{}: {name}_{mode} is at {stated:#x}, not {:#x} as the offset from {name} says", + family.family, + address - offset + ); + } + } + + peripheral.unicomm = Some(*modes); + } + + Ok(()) +} + fn apply_adc( family: &PartFamily, sysconfig: &SysconfigFile, diff --git a/mspm0-data-gen/src/header.rs b/mspm0-data-gen/src/header.rs index 3d4574f..ba80964 100644 --- a/mspm0-data-gen/src/header.rs +++ b/mspm0-data-gen/src/header.rs @@ -1,6 +1,7 @@ use std::{collections::BTreeMap, fs, path::Path, sync::LazyLock}; use anyhow::Context; +use mspm0_data_types::Unicomm; use regex::Regex; #[derive(Debug)] @@ -38,6 +39,9 @@ impl Headers { pub struct Header { pub peripheral_addresses: BTreeMap<String, u32>, + /// Which register maps each UNICOMM instance implements, keyed by instance name. + pub unicomm_modes: BTreeMap<String, Unicomm>, + pub irq_numbers: BTreeMap<i32, Vec<String>>, /// Number of bits the NVIC uses for interrupt priority levels. @@ -58,14 +62,56 @@ impl Header { // for truly handling IRQs. let irq_numbers = Self::get_irq_numbers(chip_name, &content)?; let nvic_priority_bits = Self::get_nvic_priority_bits(chip_name, &content)?; + let unicomm_modes = Self::get_unicomm_modes(&content); Ok(Self { peripheral_addresses, + unicomm_modes, irq_numbers, nvic_priority_bits, }) } + /// Read which register maps each UNICOMM instance implements. + /// + /// A UNICOMM instance is a UART, an SPI, an I2C controller or an I2C target depending on + /// `IPMODE.SELECT`, but no instance implements all four. The header's own instance table is the + /// only source which says which: it initializes a pointer per map the instance has and leaves + /// the rest null. + /// + /// ```c,no_run + /// static const UNICOMM_Inst_Regs UC4_Inst = { + /// .inst = (UNICOMM_Regs *) UC4_BASE, + /// .uart = (UNICOMMUART_Regs *) UC4_UART_BASE, + /// .spi = (UNICOMMSPI_Regs *) UC4_SPI_BASE, + /// .fixedMode = false + /// }; + /// ``` + /// + /// `fixedMode` is not read: it is true exactly when one map is initialized, which the maps + /// themselves already say. + fn get_unicomm_modes(content: &str) -> BTreeMap<String, Unicomm> { + static INSTANCE: LazyLock<Regex> = LazyLock::new(|| { + Regex::new(r"(?s)UNICOMM_Inst_Regs\s+(?<instance>\w+)_Inst\s*=\s*\{(?<body>[^}]*)\}") + .unwrap() + }); + + INSTANCE + .captures_iter(content) + .map(|capture| { + let body = &capture["body"]; + let modes = Unicomm { + uart: body.contains(".uart"), + i2c_controller: body.contains(".i2cc"), + i2c_target: body.contains(".i2ct"), + spi: body.contains(".spi"), + }; + + (capture["instance"].to_string(), modes) + }) + .collect() + } + /// Read the NVIC priority bit count from the CMSIS device header. /// /// Deliberately *not* taken from the SVDs, whose `<nvicPrioBits>` says 3 for every MSPM0 and is diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index 5558c12..bd0db07 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -20,6 +20,9 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ (".*:flashctl", "v1"), (".*:trng", "v1"), (".*:canfd", "v1"), + // One version: the SDK ships a single hw_unicomm.h for the portfolio, and the wrapper is what + // every instance has regardless of which mode maps it implements. + (".*:unicomm", "v1"), // SLAU846 ยง23.3, SLAU847 ยง19.3, SLAU893 ยง13.3 and SLAU923 ยง11.3 describe the same eight // registers with the same fields, so every family shares one version. The SVDs disagree with // the TRMs and with each other about CTL0 bits 1 and 2, which the YAML resolves in the TRMs' diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 30f6320..4b68602 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -479,6 +479,12 @@ pub struct Peripheral { /// `None` for peripherals which are not ADCs. #[serde(skip_serializing_if = "Option::is_none")] pub adc: Option<Adc>, + + /// Which register maps this UNICOMM instance implements. + /// + /// `None` for peripherals which are not UNICOMM instances. + #[serde(skip_serializing_if = "Option::is_none")] + pub unicomm: Option<Unicomm>, } /// The parts of one ADC instance which the single `adc_v1` register block does not describe. @@ -572,6 +578,34 @@ fn one() -> u8 { 1 } +/// Which register maps a UNICOMM instance implements. +/// +/// UNICOMM is one peripheral which is a UART, an SPI, an I2C controller or an I2C target depending +/// on `IPMODE.SELECT`, with a register map per mode at a fixed offset below the instance's own +/// address. **No instance implements all four**, and which it implements does not follow the +/// instance name: on MSPM0G518x `UC0` is a UART or either half of an I2C but never an SPI, `UC2` is +/// an SPI only, and `UC3` is a UART or an SPI. +/// +/// An instance with one mode has nothing to select and no `IPMODE` register to select it with, so +/// writing `IPMODE` is only meaningful where more than one of these is true. +/// +/// Read from the instance table in the SDK's device header, which populates a register pointer per +/// mode the instance has. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Unicomm { + /// Implements the UART register map, `0x80000` below the instance address. + pub uart: bool, + + /// Implements the I2C controller register map, `0x60000` below the instance address. + pub i2c_controller: bool, + + /// Implements the I2C target register map, `0x40000` below the instance address. + pub i2c_target: bool, + + /// Implements the SPI register map, `0x20000` below the instance address. + pub spi: bool, +} + /// An operating mode, ordered from shallowest to deepest. /// /// Ordering is meaningful and is what makes retention comparable: a peripheral retained through diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index f1bc26e..c61f903 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -234,6 +234,36 @@ pub struct Peripheral { /// /// `None` for peripherals which are not ADCs. pub adc: Option<Adc>, + + /// Which register maps this UNICOMM instance implements. + /// + /// `None` for peripherals which are not UNICOMM instances. + pub unicomm: Option<Unicomm>, +} + +/// Which register maps a UNICOMM instance implements. +/// +/// UNICOMM is one peripheral which is a UART, an SPI, an I2C controller or an I2C target depending +/// on `IPMODE.SELECT`, with a register map per mode at a fixed offset below the instance's own +/// address. **No instance implements all four**, and which it implements does not follow the +/// instance name: on MSPM0G518x `UC0` is a UART or either half of an I2C but never an SPI, `UC2` is +/// an SPI only, and `UC3` is a UART or an SPI. +/// +/// An instance with one mode has nothing to select and no `IPMODE` register to select it with, so +/// writing `IPMODE` is only meaningful where more than one of these is true. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct Unicomm { + /// Implements the UART register map, `0x80000` below the instance address. + pub uart: bool, + + /// Implements the I2C controller register map, `0x60000` below the instance address. + pub i2c_controller: bool, + + /// Implements the I2C target register map, `0x40000` below the instance address. + pub i2c_target: bool, + + /// Implements the SPI register map, `0x20000` below the instance address. + pub spi: bool, } /// An inclusive frequency range, in Hz. diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 5e00a86..de575ae 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -351,6 +351,22 @@ fn generate_peripheral( None => quote! { None }, }; + let unicomm = match peripheral.unicomm { + Some(unicomm) => { + let (uart, spi) = (unicomm.uart, unicomm.spi); + let (i2c_controller, i2c_target) = (unicomm.i2c_controller, unicomm.i2c_target); + quote! { + Some(Unicomm { + uart: #uart, + i2c_controller: #i2c_controller, + i2c_target: #i2c_target, + spi: #spi, + }) + } + } + None => quote! { None }, + }; + Some(quote! { Peripheral { name: #name, @@ -367,6 +383,7 @@ fn generate_peripheral( timer: #timer, clock_range_hz: #clock_range_hz, adc: #adc, + unicomm: #unicomm, } }) } diff --git a/mspm0-metapac-gen/src/peripheral.rs b/mspm0-metapac-gen/src/peripheral.rs index 4aeb9f0..57043d3 100644 --- a/mspm0-metapac-gen/src/peripheral.rs +++ b/mspm0-metapac-gen/src/peripheral.rs @@ -24,6 +24,7 @@ const GENERATE_PERIPHERALS: &[PeripheralType] = &[ PeripheralType::Tim, PeripheralType::Trng, PeripheralType::Uart, + PeripheralType::Unicomm, PeripheralType::Vref, PeripheralType::Wwdt, ]; From e612b67441b3be1798d79e48ee781004a627cbc3 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 21:49:44 +0100 Subject: [PATCH 040/134] Make each UNICOMM mode a peripheral of its own A UNICOMM instance is addressed four ways: the instance itself, and a register map per mode at a fixed offset below it. Modelling the maps as peripherals gives each one its own type, its own address and its own register block, which is how TI's SVDs describe them as well, and means a consumer is handed the right block rather than an offset to subtract. The instance stays what the device has. It owns the pins, the interrupt and whether it is clocked in STANDBY1; a view repeats only what remains true of a window onto the same silicon, which is the power domain and how deep a sleep it survives. No register maps are mapped yet, so the views generate as unit consts carrying their address, the same as any other peripheral without a block. They gain a block as each map lands. --- mspm0-data-gen/src/generate.rs | 65 +++++++++++++++++++++++++++++++++- mspm0-data-types/src/lib.rs | 16 +++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 45281be..d8dff60 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -9,7 +9,8 @@ use std::{ use anyhow::{anyhow, bail, ensure, Context}; use mspm0_data_types::{ Adc, Chip, DmaChannel, Interrupt, Memory, MemoryKind, Package, PackagePin, Peripheral, - PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, Timer, WakeTimes, + PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, Timer, Unicomm, + WakeTimes, }; use regex::Regex; @@ -958,6 +959,14 @@ const UNICOMM_MODE_OFFSETS: &[(&str, u32)] = &[ ("SPI", 0x20000), ]; +/// The register map each UNICOMM mode is described by, and how [`Unicomm`] reports it. +const UNICOMM_MODE_TYPES: &[(&str, PeripheralType, fn(&Unicomm) -> bool)] = &[ + ("UART", PeripheralType::UnicommUart, |m| m.uart), + ("I2CC", PeripheralType::UnicommI2cc, |m| m.i2c_controller), + ("I2CT", PeripheralType::UnicommI2ct, |m| m.i2c_target), + ("SPI", PeripheralType::UnicommSpi, |m| m.spi), +]; + /// Record which register maps each UNICOMM instance implements. fn apply_unicomm( family: &PartFamily, @@ -1001,6 +1010,60 @@ fn apply_unicomm( peripheral.unicomm = Some(*modes); } + // Each mode the instance implements becomes a peripheral of its own, so that a consumer gets + // the right register block at the right address rather than having to subtract an offset from + // the instance. TI's own SVDs model them this way too, as UC0_UART, UC0_I2CC and so on. + // + // The instance is what the device has: it owns the pins, the interrupt and the low power + // facts, and the views repeat only what is true of them as a window onto the same silicon. + let mut views = Vec::new(); + + for peripheral in peripherals.values() { + let (Some(modes), Some(address)) = (peripheral.unicomm, peripheral.address) else { + continue; + }; + + for (mode, ty, implemented) in UNICOMM_MODE_TYPES { + if !implemented(&modes) { + continue; + } + + let offset = UNICOMM_MODE_OFFSETS + .iter() + .find(|(name, _)| name == mode) + .map(|(_, offset)| offset) + .expect("every mode has an offset"); + + let name = format!("{}_{mode}", peripheral.name); + let version = PERIMAP + .get(&format!("{}:{ty}", family.family)) + .map(|version| version.to_string()); + + views.push(Peripheral { + name: name.clone(), + ty: *ty, + version, + address: Some(address - offset), + power_domain: peripheral.power_domain, + pins: vec![], + sys_fentries: None, + interrupts: Vec::new(), + block_async: None, + retained_through: peripheral.retained_through, + usable_through: peripheral.usable_through, + clocked_in_standby1: None, + timer: None, + clock_range_hz: None, + adc: None, + unicomm: None, + }); + } + } + + for view in views { + peripherals.insert(view.name.clone(), view); + } + Ok(()) } diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 4b68602..cbb181b 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -325,6 +325,18 @@ pub enum PeripheralType { Unicomm, + /// The UART register map of a UNICOMM instance, at a fixed offset below it. + UnicommUart, + + /// The I2C controller register map of a UNICOMM instance, at a fixed offset below it. + UnicommI2cc, + + /// The I2C target register map of a UNICOMM instance, at a fixed offset below it. + UnicommI2ct, + + /// The SPI register map of a UNICOMM instance, at a fixed offset below it. + UnicommSpi, + Usbfs, Vref, @@ -370,6 +382,10 @@ impl fmt::Display for PeripheralType { PeripheralType::Trng => "trng", PeripheralType::Uart => "uart", PeripheralType::Unicomm => "unicomm", + PeripheralType::UnicommUart => "unicommuart", + PeripheralType::UnicommI2cc => "unicommi2cc", + PeripheralType::UnicommI2ct => "unicommi2ct", + PeripheralType::UnicommSpi => "unicommspi", PeripheralType::Usbfs => "usbfs", PeripheralType::Vref => "vref", PeripheralType::Wuc => "wuc", From f94ee80214d9ff70d3f9218866e5367a38a675e4 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 21:55:06 +0100 Subject: [PATCH 041/134] Add the UNICOMM I2C controller map UC6 on the L-series parts, and UC0 and UC1 on mspm0g518x, can be I2C controllers and had no register block to be one with. The map is a peripheral of its own at the fixed offset below the instance, so a consumer is handed UC6_I2CC rather than an address to work out. It is not the standalone I2C relocated. Its controller registers start at 0x1100 where i2c_v1 has them at 0x1210, and its two DMA trigger blocks are not interchangeable: a UNICOMM instance is a controller or a target, so DMA_TRIG0 carries only TXTRG and DMA_TRIG1 only RXTRG, where the standalone peripheral is both roles at once and gives both blocks all four FIFO trigger bits. That is also why the transform merges the trigger fieldsets within a block rather than across the two. CLKDIV.RATIO is six bits rather than the three the SVD gives it. The SVD's own description of the field numbers the ratios up to 63, and UNICOMMI2CC_CLKDIV_RATIO_MASK is 0x3F. Its enum went with the width: it named 12 of the 64 values and described the last four as "divide by 8". --- data/registers/unicommi2cc_v1.yaml | 732 ++++++++++++++++++++++++++++ mspm0-data-gen/src/perimap.rs | 3 + mspm0-metapac-gen/src/peripheral.rs | 1 + transforms/UNICOMM_I2CC.yaml | 112 +++++ 4 files changed, 848 insertions(+) create mode 100644 data/registers/unicommi2cc_v1.yaml create mode 100644 transforms/UNICOMM_I2CC.yaml diff --git a/data/registers/unicommi2cc_v1.yaml b/data/registers/unicommi2cc_v1.yaml new file mode 100644 index 0000000..ab6d92a --- /dev/null +++ b/data/registers/unicommi2cc_v1.yaml @@ -0,0 +1,732 @@ +block/CPU_INT: + items: + - name: IIDX + description: Interrupt index. + byte_offset: 0 + access: Read + fieldset: IIDX + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: CPU_INT + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: CPU_INT + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: CPU_INT + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: CPU_INT + - name: ICLR + description: Interrupt clear. + byte_offset: 40 + access: Write + fieldset: ICLR +block/DMA_TRIG0: + items: + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: DMA_TRIG0 + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: DMA_TRIG0 + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: DMA_TRIG0 + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: DMA_TRIG0 +block/DMA_TRIG1: + items: + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: DMA_TRIG1 + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: DMA_TRIG1 + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: DMA_TRIG1 + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: DMA_TRIG1 +block/UNICOMMI2CC: + description: PERIPHERALREGION. + items: + - name: CLKDIV + description: Clock Divider. + byte_offset: 4096 + fieldset: CLKDIV + - name: CLKSEL + description: Clock Select for Ultra Low Power peripherals. + byte_offset: 4104 + fieldset: CLKSEL + - name: PDBGCTL + description: Peripheral Debug Control. + byte_offset: 4120 + fieldset: PDBGCTL + - name: CPU_INT + array: + len: 1 + stride: 44 + byte_offset: 4128 + block: CPU_INT + - name: DMA_TRIG1 + array: + len: 1 + stride: 36 + byte_offset: 4176 + block: DMA_TRIG1 + - name: DMA_TRIG0 + array: + len: 1 + stride: 36 + byte_offset: 4224 + block: DMA_TRIG0 + - name: INTCTL + description: Interrupt control register. + byte_offset: 4324 + access: Write + fieldset: INTCTL + - name: CTR + description: Control Register. + byte_offset: 4352 + fieldset: CTR + - name: CR + description: Configuration. + byte_offset: 4356 + fieldset: CR + - name: SR + description: Status Register. + byte_offset: 4360 + access: Read + fieldset: SR + - name: IFLS + description: Interrupt FIFO Level Select Register. + byte_offset: 4364 + fieldset: IFLS + - name: TPR + description: Timer Period. + byte_offset: 4368 + fieldset: TPR + - name: GFCTL + description: I2C Glitch Filter Control. + byte_offset: 4376 + fieldset: GFCTL + - name: BMON + description: Bus Monitor. + byte_offset: 4380 + access: Read + fieldset: BMON + - name: TXDATA + description: TXData. + byte_offset: 4384 + access: Write + fieldset: TXDATA + - name: RXDATA + description: RXData. + byte_offset: 4388 + access: Read + fieldset: RXDATA + - name: PECSR + description: PEC status register. + byte_offset: 4392 + access: Read + fieldset: PECSR + - name: TA + description: Target Address Register. + byte_offset: 4428 + fieldset: TA + - name: TIMEOUT_CNT + description: I2C Timeout Count Register. + byte_offset: 4432 + access: Read + fieldset: TIMEOUT_CNT + - name: TIMEOUT_CTL + description: I2C Timeout Count Control Register. + byte_offset: 4436 + fieldset: TIMEOUT_CTL + - name: PECCTL + description: I2C PEC control register. + byte_offset: 4440 + fieldset: PECCTL +fieldset/BMON: + description: Bus Monitor. + fields: + - name: SCL + description: I2C SCL Status. + bit_offset: 0 + bit_size: 1 + - name: SDA + description: I2C SDA Status. + bit_offset: 1 + bit_size: 1 +fieldset/CLKDIV: + description: Clock Divider. + fields: + - name: RATIO + description: 'Divide ratio of the module clock, as the divisor minus one: 0 divides by 1 and 63 by 64.' + bit_offset: 0 + bit_size: 6 +fieldset/CLKSEL: + description: Clock Select for Ultra Low Power peripherals. + fields: + - name: MFCLK_SEL + description: Selects MFCLK as clock source if enabled. + bit_offset: 2 + bit_size: 1 + - name: BUSCLK_SEL + description: Selects BUS CLK as clock source if enabled. + bit_offset: 3 + bit_size: 1 +fieldset/CPU_INT: + description: Interrupt mask. + fields: + - name: RXDONE + description: RXDONE interrupt. + bit_offset: 0 + bit_size: 1 + - name: TXDONE + description: Transmit Transaction completed Interrupt. + bit_offset: 1 + bit_size: 1 + - name: RXTRG + description: Receive Trigger. + bit_offset: 2 + bit_size: 1 + - name: TXTRG + description: Transmit Trigger. + bit_offset: 3 + bit_size: 1 + - name: RXFULL + description: RXFIFO full event. + bit_offset: 4 + bit_size: 1 + - name: TXEMPTY + description: Transmit FIFO/Buffer Empty interrupt. + bit_offset: 5 + bit_size: 1 + - name: NACK + description: NACK interrupt. + bit_offset: 7 + bit_size: 1 + - name: START + description: START interrupt. + bit_offset: 8 + bit_size: 1 + - name: STOP + description: STOP interrupt. + bit_offset: 9 + bit_size: 1 + - name: ARBLOST + description: Arbitration Lost interrupt. + bit_offset: 10 + bit_size: 1 + - name: PEC_RX_ERR + description: RX Pec Error Interrupt. + bit_offset: 11 + bit_size: 1 + - name: TIMEOUTA + description: TIMEOUTA interrupt. + bit_offset: 12 + bit_size: 1 + - name: TIMEOUTB + description: Timeout B Interrupt. + bit_offset: 13 + bit_size: 1 + - name: DMA_DONE_RX + description: DMA Done on Event Channel RX. + bit_offset: 15 + bit_size: 1 + - name: DMA_DONE_TX + description: DMA Done on Event Channel TX. + bit_offset: 16 + bit_size: 1 +fieldset/CR: + description: Configuration. + fields: + - name: ENABLE + description: Enable module. After this bit has been set, it should not be set again unless it has been cleared by writing a 0 or by a reset, otherwise transfer failures may occur. + bit_offset: 0 + bit_size: 1 + - name: MCTL + description: MultiController mode. In MultiController mode the SCL high time counts once the SCL line has been detected high. If this is not enabled the high time counts as soon as the SCL line has been set high by the I2C controller. + bit_offset: 1 + bit_size: 1 + - name: CLKSTRETCH + description: Clock Stretching. This bit controls the support for clock stretching of the I2C bus. + bit_offset: 2 + bit_size: 1 +fieldset/CTR: + description: Control Register. + fields: + - name: FRM_START + description: Start Transfer. + bit_offset: 0 + bit_size: 1 + - name: START + description: Generate START. + bit_offset: 1 + bit_size: 1 + - name: STOP + description: Generate STOP. + bit_offset: 2 + bit_size: 1 + - name: ACK + description: 'Data Acknowledge Enable. Software needs to configure this bit to send the ACK or NACK. See field decoding in Table: MCTR Field decoding.' + bit_offset: 3 + bit_size: 1 + - name: ACKOEN + description: ACK overrride Enable. + bit_offset: 4 + bit_size: 1 + - name: RD_ON_TXEMPTY + description: Read on TX Empty. + bit_offset: 5 + bit_size: 1 + - name: SUSPEND + description: Suspend external communication. + bit_offset: 6 + bit_size: 1 + - name: BLEN + description: I2C transaction length This field contains the programmed length of bytes of the Transaction. + bit_offset: 16 + bit_size: 12 +fieldset/DMA_TRIG0: + description: Interrupt mask. + fields: + - name: TXTRG + description: Transmit Trigger. + bit_offset: 3 + bit_size: 1 +fieldset/DMA_TRIG1: + description: Interrupt mask. + fields: + - name: RXTRG + description: Receive Trigger. + bit_offset: 2 + bit_size: 1 +fieldset/GFCTL: + description: I2C Glitch Filter Control. + fields: + - name: AGFEN + description: Analog Glitch Suppression Enable. + bit_offset: 8 + bit_size: 1 +fieldset/ICLR: + description: Interrupt clear. + fields: + - name: RXDONE + description: Receive Data Interrupt Signals that a byte has been received. + bit_offset: 0 + bit_size: 1 + - name: TXDONE + description: Transmit Transaction completed Interrupt. + bit_offset: 1 + bit_size: 1 + - name: RXTRG + description: Receive Trigger. + bit_offset: 2 + bit_size: 1 + - name: TXTRG + description: Transmit FIFO Trigger. + bit_offset: 3 + bit_size: 1 + - name: RXFULL + description: RXFIFO full event. + bit_offset: 4 + bit_size: 1 + - name: TXEMPTY + description: Transmit FIFO Empty interrupt mask. This interrupt is set if all data in the Transmit FIFO have been shifted out and the transmit goes into idle mode. + bit_offset: 5 + bit_size: 1 + - name: NACK + description: Address/Data NACK Interrupt. + bit_offset: 7 + bit_size: 1 + - name: START + description: START Detection Interrupt. + bit_offset: 8 + bit_size: 1 + - name: STOP + description: STOP Detection Interrupt. + bit_offset: 9 + bit_size: 1 + - name: ARBLOST + description: Arbitration Lost Interrupt. + bit_offset: 10 + bit_size: 1 + - name: PEC_RX_ERR + description: RX Pec Error Interrupt. + bit_offset: 11 + bit_size: 1 + - name: TIMEOUTA + description: Timeout A interrupt. + bit_offset: 12 + bit_size: 1 + - name: TIMEOUTB + description: Timeout B Interrupt. + bit_offset: 13 + bit_size: 1 + - name: DMA_DONE_RX + description: DMA Done on Event Channel RX. + bit_offset: 15 + bit_size: 1 + - name: DMA_DONE_TX + description: DMA Done on Event Channel TX. + bit_offset: 16 + bit_size: 1 +fieldset/IFLS: + description: Interrupt FIFO Level Select Register. + fields: + - name: TXIFLSEL + description: 'FIFO Level Select for generating events (interrupt/dma). Note: for undefined settings the default configuration is used.' + bit_offset: 0 + bit_size: 3 + enum: TXIFLSEL + - name: TXCLR + description: TX FIFO CLEAR. Setting this bit will clear the TX FIFO contents. + bit_offset: 3 + bit_size: 1 + - name: RXIFLSEL + description: 'FIFO Level Select for generating events (interrupt/dma). Note: for undefined settings the default configuration is used.' + bit_offset: 4 + bit_size: 3 + enum: RXIFLSEL + - name: RXCLR + description: RX FIFO CLEAR. Setting this bit will clear the RX FIFO contents. + bit_offset: 7 + bit_size: 1 +fieldset/IIDX: + description: Interrupt index. + fields: + - name: STAT + description: I2C Module Interrupt Vector Value. This register provides the highes priority interrupt index. A read clears the corresponding interrupt flag in RIS and MISC. 15h-1Fh = Reserved. + bit_offset: 0 + bit_size: 8 + enum: STAT +fieldset/INTCTL: + description: Interrupt control register. + fields: + - name: INTEVAL + description: Writing a 1 to this field re-evaluates the interrupt sources. + bit_offset: 0 + bit_size: 1 +fieldset/PDBGCTL: + description: Peripheral Debug Control. + fields: + - name: FREE + description: Free run control. + bit_offset: 0 + bit_size: 1 + - name: SOFT + description: Soft halt boundary control. This function is only available, if [FREE] is set to 'STOP'. + bit_offset: 1 + bit_size: 1 + enum: SOFT +fieldset/PECCTL: + description: I2C PEC control register. + fields: + - name: PECCNT + description: PEC Count When this field is non zero, the number of I2C bytes are counted (Note that although the PEC is calculated on the I2C address it is not counted at a byte). When the byte count = PECCNT and the state machine is transmitting, the contents of the LSFR is loaded into the shift register instead of the byte received from the Tx FIFO. When the state machine is receiving, after the last bit of this byte is received the LSFR is checked and if it is non-zero, a PEC RX Error interrupt is generated. The I2C packet must be padded to include the PEC byte for both transmit and receive. In transmit mode the FIFO must be loaded with a dummy PEC byte. In receive mode the PEC byte will be passed to the Rx FIFO. In the normal Controller use case, FW would set PECEN=1 and PECCNT=SMB packet length (Not including Target Address byte, but including the PEC byte). FW would then configure DMA to allow the packet to complete unassisted and write MCTR to initiate the transaction. Note that when the byte count = PEC CNT, the byte count is reset to 0 and multiple PEC calculation can automatically occur within a single I2C transaction. Note that any write to the I2PECCTL Register will clear the current PEC Byte Count in the State Machine. + bit_offset: 0 + bit_size: 9 + - name: PECEN + description: PEC Enable This bit enables the SMB Packet Error Checking (PEC). When enabled the PEC is calculated on all bits except the Start, Stop, Ack and Nack. The PEC LSFR and the Byte Counter is set to 0 when the State Machine is in the IDLE state, which occur following a Stop or when a timeout occurs. The Counter is also set to 0 after the PEC byte is sent or received. Note that the NACK is automatically send following a PEC byte that results in a PEC error. The PEC Polynomial is x^8 + x^2 + x^1 + 1. + bit_offset: 12 + bit_size: 1 +fieldset/PECSR: + description: PEC status register. + fields: + - name: PECBYTECNT + description: PEC Byte Count. This is the current PEC Byte Count of the Controller State Machine. + bit_offset: 0 + bit_size: 9 + - name: PECSTS_CHECK + description: This status bit indicates if the PEC was checked in the transaction that occurred before the last Stop. Latched on Stop. + bit_offset: 16 + bit_size: 1 + - name: PECSTS_ERROR + description: This status bit indicates if a PEC check error occurred in the transaction that occurred before the last Stop. Latched on Stop. + bit_offset: 17 + bit_size: 1 +fieldset/RXDATA: + description: RXData. + fields: + - name: DATA + description: Received Data. This field contains the last received data. + bit_offset: 0 + bit_size: 8 +fieldset/SR: + description: Status Register. + fields: + - name: BUSY + description: FSM Busy The BUSY bit is set during an ongoing transaction, so is set during the transmit/receive of the amount of data set in MBLEN including START, RESTART, Address and STOP signal generation when required for the current transaction. + bit_offset: 0 + bit_size: 1 + - name: ERR + description: Error The error can be from the Target address not being acknowledged or the transmit data not being acknowledged. + bit_offset: 1 + bit_size: 1 + - name: ADRACK + description: Acknowledge Address. + bit_offset: 2 + bit_size: 1 + - name: DATACK + description: Acknowledge Data. + bit_offset: 3 + bit_size: 1 + - name: ARBLST + description: Arbitration Lost. + bit_offset: 4 + bit_size: 1 + - name: IDLE + description: I2C Idle. + bit_offset: 5 + bit_size: 1 + - name: BUSBSY + description: I2C Bus is Busy Controller State Machine will wait until this bit to be cleared before starting a transaction. When first enabling the Controller in multi Controller environments, FW should wait for one I2C clock period after setting ACTIVE high before writing to the CTR register to start the transaction so that if SCL goes low it will trigger the BUSBSY. + bit_offset: 6 + bit_size: 1 + - name: RXCLR + description: RX FIFO Clear Status. + bit_offset: 9 + bit_size: 1 + - name: TXCLR + description: TX FIFO Clear Status. + bit_offset: 10 + bit_size: 1 + - name: RXFE + description: Receive FIFO Empty The meaning of this bit depends on the state of the FEN bit in the CTL0 register. + bit_offset: 11 + bit_size: 1 + - name: RXFF + description: Receive FIFO Full The meaning of this bit depends on the state of the FEN bit in the CTL0 register. + bit_offset: 12 + bit_size: 1 + - name: TXFE + description: Transmit FIFO Empty The meaning of this bit depends on the state of the FEN bit in the CTL0 register. + bit_offset: 13 + bit_size: 1 + - name: TXFF + description: Transmit FIFO Full The meaning of this bit depends on the state of the FEN bit in the CTL0 register. + bit_offset: 14 + bit_size: 1 + - name: BCNT + description: Transaction Count This field contains the current count-down value of the transaction. + bit_offset: 16 + bit_size: 12 +fieldset/TA: + description: Target Address Register. + fields: + - name: DIR + description: Receive/Send The DIR bit specifies if the next operation is a Receive (High) or Transmit (Low). 0h = Transmit 1h = Receive. + bit_offset: 0 + bit_size: 1 + enum: DIR + - name: ADDR + description: I2C Target Address This field specifies bits A9 through A0 of the Target address. In 7-bit addressing mode as selected by MSA.MODE bit, the top 3 bits are don't care. + bit_offset: 1 + bit_size: 10 + - name: MODE + description: This bit selects the adressing mode to be used When 0, 7-bit addressing is used. When 1, 10-bit addressing is used. + bit_offset: 15 + bit_size: 1 + enum: MODE +fieldset/TIMEOUT_CNT: + description: I2C Timeout Count Register. + fields: + - name: TCNTA + description: 'Timeout Count A Current Count: This field contains the upper 8 bits of a 12-bit current counter for timeout counter A.' + bit_offset: 0 + bit_size: 8 + - name: TCNTB + description: 'Timeout Count B Current Count: This field contains the upper 8 bits of a 12-bit current counter for timeout counter B.' + bit_offset: 16 + bit_size: 8 +fieldset/TIMEOUT_CTL: + description: I2C Timeout Count Control Register. + fields: + - name: TCNTLA + description: 'Timeout counter A load value Counter A is used for SCL low detection. This field contains the upper 8 bits of a 12-bit pre-load value for the Timeout A count. NOTE: The value of CNTLA must be greater than 1h. Each count is equal to 520 times the timeout period of functional clock. For example, with 8MHz functional clock and a 100KHz operating I2C clock, one timeout period will be equal to (1 / 8MHz) * 520 or 65 us.' + bit_offset: 0 + bit_size: 8 + - name: TCNTAEN + description: Timeout Counter A Enable. + bit_offset: 15 + bit_size: 1 + - name: TCNTLB + description: 'Timeout Count B Load: Counter B is used for SCL High Detection. This field contains the upper 8 bits of a 12-bit pre-load value for the Timeout B count. NOTE: The value of CNTLB must be greater than 1h. Each count is equal to 1* clock period. For example, with 10MHz functional clock one timeout period will be equal to1*100ns.' + bit_offset: 16 + bit_size: 8 + - name: TCNTBEN + description: Timeout Counter B Enable. + bit_offset: 31 + bit_size: 1 +fieldset/TPR: + description: Timer Period. + fields: + - name: TPR + description: 'Timer Period This field is used in the equation to configure SCL_PERIOD : SCL_PERIOD = (1 + TPR ) * (SCL_LP + SCL_HP ) * INT_CLK_PRD where: SCL_PRD is the SCL line period (I2C clock). TPR is the Timer Period register value (range of 1 to 127). SCL_LP is the SCL Low period (fixed at 6). SCL_HP is the SCL High period (fixed at 4). CLK_PRD is the functional clock period in ns.' + bit_offset: 0 + bit_size: 7 +fieldset/TXDATA: + description: TXData. + fields: + - name: DATA + description: Transmit Data This byte contains the data to be transferred during the next transaction. + bit_offset: 0 + bit_size: 8 +enum/DIR: + bit_size: 1 + variants: + - name: TRANSMIT + description: in transmit mode. + value: 0 + - name: RECEIVE + description: is in receive mode. + value: 1 +enum/MODE: + bit_size: 1 + variants: + - name: MODE7 + description: 7-bit addressing mode. + value: 0 + - name: MODE10 + description: 10-bit addressing mode. + value: 1 +enum/RXIFLSEL: + bit_size: 3 + variants: + - name: LVL_1_4 + description: RX FIFO >= 1/4 full. + value: 1 + - name: LVL_1_2 + description: RX FIFO >= 1/2 full (default). + value: 2 + - name: LVL_3_4 + description: RX FIFO >= 3/4 full. + value: 3 + - name: LVL_NOT_EMPTY + description: Opposite of empty. + value: 4 + - name: LVL_FULL + description: RX FIFO is full. + value: 5 + - name: LVL_ALMOST_FULL + description: RX_FIFO >= (MAX_FIFO_LEN -1). + value: 6 + - name: LVL_ALMOST_EMPTY + description: RX_FIFO <= 1. + value: 7 +enum/SOFT: + bit_size: 1 + variants: + - name: DEPRECATED + description: Not supported. + value: 0 + - name: DELAYED + description: The peripheral blocks the debug freeze until it has reached a boundary where it can resume without corruption. + value: 1 +enum/STAT: + bit_size: 8 + variants: + - name: NO_INTR + description: No interrupt pending. + value: 0 + - name: RXDONEFG + description: Data received. + value: 1 + - name: TXDONEFG + description: data transmitted. + value: 2 + - name: RXTRG + description: Receive Trigger. + value: 3 + - name: TXTRG + description: Transmit Trigger. + value: 4 + - name: RXFULL + description: RX FIFO FULL Event/interrupt pending. + value: 5 + - name: TXEMPTY + description: Transmit FIFO/Buffer Empty Event/interrupt pending. + value: 6 + - name: NACKFG + description: Address/Data NACK. + value: 8 + - name: STARTFG + description: Start Event. + value: 9 + - name: STOPFG + description: Stop Event. + value: 10 + - name: ARBLOSTFG + description: Arbitration Lost. + value: 11 + - name: PEC_RX_ERR + description: PEC Receive Error Event. + value: 12 + - name: TIMEOUTA + description: Timeout A Event. + value: 13 + - name: TIMEOUTB + description: Timeout B Event. + value: 14 + - name: DMA_DONE_RX + description: DMA DONE on Channel RX. + value: 16 + - name: DMA_DONE_TX + description: DMA DONE on Channel TX. + value: 17 + - name: DMA_PREIRQ_RX + description: DMA PRE IRQ INTERRUPT. + value: 19 + - name: DMA_PREIRQ_TX + description: DMA PRE IRQ INTERRUPT. + value: 20 +enum/TXIFLSEL: + bit_size: 3 + variants: + - name: LVL_3_4 + description: TX FIFO <= 3/4 empty. + value: 1 + - name: LVL_1_2 + description: TX FIFO <= 1/2 empty (default). + value: 2 + - name: LVL_1_4 + description: TX FIFO <= 1/4 empty. + value: 3 + - name: LVL_NOT_FULL + description: Opposite of full. + value: 4 + - name: LVL_EMPTY + description: TX FIFO is empty. + value: 5 + - name: LVL_ALMOST_EMPTY + description: TX FIFO <= 1. + value: 6 + - name: LVL_ALMOST_FULL + description: TX_FIFO >= (MAX_FIFO_LEN -1). + value: 7 diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index bd0db07..18b86b9 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -23,6 +23,9 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ // One version: the SDK ships a single hw_unicomm.h for the portfolio, and the wrapper is what // every instance has regardless of which mode maps it implements. (".*:unicomm", "v1"), + // The mode maps of a UNICOMM instance. The SVDs describe them per instance and identically, so + // one version each covers the portfolio, as with the wrapper. + (".*:unicommi2cc", "v1"), // SLAU846 ยง23.3, SLAU847 ยง19.3, SLAU893 ยง13.3 and SLAU923 ยง11.3 describe the same eight // registers with the same fields, so every family shares one version. The SVDs disagree with // the TRMs and with each other about CTL0 bits 1 and 2, which the YAML resolves in the TRMs' diff --git a/mspm0-metapac-gen/src/peripheral.rs b/mspm0-metapac-gen/src/peripheral.rs index 57043d3..96e64cc 100644 --- a/mspm0-metapac-gen/src/peripheral.rs +++ b/mspm0-metapac-gen/src/peripheral.rs @@ -25,6 +25,7 @@ const GENERATE_PERIPHERALS: &[PeripheralType] = &[ PeripheralType::Trng, PeripheralType::Uart, PeripheralType::Unicomm, + PeripheralType::UnicommI2cc, PeripheralType::Vref, PeripheralType::Wwdt, ]; diff --git a/transforms/UNICOMM_I2CC.yaml b/transforms/UNICOMM_I2CC.yaml new file mode 100644 index 0000000..8cbacf6 --- /dev/null +++ b/transforms/UNICOMM_I2CC.yaml @@ -0,0 +1,112 @@ +# Transform for UART registers based on UC0_I2CC on G350x +transforms: + - !DeleteUselessEnums + + # However DeleteUselessEnums does not delete MIS and IMASK (due to CLR being 0 and SET being 1) + # + # TODO: Add this to chiptool + - !DeleteEnumsWithVariants + variants: + 0: CLR + 1: SET + + - !DeleteEnumsWithVariants + variants: + 0: CLEARED + 1: SET + + # FREE is useless + - !DeleteEnumsWithVariants + variants: + 0: STOP + 1: RUN + + # RESETASSERT is useless + - !DeleteEnumsWithVariants + variants: + 0: NOP + 1: ASSERT + + # RESETSTKY is useless + - !DeleteEnumsWithVariants + variants: + 0: NORES + 1: RESET + + # RESETSTKYCLR is useless + - !DeleteEnumsWithVariants + variants: + 0: NOP + 1: CLR + + # INTEVAL is useless + - !DeleteEnumsWithVariants + variants: + 0: DISABLE + 1: EVAL + + # TODO: Something better than a cursed rename as merge + - !MergeBlocks + from: UC0_I2CC + main: UC0_I2CC + to: I2C + + # Remove prefixes + - !RenameRegisters + block: .* + from: UC0_I2CC_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: UC0_I2CC_(.+) + to: $1 + + - !Rename + type: All + from: UC0_I2CC_(.+) + to: $1 + + ## Interrupts + # CPU_INT interrupt fields are all the same layout. + - !MergeFieldsets + from: CPU_INT_(IMASK|ISET|MIS|RIS|ICLR) + to: CPU_INT + + # Remove redundant prefixes in the CPU_INT block + - !RenameRegisters + block: .* + from: CPU_INT_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: CPU_INT_(.+) + to: $1 + + ## DMA Triggers + # DMA_TRIG0 and DMA_TRIG1 are not the same block here and are not merged. The standalone I2C is + # one peripheral for both roles, so both of its trigger blocks carry all four FIFO trigger bits; + # a UNICOMM instance is a controller or a target, so DMA_TRIG0 has only TXTRG and DMA_TRIG1 only + # RXTRG. + - !RenameRegisters + block: DMA_TRIG(\d+) + from: DMA_TRIG(\d+)_(.+) + to: $2 + + # Within one trigger the interrupt registers do share a layout. + - !MergeFieldsets + from: DMA_TRIG0_(IMASK|ISET|MIS|RIS|ICLR) + to: DMA_TRIG0 + + - !MergeFieldsets + from: DMA_TRIG1_(IMASK|ISET|MIS|RIS|ICLR) + to: DMA_TRIG1 + + ## CMODE/TMODE: same enum + - !MergeEnums + from: (C|T)MODE + to: MODE + + ## Cleanup + - !Sort From 15bc5e52e70bfcaa4096fbd23778c56a9f6fad6d Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 21:57:45 +0100 Subject: [PATCH 042/134] Add the UNICOMM UART map Every UNICOMM instance other than the SPI-only and I2C-only ones can be a UART, which is seven of the nine across mspm0g518x, mspm0l112x and mspm0l211x, and none of them had a register block. Taken from UC0_UART on G120x rather than G518x. The instances are not identical: the G518x SVD omits GFCTL and LINCTL.EN_FRM_ERR, and hw_unicommuart.h has both, so the G120x extraction is the superset the README asks for. UC3_UART is a smaller instance again, without LIN or IrDA at all. CLKDIV.RATIO is six bits here too, for the reason it is in the I2C controller map, and its enum went the same way. --- data/registers/unicommuart_v1.yaml | 885 ++++++++++++++++++++++++++++ mspm0-data-gen/src/perimap.rs | 1 + mspm0-metapac-gen/src/peripheral.rs | 1 + transforms/UNICOMM_UART.yaml | 211 +++++++ 4 files changed, 1098 insertions(+) create mode 100644 data/registers/unicommuart_v1.yaml create mode 100644 transforms/UNICOMM_UART.yaml diff --git a/data/registers/unicommuart_v1.yaml b/data/registers/unicommuart_v1.yaml new file mode 100644 index 0000000..fe0e1af --- /dev/null +++ b/data/registers/unicommuart_v1.yaml @@ -0,0 +1,885 @@ +block/CPU_INT: + items: + - name: IIDX + description: Interrupt index. + byte_offset: 0 + access: Read + fieldset: IIDX + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: CPU_INT + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: CPU_INT + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: CPU_INT + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: CPU_INT + - name: ICLR + description: Interrupt clear. + byte_offset: 40 + access: Write + fieldset: ICLR +block/DMA_TRIG_RX: + items: + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: DMA_TRIG_RX + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: DMA_TRIG_RX + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: DMA_TRIG_RX + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: DMA_TRIG_RX +block/DMA_TRIG_TX: + items: + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: DMA_TRIG_TX + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: DMA_TRIG_TX + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: DMA_TRIG_TX + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: DMA_TRIG_TX +block/UNICOMMUART: + description: PERIPHERALREGION. + items: + - name: CLKDIV + description: Clock Divider. + byte_offset: 4096 + fieldset: CLKDIV + - name: CLKSEL + description: Clock Select for Ultra Low Power peripherals. + byte_offset: 4104 + fieldset: CLKSEL + - name: PDBGCTL + description: Peripheral Debug Control. + byte_offset: 4120 + fieldset: PDBGCTL + - name: CPU_INT + array: + len: 1 + stride: 44 + byte_offset: 4128 + block: CPU_INT + - name: DMA_TRIG_RX + array: + len: 1 + stride: 36 + byte_offset: 4176 + block: DMA_TRIG_RX + - name: DMA_TRIG_TX + array: + len: 1 + stride: 36 + byte_offset: 4224 + block: DMA_TRIG_TX + - name: INTCTL + description: Interrupt control register. + byte_offset: 4324 + access: Write + fieldset: INTCTL + - name: CTL0 + description: UART Control Register 0. + byte_offset: 4352 + fieldset: CTL0 + - name: LCRH + description: UART Line Control Register. + byte_offset: 4356 + fieldset: LCRH + - name: STAT + description: UART Status Register. + byte_offset: 4360 + access: Read + fieldset: STAT + - name: IFLS + description: UART Interrupt FIFO Level Select Register. + byte_offset: 4364 + fieldset: IFLS + - name: IBRD + description: UART Integer Baud-Rate Divisor Register. + byte_offset: 4368 + fieldset: IBRD + - name: FBRD + description: UART Fractional Baud-Rate Divisor Register. + byte_offset: 4372 + fieldset: FBRD + - name: GFCTL + description: Glitch Filter Control. + byte_offset: 4376 + fieldset: GFCTL + - name: TXDATA + description: UART Transmit Data Register. + byte_offset: 4384 + access: Write + fieldset: TXDATA + - name: RXDATA + description: UART Receive Data Register. + byte_offset: 4388 + access: Read + fieldset: RXDATA + - name: LINCNT + description: UART LIN Mode Counter Register. + byte_offset: 4400 + fieldset: LINCNT + - name: LINCTL + description: UART LIN Mode Control Register. + byte_offset: 4404 + fieldset: LINCTL + - name: LINC0 + description: UART LIN Mode Capture 0 Register. + byte_offset: 4408 + fieldset: LINC + - name: LINC1 + description: UART LIN Mode Capture 1 Register. + byte_offset: 4412 + fieldset: LINC + - name: IRCTL + description: eUSCI_Ax IrDA Control Word Register. + byte_offset: 4416 + fieldset: IRCTL + - name: AMASK + description: Self Address Mask Register. + byte_offset: 4424 + fieldset: AMASK + - name: ADDR + description: Self Address Register. + byte_offset: 4428 + fieldset: ADDR +fieldset/ADDR: + description: Self Address Register. + fields: + - name: ADDR + description: Self Address for 9-Bit Mode This field contains the address that should be matched when UARTxAMASK is FFh. + bit_offset: 0 + bit_size: 8 +fieldset/AMASK: + description: Self Address Mask Register. + fields: + - name: MSK + description: Self Address Mask for 9-Bit Mode This field contains the address mask that creates a set of addresses that should be matched. A 0 bit in the MSK bitfield configures, that the corresponding bit in the ADDR bitfield of the UARTxADDR register is don't care. A 1 bit in the MSK bitfield configures, that the corresponding bit in the ADDR bitfield of the UARTxADDR register must match. + bit_offset: 0 + bit_size: 8 +fieldset/CLKDIV: + description: Clock Divider. + fields: + - name: RATIO + description: 'Divide ratio of the module clock, as the divisor minus one: 0 divides by 1 and 63 by 64.' + bit_offset: 0 + bit_size: 6 +fieldset/CLKSEL: + description: Clock Select for Ultra Low Power peripherals. + fields: + - name: LFCLK_SEL + description: Selects LFCLK as clock source if enabled. + bit_offset: 1 + bit_size: 1 + - name: MFCLK_SEL + description: Selects MFCLK as clock source if enabled. + bit_offset: 2 + bit_size: 1 + - name: BUSCLK_SEL + description: Selects BUS CLK as clock source if enabled. + bit_offset: 3 + bit_size: 1 +fieldset/CPU_INT: + description: Interrupt mask. + fields: + - name: RTOUT + description: Enable UARTOUT Receive Time-Out Interrupt. + bit_offset: 0 + bit_size: 1 + - name: FRMERR + description: Enable UART Framing Error Interrupt. + bit_offset: 1 + bit_size: 1 + - name: PARERR + description: Enable UART Parity Error Interrupt. + bit_offset: 2 + bit_size: 1 + - name: BRKERR + description: Enable UART Break Error Interrupt. + bit_offset: 3 + bit_size: 1 + - name: OVRERR + description: Enable UART Receive Overrun Error Interrupt. + bit_offset: 4 + bit_size: 1 + - name: RXNE + description: Enable Negative Edge on UARTxRXD Interrupt. + bit_offset: 5 + bit_size: 1 + - name: RXPE + description: Enable Positive Edge on UARTxRXD Interrupt. + bit_offset: 6 + bit_size: 1 + - name: LINC0 + description: Enable LIN Capture 0 / Match Interrupt. + bit_offset: 7 + bit_size: 1 + - name: LINC1 + description: Enable LIN Capture 1 Interrupt. + bit_offset: 8 + bit_size: 1 + - name: LINOVF + description: Enable LIN Hardware Counter Overflow Interrupt. + bit_offset: 9 + bit_size: 1 + - name: RXINT + description: Enable UART Receive Interrupt. + bit_offset: 10 + bit_size: 1 + - name: TXINT + description: Enable UART Transmit Interrupt. + bit_offset: 11 + bit_size: 1 + - name: EOT + description: Enable UART End of Transmission Interrupt Indicates that the last bit of all transmitted data and flags has left the serializer and without any further Data in the TX Fifo or Buffer. + bit_offset: 12 + bit_size: 1 + - name: ADDR_MATCH + description: Enable Address Match Interrupt. + bit_offset: 13 + bit_size: 1 + - name: CTS + description: Enable UART Clear to Send Modem Interrupt. + bit_offset: 14 + bit_size: 1 + - name: DMA_DONE_RX + description: Enable DMA Done on RX Event Channel Interrupt. + bit_offset: 15 + bit_size: 1 + - name: DMA_DONE_TX + description: Enable DMA Done on TX Event Channel Interrupt. + bit_offset: 16 + bit_size: 1 + - name: NERR + description: Noise Error on triple voting. Asserted when the 3 samples of majority voting are not equal. + bit_offset: 17 + bit_size: 1 + - name: LTOUT + description: Enable UARTOUT Line Time-Out Interrupt. + bit_offset: 20 + bit_size: 1 +fieldset/CTL0: + description: UART Control Register 0. + fields: + - name: ENABLE + description: UART Module Enable. If the UART is disabled in the middle of transmission or reception, it completes the current character before stopping. If the ENABLE bit is not set, all registers can still be accessed and updated. It is recommended to setup and change the UART operation mode with having the ENABLE bit cleared to avoid unpredictable behavior during the setup or update. If disabled the UART module will not send or receive any data and the logic is held in reset state. + bit_offset: 0 + bit_size: 1 + - name: LBE + description: UART Loop Back Enable. + bit_offset: 2 + bit_size: 1 + - name: RXE + description: 'UART Receive Enable If the UART is disabled in the middle of a receive, it completes the current character before stopping. #b#NOTE:#/b# To enable reception, the UARTEN bit must be set.' + bit_offset: 3 + bit_size: 1 + - name: TXE + description: 'UART Transmit Enable If the UART is disabled in the middle of a transmission, it completes the current character before stopping. #b#NOTE:#/b# To enable transmission, the UARTEN bit must be set.' + bit_offset: 4 + bit_size: 1 + - name: TXD_OUT_EN + description: TXD Pin Control Enable. When the transmit section of the UART is disabled (TXE = 0), the TXD pin can be controlled by the TXD_OUT bit. + bit_offset: 5 + bit_size: 1 + - name: TXD_OUT + description: TXD Pin Control Controls the TXD pin when TXD_OUT_EN = 1 and TXE = 0. + bit_offset: 6 + bit_size: 1 + - name: MENC + description: Manchester Encode enable. + bit_offset: 7 + bit_size: 1 + - name: MODE + description: 'Set the communication mode and protocol used. (Not defined settings uses the default setting: 0).' + bit_offset: 8 + bit_size: 3 + enum: MODE + - name: RTS + description: Request to Send If RTSEN is set the RTS output signals is controlled by the hardware logic using the FIFO fill level or buffer status. If RTSEN is cleared the RTS output is controlled by the RTS bit. The bit is the complement of the UART request to send, RTS modem status output. + bit_offset: 12 + bit_size: 1 + - name: RTSEN + description: Enable hardware controlled Request to Send. + bit_offset: 13 + bit_size: 1 + - name: CTSEN + description: Enable Clear To Send. + bit_offset: 14 + bit_size: 1 + - name: HSE + description: 'High-Speed Bit Oversampling Enable #b#NOTE:#/b# The bit oversampling influences the UART baud-rate configuration. The state of this bit has no effect on clock generation in ISO7816 smart card mode (the SMART bit is set).' + bit_offset: 15 + bit_size: 2 + enum: HSE + - name: MAJVOTE + description: 'Majority Vote Enable When Majority Voting is enabled, the three center bits are used to determine received sample value. In case of error (i.e. all 3 bits are not the same), noise error is detected and bits RIS.NERR and register RXDATA.NERR are set. Oversampling of 16 : bits 7, 8, 9 are used Oversampling of 8 : bits 3, 4, 5 are used Disabled : Single sample value (center value) used.' + bit_offset: 18 + bit_size: 1 + - name: MSBFIRST + description: 'Most Significant Bit First This bit has effect both on the way protocol byte is transmitted and received. Notes: User needs to match the protocol to the correct value of this bit to send MSb or LSb first. The hardware engine will send the byte entirely based on this bit.' + bit_offset: 19 + bit_size: 1 +fieldset/DMA_TRIG_RX: + description: Interrupt mask. + fields: + - name: RTOUT + description: Enable UARTOUT Receive Time-Out Interrupt. + bit_offset: 0 + bit_size: 1 + - name: RXINT + description: Enable UART Receive Interrupt. + bit_offset: 10 + bit_size: 1 +fieldset/DMA_TRIG_TX: + description: Interrupt mask. + fields: + - name: TXINT + description: Enable UART Transmit Interrupt. + bit_offset: 11 + bit_size: 1 +fieldset/FBRD: + description: UART Fractional Baud-Rate Divisor Register. + fields: + - name: DIVFRAC + description: Fractional Baud-Rate Divisor. + bit_offset: 0 + bit_size: 6 +fieldset/GFCTL: + description: Glitch Filter Control. + fields: + - name: DGFSEL + description: 'Glitch Suppression Pulse Width This field controls the pulse width select for glitch suppression on the RX line. The value programmed in this field gives the number of cycles of functional clock up to which the glitch has to be suppressed on the RX line. In IRDA mode: The minimum pulse length for receive is given by: t(MIN) = (DGFSEL) / f(IRTXCLK).' + bit_offset: 0 + bit_size: 6 +fieldset/IBRD: + description: UART Integer Baud-Rate Divisor Register. + fields: + - name: DIVINT + description: Integer Baud-Rate Divisor. + bit_offset: 0 + bit_size: 16 +fieldset/ICLR: + description: Interrupt clear. + fields: + - name: RTOUT + description: Clear UARTOUT Receive Time-Out Interrupt. + bit_offset: 0 + bit_size: 1 + - name: FRMERR + description: Clear UART Framing Error Interrupt. + bit_offset: 1 + bit_size: 1 + - name: PARERR + description: Clear UART Parity Error Interrupt. + bit_offset: 2 + bit_size: 1 + - name: BRKERR + description: Clear UART Break Error Interrupt. + bit_offset: 3 + bit_size: 1 + - name: OVRERR + description: Clear UART Receive Overrun Error Interrupt. + bit_offset: 4 + bit_size: 1 + - name: RXNE + description: Clear Negative Edge on UARTxRXD Interrupt. + bit_offset: 5 + bit_size: 1 + - name: RXPE + description: Clear Positive Edge on UARTxRXD Interrupt. + bit_offset: 6 + bit_size: 1 + - name: LINC0 + description: Clear LIN Capture 0 / Match Interrupt. + bit_offset: 7 + bit_size: 1 + - name: LINC1 + description: Clear LIN Capture 1 Interrupt. + bit_offset: 8 + bit_size: 1 + - name: LINOVF + description: Clear LIN Hardware Counter Overflow Interrupt. + bit_offset: 9 + bit_size: 1 + - name: RXINT + description: Clear UART Receive Interrupt. + bit_offset: 10 + bit_size: 1 + - name: TXINT + description: Clear UART Transmit Interrupt. + bit_offset: 11 + bit_size: 1 + - name: EOT + description: Clear UART End of Transmission Interrupt Indicates that the last bit of all transmitted data and flags has left the serializer and without any further Data in the TX Fifo or Buffer. + bit_offset: 12 + bit_size: 1 + - name: ADDR_MATCH + description: Clear Address Match Interrupt. + bit_offset: 13 + bit_size: 1 + - name: CTS + description: Clear UART Clear to Send Modem Interrupt. + bit_offset: 14 + bit_size: 1 + - name: DMA_DONE_RX + description: Clear DMA Done on RX Event Channel Interrupt. + bit_offset: 15 + bit_size: 1 + - name: DMA_DONE_TX + description: Clear DMA Done on TX Event Channel Interrupt. + bit_offset: 16 + bit_size: 1 + - name: NERR + description: Noise Error on triple voting. Asserted when the 3 samples of majority voting are not equal. + bit_offset: 17 + bit_size: 1 + - name: LTOUT + description: Clear UARTOUT Line Time-Out Interrupt. + bit_offset: 20 + bit_size: 1 +fieldset/IFLS: + description: UART Interrupt FIFO Level Select Register. + fields: + - name: TXIFLSEL + description: 'FIFO Level Select for generating events (interrupt/dma). Note: for undefined settings the default configuration is used.' + bit_offset: 0 + bit_size: 3 + enum: IFLSSEL + - name: TXCLR + description: TX FIFO CLEAR. Setting this bit will clear the TX FIFO contents. + bit_offset: 3 + bit_size: 1 + - name: RXIFLSEL + description: 'FIFO Level Select for generating events (interrupt/dma). Note: for undefined settings the default configuration is used.' + bit_offset: 4 + bit_size: 3 + enum: IFLSSEL + - name: RXCLR + description: RX FIFO CLEAR. Setting this bit will clear the RX FIFO contents. + bit_offset: 7 + bit_size: 1 + - name: RXTOSEL + description: UART Receive Interrupt Timeout Select. When receiving no start edge for an additional character within the set bittimes a RX interrupt is set even if the FIFO level is not reached. A value of 0 disables this function. + bit_offset: 8 + bit_size: 4 +fieldset/IIDX: + description: Interrupt index. + fields: + - name: STAT + description: UART Module Interrupt Vector Value. This register provides the highes priority interrupt index. A read clears the corresponding interrupt flag in RIS and MIS registers. 15h-1Fh = Reserved. + bit_offset: 0 + bit_size: 8 + enum: STAT +fieldset/INTCTL: + description: Interrupt control register. + fields: + - name: INTEVAL + description: Writing a 1 to this field re-evaluates the interrupt sources. + bit_offset: 0 + bit_size: 1 +fieldset/IRCTL: + description: eUSCI_Ax IrDA Control Word Register. + fields: + - name: IREN + description: IrDA encoder/decoder enable. + bit_offset: 0 + bit_size: 1 + - name: IRTXCLK + description: IrDA transmit pulse clock select. + bit_offset: 1 + bit_size: 1 + enum: IRTXCLK + - name: IRTXPL + description: Transmit pulse length. Pulse length t(PULSE) = (IRTXPLx + 1) / [2 * f(IRTXCLK)] (IRTXCLK = functional clock of the UART). + bit_offset: 2 + bit_size: 6 + - name: IRRXPL + description: IrDA receive input UCAxRXD polarity. + bit_offset: 9 + bit_size: 1 + enum: IRRXPL +fieldset/LCRH: + description: UART Line Control Register. + fields: + - name: BRK + description: UART Send Break (for LIN Protocol) 1. Break condition is sent on the line for as long as this bit is set. + bit_offset: 0 + bit_size: 1 + - name: PEN + description: UART Parity Enable. + bit_offset: 1 + bit_size: 1 + - name: EPS + description: UART Even Parity Select This bit has no effect when parity is disabled by the PEN bit. For 9-Bit UART Mode transmissions, this bit controls the address byte and data byte indication (9th bit). 0 = The transferred byte is a data byte 1 = The transferred byte is an address byte. + bit_offset: 2 + bit_size: 1 + enum: EPS + - name: STP2 + description: UART Two Stop Bits Select When in 7816 smart card mode (the SMART bit is set in the UARTCTL register), the number of stop bits is forced to 2. + bit_offset: 3 + bit_size: 1 + - name: WLEN + description: UART Word Length The bits indicate the number of data bits transmitted or received in a frame as follows:. + bit_offset: 4 + bit_size: 2 + enum: WLEN + - name: SPS + description: UART Stick Parity Select The Stick Parity Select (SPS) bit is used to set either a permanent '1' or a permanent '0' as parity when transmitting or receiving data. Its purpose is to typically indicate the first byte of a package or to mark an address byte, for example in a multi-drop RS-485 network. When bits PEN, EPS, and SPS of UARTLCRH are set, the parity bit is transmitted and checked as a 0. When bits PEN and SPS are set and EPS is cleared, the parity bit is transmitted and checked as a 1. + bit_offset: 6 + bit_size: 1 + - name: SENDIDLE + description: UART send IDLE pattern. When this bit is set, SENDIDLE period of 11 bit times will be sent on the TX line. Read STAT.SENDIDLE bit to readback current status of SENDIDLE. + bit_offset: 7 + bit_size: 1 + - name: EXTDIR_SETUP + description: Defines the number of UARTclk ticks the signal to control the external driver for the RS485 will be set before the START bit is send. + bit_offset: 16 + bit_size: 5 + - name: EXTDIR_HOLD + description: Defines the number of UARTclk ticks the signal to control the external driver for the RS485 will be reset after the beginning of the stop bit. (If 2 STOP bits are enabled the beginning of the 2nd STOP bit.). + bit_offset: 21 + bit_size: 5 + - name: SUSPEND + description: Suspend external communication. + bit_offset: 26 + bit_size: 1 +fieldset/LINC: + description: UART LIN Mode Capture 0 Register. + fields: + - name: DATA + description: 16 Bit Capture / Compare Register Captures current LINCTR value on RXD falling edge and can generate a LINC0 interrupt when capture is enabled (LINC0CAP = 1). If compare mode is enabled (LINC0_MATCH = 1), a counter match can generate a LINC0 interrupt. + bit_offset: 0 + bit_size: 16 +fieldset/LINCNT: + description: UART LIN Mode Counter Register. + fields: + - name: VALUE + description: 16 bit up counter clocked by the functional clock of the UART. + bit_offset: 0 + bit_size: 16 +fieldset/LINCTL: + description: UART LIN Mode Control Register. + fields: + - name: CTRENA + description: LIN Counter Enable. LIN counter will only count when enabled. + bit_offset: 0 + bit_size: 1 + - name: ZERONE + description: Zero on negative Edge of RXD. When enabled the counter is set to 0 and starts counting on a negative edge of RXD. + bit_offset: 1 + bit_size: 1 + - name: CNTRXLOW + description: Count while low Signal on RXD When counter is enabled and the signal on RXD is low, the counter increments. + bit_offset: 2 + bit_size: 1 + - name: LINC0CAP + description: Capture Counter on negative RXD Edge. When enabled the counter value is captured to LINC0 register on each negative RXD edge. A LINC0 interrupt is triggered when enabled. + bit_offset: 4 + bit_size: 1 + - name: LINC1CAP + description: Capture Counter on positive RXD Edge. When enabled the counter value is captured to LINC1 register on each positive RXD edge. A LINC1 interrupt is triggered when enabled. + bit_offset: 5 + bit_size: 1 + - name: LINC0_MATCH + description: Counter Compare Match Mode When this bit is set to 1 a counter compare match with LINC0 register triggers an LINC0 interrupt when enabled. + bit_offset: 6 + bit_size: 1 + - name: EN_FRM_ERR + description: Enable FRAME ERROR. + bit_offset: 7 + bit_size: 1 +fieldset/PDBGCTL: + description: Peripheral Debug Control. + fields: + - name: FREE + description: Free run control. + bit_offset: 0 + bit_size: 1 + - name: SOFT + description: Soft halt boundary control. This function is only available, if [FREE] is set to 'STOP'. + bit_offset: 1 + bit_size: 1 + enum: SOFT +fieldset/RXDATA: + description: UART Receive Data Register. + fields: + - name: DATA + description: Received Data. When read, this field contains the data that was received by the UART. + bit_offset: 0 + bit_size: 8 + - name: FRMERR + description: UART Framing Error Writing to this bit has no effect. The flag is cleared by writing 1 to the FRMERR bit in the UART EVENT ICLR register. This error is associated with the character at the top of the FIFO. + bit_offset: 8 + bit_size: 1 + - name: PARERR + description: UART Parity Error Writing to this bit has no effect. The flag is cleared by writing 1 to the PARERR bit in the UART EVENT ICLR register. + bit_offset: 9 + bit_size: 1 + - name: BRKERR + description: UART Break Error Writing to this bit has no effect. The flag is cleared by writing 1 to the BRKERR bit in the UART EVENT ICLR register. This error is associated with the character at the top of the FIFO. When a break occurs, only one 0 character is loaded into the FIFO. The next character is only enabled after the receive data input goes to a 1 (marking state) and the next valid start bit is received. + bit_offset: 10 + bit_size: 1 + - name: NERR + description: Noise Error. Writing to this bit has no effect. The flag is cleared by writing 1 to the NERR bit in the UART EVENT ICLR register. + bit_offset: 11 + bit_size: 1 +fieldset/STAT: + description: UART Status Register. + fields: + - name: BUSY + description: UART Busy. This bit is set as soon as the transmit FIFO/buffer becomes non-empty (regardless of whether UART is enabled) or if a receive data is currently ongoing (after the start edge have been detected until a complete byte, including all stop bits, has been received by the shift register). In IDLE_Line mode the Busy signal also stays set during the idle time generation. + bit_offset: 0 + bit_size: 1 + - name: RXFE + description: UART Receive FIFO Empty. + bit_offset: 2 + bit_size: 1 + - name: RXFF + description: UART Receive FIFO Full. + bit_offset: 3 + bit_size: 1 + - name: RXCLR + description: RX FIFO Clear Status. + bit_offset: 4 + bit_size: 1 + - name: TXFE + description: UART Transmit FIFO Empty. + bit_offset: 5 + bit_size: 1 + - name: TXFF + description: UART Transmit FIFO Full The meaning of this bit depends on the state of the FEN bit in the CTL0 register. + bit_offset: 6 + bit_size: 1 + - name: TXCLR + description: TX FIFO Clear Status. + bit_offset: 7 + bit_size: 1 + - name: CTS + description: Clear To Send. + bit_offset: 8 + bit_size: 1 + - name: IDLE + description: IDLE mode has been detected in Idleline-Multiprocessor-Mode. The IDLE bit is used as an address tag for each block of characters. In idle-line multiprocessor format, this bit is set when a received character is an address. + bit_offset: 9 + bit_size: 1 + - name: SENDIDLE + description: TX FIFO Clear Status. + bit_offset: 10 + bit_size: 1 +fieldset/TXDATA: + description: UART Transmit Data Register. + fields: + - name: DATA + description: Data Transmitted or Received Data that is to be transmitted via the UART is written to this field. When read, this field contains the data that was received by the UART. + bit_offset: 0 + bit_size: 8 +enum/EPS: + bit_size: 1 + variants: + - name: ODD + description: Odd parity is performed, which checks for an odd number of 1s. + value: 0 + - name: EVEN + description: Even parity generation and checking is performed during transmission and reception, which checks for an even number of 1s in data and parity bits. + value: 1 +enum/HSE: + bit_size: 2 + variants: + - name: OVS16 + description: 16x oversampling. + value: 0 + - name: OVS8 + description: 8x oversampling. + value: 1 + - name: OVS3 + description: 3x oversampling. IrDA, Manchester and DALI not supported when 3x oversampling is enabled. + value: 2 +enum/IFLSSEL: + bit_size: 3 + variants: + - name: ONE_FOURTH_ULP + description: RX FIFO >= 1/4 full, for ULP domain + value: 0 + - name: ONE_FOURTH + description: RX FIFO >= 1/4 full or TX FIFO <= 1/4 full + value: 1 + - name: HALF + description: RX FIFO >= 1/2 full or TX FIFO <= 1/2 full + value: 2 + - name: THREE_FOURTH + description: RX FIFO >= 3/4 full or TX FIFO <= 3/4 full + value: 3 + - name: FULL_ULP + description: RX FIFO is full, for ULP domain + value: 4 + - name: FULL + description: RX or TX FIFO is full + value: 5 + - name: AT_LEAST_ONE + description: RX or TX FIFO >= 1 entry available/free + value: 7 +enum/IRRXPL: + bit_size: 1 + variants: + - name: HIGH + description: IrDA transceiver delivers a high pulse when a light pulse is seen. + value: 0 + - name: LOW + description: IrDA transceiver delivers a low pulse when a light pulse is seen. + value: 1 +enum/IRTXCLK: + bit_size: 1 + variants: + - name: BITCLK + description: IrDA encode data is based on the functional clock. + value: 0 + - name: BRCLK + description: IrDA encode data is based on the Baud Rate clock< when select 8x oversampling, the IRTXPL cycle should less 8; when select 16x oversampling, the IRTXPL cycle should less 16. + value: 1 +enum/MODE: + bit_size: 3 + variants: + - name: UART + description: Normal operation. + value: 0 + - name: RS485 + description: 'RS485 mode: UART needs to be IDLE with receiving data for the in EXTDIR_HOLD set time. EXTDIR_SETUP defines the time the RTS line is set to high before sending. When the buffer is empty the RTS line is set low again. A transmit will be delayed as long the UART is receiving data.' + value: 1 + - name: IDLELINE + description: The UART operates in IDLE Line Mode. + value: 2 + - name: ADDR9BIT + description: The UART operates in 9 Bit Address mode. + value: 3 + - name: SMART + description: ISO7816 Smart Card Support The application must ensure that it sets 8-bit word length (WLEN set to 3h) and even parity (PEN set to 1, EPS set to 1, SPS set to 0) in UARTLCRH when using ISO7816 mode. The value of the STP2 bit in UARTLCRH is ignored and the number of stop bits is forced to 2. + value: 4 + - name: DALI + description: DALI Mode:. + value: 5 +enum/SOFT: + bit_size: 1 + variants: + - name: DEPRECATED + description: Not supported. + value: 0 + - name: DELAYED + description: The peripheral blocks the debug freeze until it has reached a boundary where it can resume without corruption. + value: 1 +enum/STAT: + bit_size: 8 + variants: + - name: NO_INTR + description: No interrupt pending. + value: 0 + - name: RTFG + description: 'UART receive time-out interrupt; Interrupt Flag: RT; Interrupt Priority: Highest.' + value: 1 + - name: FEFG + description: 'UART framing error interrupt; Interrupt Flag: FE.' + value: 2 + - name: PEFG + description: 'UART parity error interrupt; Interrupt Flag: PE.' + value: 3 + - name: BEFG + description: 'UART break error interrupt; Interrupt Flag: BE.' + value: 4 + - name: OEFG + description: 'UART receive overrun error interrupt; Interrupt Flag: OE.' + value: 5 + - name: RXNE + description: 'Negative edge on UARTxRXD interrupt; Interrupt Flag: RXNE.' + value: 6 + - name: RXPE + description: 'Positive edge on UARTxRXD interrupt; Interrupt Flag: RXPE.' + value: 7 + - name: LINC0 + description: 'LIN capture 0 / match interrupt; Interrupt Flag: LINC0.' + value: 8 + - name: LINC1 + description: 'LIN capture 1 interrupt; Interrupt Flag: LINC1.' + value: 9 + - name: LINOVF + description: 'LIN hardware counter overflow interrupt; Interrupt Flag: LINOVF.' + value: 10 + - name: RXIFG + description: 'UART receive interrupt; Interrupt Flag: RX.' + value: 11 + - name: TXIFG + description: 'UART transmit interrupt; Interrupt Flag: TX.' + value: 12 + - name: EOT + description: 'UART end of transmission interrupt (transmit serializer empty); Interrupt Flag: EOT.' + value: 13 + - name: ADDR_MATCH + description: '9-bit mode address match interrupt; Interrupt Flag: ADDR_MATCH.' + value: 14 + - name: CTS + description: 'UART Clear to Send Modem interrupt; Interrupt Flag: CTS.' + value: 15 + - name: DMA_DONE_RX + description: DMA DONE on RX. + value: 16 + - name: DMA_DONE_TX + description: DMA DONE on TX. + value: 17 + - name: NERR_EVT + description: Noise Error Event. + value: 18 + - name: DMA_PREIRQ_RX + description: DMA PRE IRQ INTERRUPT. + value: 19 + - name: DMA_PREIRQ_TX + description: DMA PRE IRQ INTERRUPT. + value: 20 + - name: LTFG + description: UART line time-out interrupt; Interrupt Flag. + value: 21 +enum/WLEN: + bit_size: 2 + variants: + - name: DATABIT5 + description: 5 bits (default). + value: 0 + - name: DATABIT6 + description: 6 bits. + value: 1 + - name: DATABIT7 + description: 7 bits. + value: 2 + - name: DATABIT8 + description: 8 bits. + value: 3 diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index 18b86b9..b4b0474 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -26,6 +26,7 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ // The mode maps of a UNICOMM instance. The SVDs describe them per instance and identically, so // one version each covers the portfolio, as with the wrapper. (".*:unicommi2cc", "v1"), + (".*:unicommuart", "v1"), // SLAU846 ยง23.3, SLAU847 ยง19.3, SLAU893 ยง13.3 and SLAU923 ยง11.3 describe the same eight // registers with the same fields, so every family shares one version. The SVDs disagree with // the TRMs and with each other about CTL0 bits 1 and 2, which the YAML resolves in the TRMs' diff --git a/mspm0-metapac-gen/src/peripheral.rs b/mspm0-metapac-gen/src/peripheral.rs index 96e64cc..3b28a92 100644 --- a/mspm0-metapac-gen/src/peripheral.rs +++ b/mspm0-metapac-gen/src/peripheral.rs @@ -26,6 +26,7 @@ const GENERATE_PERIPHERALS: &[PeripheralType] = &[ PeripheralType::Uart, PeripheralType::Unicomm, PeripheralType::UnicommI2cc, + PeripheralType::UnicommUart, PeripheralType::Vref, PeripheralType::Wwdt, ]; diff --git a/transforms/UNICOMM_UART.yaml b/transforms/UNICOMM_UART.yaml new file mode 100644 index 0000000..3fb9f8a --- /dev/null +++ b/transforms/UNICOMM_UART.yaml @@ -0,0 +1,211 @@ +# Transform for the UNICOMM UART register map, based on UC0_UART on G120x, which has the +# GFCTL glitch filter and LINCTL.EN_FRM_ERR that the G518x SVD leaves out and hw_unicommuart.h +# confirms. +# +# The wrapper (GPRCM, IPMODE) is a peripheral of its own, so this map has neither, and no +# EVT_MODE: the mode maps do not carry one. +transforms: + - !DeleteUselessEnums + + - !DeleteFieldsets + from: .* + useless: true + + # However DeleteUselessEnums does not delete MIS and IMASK (due to CLR being 0 and SET being 1) + # + # TODO: Add this to chiptool + - !DeleteEnumsWithVariants + variants: + 0: CLR + 1: SET + + # BUSY is useless + - !DeleteEnumsWithVariants + variants: + 0: CLEARED + 1: SET + + # FREE is useless + - !DeleteEnumsWithVariants + variants: + 0: STOP + 1: RUN + + # RESETASSERT is useless + - !DeleteEnumsWithVariants + variants: + 0: NOP + 1: ASSERT + + # RESETSTKY is useless + - !DeleteEnumsWithVariants + variants: + 0: NORES + 1: RESET + + # RESETSTKYCLR is useless + - !DeleteEnumsWithVariants + variants: + 0: NOP + 1: CLR + + # TXD_OUT is useless + - !DeleteEnumsWithVariants + variants: + 0: LOW + 1: HIGH + + # INTEVAL is useless + - !DeleteEnumsWithVariants + variants: + 0: DISABLE + 1: EVAL + + # TODO: Something better than a cursed rename as merge + - !MergeBlocks + from: UC0_UART + main: UC0_UART + to: UNICOMMUART + + # Remove prefixes + - !RenameRegisters + block: .* + from: UC0_UART_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: UC0_UART_(.+) + to: $1 + + - !Rename + type: All + from: UC0_UART_(.+) + to: $1 + + ## Interrupts + # The three event blocks are already named here, unlike the standalone UART's INT_EVENTn. + - !MergeFieldsets + from: CPU_INT_(IMASK|ISET|MIS|RIS|ICLR) + to: CPU_INT + check: NoCheck + + - !RenameRegisters + block: .* + from: CPU_INT_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: CPU_INT_(.+) + to: $1 + + - !MergeFieldsets + from: DMA_TRIG_RX_(IMASK|ISET|MIS|RIS|ICLR) + to: DMA_TRIG_RX + check: NoCheck + + - !RenameRegisters + block: .* + from: DMA_TRIG_RX_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: DMA_TRIG_RX_(.+) + to: $1 + + - !MergeFieldsets + from: DMA_TRIG_TX_(IMASK|ISET|MIS|RIS|ICLR) + to: DMA_TRIG_TX + check: NoCheck + + - !RenameRegisters + block: .* + from: DMA_TRIG_TX_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: DMA_TRIG_TX_(.+) + to: $1 + + ## DGFSEL is an integer value, so the enum is useless + + - !DeleteEnums + from: DGFSEL + + ## EVT_MODE + # Merge INTx_CFG enums + - !MergeEnums + from: INT(\d+)_CFG + to: INT_CFG + + # Rename fields to match interrupt names + - !RenameFields + fieldset: EVT_MODE + from: INT0_CFG + to: CPU + - !RenameFields + fieldset: EVT_MODE + from: INT1_CFG + to: DMA_TRIG_RX + - !RenameFields + fieldset: EVT_MODE + from: INT2_CFG + to: DMA_TRIG_TX + + ## Combine LINCx fields, these are the same + - !MergeFieldsets + from: LINC(\d+) + to: LINC + + - !MakeRegisterArray + blocks: UART + from: LINC(\d+) + to: LINC + + ### Set RXIFLSEL and TXIFLSEL to enum to IFLSSEL + - !Add + ir: + enum/IFLSSEL: + bit_size: 3 + variants: + - name: ONE_FOURTH_ULP + description: RX FIFO >= 1/4 full, for ULP domain + value: 0 + - name: ONE_FOURTH + description: RX FIFO >= 1/4 full or TX FIFO <= 1/4 full + value: 1 + - name: HALF + description: RX FIFO >= 1/2 full or TX FIFO <= 1/2 full + value: 2 + - name: THREE_FOURTH + description: RX FIFO >= 3/4 full or TX FIFO <= 3/4 full + value: 3 + - name: FULL_ULP + description: RX FIFO is full, for ULP domain + value: 4 + - name: FULL + description: RX or TX FIFO is full + value: 5 + - name: AT_LEAST_ONE + description: RX or TX FIFO >= 1 entry available/free + value: 7 + + - !ModifyFieldsEnum + fieldset: IFLS + field: (RX|TX)IFLSEL + enum: IFLSSEL + + ## CLKDIV[2] is same enum and fieldset + - !MergeEnums + from: CLKDIV(.*) + to: CLKDIV + + - !MergeFieldsets + from: CLKDIV(.*) + to: CLKDIV + + ## Cleanup + - !Sort From 7f33decf6caf576dc553d5348eaba08c06aee945 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 21:59:53 +0100 Subject: [PATCH 043/134] Add the UNICOMM SPI map UC2 and UC3 on mspm0g518x and UC4 and UC8 on the L-series parts can be SPIs. Taken from UC2_SPI, the superset: UC3_SPI has neither the receive and transmit timeouts nor CSSEL, the command/data framing or REPEATTX. CLKDIV.RATIO is three bits here and the SVD is right about it, which is worth recording after the UART and I2C controller maps: their six bit fields make this look like a blanket SVD error, and it is not. UNICOMMSPI_CLKDIV_RATIO_MASK is 0x7 and the enum names all eight values, so it stays. --- data/registers/unicommspi_v1.yaml | 726 ++++++++++++++++++++++++++++ mspm0-data-gen/src/perimap.rs | 1 + mspm0-metapac-gen/src/peripheral.rs | 1 + transforms/UNICOMM_SPI.yaml | 134 +++++ 4 files changed, 862 insertions(+) create mode 100644 data/registers/unicommspi_v1.yaml create mode 100644 transforms/UNICOMM_SPI.yaml diff --git a/data/registers/unicommspi_v1.yaml b/data/registers/unicommspi_v1.yaml new file mode 100644 index 0000000..367ff37 --- /dev/null +++ b/data/registers/unicommspi_v1.yaml @@ -0,0 +1,726 @@ +block/CPU_INT: + items: + - name: IIDX + description: Interrupt Index Register. + byte_offset: 0 + access: Read + fieldset: IIDX + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: CPU_INT + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: CPU_INT + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: CPU_INT + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: CPU_INT + - name: ICLR + description: Interrupt clear. + byte_offset: 40 + access: Write + fieldset: ICLR +block/DMA_TRIG_RX: + items: + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: DMA_TRIG_RX + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: DMA_TRIG_RX + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: DMA_TRIG_RX + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: DMA_TRIG_RX +block/DMA_TRIG_TX: + items: + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: DMA_TRIG_TX + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: DMA_TRIG_TX + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: DMA_TRIG_TX + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: DMA_TRIG_TX +block/UNICOMMSPI: + description: PERIPHERALREGION. + items: + - name: CLKDIV + description: Clock Divider. + byte_offset: 4096 + fieldset: CLKDIV + - name: CLKSEL + description: Clock Select for Ultra Low Power peripherals. + byte_offset: 4104 + fieldset: CLKSEL + - name: PDBGCTL + description: Peripheral Debug Control. + byte_offset: 4120 + fieldset: PDBGCTL + - name: CPU_INT + array: + len: 1 + stride: 44 + byte_offset: 4128 + block: CPU_INT + - name: DMA_TRIG_RX + array: + len: 1 + stride: 36 + byte_offset: 4176 + block: DMA_TRIG_RX + - name: DMA_TRIG_TX + array: + len: 1 + stride: 36 + byte_offset: 4224 + block: DMA_TRIG_TX + - name: INTCTL + description: Interrupt control register. + byte_offset: 4324 + access: Write + fieldset: INTCTL + - name: CTL0 + description: SPI control register 0. + byte_offset: 4352 + fieldset: CTL0 + - name: STAT + description: Status Register. + byte_offset: 4360 + access: Read + fieldset: STAT + - name: IFLS + description: Interrupt FIFO Level Select Register. + byte_offset: 4364 + fieldset: IFLS + - name: CLKCTL + description: Clock prescaler and divider register. + byte_offset: 4368 + fieldset: CLKCTL + - name: TXDATA + description: TXDATA Register. + byte_offset: 4384 + access: Write + fieldset: TXDATA + - name: RXDATA + description: RXDATA Register. + byte_offset: 4388 + access: Read + fieldset: RXDATA + - name: CTL1 + description: SPI control register 1. + byte_offset: 4428 + fieldset: CTL1 +fieldset/CLKCTL: + description: Clock prescaler and divider register. + fields: + - name: SCR + description: 'Serial clock divider: This is used to generate the transmit and receive bit rate of the SPI. The SPI bit rate is (SPI''s functional clock frequency)/((SCR+1)*2). SCR is a value from 0-1023.' + bit_offset: 0 + bit_size: 10 + - name: DSAMPLE + description: 'Delayed sampling value. In controller mode the data on the input pin will be delayed sampled by the defined clock cycles of internal functional clock hence relaxing the setup time of input data. This setting is useful in systems where the board delays and external peripheral delays are more than the input setup time of the controller. Please refer to the datasheet for values of controller input setup time and assess what DSAMPLE value meets the requirement of the system. Note: High values of DSAMPLE can cause HOLD time violations and must be factored in the calculations.' + bit_offset: 28 + bit_size: 4 +fieldset/CLKDIV: + description: Clock Divider. + fields: + - name: RATIO + description: Selects divide ratio of module clock. + bit_offset: 0 + bit_size: 3 + enum: RATIO +fieldset/CLKSEL: + description: Clock Select for Ultra Low Power peripherals. + fields: + - name: LFCLK_SEL + description: Selects LFCLK as clock source if enabled. + bit_offset: 1 + bit_size: 1 + - name: MFCLK_SEL + description: Selects MFCLK as clock source if enabled. + bit_offset: 2 + bit_size: 1 + - name: BUSCLK_SEL + description: Selects buscclk as clock source if enabled. + bit_offset: 3 + bit_size: 1 +fieldset/CPU_INT: + description: Interrupt mask. + fields: + - name: PER + description: Parity error event mask. + bit_offset: 0 + bit_size: 1 + - name: RXFIFO_OVF + description: RXFIFO overflow event mask. + bit_offset: 1 + bit_size: 1 + - name: RX + description: Mask Receive Event. + bit_offset: 2 + bit_size: 1 + - name: RXFULL + description: RX FIFO Full Interrupt Mask. + bit_offset: 3 + bit_size: 1 + - name: TXFIFO_UNF + description: TX FIFO underflow interrupt mask. + bit_offset: 4 + bit_size: 1 + - name: TX + description: Mask Transmit Event. + bit_offset: 5 + bit_size: 1 + - name: TXEMPTY + description: Transmit FIFO Empty event mask. + bit_offset: 6 + bit_size: 1 + - name: IDLE + description: SPI Idle event mask. + bit_offset: 8 + bit_size: 1 + - name: RTOUT + description: Enable SPI Receive Time-Out event mask. + bit_offset: 9 + bit_size: 1 + - name: DMA_DONE_RX + description: Enable DMA Done on RX Event Channel Interrupt. + bit_offset: 15 + bit_size: 1 + - name: DMA_DONE_TX + description: Enable DMA Done on TX Event Channel Interrupt. + bit_offset: 16 + bit_size: 1 + - name: LTOUT + description: Enable SPI Line Time-Out event mask. + bit_offset: 20 + bit_size: 1 +fieldset/CTL0: + description: SPI control register 0. + fields: + - name: DSS + description: 'Data Size Select. Values 0 - 2 are reserved and shall not be used. 3h = 4_BIT : 4-bit data SPI allows only values up to 16 Bit.' + bit_offset: 0 + bit_size: 5 + enum: DSS + - name: FRF + description: Frame format Select. + bit_offset: 5 + bit_size: 2 + enum: FRF + - name: SPO + description: CLKOUT polarity (Motorola SPI frame format only). + bit_offset: 8 + bit_size: 1 + - name: SPH + description: CLKOUT phase (Motorola SPI frame format only) This bit selects the clock edge that captures data and enables it to change state. It has the most impact on the first bit transmitted by either permitting or not permitting a clock transition before the first data capture edge. + bit_offset: 9 + bit_size: 1 + enum: SPH + - name: CSSEL + description: Select the CS line to control on data transfer This bit is applicable for both controller/target mode. + bit_offset: 12 + bit_size: 2 + enum: CSSEL + - name: CSCLR + description: Clear shift register counter on CS inactive This bit is relevant only in the peripheral, CTL1.CP=0. + bit_offset: 14 + bit_size: 1 +fieldset/CTL1: + description: SPI control register 1. + fields: + - name: ENABLE + description: SPI enable. + bit_offset: 0 + bit_size: 1 + - name: LBM + description: Loop back mode. + bit_offset: 1 + bit_size: 1 + - name: CP + description: Controller or peripheral mode select. This bit can be modified only when SPI is disabled, CTL1.ENABLE=0. + bit_offset: 2 + bit_size: 1 + - name: POD + description: 'Peripheral-mode: Data output disabled This bit is relevant only in Peripheral mode. In multiple-peripheral system topologies, SPI controller can broadcast a message to all peripherals, while only one peripheral drives the line. POD can be used by the SPI peripheral to disable driving data on the line.' + bit_offset: 3 + bit_size: 1 + - name: MSB + description: MSB first select. Controls the direction of the receive and transmit shift register. + bit_offset: 4 + bit_size: 1 + - name: PREN + description: Parity receive enable If enabled, parity reception check will be done for both controller and peripheral modes In case of a parity miss-match the parity error flag RIS.PER will be set. + bit_offset: 5 + bit_size: 1 + - name: PES + description: Even Parity Select. + bit_offset: 6 + bit_size: 1 + - name: PTEN + description: Parity transmit enable If enabled, parity transmission will be done for both controller and peripheral modes. + bit_offset: 8 + bit_size: 1 + - name: SUSPEND + description: Suspend external communication. + bit_offset: 9 + bit_size: 1 + - name: CDENABLE + description: Command/Data Mode enable. + bit_offset: 11 + bit_size: 1 + - name: CDMODE + description: 'Command/Data Mode Value When CTL1.CDENABLE is 1, CS3 line is used as C/D signal to distinguish between Command (C/D low) and Data (C/D high) information. When a value is written into the CTL1.CDMODE bits, the C/D (CS3) line will go low for the given numbers of byte which are sent by the SPI, starting with the next value to be transmitted after which, C/D line will go high automatically 0: Manual mode with C/D signal as High 1-14: C/D is low while this number of bytes are being sent after which, this field sets to 0 and C/D goes high. Reading this field at any time returns the remaining number of command bytes. 15: Manual mode with C/D signal as Low.' + bit_offset: 12 + bit_size: 4 + enum: CTL1_CDMODE + - name: REPEATTX + description: 'Counter to repeat last transfer 0: repeat last transfer is disabled. x: repeat the last transfer with the given number. The transfer will be started with writing a data into the TX Buffer. Sending the data will be repeated with the given value, so the data will be transferred X+1 times in total. The behavior is identical as if the data would be written into the TX Buffer that many times as defined by the value here. It can be used to clean a transfer or to pull a certain amount of data by a peripheral.' + bit_offset: 16 + bit_size: 8 + enum: REPEATTX + - name: RXTIMEOUT + description: Receive Timeout (only for Peripheral mode) Defines the number of Clock Cycles before after which the Receive Timeout flag RTOUT is set. The time is calculated using the control register for the clock selection and divider in the Controller mode configuration. A value of 0 disables this function. + bit_offset: 24 + bit_size: 6 +fieldset/DMA_TRIG_RX: + description: Interrupt mask. + fields: + - name: RX + description: Receive FIFO event mask. + bit_offset: 2 + bit_size: 1 + - name: RTOUT + description: SPI Receive Time-Out event mask. + bit_offset: 9 + bit_size: 1 +fieldset/DMA_TRIG_TX: + description: Interrupt mask. + fields: + - name: TX + description: Transmit FIFO event mask. + bit_offset: 5 + bit_size: 1 +fieldset/ICLR: + description: Interrupt clear. + fields: + - name: PER + description: Clear Parity error event. + bit_offset: 0 + bit_size: 1 + - name: RXFIFO_OVF + description: Clear RXFIFO overflow event. + bit_offset: 1 + bit_size: 1 + - name: RX + description: Clear Receive event. + bit_offset: 2 + bit_size: 1 + - name: RXFULL + description: Clear RX FIFO underflow event. + bit_offset: 3 + bit_size: 1 + - name: TXFIFO_UNF + description: Clear TXFIFO underflow event. + bit_offset: 4 + bit_size: 1 + - name: TX + description: Clear Transmit event. + bit_offset: 5 + bit_size: 1 + - name: TXEMPTY + description: Clear Transmit FIFO Empty event. + bit_offset: 6 + bit_size: 1 + - name: IDLE + description: Clear SPI IDLE mode event. + bit_offset: 8 + bit_size: 1 + - name: RTOUT + description: Clear SPI Receive Time-Out Event. + bit_offset: 9 + bit_size: 1 + - name: DMA_DONE_RX + description: Clear DMA Done on RX Event Channel Interrupt. + bit_offset: 15 + bit_size: 1 + - name: DMA_DONE_TX + description: Clear DMA Done on TX Event Channel Interrupt. + bit_offset: 16 + bit_size: 1 + - name: LTOUT + description: Clear SPI Line Time-Out Event. + bit_offset: 20 + bit_size: 1 +fieldset/IFLS: + description: Interrupt FIFO Level Select Register. + fields: + - name: TXIFLSEL + description: 'FIFO Level Select for generating events (interrupt/dma). Note: for undefined settings the default configuration is used.' + bit_offset: 0 + bit_size: 3 + enum: TXIFLSEL + - name: TXCLR + description: TX FIFO CLEAR. Setting this bit will clear the TX FIFO contents. + bit_offset: 3 + bit_size: 1 + - name: RXIFLSEL + description: 'FIFO Level Select for generating events (interrupt/dma). Note: for undefined settings the default configuration is used.' + bit_offset: 4 + bit_size: 3 + enum: RXIFLSEL + - name: RXCLR + description: RX FIFO CLEAR. Setting this bit will clear the RX FIFO contents. + bit_offset: 7 + bit_size: 1 +fieldset/IIDX: + description: Interrupt Index Register. + fields: + - name: STAT + description: Interrupt index status. + bit_offset: 0 + bit_size: 8 + enum: STAT +fieldset/INTCTL: + description: Interrupt control register. + fields: + - name: INTEVAL + description: Writing a 1 to this field re-evaluates the interrupt sources. + bit_offset: 0 + bit_size: 1 +fieldset/PDBGCTL: + description: Peripheral Debug Control. + fields: + - name: FREE + description: Free run control. + bit_offset: 0 + bit_size: 1 + - name: SOFT + description: Soft halt boundary control. This function is only available, if [FREE] is set to 'STOP'. + bit_offset: 1 + bit_size: 1 + enum: SOFT +fieldset/RXDATA: + description: RXDATA Register. + fields: + - name: DATA + description: Received Data When PACKEN=1,two entries of the FIFO are returned as a 32-bit value. When PACKEN=0, 1 entry of FIFO is returned as 16-bit value. As data values are removed by the receive logic from the incoming data frame, they are placed into the entry in the receive FIFO, pointed to by the current FIFO write pointer. Received data less than 16 bits is automatically right justified in the receive buffer. + bit_offset: 0 + bit_size: 16 +fieldset/STAT: + description: Status Register. + fields: + - name: RXFE + description: Receive FIFO empty. + bit_offset: 2 + bit_size: 1 + - name: RXFF + description: Receive FIFO Full The meaning of this bit depends on the state of the FEN bit in the CTL0 register. + bit_offset: 3 + bit_size: 1 + - name: RXCLR + description: RX FIFO Clear Status. + bit_offset: 4 + bit_size: 1 + - name: TXFE + description: Transmit FIFO empty. + bit_offset: 5 + bit_size: 1 + - name: TXFF + description: Transmit FIFO Full The meaning of this bit depends on the state of the FEN bit in the CTL0 register. + bit_offset: 6 + bit_size: 1 + - name: TXCLR + description: TX FIFO Clear Status. + bit_offset: 7 + bit_size: 1 + - name: BUSY + description: Busy. + bit_offset: 8 + bit_size: 1 + enum: BUSY + - name: CDMODE + description: Current CDMODE status. This reflects the current counter counter value. + bit_offset: 12 + bit_size: 4 + enum: STAT_CDMODE +fieldset/TXDATA: + description: TXDATA Register. + fields: + - name: DATA + description: Transmit Data When read, last written value will be returned. If the last write to this field was a 32-bit write (with PACKEN=1), 32-bits will be returned and if the last write was a 16-bit write (PACKEN=0), those 16-bits will be returned. When written, one or two FIFO entries will be written depending on PACKEN value. Data values are removed from the transmit FIFO one value at a time by the transmit logic. It is loaded into the transmit serial shifter, then serially shifted out onto the TXD output pin at the programmed bit rate. When a data size of less than 16 bits is selected, the user must right-justify data written to the transmit FIFO. The transmit logic ignores the unused bits. + bit_offset: 0 + bit_size: 16 +enum/BUSY: + bit_size: 1 + variants: + - name: IDLE + description: SPI is in idle mode. + value: 0 + - name: ACTIVE + description: SPI is currently transmitting and/or receiving data. + value: 1 +enum/CSSEL: + bit_size: 2 + variants: + - name: CSSEL_0 + description: 'CS line select: 0.' + value: 0 + - name: CSSEL_1 + description: 'CS line select: 1.' + value: 1 + - name: CSSEL_2 + description: 'CS line select: 2.' + value: 2 + - name: CSSEL_3 + description: 'CS line select: 3.' + value: 3 +enum/CTL1_CDMODE: + bit_size: 4 + variants: + - name: DATA + description: 'Manual mode: Data.' + value: 0 + - name: COMMAND + description: 'Manual mode: Command.' + value: 15 +enum/DSS: + bit_size: 5 + variants: + - name: DSS_4 + description: 'Data Size Select bits: 4.' + value: 3 + - name: DSS_5 + description: 'Data Size Select bits: 5.' + value: 4 + - name: DSS_6 + description: 'Data Size Select bits: 6.' + value: 5 + - name: DSS_7 + description: 'Data Size Select bits: 7.' + value: 6 + - name: DSS_8 + description: 'Data Size Select bits: 8.' + value: 7 + - name: DSS_9 + description: 'Data Size Select bits: 9.' + value: 8 + - name: DSS_10 + description: 'Data Size Select bits: 10.' + value: 9 + - name: DSS_11 + description: 'Data Size Select bits: 11.' + value: 10 + - name: DSS_12 + description: 'Data Size Select bits: 12.' + value: 11 + - name: DSS_13 + description: 'Data Size Select bits: 13.' + value: 12 + - name: DSS_14 + description: 'Data Size Select bits: 14.' + value: 13 + - name: DSS_15 + description: 'Data Size Select bits: 15.' + value: 14 + - name: DSS_16 + description: 'Data Size Select bits: 16.' + value: 15 +enum/FRF: + bit_size: 2 + variants: + - name: MOTOROLA_3WIRE + description: Motorola SPI frame format (3 wire mode). + value: 0 + - name: MOTOROLA_4WIRE + description: Motorola SPI frame format (4 wire mode). + value: 1 + - name: TI_SYNC + description: TI synchronous serial frame format. + value: 2 +enum/RATIO: + bit_size: 3 + variants: + - name: DIV_BY_1 + description: Do not divide clock source. + value: 0 + - name: DIV_BY_2 + description: Divide clock source by 2. + value: 1 + - name: DIV_BY_3 + description: Divide clock source by 3. + value: 2 + - name: DIV_BY_4 + description: Divide clock source by 4. + value: 3 + - name: DIV_BY_5 + description: Divide clock source by 5. + value: 4 + - name: DIV_BY_6 + description: Divide clock source by 6. + value: 5 + - name: DIV_BY_7 + description: Divide clock source by 7. + value: 6 + - name: DIV_BY_8 + description: Divide clock source by 8. + value: 7 +enum/REPEATTX: + bit_size: 8 + variants: + - name: DISABLE + description: REPEATTX disable. + value: 0 +enum/RXIFLSEL: + bit_size: 3 + variants: + - name: LVL_1_4 + description: RX FIFO >= 1/4 full. + value: 1 + - name: LVL_1_2 + description: RX FIFO >= 1/2 full (default). + value: 2 + - name: LVL_3_4 + description: RX FIFO >= 3/4 full. + value: 3 + - name: LVL_NOT_EMPTY + description: Opposite of empty. + value: 4 + - name: LVL_FULL + description: RX FIFO is full. + value: 5 + - name: LVL_ALMOST_FULL + description: RX_FIFO >= (MAX_FIFO_LEN -1). + value: 6 + - name: LVL_ALMOST_EMPTY + description: RX_FIFO <= 1. + value: 7 +enum/SOFT: + bit_size: 1 + variants: + - name: DEPRECATED + description: Not supported. + value: 0 + - name: DELAYED + description: The peripheral blocks the debug freeze until it has reached a boundary where it can resume without corruption. + value: 1 +enum/SPH: + bit_size: 1 + variants: + - name: FIRST + description: Data is captured on the first clock edge transition. + value: 0 + - name: SECOND + description: Data is captured on the second clock edge transition. + value: 1 +enum/STAT: + bit_size: 8 + variants: + - name: NO_INTR + description: No interrupt pending. + value: 0 + - name: PER_EVT + description: Transmit Parity Event/interrupt pending. + value: 1 + - name: RXFIFO_OFV_EVT + description: RX FIFO Overflow Event/interrupt pending. + value: 2 + - name: RX_EVT + description: Receive interrupt. + value: 3 + - name: RXFULL_EVT + description: RX FIFO Full Interrupt. + value: 4 + - name: TXFIFO_UNF_EVT + description: TX FIFO underflow interrupt. + value: 5 + - name: TX_EVT + description: Transmit Event. + value: 6 + - name: TX_EMPTY + description: Transmit Buffer Empty Event/interrupt pending. + value: 7 + - name: IDLE_EVT + description: End of Transmit Event/interrupt pending. + value: 9 + - name: RTOUT_EVT + description: SPI receive time-out interrupt. + value: 10 + - name: DMA_DONE_RX + description: DMA DONE on RX. + value: 16 + - name: DMA_DONE_TX + description: DMA DONE on TX. + value: 17 + - name: DMA_PREIRQ_RX + description: DMA PRE IRQ RX INTERRUPT. + value: 19 + - name: DMA_PREIRQ_TX + description: DMA PRE IRQ TX INTERRUPT. + value: 20 + - name: LTOUT + description: SPI line time-out interrupt. + value: 21 +enum/STAT_CDMODE: + bit_size: 4 + variants: + - name: DATA + description: 'Manual mode: Data.' + value: 0 + - name: COMMAND + description: 'Manual mode: Command.' + value: 15 +enum/TXIFLSEL: + bit_size: 3 + variants: + - name: LVL_3_4 + description: TX FIFO <= 3/4 empty. + value: 1 + - name: LVL_1_2 + description: TX FIFO <= 1/2 empty (default). + value: 2 + - name: LVL_1_4 + description: TX FIFO <= 1/4 empty. + value: 3 + - name: LVL_NOT_FULL + description: Opposite of full. + value: 4 + - name: LVL_EMPTY + description: TX FIFO is empty. + value: 5 + - name: LVL_ALMOST_EMPTY + description: TX FIFO <= 1. + value: 6 + - name: LVL_ALMOST_FULL + description: TX_FIFO >= (MAX_FIFO_LEN -1). + value: 7 diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index b4b0474..3227b6a 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -26,6 +26,7 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ // The mode maps of a UNICOMM instance. The SVDs describe them per instance and identically, so // one version each covers the portfolio, as with the wrapper. (".*:unicommi2cc", "v1"), + (".*:unicommspi", "v1"), (".*:unicommuart", "v1"), // SLAU846 ยง23.3, SLAU847 ยง19.3, SLAU893 ยง13.3 and SLAU923 ยง11.3 describe the same eight // registers with the same fields, so every family shares one version. The SVDs disagree with diff --git a/mspm0-metapac-gen/src/peripheral.rs b/mspm0-metapac-gen/src/peripheral.rs index 3b28a92..eca6fcc 100644 --- a/mspm0-metapac-gen/src/peripheral.rs +++ b/mspm0-metapac-gen/src/peripheral.rs @@ -26,6 +26,7 @@ const GENERATE_PERIPHERALS: &[PeripheralType] = &[ PeripheralType::Uart, PeripheralType::Unicomm, PeripheralType::UnicommI2cc, + PeripheralType::UnicommSpi, PeripheralType::UnicommUart, PeripheralType::Vref, PeripheralType::Wwdt, diff --git a/transforms/UNICOMM_SPI.yaml b/transforms/UNICOMM_SPI.yaml new file mode 100644 index 0000000..98d0313 --- /dev/null +++ b/transforms/UNICOMM_SPI.yaml @@ -0,0 +1,134 @@ +# Transform for the UNICOMM UART register map, based on UC2_SPI on G120x, which has the +# GFCTL glitch filter and LINCTL.EN_FRM_ERR that the G518x SVD leaves out and hw_unicommuart.h +# confirms. +# +# The wrapper (GPRCM, IPMODE) is a peripheral of its own, so this map has neither, and no +# EVT_MODE: the mode maps do not carry one. +transforms: + - !DeleteUselessEnums + + - !DeleteFieldsets + from: .* + useless: true + + # However DeleteUselessEnums does not delete MIS and IMASK (due to CLR being 0 and SET being 1) + # + # TODO: Add this to chiptool + - !DeleteEnumsWithVariants + variants: + 0: CLR + 1: SET + + # BUSY is useless + - !DeleteEnumsWithVariants + variants: + 0: CLEARED + 1: SET + + # FREE is useless + - !DeleteEnumsWithVariants + variants: + 0: STOP + 1: RUN + + # RESETASSERT is useless + - !DeleteEnumsWithVariants + variants: + 0: NOP + 1: ASSERT + + # RESETSTKY is useless + - !DeleteEnumsWithVariants + variants: + 0: NORES + 1: RESET + + # RESETSTKYCLR is useless + - !DeleteEnumsWithVariants + variants: + 0: NOP + 1: CLR + + # TXD_OUT is useless + - !DeleteEnumsWithVariants + variants: + 0: LOW + 1: HIGH + + # INTEVAL is useless + - !DeleteEnumsWithVariants + variants: + 0: DISABLE + 1: EVAL + + # TODO: Something better than a cursed rename as merge + - !MergeBlocks + from: UC2_SPI + main: UC2_SPI + to: UNICOMMSPI + + # Remove prefixes + - !RenameRegisters + block: .* + from: UC2_SPI_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: UC2_SPI_(.+) + to: $1 + + - !Rename + type: All + from: UC2_SPI_(.+) + to: $1 + + ## Interrupts + # The three event blocks are already named here, unlike the standalone UART's INT_EVENTn. + - !MergeFieldsets + from: CPU_INT_(IMASK|ISET|MIS|RIS|ICLR) + to: CPU_INT + check: NoCheck + + - !RenameRegisters + block: .* + from: CPU_INT_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: CPU_INT_(.+) + to: $1 + + - !MergeFieldsets + from: DMA_TRIG_RX_(IMASK|ISET|MIS|RIS|ICLR) + to: DMA_TRIG_RX + check: NoCheck + + - !RenameRegisters + block: .* + from: DMA_TRIG_RX_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: DMA_TRIG_RX_(.+) + to: $1 + + - !MergeFieldsets + from: DMA_TRIG_TX_(IMASK|ISET|MIS|RIS|ICLR) + to: DMA_TRIG_TX + check: NoCheck + + - !RenameRegisters + block: .* + from: DMA_TRIG_TX_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: DMA_TRIG_TX_(.+) + to: $1 + + ## Cleanup + - !Sort From 81b633f3fd6f99db16be6102e1be3f15345fc72d Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 22:02:02 +0100 Subject: [PATCH 044/134] Add the UNICOMM I2C target map UC7 on the L-series parts and UC0 and UC1 on mspm0g518x can be I2C targets, which is the last of the four modes to gain a register block. The two SVD variants of the map differ only in the wording of one field description, so the G518x one is taken as the other maps are. CLKDIV.RATIO is six bits, as in the UART and I2C controller maps. Also names the merged block in the I2C controller transform, which said I2C where the checked-in YAML says UNICOMMI2CC, so re-running it would not have reproduced the file. --- data/registers/unicommi2ct_v1.yaml | 772 ++++++++++++++++++++++++++++ mspm0-data-gen/src/perimap.rs | 1 + mspm0-metapac-gen/src/peripheral.rs | 1 + transforms/UNICOMM_I2CC.yaml | 2 +- transforms/UNICOMM_I2CT.yaml | 112 ++++ 5 files changed, 887 insertions(+), 1 deletion(-) create mode 100644 data/registers/unicommi2ct_v1.yaml create mode 100644 transforms/UNICOMM_I2CT.yaml diff --git a/data/registers/unicommi2ct_v1.yaml b/data/registers/unicommi2ct_v1.yaml new file mode 100644 index 0000000..e575607 --- /dev/null +++ b/data/registers/unicommi2ct_v1.yaml @@ -0,0 +1,772 @@ +block/CPU_INT: + items: + - name: IIDX + description: Interrupt index. + byte_offset: 0 + access: Read + fieldset: IIDX + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: CPU_INT + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: CPU_INT + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: CPU_INT + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: CPU_INT + - name: ICLR + description: Interrupt clear. + byte_offset: 40 + access: Write + fieldset: ICLR +block/DMA_TRIG0: + items: + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: DMA_TRIG0 + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: DMA_TRIG0 + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: DMA_TRIG0 + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: DMA_TRIG0 +block/DMA_TRIG1: + items: + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: DMA_TRIG1 + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: DMA_TRIG1 + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: DMA_TRIG1 + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: DMA_TRIG1 +block/UNICOMMI2CT: + description: PERIPHERALREGION. + items: + - name: CLKDIV + description: Clock Divider. + byte_offset: 4096 + fieldset: CLKDIV + - name: CLKSEL + description: Clock Select for Ultra Low Power peripherals. + byte_offset: 4104 + fieldset: CLKSEL + - name: PDBGCTL + description: Peripheral Debug Control. + byte_offset: 4120 + fieldset: PDBGCTL + - name: CPU_INT + array: + len: 1 + stride: 44 + byte_offset: 4128 + block: CPU_INT + - name: DMA_TRIG1 + array: + len: 1 + stride: 36 + byte_offset: 4176 + block: DMA_TRIG1 + - name: DMA_TRIG0 + array: + len: 1 + stride: 36 + byte_offset: 4224 + block: DMA_TRIG0 + - name: INTCTL + description: Interrupt control register. + byte_offset: 4324 + access: Write + fieldset: INTCTL + - name: CTR + description: I2C Target Control Register. + byte_offset: 4352 + fieldset: CTR + - name: ACKCTL + description: I2C Target ACK Control. + byte_offset: 4356 + fieldset: ACKCTL + - name: SR + description: Status Register. + byte_offset: 4360 + access: Read + fieldset: SR + - name: IFLS + description: Interrupt FIFO Level Select Register. + byte_offset: 4364 + fieldset: IFLS + - name: GFCTL + description: I2C Glitch Filter Control. + byte_offset: 4376 + fieldset: GFCTL + - name: TTXDATA + description: I2C TXData. + byte_offset: 4384 + access: Write + fieldset: TTXDATA + - name: RXDATA + description: I2C RXData. + byte_offset: 4388 + access: Read + fieldset: RXDATA + - name: PECSR + description: PEC status register. + byte_offset: 4392 + access: Read + fieldset: PECSR + - name: OAR2 + description: Own Address 2. + byte_offset: 4424 + fieldset: OAR2 + - name: OAR + description: I2C Own Address. + byte_offset: 4428 + fieldset: OAR + - name: TIMEOUT_CNT + description: I2C Timeout Count Register. + byte_offset: 4432 + access: Read + fieldset: TIMEOUT_CNT + - name: TIMEOUT_CTL + description: I2C Timeout Count Control Register. + byte_offset: 4436 + fieldset: TIMEOUT_CTL + - name: PECCTL + description: I2C PEC control register. + byte_offset: 4440 + fieldset: PECCTL +fieldset/ACKCTL: + description: I2C Target ACK Control. + fields: + - name: ACKOEN + description: I2C Target ACK Override Enable Read SR.ACKOEN to check current status of this bit. + bit_offset: 0 + bit_size: 1 + - name: ACKOVAL + description: 'ACK Override Value Note: for General Call this bit will be ignored if set to NACK and Target continues to receive data.' + bit_offset: 1 + bit_size: 1 + - name: ACKOEN_ON_START + description: When set this bit will automatically enable target override acknowlede following a Start Condition. Status bit ACKOEN will reflect a value of '1' when automatic override enable is used. + bit_offset: 2 + bit_size: 1 + - name: ACKOEN_ON_PECNEXT + description: When set this bit will automatically turn on the Target acknowledge override following a ACK/NACK of the byte received just prior to the PEC byte. However, setting ACKCTL.ACKOEN bit will not automatically be ACKed/NACKed by the State Machine and firmware must perform this function by writing Target.ACKCTL register. Status bit ACKOEN will reflect a value of '1' when automatic override enable is used. + bit_offset: 3 + bit_size: 1 + - name: ACKOEN_ON_PECDONE + description: When set, this bit will automatically turn on target acknowledge enable field following the ACK/NACK of the received PEC byte. Status bit ACKOEN will reflect a value of '1' when automatic override enable is used. + bit_offset: 4 + bit_size: 1 +fieldset/CLKDIV: + description: Clock Divider. + fields: + - name: RATIO + description: 'Divide ratio of the module clock, as the divisor minus one: 0 divides by 1 and 63 by 64.' + bit_offset: 0 + bit_size: 6 +fieldset/CLKSEL: + description: Clock Select for Ultra Low Power peripherals. + fields: + - name: MFCLK_SEL + description: Selects MFCLK as clock source if enabled. + bit_offset: 2 + bit_size: 1 + - name: BUSCLK_SEL + description: Selects BUS CLK as clock source if enabled. + bit_offset: 3 + bit_size: 1 +fieldset/CPU_INT: + description: Interrupt mask. + fields: + - name: RXDONE + description: Receive Data Interrupt Signals that a byte has been received. + bit_offset: 0 + bit_size: 1 + - name: TXDONE + description: Transmit Transaction completed Interrupt. + bit_offset: 1 + bit_size: 1 + - name: RXTRG + description: Receive Trigger. + bit_offset: 2 + bit_size: 1 + - name: TXTRG + description: Transmit Trigger. + bit_offset: 3 + bit_size: 1 + - name: RXFULL + description: RXFIFO full event. This interrupt is set if an Target RX FIFO is full. + bit_offset: 4 + bit_size: 1 + - name: TXEMPTY + description: Target Transmit FIFO Empty interrupt mask. This interrupt is set if all data in the Transmit FIFO have been shifted out and the transmit goes into idle mode. + bit_offset: 5 + bit_size: 1 + - name: TX_UNFL + description: Target TX FIFO underflow. + bit_offset: 6 + bit_size: 1 + - name: RX_OVFL + description: Target RX FIFO overflow. + bit_offset: 7 + bit_size: 1 + - name: GENCALL + description: General Call Interrupt. + bit_offset: 8 + bit_size: 1 + - name: START + description: Start Condition Interrupt. + bit_offset: 9 + bit_size: 1 + - name: STOP + description: Stop Condition Interrupt. + bit_offset: 10 + bit_size: 1 + - name: PEC_RX_ERR + description: RX Pec Error Interrupt. + bit_offset: 11 + bit_size: 1 + - name: TIMEOUTA + description: Timeout A Interrupt. + bit_offset: 12 + bit_size: 1 + - name: TIMEOUTB + description: Timeout B Interrupt. + bit_offset: 13 + bit_size: 1 + - name: DMA_DONE_RX + description: DMA Done on Event Channel RX. + bit_offset: 15 + bit_size: 1 + - name: DMA_DONE_TX + description: DMA Done on Event Channel TX. + bit_offset: 16 + bit_size: 1 + - name: ARBLOST + description: Arbitration Lost Interrupt. + bit_offset: 17 + bit_size: 1 +fieldset/CTR: + description: I2C Target Control Register. + fields: + - name: ENABLE + description: Setting this bit enables the module. + bit_offset: 0 + bit_size: 1 + - name: GENCALL + description: General call response enable Modify only when UCSWRST = 1. 0b = Do not respond to a general call 1b = Respond to a general call. + bit_offset: 1 + bit_size: 1 + - name: TXEMPTY_ON_TREQ + description: Tx Empty Interrupt on TREQ. + bit_offset: 3 + bit_size: 1 + - name: TXTRIG_TXMODE + description: Tx Trigger when Target FSM is in Tx Mode. + bit_offset: 4 + bit_size: 1 + - name: TXWAIT_STALE_TXFIFO + description: 'Tx transfer waits when stale data in Tx FIFO. This prevents stale bytes left in the TX FIFO from automatically being sent on the next I2C packet. Note: this should be used with TXEMPTY_ON_TREQ set to prevent the Target State Machine from waiting for TX FIFO data without an interrupt notification when the FIFO data is stale.' + bit_offset: 5 + bit_size: 1 + - name: RXFULL_ON_RREQ + description: Rx full interrupt generated on RREQ condition as indicated in SSR. + bit_offset: 6 + bit_size: 1 + - name: EN_DEFHOSTADR + description: Enable Default Host Address. + bit_offset: 7 + bit_size: 1 + - name: EN_ALRESPADR + description: Enable Alert Response Address. + bit_offset: 8 + bit_size: 1 + - name: EN_DEFDEVADR + description: Enable Default device address. + bit_offset: 9 + bit_size: 1 + - name: SUSPEND + description: Suspend external communication. + bit_offset: 11 + bit_size: 1 + - name: CLKSTRETCH + description: Clock Stretch Enable. + bit_offset: 20 + bit_size: 1 + - name: WUEN + description: Target Wakeup Enable. + bit_offset: 21 + bit_size: 1 +fieldset/DMA_TRIG0: + description: Interrupt mask. + fields: + - name: TXTRG + description: Transmit Trigger. + bit_offset: 3 + bit_size: 1 +fieldset/DMA_TRIG1: + description: Interrupt mask. + fields: + - name: RXTRG + description: Receive Trigger. + bit_offset: 2 + bit_size: 1 +fieldset/GFCTL: + description: I2C Glitch Filter Control. + fields: + - name: AGFEN + description: Analog Glitch Suppression Enable. + bit_offset: 8 + bit_size: 1 +fieldset/ICLR: + description: Interrupt clear. + fields: + - name: RXDONE + description: Target Receive Data Interrupt Signals that a byte has been received. + bit_offset: 0 + bit_size: 1 + - name: TXDONE + description: Target Transmit Transaction completed Interrupt. + bit_offset: 1 + bit_size: 1 + - name: RXTRG + description: Receive Trigger. + bit_offset: 2 + bit_size: 1 + - name: TXTRG + description: Transmit Trigger. + bit_offset: 3 + bit_size: 1 + - name: RXFULL + description: RXFIFO full event. This interrupt is set if an RX FIFO is full. + bit_offset: 4 + bit_size: 1 + - name: TXEMPTY + description: Transmit FIFO Empty interrupt mask. This interrupt is set if all data in the Transmit FIFO have been shifted out and the transmit goes into idle mode. + bit_offset: 5 + bit_size: 1 + - name: TX_UNFL + description: Target TX FIFO underflow. + bit_offset: 6 + bit_size: 1 + - name: RX_OVFL + description: Target RX FIFO overflow. + bit_offset: 7 + bit_size: 1 + - name: GENCALL + description: General Call Interrupt. + bit_offset: 8 + bit_size: 1 + - name: START + description: Target START Detection Interrupt. + bit_offset: 9 + bit_size: 1 + - name: STOP + description: Target STOP Detection Interrupt. + bit_offset: 10 + bit_size: 1 + - name: PEC_RX_ERR + description: Target RX Pec Error Interrupt. + bit_offset: 11 + bit_size: 1 + - name: TIMEOUTA + description: Timeout A interrupt. + bit_offset: 12 + bit_size: 1 + - name: TIMEOUTB + description: Timeout B Interrupt. + bit_offset: 13 + bit_size: 1 + - name: DMA_DONE_RX + description: DMA Done on Event Channel RX. + bit_offset: 15 + bit_size: 1 + - name: DMA_DONE_TX + description: DMA Done on Event Channel TX. + bit_offset: 16 + bit_size: 1 + - name: ARBLOST + description: Arbitration Lost Interrupt. + bit_offset: 17 + bit_size: 1 +fieldset/IFLS: + description: Interrupt FIFO Level Select Register. + fields: + - name: TXIFLSEL + description: 'FIFO Level Select for generating events (interrupt/dma). Note: for undefined settings the default configuration is used.' + bit_offset: 0 + bit_size: 3 + enum: TXIFLSEL + - name: TXCLR + description: TX FIFO CLEAR. Setting this bit will clear the TX FIFO contents. + bit_offset: 3 + bit_size: 1 + - name: RXIFLSEL + description: 'FIFO Level Select for generating events (interrupt/dma). Note: for undefined settings the default configuration is used.' + bit_offset: 4 + bit_size: 3 + enum: RXIFLSEL + - name: RXCLR + description: RX FIFO CLEAR. Setting this bit will clear the RX FIFO contents. + bit_offset: 7 + bit_size: 1 +fieldset/IIDX: + description: Interrupt index. + fields: + - name: STAT + description: I2C Module Interrupt Vector Value. This register provides the highes priority interrupt index. A read clears the corresponding interrupt flag in RIS and MISC. 15h-1Fh = Reserved. + bit_offset: 0 + bit_size: 8 + enum: STAT +fieldset/INTCTL: + description: Interrupt control register. + fields: + - name: INTEVAL + description: Writing a 1 to this field re-evaluates the interrupt sources. + bit_offset: 0 + bit_size: 1 +fieldset/OAR: + description: I2C Own Address. + fields: + - name: OAR + description: 'Own Address: This field specifies bits A9 through A0 of the Target address. In 7-bit addressing mode as selected by I2CSOAR.MODE bit, the top 3 bits are don''t care.' + bit_offset: 0 + bit_size: 10 + - name: OAREN + description: Own Address Enable. + bit_offset: 14 + bit_size: 1 + - name: MODE + description: This bit selects the adressing mode to be used. When 0, 7-bit addressing is used. When 1, 10-bit addressing is used. + bit_offset: 15 + bit_size: 1 + enum: MODE +fieldset/OAR2: + description: Own Address 2. + fields: + - name: OAR2 + description: Own Address 2This field specifies the alternate OAR2 address. + bit_offset: 0 + bit_size: 7 + - name: OAR2EN + description: Own Address 2 Enable. + bit_offset: 7 + bit_size: 1 + - name: OAR2_MASK + description: 'Own Address 2 Mask: This field specifies bits A6 through A0 of the Target address. The bits with value 1 in SOAR2.OAR2_MASK field will make the corresponding incoming address bits to match by default regardless of the value inside SOAR2.OAR2 i.e. corresponding SOAR2.OAR2 bit is a dont care.' + bit_offset: 16 + bit_size: 7 +fieldset/PDBGCTL: + description: Peripheral Debug Control. + fields: + - name: FREE + description: Free run control. + bit_offset: 0 + bit_size: 1 + - name: SOFT + description: Soft halt boundary control. This function is only available, if [FREE] is set to 'STOP'. + bit_offset: 1 + bit_size: 1 + enum: SOFT +fieldset/PECCTL: + description: I2C PEC control register. + fields: + - name: PECCNT + description: When this field is non zero, the number of I2C data bytes are counted. When the byte count = PECCNT and the state machine is transmitting, the contents of the LSFR is loaded into the shift register instead of the byte received from the Tx FIFO. When the state machine is receiving, after the last bit of this byte is received the LSFR is checked and if it is non-zero, a PEC RX Error interrupt is generated. The I2C packet must be padded to include the PEC byte for both transmit and receive. In transmit mode the FIFO must be loaded with a dummy PEC byte. In receive mode the PEC byte will be passed to the Rx FIFO. In the normal use case, FW would set PECEN=1 and PECCNT=0 and use the ACKOEN until the remaining SMB packet length is known. FW would then set the PECCNT to the remaining packet length (Including PEC bye). FW would then configure DMA to allow the packet to complete unassisted and exit NoAck mode. Note that when the byte count = PEC CNT, the byte count is reset to 0 and multiple PEC calculation can automatically occur within a single I2C transaction. + bit_offset: 0 + bit_size: 9 + - name: PECEN + description: PEC Enable This bit enables the SMB Packet Error Checking (PEC). When enabled the PEC is calculated on all bits except the Start, Stop, Ack and Nack. The PEC LSFR and the Byte Counter is set to 0 when the State Machine is in the IDLE state, which occur following a Stop or when a timeout occurs. The Counter is also set to 0 after the PEC byte is sent or received. Note that the NACK is automatically send following a PEC byte that results in a PEC error. The PEC Polynomial is x^8 + x^2 + x^1 + 1. + bit_offset: 12 + bit_size: 1 +fieldset/PECSR: + description: PEC status register. + fields: + - name: PECBYTECNT + description: This is the current PEC Byte Count of the State Machine. + bit_offset: 0 + bit_size: 9 + - name: PECSTS_CHECK + description: This status bit indicates if the PEC was checked in the transaction that occurred before the last Stop. Latched on Stop. + bit_offset: 16 + bit_size: 1 + - name: PECSTS_ERROR + description: This status bit indicates if a PEC check error occurred in the transaction that occurred before the last Stop. Latched on Stop. + bit_offset: 17 + bit_size: 1 +fieldset/RXDATA: + description: I2C RXData. + fields: + - name: DATA + description: Received Data. This field contains the last received data. + bit_offset: 0 + bit_size: 8 +fieldset/SR: + description: Status Register. + fields: + - name: RREQ + description: Receive Request. + bit_offset: 0 + bit_size: 1 + - name: TREQ + description: Transmit Request. + bit_offset: 1 + bit_size: 1 + - name: RXMODE + description: Target FSM is in Rx MODE. + bit_offset: 2 + bit_size: 1 + - name: OAR2SEL + description: OAR2 Address Matched This bit gets reevaluated after every address comparison. + bit_offset: 3 + bit_size: 1 + - name: QCMDST + description: 'Quick Command Status Value Description: 0: The last transaction was a normal transaction or a transaction has not occurred. 1: The last transaction was a Quick Command transaction.' + bit_offset: 4 + bit_size: 1 + - name: QCMDRW + description: 'Quick Command Read / Write This bit only has meaning when the QCMDST bit is set. Value Description: 0: Quick command was a write 1: Quick command was a read.' + bit_offset: 5 + bit_size: 1 + - name: BUSBSY + description: I2C bus is busy. + bit_offset: 6 + bit_size: 1 + - name: TXMODE + description: FSM is in TX MODE. + bit_offset: 7 + bit_size: 1 + - name: STALE_TXFIFO + description: Stale Tx FIFO. + bit_offset: 8 + bit_size: 1 + - name: RXCLR + description: RX FIFO Clear Status. + bit_offset: 9 + bit_size: 1 + - name: TXCLR + description: TX FIFO Clear Status. + bit_offset: 10 + bit_size: 1 + - name: RXFE + description: Receive FIFO Empty The meaning of this bit depends on the state of the FEN bit in the CTL0 register. + bit_offset: 11 + bit_size: 1 + - name: RXFF + description: Receive FIFO Full The meaning of this bit depends on the state of the FEN bit in the CTL0 register. + bit_offset: 12 + bit_size: 1 + - name: TXFE + description: Transmit FIFO Empty The meaning of this bit depends on the state of the FEN bit in the CTL0 register. + bit_offset: 13 + bit_size: 1 + - name: TXFF + description: Transmit FIFO Full The meaning of this bit depends on the state of the FEN bit in the CTL0 register. + bit_offset: 14 + bit_size: 1 + - name: ACKOEN + description: Status of ACK Override Enable. + bit_offset: 15 + bit_size: 1 + - name: ADDRMATCH + description: Indicates the address for which Target address match happened. + bit_offset: 16 + bit_size: 10 +fieldset/TIMEOUT_CNT: + description: I2C Timeout Count Register. + fields: + - name: TCNTA + description: 'Timeout Count A Current Count: This field contains the upper 8 bits of a 12-bit current counter for timeout counter A.' + bit_offset: 0 + bit_size: 8 + - name: TCNTB + description: 'Timeout Count B Current Count: This field contains the upper 8 bits of a 12-bit current counter for timeout counter B.' + bit_offset: 16 + bit_size: 8 +fieldset/TIMEOUT_CTL: + description: I2C Timeout Count Control Register. + fields: + - name: TCNTLA + description: 'Timeout counter A load value Counter A is used for SCL low detection. This field contains the upper 8 bits of a 12-bit pre-load value for the Timeout A count. NOTE: The value of CNTLA must be greater than 1h. Each count is equal to 520 times the timeout period of functional clock. For example, with 8MHz functional clock and a 100KHz operating I2C clock, one timeout period will be equal to (1 / 8MHz) * 520 or 65 us.' + bit_offset: 0 + bit_size: 8 + - name: TCNTAEN + description: Timeout Counter A Enable. + bit_offset: 15 + bit_size: 1 + - name: TCNTLB + description: 'Timeout Count B Load: Counter B is used for SCL High Detection. This field contains the upper 8 bits of a 12-bit pre-load value for the Timeout B count. NOTE: The value of CNTLB must be greater than 1h. Each count is equal to 1* clock period. For example, with 10MHz functional clock one timeout period will be equal to1*100ns.' + bit_offset: 16 + bit_size: 8 + - name: TCNTBEN + description: Timeout Counter B Enable. + bit_offset: 31 + bit_size: 1 +fieldset/TTXDATA: + description: I2C TXData. + fields: + - name: DATA + description: Transmit Data This byte contains the data to be transferred during the next transaction. + bit_offset: 0 + bit_size: 8 +enum/MODE: + bit_size: 1 + variants: + - name: MODE7 + description: Enable 7-bit addressing. + value: 0 + - name: MODE10 + description: Enable 10-bit addressing. + value: 1 +enum/RXIFLSEL: + bit_size: 3 + variants: + - name: LVL_1_4 + description: RX FIFO >= 1/4 full. + value: 1 + - name: LVL_1_2 + description: RX FIFO >= 1/2 full (default). + value: 2 + - name: LVL_3_4 + description: RX FIFO >= 3/4 full. + value: 3 + - name: LVL_NOT_EMPTY + description: Opposite of empty. + value: 4 + - name: LVL_FULL + description: RX FIFO is full. + value: 5 + - name: LVL_ALMOST_FULL + description: RX_FIFO >= (MAX_FIFO_LEN -1). + value: 6 + - name: LVL_ALMOST_EMPTY + description: RX_FIFO <= 1. + value: 7 +enum/SOFT: + bit_size: 1 + variants: + - name: DEPRECATED + description: Not supported. + value: 0 + - name: DELAYED + description: The peripheral blocks the debug freeze until it has reached a boundary where it can resume without corruption. + value: 1 +enum/STAT: + bit_size: 8 + variants: + - name: NO_INTR + description: No interrupt pending. + value: 0 + - name: RXDONEFG + description: Receive Done Flag. + value: 1 + - name: TXDONEFG + description: Transmit Done Flag. + value: 2 + - name: RXTRG + description: receive FIFO Trigger Level. + value: 3 + - name: TXTRG + description: transmit FIFO Trigger level. + value: 4 + - name: RXFULL + description: RX FIFO FULL Event/interrupt pending. + value: 5 + - name: TXEMPTY + description: Transmit FIFO/Buffer Empty Event/interrupt pending. + value: 6 + - name: TX_UNFL + description: Target TX FIFO underflow. + value: 7 + - name: RX_OVFL + description: Target RX FIFO overflow event. + value: 8 + - name: GENCALL + description: General Call Event. + value: 9 + - name: STARTFG + description: Start Event. + value: 10 + - name: STOPFG + description: Stop Event. + value: 11 + - name: PEC_RX_ERR + description: PEC receive error event. + value: 12 + - name: TIMEOUTA + description: Timeout A Event. + value: 13 + - name: TIMEOUTB + description: Timeout B Event. + value: 14 + - name: DMA_DONE_RX + description: DMA DONE on Channel RX. + value: 16 + - name: DMA_DONE_TX + description: DMA DONE on Channel TX. + value: 17 + - name: ARBLOSTFG + description: Arbitration Lost. + value: 18 + - name: DMA_PREIRQ_RX + description: DMA PRE IRQ INTERRUPT. + value: 19 + - name: DMA_PREIRQ_TX + description: DMA PRE IRQ INTERRUPT. + value: 20 +enum/TXIFLSEL: + bit_size: 3 + variants: + - name: LVL_3_4 + description: TX FIFO <= 3/4 empty. + value: 1 + - name: LVL_1_2 + description: TX FIFO <= 1/2 empty (default). + value: 2 + - name: LVL_1_4 + description: TX FIFO <= 1/4 empty. + value: 3 + - name: LVL_NOT_FULL + description: Opposite of full. + value: 4 + - name: LVL_EMPTY + description: TX FIFO is empty. + value: 5 + - name: LVL_ALMOST_EMPTY + description: TX FIFO <= 1. + value: 6 + - name: LVL_ALMOST_FULL + description: TX_FIFO >= (MAX_FIFO_LEN -1). + value: 7 diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index 3227b6a..a3c1554 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -26,6 +26,7 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ // The mode maps of a UNICOMM instance. The SVDs describe them per instance and identically, so // one version each covers the portfolio, as with the wrapper. (".*:unicommi2cc", "v1"), + (".*:unicommi2ct", "v1"), (".*:unicommspi", "v1"), (".*:unicommuart", "v1"), // SLAU846 ยง23.3, SLAU847 ยง19.3, SLAU893 ยง13.3 and SLAU923 ยง11.3 describe the same eight diff --git a/mspm0-metapac-gen/src/peripheral.rs b/mspm0-metapac-gen/src/peripheral.rs index eca6fcc..bfd145f 100644 --- a/mspm0-metapac-gen/src/peripheral.rs +++ b/mspm0-metapac-gen/src/peripheral.rs @@ -26,6 +26,7 @@ const GENERATE_PERIPHERALS: &[PeripheralType] = &[ PeripheralType::Uart, PeripheralType::Unicomm, PeripheralType::UnicommI2cc, + PeripheralType::UnicommI2ct, PeripheralType::UnicommSpi, PeripheralType::UnicommUart, PeripheralType::Vref, diff --git a/transforms/UNICOMM_I2CC.yaml b/transforms/UNICOMM_I2CC.yaml index 8cbacf6..855157c 100644 --- a/transforms/UNICOMM_I2CC.yaml +++ b/transforms/UNICOMM_I2CC.yaml @@ -49,7 +49,7 @@ transforms: - !MergeBlocks from: UC0_I2CC main: UC0_I2CC - to: I2C + to: UNICOMMI2CC # Remove prefixes - !RenameRegisters diff --git a/transforms/UNICOMM_I2CT.yaml b/transforms/UNICOMM_I2CT.yaml new file mode 100644 index 0000000..b0ddb4e --- /dev/null +++ b/transforms/UNICOMM_I2CT.yaml @@ -0,0 +1,112 @@ +# Transform for UART registers based on UC0_I2CT on G350x +transforms: + - !DeleteUselessEnums + + # However DeleteUselessEnums does not delete MIS and IMASK (due to CLR being 0 and SET being 1) + # + # TODO: Add this to chiptool + - !DeleteEnumsWithVariants + variants: + 0: CLR + 1: SET + + - !DeleteEnumsWithVariants + variants: + 0: CLEARED + 1: SET + + # FREE is useless + - !DeleteEnumsWithVariants + variants: + 0: STOP + 1: RUN + + # RESETASSERT is useless + - !DeleteEnumsWithVariants + variants: + 0: NOP + 1: ASSERT + + # RESETSTKY is useless + - !DeleteEnumsWithVariants + variants: + 0: NORES + 1: RESET + + # RESETSTKYCLR is useless + - !DeleteEnumsWithVariants + variants: + 0: NOP + 1: CLR + + # INTEVAL is useless + - !DeleteEnumsWithVariants + variants: + 0: DISABLE + 1: EVAL + + # TODO: Something better than a cursed rename as merge + - !MergeBlocks + from: UC0_I2CT + main: UC0_I2CT + to: UNICOMMI2CT + + # Remove prefixes + - !RenameRegisters + block: .* + from: UC0_I2CT_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: UC0_I2CT_(.+) + to: $1 + + - !Rename + type: All + from: UC0_I2CT_(.+) + to: $1 + + ## Interrupts + # CPU_INT interrupt fields are all the same layout. + - !MergeFieldsets + from: CPU_INT_(IMASK|ISET|MIS|RIS|ICLR) + to: CPU_INT + + # Remove redundant prefixes in the CPU_INT block + - !RenameRegisters + block: .* + from: CPU_INT_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: CPU_INT_(.+) + to: $1 + + ## DMA Triggers + # DMA_TRIG0 and DMA_TRIG1 are not the same block here and are not merged. The standalone I2C is + # one peripheral for both roles, so both of its trigger blocks carry all four FIFO trigger bits; + # a UNICOMM instance is a controller or a target, so DMA_TRIG0 has only TXTRG and DMA_TRIG1 only + # RXTRG. + - !RenameRegisters + block: DMA_TRIG(\d+) + from: DMA_TRIG(\d+)_(.+) + to: $2 + + # Within one trigger the interrupt registers do share a layout. + - !MergeFieldsets + from: DMA_TRIG0_(IMASK|ISET|MIS|RIS|ICLR) + to: DMA_TRIG0 + + - !MergeFieldsets + from: DMA_TRIG1_(IMASK|ISET|MIS|RIS|ICLR) + to: DMA_TRIG1 + + ## CMODE/TMODE: same enum + - !MergeEnums + from: (C|T)MODE + to: MODE + + ## Cleanup + - !Sort From c0c780efa5086265cba1a39f1332ec8e9a68b728 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 22:29:12 +0100 Subject: [PATCH 045/134] Finish the cleanup of the UNICOMM mode maps All four carried ICLR as a fieldset of its own, identical field for field to CPU_INT, where uart_v1 and i2c_v1 have one fieldset for all five interrupt registers. Two copies of a fieldset is the enum and struct bloat the README asks to avoid. The UART map also kept LINC0 and LINC1 as separate registers. Its transform inherited the MakeRegisterArray step from the standalone UART, which names the block it applies to, and that name no longer matched, so the step quietly did nothing. Worth remembering: a transform selector which misses is silent. In the SPI map, REPEATTX named only its disabled value, BUSY was IDLE/ACTIVE on a status bit, and CTL1 and STAT described the command and data framing with two identical enums. Shape only. Every register keeps its offset and every fieldset its fields, checked by flattening both revisions. --- data/registers/unicommi2cc_v1.yaml | 65 +------------------- data/registers/unicommi2ct_v1.yaml | 73 +--------------------- data/registers/unicommspi_v1.yaml | 97 +++--------------------------- data/registers/unicommuart_v1.yaml | 90 ++------------------------- transforms/UNICOMM_I2CC.yaml | 8 +++ transforms/UNICOMM_I2CT.yaml | 8 +++ transforms/UNICOMM_SPI.yaml | 17 ++++++ transforms/UNICOMM_UART.yaml | 10 ++- 8 files changed, 59 insertions(+), 309 deletions(-) diff --git a/data/registers/unicommi2cc_v1.yaml b/data/registers/unicommi2cc_v1.yaml index ab6d92a..087cf0d 100644 --- a/data/registers/unicommi2cc_v1.yaml +++ b/data/registers/unicommi2cc_v1.yaml @@ -28,7 +28,7 @@ block/CPU_INT: description: Interrupt clear. byte_offset: 40 access: Write - fieldset: ICLR + fieldset: CPU_INT block/DMA_TRIG0: items: - name: IMASK @@ -334,69 +334,6 @@ fieldset/GFCTL: description: Analog Glitch Suppression Enable. bit_offset: 8 bit_size: 1 -fieldset/ICLR: - description: Interrupt clear. - fields: - - name: RXDONE - description: Receive Data Interrupt Signals that a byte has been received. - bit_offset: 0 - bit_size: 1 - - name: TXDONE - description: Transmit Transaction completed Interrupt. - bit_offset: 1 - bit_size: 1 - - name: RXTRG - description: Receive Trigger. - bit_offset: 2 - bit_size: 1 - - name: TXTRG - description: Transmit FIFO Trigger. - bit_offset: 3 - bit_size: 1 - - name: RXFULL - description: RXFIFO full event. - bit_offset: 4 - bit_size: 1 - - name: TXEMPTY - description: Transmit FIFO Empty interrupt mask. This interrupt is set if all data in the Transmit FIFO have been shifted out and the transmit goes into idle mode. - bit_offset: 5 - bit_size: 1 - - name: NACK - description: Address/Data NACK Interrupt. - bit_offset: 7 - bit_size: 1 - - name: START - description: START Detection Interrupt. - bit_offset: 8 - bit_size: 1 - - name: STOP - description: STOP Detection Interrupt. - bit_offset: 9 - bit_size: 1 - - name: ARBLOST - description: Arbitration Lost Interrupt. - bit_offset: 10 - bit_size: 1 - - name: PEC_RX_ERR - description: RX Pec Error Interrupt. - bit_offset: 11 - bit_size: 1 - - name: TIMEOUTA - description: Timeout A interrupt. - bit_offset: 12 - bit_size: 1 - - name: TIMEOUTB - description: Timeout B Interrupt. - bit_offset: 13 - bit_size: 1 - - name: DMA_DONE_RX - description: DMA Done on Event Channel RX. - bit_offset: 15 - bit_size: 1 - - name: DMA_DONE_TX - description: DMA Done on Event Channel TX. - bit_offset: 16 - bit_size: 1 fieldset/IFLS: description: Interrupt FIFO Level Select Register. fields: diff --git a/data/registers/unicommi2ct_v1.yaml b/data/registers/unicommi2ct_v1.yaml index e575607..9056669 100644 --- a/data/registers/unicommi2ct_v1.yaml +++ b/data/registers/unicommi2ct_v1.yaml @@ -28,7 +28,7 @@ block/CPU_INT: description: Interrupt clear. byte_offset: 40 access: Write - fieldset: ICLR + fieldset: CPU_INT block/DMA_TRIG0: items: - name: IMASK @@ -350,77 +350,6 @@ fieldset/GFCTL: description: Analog Glitch Suppression Enable. bit_offset: 8 bit_size: 1 -fieldset/ICLR: - description: Interrupt clear. - fields: - - name: RXDONE - description: Target Receive Data Interrupt Signals that a byte has been received. - bit_offset: 0 - bit_size: 1 - - name: TXDONE - description: Target Transmit Transaction completed Interrupt. - bit_offset: 1 - bit_size: 1 - - name: RXTRG - description: Receive Trigger. - bit_offset: 2 - bit_size: 1 - - name: TXTRG - description: Transmit Trigger. - bit_offset: 3 - bit_size: 1 - - name: RXFULL - description: RXFIFO full event. This interrupt is set if an RX FIFO is full. - bit_offset: 4 - bit_size: 1 - - name: TXEMPTY - description: Transmit FIFO Empty interrupt mask. This interrupt is set if all data in the Transmit FIFO have been shifted out and the transmit goes into idle mode. - bit_offset: 5 - bit_size: 1 - - name: TX_UNFL - description: Target TX FIFO underflow. - bit_offset: 6 - bit_size: 1 - - name: RX_OVFL - description: Target RX FIFO overflow. - bit_offset: 7 - bit_size: 1 - - name: GENCALL - description: General Call Interrupt. - bit_offset: 8 - bit_size: 1 - - name: START - description: Target START Detection Interrupt. - bit_offset: 9 - bit_size: 1 - - name: STOP - description: Target STOP Detection Interrupt. - bit_offset: 10 - bit_size: 1 - - name: PEC_RX_ERR - description: Target RX Pec Error Interrupt. - bit_offset: 11 - bit_size: 1 - - name: TIMEOUTA - description: Timeout A interrupt. - bit_offset: 12 - bit_size: 1 - - name: TIMEOUTB - description: Timeout B Interrupt. - bit_offset: 13 - bit_size: 1 - - name: DMA_DONE_RX - description: DMA Done on Event Channel RX. - bit_offset: 15 - bit_size: 1 - - name: DMA_DONE_TX - description: DMA Done on Event Channel TX. - bit_offset: 16 - bit_size: 1 - - name: ARBLOST - description: Arbitration Lost Interrupt. - bit_offset: 17 - bit_size: 1 fieldset/IFLS: description: Interrupt FIFO Level Select Register. fields: diff --git a/data/registers/unicommspi_v1.yaml b/data/registers/unicommspi_v1.yaml index 367ff37..4f287fe 100644 --- a/data/registers/unicommspi_v1.yaml +++ b/data/registers/unicommspi_v1.yaml @@ -28,7 +28,7 @@ block/CPU_INT: description: Interrupt clear. byte_offset: 40 access: Write - fieldset: ICLR + fieldset: CPU_INT block/DMA_TRIG_RX: items: - name: IMASK @@ -303,12 +303,11 @@ fieldset/CTL1: description: 'Command/Data Mode Value When CTL1.CDENABLE is 1, CS3 line is used as C/D signal to distinguish between Command (C/D low) and Data (C/D high) information. When a value is written into the CTL1.CDMODE bits, the C/D (CS3) line will go low for the given numbers of byte which are sent by the SPI, starting with the next value to be transmitted after which, C/D line will go high automatically 0: Manual mode with C/D signal as High 1-14: C/D is low while this number of bytes are being sent after which, this field sets to 0 and C/D goes high. Reading this field at any time returns the remaining number of command bytes. 15: Manual mode with C/D signal as Low.' bit_offset: 12 bit_size: 4 - enum: CTL1_CDMODE + enum: CDMODE - name: REPEATTX description: 'Counter to repeat last transfer 0: repeat last transfer is disabled. x: repeat the last transfer with the given number. The transfer will be started with writing a data into the TX Buffer. Sending the data will be repeated with the given value, so the data will be transferred X+1 times in total. The behavior is identical as if the data would be written into the TX Buffer that many times as defined by the value here. It can be used to clean a transfer or to pull a certain amount of data by a peripheral.' bit_offset: 16 bit_size: 8 - enum: REPEATTX - name: RXTIMEOUT description: Receive Timeout (only for Peripheral mode) Defines the number of Clock Cycles before after which the Receive Timeout flag RTOUT is set. The time is calculated using the control register for the clock selection and divider in the Controller mode configuration. A value of 0 disables this function. bit_offset: 24 @@ -331,57 +330,6 @@ fieldset/DMA_TRIG_TX: description: Transmit FIFO event mask. bit_offset: 5 bit_size: 1 -fieldset/ICLR: - description: Interrupt clear. - fields: - - name: PER - description: Clear Parity error event. - bit_offset: 0 - bit_size: 1 - - name: RXFIFO_OVF - description: Clear RXFIFO overflow event. - bit_offset: 1 - bit_size: 1 - - name: RX - description: Clear Receive event. - bit_offset: 2 - bit_size: 1 - - name: RXFULL - description: Clear RX FIFO underflow event. - bit_offset: 3 - bit_size: 1 - - name: TXFIFO_UNF - description: Clear TXFIFO underflow event. - bit_offset: 4 - bit_size: 1 - - name: TX - description: Clear Transmit event. - bit_offset: 5 - bit_size: 1 - - name: TXEMPTY - description: Clear Transmit FIFO Empty event. - bit_offset: 6 - bit_size: 1 - - name: IDLE - description: Clear SPI IDLE mode event. - bit_offset: 8 - bit_size: 1 - - name: RTOUT - description: Clear SPI Receive Time-Out Event. - bit_offset: 9 - bit_size: 1 - - name: DMA_DONE_RX - description: Clear DMA Done on RX Event Channel Interrupt. - bit_offset: 15 - bit_size: 1 - - name: DMA_DONE_TX - description: Clear DMA Done on TX Event Channel Interrupt. - bit_offset: 16 - bit_size: 1 - - name: LTOUT - description: Clear SPI Line Time-Out Event. - bit_offset: 20 - bit_size: 1 fieldset/IFLS: description: Interrupt FIFO Level Select Register. fields: @@ -468,12 +416,11 @@ fieldset/STAT: description: Busy. bit_offset: 8 bit_size: 1 - enum: BUSY - name: CDMODE description: Current CDMODE status. This reflects the current counter counter value. bit_offset: 12 bit_size: 4 - enum: STAT_CDMODE + enum: CDMODE fieldset/TXDATA: description: TXDATA Register. fields: @@ -481,15 +428,15 @@ fieldset/TXDATA: description: Transmit Data When read, last written value will be returned. If the last write to this field was a 32-bit write (with PACKEN=1), 32-bits will be returned and if the last write was a 16-bit write (PACKEN=0), those 16-bits will be returned. When written, one or two FIFO entries will be written depending on PACKEN value. Data values are removed from the transmit FIFO one value at a time by the transmit logic. It is loaded into the transmit serial shifter, then serially shifted out onto the TXD output pin at the programmed bit rate. When a data size of less than 16 bits is selected, the user must right-justify data written to the transmit FIFO. The transmit logic ignores the unused bits. bit_offset: 0 bit_size: 16 -enum/BUSY: - bit_size: 1 +enum/CDMODE: + bit_size: 4 variants: - - name: IDLE - description: SPI is in idle mode. + - name: DATA + description: 'Manual mode: Data.' value: 0 - - name: ACTIVE - description: SPI is currently transmitting and/or receiving data. - value: 1 + - name: COMMAND + description: 'Manual mode: Command.' + value: 15 enum/CSSEL: bit_size: 2 variants: @@ -505,15 +452,6 @@ enum/CSSEL: - name: CSSEL_3 description: 'CS line select: 3.' value: 3 -enum/CTL1_CDMODE: - bit_size: 4 - variants: - - name: DATA - description: 'Manual mode: Data.' - value: 0 - - name: COMMAND - description: 'Manual mode: Command.' - value: 15 enum/DSS: bit_size: 5 variants: @@ -595,12 +533,6 @@ enum/RATIO: - name: DIV_BY_8 description: Divide clock source by 8. value: 7 -enum/REPEATTX: - bit_size: 8 - variants: - - name: DISABLE - description: REPEATTX disable. - value: 0 enum/RXIFLSEL: bit_size: 3 variants: @@ -691,15 +623,6 @@ enum/STAT: - name: LTOUT description: SPI line time-out interrupt. value: 21 -enum/STAT_CDMODE: - bit_size: 4 - variants: - - name: DATA - description: 'Manual mode: Data.' - value: 0 - - name: COMMAND - description: 'Manual mode: Command.' - value: 15 enum/TXIFLSEL: bit_size: 3 variants: diff --git a/data/registers/unicommuart_v1.yaml b/data/registers/unicommuart_v1.yaml index fe0e1af..0b06b36 100644 --- a/data/registers/unicommuart_v1.yaml +++ b/data/registers/unicommuart_v1.yaml @@ -28,7 +28,7 @@ block/CPU_INT: description: Interrupt clear. byte_offset: 40 access: Write - fieldset: ICLR + fieldset: CPU_INT block/DMA_TRIG_RX: items: - name: IMASK @@ -156,14 +156,13 @@ block/UNICOMMUART: description: UART LIN Mode Control Register. byte_offset: 4404 fieldset: LINCTL - - name: LINC0 + - name: LINC description: UART LIN Mode Capture 0 Register. + array: + len: 2 + stride: 4 byte_offset: 4408 fieldset: LINC - - name: LINC1 - description: UART LIN Mode Capture 1 Register. - byte_offset: 4412 - fieldset: LINC - name: IRCTL description: eUSCI_Ax IrDA Control Word Register. byte_offset: 4416 @@ -391,85 +390,6 @@ fieldset/IBRD: description: Integer Baud-Rate Divisor. bit_offset: 0 bit_size: 16 -fieldset/ICLR: - description: Interrupt clear. - fields: - - name: RTOUT - description: Clear UARTOUT Receive Time-Out Interrupt. - bit_offset: 0 - bit_size: 1 - - name: FRMERR - description: Clear UART Framing Error Interrupt. - bit_offset: 1 - bit_size: 1 - - name: PARERR - description: Clear UART Parity Error Interrupt. - bit_offset: 2 - bit_size: 1 - - name: BRKERR - description: Clear UART Break Error Interrupt. - bit_offset: 3 - bit_size: 1 - - name: OVRERR - description: Clear UART Receive Overrun Error Interrupt. - bit_offset: 4 - bit_size: 1 - - name: RXNE - description: Clear Negative Edge on UARTxRXD Interrupt. - bit_offset: 5 - bit_size: 1 - - name: RXPE - description: Clear Positive Edge on UARTxRXD Interrupt. - bit_offset: 6 - bit_size: 1 - - name: LINC0 - description: Clear LIN Capture 0 / Match Interrupt. - bit_offset: 7 - bit_size: 1 - - name: LINC1 - description: Clear LIN Capture 1 Interrupt. - bit_offset: 8 - bit_size: 1 - - name: LINOVF - description: Clear LIN Hardware Counter Overflow Interrupt. - bit_offset: 9 - bit_size: 1 - - name: RXINT - description: Clear UART Receive Interrupt. - bit_offset: 10 - bit_size: 1 - - name: TXINT - description: Clear UART Transmit Interrupt. - bit_offset: 11 - bit_size: 1 - - name: EOT - description: Clear UART End of Transmission Interrupt Indicates that the last bit of all transmitted data and flags has left the serializer and without any further Data in the TX Fifo or Buffer. - bit_offset: 12 - bit_size: 1 - - name: ADDR_MATCH - description: Clear Address Match Interrupt. - bit_offset: 13 - bit_size: 1 - - name: CTS - description: Clear UART Clear to Send Modem Interrupt. - bit_offset: 14 - bit_size: 1 - - name: DMA_DONE_RX - description: Clear DMA Done on RX Event Channel Interrupt. - bit_offset: 15 - bit_size: 1 - - name: DMA_DONE_TX - description: Clear DMA Done on TX Event Channel Interrupt. - bit_offset: 16 - bit_size: 1 - - name: NERR - description: Noise Error on triple voting. Asserted when the 3 samples of majority voting are not equal. - bit_offset: 17 - bit_size: 1 - - name: LTOUT - description: Clear UARTOUT Line Time-Out Interrupt. - bit_offset: 20 - bit_size: 1 fieldset/IFLS: description: UART Interrupt FIFO Level Select Register. fields: diff --git a/transforms/UNICOMM_I2CC.yaml b/transforms/UNICOMM_I2CC.yaml index 855157c..b3cad09 100644 --- a/transforms/UNICOMM_I2CC.yaml +++ b/transforms/UNICOMM_I2CC.yaml @@ -108,5 +108,13 @@ transforms: from: (C|T)MODE to: MODE + + # ICLR has the same layout as the other CPU_INT registers. The standalone uart_v1 and i2c_v1 + # carry one fieldset for all five, and two identical fieldsets is the bloat the README warns of. + - !MergeFieldsets + from: (CPU_INT|ICLR) + to: CPU_INT + check: NoCheck + ## Cleanup - !Sort diff --git a/transforms/UNICOMM_I2CT.yaml b/transforms/UNICOMM_I2CT.yaml index b0ddb4e..8425924 100644 --- a/transforms/UNICOMM_I2CT.yaml +++ b/transforms/UNICOMM_I2CT.yaml @@ -108,5 +108,13 @@ transforms: from: (C|T)MODE to: MODE + + # ICLR has the same layout as the other CPU_INT registers. The standalone uart_v1 and i2c_v1 + # carry one fieldset for all five, and two identical fieldsets is the bloat the README warns of. + - !MergeFieldsets + from: (CPU_INT|ICLR) + to: CPU_INT + check: NoCheck + ## Cleanup - !Sort diff --git a/transforms/UNICOMM_SPI.yaml b/transforms/UNICOMM_SPI.yaml index 98d0313..dfbac81 100644 --- a/transforms/UNICOMM_SPI.yaml +++ b/transforms/UNICOMM_SPI.yaml @@ -130,5 +130,22 @@ transforms: from: DMA_TRIG_TX_(.+) to: $1 + + # ICLR has the same layout as the other CPU_INT registers. The standalone uart_v1 and i2c_v1 + # carry one fieldset for all five, and two identical fieldsets is the bloat the README warns of. + - !MergeFieldsets + from: (CPU_INT|ICLR) + to: CPU_INT + check: NoCheck + + ## REPEATTX names only its disabled value, and BUSY is IDLE/ACTIVE on a status bit + - !DeleteEnums + from: (REPEATTX|BUSY) + + ## CTL1 and STAT describe the command/data framing with the same two values + - !MergeEnums + from: (CTL1|STAT)_CDMODE + to: CDMODE + ## Cleanup - !Sort diff --git a/transforms/UNICOMM_UART.yaml b/transforms/UNICOMM_UART.yaml index 3fb9f8a..bf3ed3a 100644 --- a/transforms/UNICOMM_UART.yaml +++ b/transforms/UNICOMM_UART.yaml @@ -161,7 +161,7 @@ transforms: to: LINC - !MakeRegisterArray - blocks: UART + blocks: UNICOMMUART from: LINC(\d+) to: LINC @@ -207,5 +207,13 @@ transforms: from: CLKDIV(.*) to: CLKDIV + + # ICLR has the same layout as the other CPU_INT registers. The standalone uart_v1 and i2c_v1 + # carry one fieldset for all five, and two identical fieldsets is the bloat the README warns of. + - !MergeFieldsets + from: (CPU_INT|ICLR) + to: CPU_INT + check: NoCheck + ## Cleanup - !Sort From 66a4527e163eb597ff168cf8f3d7da282d8b7a6c Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 22:31:44 +0100 Subject: [PATCH 046/134] Build the generated chips in parallel `check` built all 216 chips one after another, taking eight minutes of which one core was busy. Cargo holds an exclusive lock on its target directory for the length of a build, so parallel invocations against one directory only queue; the chips are dealt out to JOBS shards with a target directory each instead. On a 32 core machine that is 1m35s. Cargo keeps its own -j. Giving each shard -j1 so that the shards alone decide the parallelism measured slower, because a shard can use the cores others leave idle while rustc is in its single threaded front end. Output is captured per chip and only failures are printed, since 32 interleaved cargo outputs are unreadable. Every chip is attempted rather than the run stopping at the first failure, so one broken family no longer hides the rest. --- d | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/d b/d index bc9c27e..4f75539 100755 --- a/d +++ b/d @@ -86,13 +86,50 @@ case "$CMD" in ./d check ;; check) - # Iterate over each chip that was generated in metapac and build it. + # Build every chip the metapac generated. # - # TODO: Parallelize this to speed up checks? - for feature in build/mspm0-metapac/src/chips/*/; do - feature=$(basename "$feature") - cargo build --release --manifest-path build/mspm0-metapac/Cargo.toml --features pac,metadata,$feature + # Cargo holds an exclusive lock on its target directory for the length of a build, so + # running these in parallel against one directory only makes them queue. The chips are + # dealt out to JOBS shards instead, each with a target directory of its own: the + # dependencies are built once per shard and the chips within a shard reuse them. The + # directories sit under build/mspm0-metapac/ so that `build-metapac` clears them. + # + # JOBS=1 restores the sequential behaviour. Do not regenerate while this runs. + jobs=${JOBS:-$(nproc)} + chips=(build/mspm0-metapac/src/chips/*/) + logs=$(mktemp -d) + + for shard in $(seq 0 $((jobs - 1))); do + ( + export CARGO_TARGET_DIR="build/mspm0-metapac/target/shard-$shard" + + for i in $(seq "$shard" "$jobs" $((${#chips[@]} - 1))); do + feature=$(basename "${chips[i]}") + + if cargo build --release --manifest-path build/mspm0-metapac/Cargo.toml \ + --features pac,metadata,"$feature" > "$logs/$feature" 2>&1 + then + rm "$logs/$feature" + echo "ok $feature" + else + echo "FAILED $feature" + fi + done + ) & done + wait + + # A chip's log is left behind only if it failed to build. + failures=$(ls "$logs") + + for feature in $failures; do + echo + echo "=== $feature" + cat "$logs/$feature" + done + + rm -rf "$logs" + [ -z "$failures" ] ;; *) echo "unknown command" From ff6c69b9ff5be57d324aefe4795498ea147ec693 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 22:52:43 +0100 Subject: [PATCH 047/134] Share one jobserver between the check shards Each shard's cargo assumed the whole machine was its own, so while the 32 of them built their copies of the dependencies they peaked at 358 concurrent rustc processes holding 29GB, which is more memory than this machine has. Cargo is a jobserver client and takes its pool from MAKEFLAGS, so the shards now share one made with a fifo: 61 processes and 10.8GB, and the wall time is unchanged at 1m33s. The floor is the shard count rather than the pool size. A jobserver client gets one implicit token for free and only asks the pool for further concurrency, so this cannot bound the total below the number of cargos; what it bounds is what each does beyond its own unit. The fifo has to live outside the log directory, since every entry left in there is taken for a failed chip's log. --- d | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/d b/d index 4f75539..a1e7c26 100755 --- a/d +++ b/d @@ -99,6 +99,18 @@ case "$CMD" in chips=(build/mspm0-metapac/src/chips/*/) logs=$(mktemp -d) + # Hand the shards one jobserver between them. Cargo is a jobserver client and takes the + # pool from MAKEFLAGS, so this is what stops each shard assuming the whole machine is its + # own while building its copy of the dependencies. Note the floor: a client gets one + # implicit token for free, so the total is the shard count plus the pool, not the pool. + # + # The fifo lives outside $logs, whose every remaining entry is a failed chip's log. + tokens=$(mktemp -u) + mkfifo "$tokens" + exec 9<>"$tokens" + printf "%*s" "$jobs" "" | tr " " "+" >&9 + export MAKEFLAGS="-j$jobs --jobserver-auth=fifo:$tokens" + for shard in $(seq 0 $((jobs - 1))); do ( export CARGO_TARGET_DIR="build/mspm0-metapac/target/shard-$shard" @@ -129,6 +141,8 @@ case "$CMD" in done rm -rf "$logs" + exec 9>&- + rm -f "$tokens" [ -z "$failures" ] ;; *) From 2b8440ff74dfe050cfe715972fc2c749616ad019 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 23:27:05 +0100 Subject: [PATCH 048/134] cpuss: widen the interrupt group's MIS, ISET and ICLR to 8 bits The SVD gave these three a single bit where IMASK and RIS got eight, so only interrupt 0 of the group was reachable through them. hw_cpuss.h masks all five with 0xFF. --- data/registers/cpuss_v1.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/registers/cpuss_v1.yaml b/data/registers/cpuss_v1.yaml index 6144508..c770c88 100644 --- a/data/registers/cpuss_v1.yaml +++ b/data/registers/cpuss_v1.yaml @@ -128,7 +128,7 @@ fieldset/MIS: - name: INT description: Masked interrupt status for INT. bit_offset: 0 - bit_size: 1 + bit_size: 8 enum: MIS fieldset/ISET: description: Interrupt set. @@ -136,7 +136,7 @@ fieldset/ISET: - name: INT description: Sets INT in RIS register. bit_offset: 0 - bit_size: 1 + bit_size: 8 enum: ISET fieldset/ICLR: description: Interrupt clear. @@ -144,7 +144,7 @@ fieldset/ICLR: - name: INT description: Clears INT in RIS register. bit_offset: 0 - bit_size: 1 + bit_size: 8 enum: ICLR enum/IIDX: @@ -196,7 +196,7 @@ enum/RIS: description: INT occurred. value: 1 enum/MIS: - bit_size: 1 + bit_size: 8 variants: - name: CLR description: INT does not request an interrupt service routine. @@ -205,7 +205,7 @@ enum/MIS: description: INT requests an interrupt service routine. value: 1 enum/ISET: - bit_size: 1 + bit_size: 8 variants: - name: NO_EFFECT description: Writing a 0 has no effect. @@ -214,7 +214,7 @@ enum/ISET: description: RIS bit corresponding to INT is set. value: 1 enum/ICLR: - bit_size: 1 + bit_size: 8 variants: - name: NO_EFFECT description: Writing a 0 has no effect. From 385d7c462f836ed2ccde47d9ed0c69320894c8a2 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 23:29:10 +0100 Subject: [PATCH 049/134] cpuss: drop the enums which say nothing DISABLE/ENABLE on the three CTL enable bits, and CLR/SET on the interrupt registers, whose fields hw_cpuss.h gives no enumerated values at all. That leaves IMASK, RIS, MIS, ISET and ICLR sharing one fieldset, as in uart. --- data/registers/cpuss_v1.yaml | 230 +++++++++-------------------------- 1 file changed, 58 insertions(+), 172 deletions(-) diff --git a/data/registers/cpuss_v1.yaml b/data/registers/cpuss_v1.yaml index c770c88..6c41270 100644 --- a/data/registers/cpuss_v1.yaml +++ b/data/registers/cpuss_v1.yaml @@ -12,57 +12,16 @@ block/CPUSS: access: Read fieldset: DESC - name: INT_GROUP + description: CPUSS Interrupt group (x = 0..2). Depending on the model, the later elements of this array may be undefined. For C110x only index 0 is valid. Meanwhile for L222x indices 0 and 1 are valid. array: len: 2 stride: 48 - description: CPUSS Interrupt group (x = 0..2). Depending on the model, the later elements of this array may be undefined. For C110x only index 0 is valid. Meanwhile for L222x indices 0 and 1 are valid. byte_offset: 4352 block: INT_GROUP - name: CTL description: Prefetch/Cache control. byte_offset: 4864 fieldset: CTL - -fieldset/EVT_MODE: - description: Event Mode. - fields: - - name: INT_CFG - description: Event line mode select. - bit_offset: 0 - bit_size: 2 - enum: INT_CFG -fieldset/DESC: - description: Module Description. - fields: - - name: MINREV - description: Minor rev of the IP. - bit_offset: 0 - bit_size: 4 - - name: MAJREV - description: Major rev of the IP. - bit_offset: 4 - bit_size: 4 - - name: FEATUREVER - description: Feature Set for the module *instance*. - bit_offset: 12 - bit_size: 4 - - name: MODULEID - description: Module identification contains a unique peripheral identification number. The assignments are maintained in a central database for all of the platform modules to ensure uniqueness. - bit_offset: 16 - bit_size: 16 -enum/INT_CFG: - bit_size: 2 - variants: - - name: DISABLE - description: The interrupt or event line is disabled. - value: 0 - - name: SOFTWARE - description: Event handled by software. Software must clear the associated RIS flag. - value: 1 - - name: HARDWARE - description: Event handled by hardware. The hardware (another module) clears automatically the associated RIS flag. - value: 2 - # The interrupt group is shared across all CPUSS variants. block/INT_GROUP: description: Interrupt group. @@ -76,77 +35,84 @@ block/INT_GROUP: description: Interrupt mask. byte_offset: 8 access: Read - fieldset: IMASK + fieldset: INT - name: RIS description: Raw interrupt status. byte_offset: 16 access: Read - fieldset: RIS + fieldset: INT - name: MIS description: Masked interrupt status. byte_offset: 24 access: Read - fieldset: MIS + fieldset: INT - name: ISET description: Interrupt set. byte_offset: 32 access: Write - fieldset: ISET + fieldset: INT - name: ICLR description: Interrupt clear. byte_offset: 40 access: Write - fieldset: ICLR - -fieldset/IIDX: - description: Interrupt index. - fields: - - name: STAT - description: Interrupt index status. - bit_offset: 0 - bit_size: 8 - enum: IIDX -fieldset/IMASK: - description: Interrupt mask. + fieldset: INT +fieldset/CTL: + description: Prefetch/Cache control. fields: - - name: INT - description: Masks the corresponding interrupt. + - name: PREFETCH + description: Used to enable/disable instruction prefetch to Flash. bit_offset: 0 - bit_size: 8 - enum: IMASK -fieldset/RIS: - description: Raw interrupt status. + bit_size: 1 + - name: ICACHE + description: Used to enable/disable Instruction caching on flash access. + bit_offset: 1 + bit_size: 1 + - name: LITEN + description: Literal caching and prefetch enable. This bit is a subset of ICACHE/PREFETCH bit i.e. literal caching or literal prefetching will only happen if ICACHE or PREFETCH bits have been set respectively When enabled, the cache and prefetcher structures inside CPUSS will cache and prefetch literals When disabled, the cache and prefetcher structures inside CPUSS will not cache and prefetch literals. + bit_offset: 2 + bit_size: 1 +fieldset/DESC: + description: Module Description. fields: - - name: INT - description: Raw interrupt status for INT. + - name: MINREV + description: Minor rev of the IP. bit_offset: 0 - bit_size: 8 - enum: RIS -fieldset/MIS: - description: Masked interrupt status. + bit_size: 4 + - name: MAJREV + description: Major rev of the IP. + bit_offset: 4 + bit_size: 4 + - name: FEATUREVER + description: Feature Set for the module *instance*. + bit_offset: 12 + bit_size: 4 + - name: MODULEID + description: Module identification contains a unique peripheral identification number. The assignments are maintained in a central database for all of the platform modules to ensure uniqueness. + bit_offset: 16 + bit_size: 16 +fieldset/EVT_MODE: + description: Event Mode. fields: - - name: INT - description: Masked interrupt status for INT. + - name: INT_CFG + description: Event line mode select. bit_offset: 0 - bit_size: 8 - enum: MIS -fieldset/ISET: - description: Interrupt set. + bit_size: 2 + enum: INT_CFG +fieldset/IIDX: + description: Interrupt index. fields: - - name: INT - description: Sets INT in RIS register. + - name: STAT + description: Interrupt index status. bit_offset: 0 bit_size: 8 - enum: ISET -fieldset/ICLR: - description: Interrupt clear. + enum: IIDX +fieldset/INT: + description: One bit per interrupt in the group. Shared by IMASK, RIS, MIS, ISET and ICLR. fields: - name: INT - description: Clears INT in RIS register. + description: The interrupts of the group, as a mask. bit_offset: 0 bit_size: 8 - enum: ICLR - enum/IIDX: bit_size: 8 variants: @@ -177,95 +143,15 @@ enum/IIDX: - name: INT7 description: Interrupt 7. value: 8 -enum/IMASK: - bit_size: 8 - variants: - - name: CLR - description: Interrupt is masked out. - value: 0 - - name: SET - description: Interrupt will request an interrupt service routine and corresponding bit in MIS will be set. - value: 1 -enum/RIS: - bit_size: 8 - variants: - - name: CLR - description: INT did not occur. - value: 0 - - name: SET - description: INT occurred. - value: 1 -enum/MIS: - bit_size: 8 - variants: - - name: CLR - description: INT does not request an interrupt service routine. - value: 0 - - name: SET - description: INT requests an interrupt service routine. - value: 1 -enum/ISET: - bit_size: 8 - variants: - - name: NO_EFFECT - description: Writing a 0 has no effect. - value: 0 - - name: SET - description: RIS bit corresponding to INT is set. - value: 1 -enum/ICLR: - bit_size: 8 - variants: - - name: NO_EFFECT - description: Writing a 0 has no effect. - value: 0 - - name: CLR - description: RIS bit corresponding to INT is cleared. - value: 1 - -fieldset/CTL: - description: Prefetch/Cache control. - fields: - - name: PREFETCH - description: Used to enable/disable instruction prefetch to Flash. - bit_offset: 0 - bit_size: 1 - enum: PREFETCH - - name: ICACHE - description: Used to enable/disable Instruction caching on flash access. - bit_offset: 1 - bit_size: 1 - enum: ICACHE - - name: LITEN - description: Literal caching and prefetch enable. This bit is a subset of ICACHE/PREFETCH bit i.e. literal caching or literal prefetching will only happen if ICACHE or PREFETCH bits have been set respectively When enabled, the cache and prefetcher structures inside CPUSS will cache and prefetch literals When disabled, the cache and prefetcher structures inside CPUSS will not cache and prefetch literals. - bit_offset: 2 - bit_size: 1 - enum: LITEN - -enum/ICACHE: - bit_size: 1 - variants: - - name: DISABLE - description: Disable instruction caching. - value: 0 - - name: ENABLE - description: Enable instruction caching. - value: 1 -enum/LITEN: - bit_size: 1 - variants: - - name: DISABLE - description: Literal caching disabled. - value: 0 - - name: ENABLE - description: Literal caching enabled. - value: 1 -enum/PREFETCH: - bit_size: 1 +enum/INT_CFG: + bit_size: 2 variants: - name: DISABLE - description: Disable instruction prefetch. + description: The interrupt or event line is disabled. value: 0 - - name: ENABLE - description: Enable instruction prefetch. + - name: SOFTWARE + description: Event handled by software. Software must clear the associated RIS flag. value: 1 + - name: HARDWARE + description: Event handled by hardware. The hardware (another module) clears automatically the associated RIS flag. + value: 2 From 05c84dc3258dd4499e13bf5687c234e41e9405ae Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 23:35:20 +0100 Subject: [PATCH 050/134] tim: index the capture/compare events, share one interrupt fieldset CCD0-5 and CCU0-5 are not contiguous - channels 4 and 5 are compare only and sit above the CCU bits - so they need chiptool's offset list rather than a stride. The CPU interrupt and generic event blocks enumerate the same events, and their IIDX enums were identical too. Also merges LCOND with ZCOND, CCUPD with CCACTUPD, DRB with FRB, and the two spellings of CPV. --- data/registers/tim_v1.yaml | 359 ++++++------------------------------- 1 file changed, 53 insertions(+), 306 deletions(-) diff --git a/data/registers/tim_v1.yaml b/data/registers/tim_v1.yaml index 419ebd7..7d936b9 100644 --- a/data/registers/tim_v1.yaml +++ b/data/registers/tim_v1.yaml @@ -119,62 +119,62 @@ block/CPU_INT: description: Interrupt index. byte_offset: 0 access: Read - fieldset: CPU_INT_IIDX + fieldset: IIDX - name: IMASK description: Interrupt mask. byte_offset: 8 - fieldset: CPU_INT + fieldset: INT - name: RIS description: Raw interrupt status. byte_offset: 16 access: Read - fieldset: CPU_INT + fieldset: INT - name: MIS description: Masked interrupt status. byte_offset: 24 access: Read - fieldset: CPU_INT + fieldset: INT - name: ISET description: Interrupt set. byte_offset: 32 access: Write - fieldset: CPU_INT + fieldset: INT - name: ICLR description: Interrupt clear. byte_offset: 40 access: Write - fieldset: CPU_INT + fieldset: INT block/GEN_EVENT: items: - name: IIDX description: Interrupt index. byte_offset: 0 access: Read - fieldset: GEN_EVENT_IIDX + fieldset: IIDX - name: IMASK description: Interrupt mask. byte_offset: 8 - fieldset: GEN_EVENT_INT + fieldset: INT - name: RIS description: Raw interrupt status. byte_offset: 16 access: Read - fieldset: GEN_EVENT_INT + fieldset: INT - name: MIS description: Masked interrupt status. byte_offset: 24 access: Read - fieldset: GEN_EVENT_INT + fieldset: INT - name: ISET description: Interrupt set. byte_offset: 32 access: Write - fieldset: GEN_EVENT_INT + fieldset: INT - name: ICLR description: Interrupt clear. byte_offset: 40 access: Write - fieldset: GEN_EVENT_INT + fieldset: INT block/GPRCM: items: - name: PWREN @@ -332,12 +332,12 @@ fieldset/CCCTL: description: 'Load Condition. #br# Specifies the condition that generates a load pulse.' bit_offset: 8 bit_size: 3 - enum: LCOND + enum: LZCOND - name: ZCOND description: 'Zero Condition. #br# This field specifies the condition that generates a zero pulse.' bit_offset: 12 bit_size: 3 - enum: ZCOND + enum: LZCOND - name: COC description: 'Capture or Compare. #br# Specifies whether the corresponding CC register is used as a capture register or a compare register (never both).' bit_offset: 17 @@ -347,7 +347,7 @@ fieldset/CCCTL: description: Capture and Compare Update Method This field controls how updates to the shadow capture and compare register are performed (when operating in compare mode, COC=0). bit_offset: 18 bit_size: 3 - enum: CCUPD + enum: UPD - name: CC2SELU description: Selects the source second CCU event. bit_offset: 22 @@ -361,7 +361,7 @@ fieldset/CCCTL: description: CCACT shadow register Update Method This field controls how updates to the CCACT shadow register are performed. bit_offset: 26 bit_size: 3 - enum: CCACTUPD + enum: UPD - name: CC2SELD description: Selects the source second CCD event. bit_offset: 29 @@ -420,93 +420,57 @@ fieldset/CPSV: description: Current Prescale Count Value. bit_offset: 0 bit_size: 8 -fieldset/CPU_INT: - description: Interrupt clear. +fieldset/INT: + description: The timer's events. Shared by IMASK, RIS, MIS, ISET and ICLR of both the CPU interrupt and the generic event blocks. fields: - name: Z - description: Zero event CLEAR. + description: Zero event. bit_offset: 0 bit_size: 1 - name: L - description: Load event CLEAR. + description: Load event. bit_offset: 1 bit_size: 1 - - name: CCD0 - description: Capture or compare down event CLEAR. + - name: CCD + description: Capture or compare down event. Channels 4 and 5 are compare only, and sit above the CCU bits rather than next to CCD0-3. bit_offset: 4 bit_size: 1 - - name: CCD1 - description: Capture or compare down event CLEAR. - bit_offset: 5 - bit_size: 1 - - name: CCD2 - description: Capture or compare down event CLEAR. - bit_offset: 6 - bit_size: 1 - - name: CCD3 - description: Capture or compare down event CLEAR. - bit_offset: 7 - bit_size: 1 - - name: CCU0 - description: Capture or compare up event CLEAR. + array: + offsets: [0, 1, 2, 3, 8, 9] + - name: CCU + description: Capture or compare up event. Channels 4 and 5 are compare only, and sit above the CCD4 and CCD5 bits. bit_offset: 8 bit_size: 1 - - name: CCU1 - description: Capture or compare up event CLEAR. - bit_offset: 9 - bit_size: 1 - - name: CCU2 - description: Capture or compare up event CLEAR. - bit_offset: 10 - bit_size: 1 - - name: CCU3 - description: Capture or compare up event CLEAR. - bit_offset: 11 - bit_size: 1 - - name: CCD4 - description: Compare down event 4 CLEAR. - bit_offset: 12 - bit_size: 1 - - name: CCD5 - description: Compare down event 5 CLEAR. - bit_offset: 13 - bit_size: 1 - - name: CCU4 - description: Compare up event 4 CLEAR. - bit_offset: 14 - bit_size: 1 - - name: CCU5 - description: Compare up event 5 CLEAR. - bit_offset: 15 - bit_size: 1 + array: + offsets: [0, 1, 2, 3, 6, 7] - name: F - description: Fault event CLEAR. + description: Fault event. bit_offset: 24 bit_size: 1 - name: TOV - description: Trigger Overflow event CLEAR. + description: Trigger Overflow event. bit_offset: 25 bit_size: 1 - name: REPC - description: Repeat Counter Zero event CLEAR. + description: Repeat Counter Zero event. bit_offset: 26 bit_size: 1 - name: DC - description: Direction change event + description: Direction change event. bit_offset: 27 bit_size: 1 - name: QEIERR - description: QEIERR event + description: QEIERR event. bit_offset: 28 bit_size: 1 -fieldset/CPU_INT_IIDX: +fieldset/IIDX: description: Interrupt index. fields: - name: STAT description: Interrupt index status. bit_offset: 0 bit_size: 8 - enum: CPU_INT_IIDX_STAT + enum: IIDX_STAT fieldset/CTRCTL: description: Counter Control Register. fields: @@ -543,7 +507,7 @@ fieldset/CTRCTL: description: Debug Resume Behavior This bit specifies what the device does following the release/exit of debug mode. bit_offset: 17 bit_size: 1 - enum: DRB + enum: RB - name: FB description: Fault Behavior This bit specifies whether the counter continues running or suspends during a fault mode. There is a separate control under REPEAT to indicate whether counting is to suspend at next Counter==0. bit_offset: 18 @@ -553,7 +517,7 @@ fieldset/CTRCTL: description: Fault Resume Behavior This bit specifies what the device does following the release/exit of fault condition. bit_offset: 19 bit_size: 1 - enum: FRB + enum: RB - name: SLZERCNEZ description: Suppress Load and Zero Events if Repeat Counter is Not Equal to Zero. This bit suppresses the generation of the Z (zero) and L (load) events from the counter when the repeat counter (RC) value is not 0. bit_offset: 23 @@ -664,30 +628,20 @@ fieldset/FCTL: description: Trigger Fault Input Mask Specifies whether the selected trigger participates as a fault input. If enabled and the trigger asserts, the trigger is treated as a fault. bit_offset: 7 bit_size: 1 - - name: FSENAC0 - description: Specifies whether the COMP0 output high/low is treated as fault condition. + - name: FSENAC + description: Specifies whether the COMPn output high/low is treated as fault condition. bit_offset: 8 bit_size: 1 - - name: FSENAC1 - description: Specifies whether the COMP1 output high/low is treated as fault condition. - bit_offset: 9 - bit_size: 1 - - name: FSENAC2 - description: Specifies whether the COMP2 output high/low is treated as fault condition. - bit_offset: 10 - bit_size: 1 - - name: FSENEXT0 - description: Specifies whether the external fault pin0 high/low is treated as fault condition. + array: + len: 3 + stride: 1 + - name: FSENEXT + description: Specifies whether the external fault pin n high/low is treated as fault condition. bit_offset: 11 bit_size: 1 - - name: FSENEXT1 - description: Specifies whether the external fault pin1 high/low is treated as fault condition. - bit_offset: 12 - bit_size: 1 - - name: FSENEXT2 - description: Specifies whether the external fault pin2 high/low is treated as fault condition. - bit_offset: 13 - bit_size: 1 + array: + len: 3 + stride: 1 fieldset/FIFCTL: description: Fault input Filter control register. fields: @@ -700,7 +654,7 @@ fieldset/FIFCTL: description: Consecutive Period/Voting Select This bit controls whether the input filter uses a stricter consecutive period count or majority voting. bit_offset: 3 bit_size: 1 - enum: FIFCTL_CPV + enum: CPV - name: FILTEN description: Filter Enable This bit controls whether the input is filtered by the input filter or bypasses to go directly to the optional pre-scale filter and then to the edge detect. bit_offset: 4 @@ -750,93 +704,6 @@ fieldset/GCTL: description: Enables shadow to active load of bufferred registers and register fields. bit_offset: 0 bit_size: 1 -fieldset/GEN_EVENT_IIDX: - description: Interrupt index. - fields: - - name: STAT - description: Interrupt index status. - bit_offset: 0 - bit_size: 8 - enum: GEN_EVENT_IIDX_STAT -fieldset/GEN_EVENT_INT: - description: Interrupt clear. - fields: - - name: Z - description: Zero event CLEAR. - bit_offset: 0 - bit_size: 1 - - name: L - description: Load event CLEAR. - bit_offset: 1 - bit_size: 1 - - name: CCD0 - description: Capture or compare down event CLEAR. - bit_offset: 4 - bit_size: 1 - - name: CCD1 - description: Capture or compare down event CLEAR. - bit_offset: 5 - bit_size: 1 - - name: CCD2 - description: Capture or compare down event CLEAR. - bit_offset: 6 - bit_size: 1 - - name: CCD3 - description: Capture or compare down event CLEAR. - bit_offset: 7 - bit_size: 1 - - name: CCU0 - description: Capture or compare up event CLEAR. - bit_offset: 8 - bit_size: 1 - - name: CCU1 - description: Capture or compare up event CLEAR. - bit_offset: 9 - bit_size: 1 - - name: CCU2 - description: Capture or compare up event CLEAR. - bit_offset: 10 - bit_size: 1 - - name: CCU3 - description: Capture or compare up event CLEAR. - bit_offset: 11 - bit_size: 1 - - name: CCD4 - description: Compare down event 4 CLEAR. - bit_offset: 12 - bit_size: 1 - - name: CCD5 - description: Compare down event 5 CLEAR. - bit_offset: 13 - bit_size: 1 - - name: CCU4 - description: Compare up event 4 CLEAR. - bit_offset: 14 - bit_size: 1 - - name: CCU5 - description: Compare up event 5 CLEAR. - bit_offset: 15 - bit_size: 1 - - name: F - description: Fault event CLEAR. - bit_offset: 24 - bit_size: 1 - - name: TOV - description: Trigger Overflow event CLEAR. - bit_offset: 25 - bit_size: 1 - - name: REPC - description: Repeat Counter Zero event CLEAR. - bit_offset: 26 - bit_size: 1 - - name: DC - description: Direction change event - bit_offset: 27 - bit_size: 1 - - name: QEIERR - description: QEIERR event - bit_offset: 28 - bit_size: 1 fieldset/IFCTL: description: Input Filter Control Register 0/1. fields: @@ -1050,30 +917,6 @@ enum/CC2SELU: - name: SEL_CCU5 description: Selects CCU from CC5. value: 5 -enum/CCACTUPD: - bit_size: 3 - variants: - - name: Immediately - description: Value written to the CCACT register has immediate effect. - value: 0 - - name: Zero_EVT - description: Following a zero event (CTR=0) Writes to the CCACTx_y register are stored in shadow register and transferred to CCACTx_y in the TIMCLK cycle following CTR equals 0. - value: 1 - - name: Compare_Down_EVT - description: Following a CCD event (CTR=CC_xy) Writes to the CCACTx_y register are stored in shadow register and transferred to CCACTx_y in the TIMCLK cycle following CTR equals the CCx_y register value. - value: 2 - - name: Compare_UP_EVT - description: Following a CCU event (CTR=CC_xy) Writes to the CCACTx_y register are stored in shadow register and transferred to CCACTx_y in the TIMCLK cycle following CTR equals the CCx_y register value. - value: 3 - - name: ZERO_LOAD_EVT - description: Following a zero event (CTR=0) or load event (CTR = LOAD) Writes to the CCACTx_y register are stored in shadow register and transferred to CCACTx_y in the TIMCLK cycle following CTR equals 0 or CTR. Equals LDn. Note this update mechanism is defined for use only in configurations using up/down counting. This mode is not intended for use in down count configurations. - value: 4 - - name: ZERO_RC_ZERO_EVT - description: Following a zero event (CTR=0) with repeat count also zero (RC=0). Writes to the CCACTx_y register are stored in shadow register and transferred to CCACTx_y in the TIMCLK cycle following CTR equals 0 and if RC equal 0. - value: 5 - - name: TRIG - description: On a TRIG pulse, the value stored in CCACT_xy shadow register is loaded into CCACT_xy register. - value: 6 enum/CCOND: bit_size: 3 variants: @@ -1131,7 +974,7 @@ enum/CCPO: - name: CNTDIR description: Counter direction. value: 13 -enum/CCUPD: +enum/UPD: bit_size: 3 variants: - name: Immediately @@ -1176,7 +1019,7 @@ enum/COC: - name: CAPTURE description: Capture. value: 1 -enum/CPU_INT_IIDX_STAT: +enum/IIDX_STAT: bit_size: 8 variants: - name: NO_INTR @@ -1239,15 +1082,6 @@ enum/CPU_INT_IIDX_STAT: - name: QEIERR description: Interrupt Source:QEI Incorrect state transition error (QEIERR). value: 29 -enum/CPV: - bit_size: 1 - variants: - - name: Consecutive - description: Consecutive Periods The input must be at a specific logic level for the period defined by FP before it is passed to the filter output. - value: 0 - - name: Voting - description: Voting The filter ignores one clock of opposite logic over the filter period. I.e. Over FP samples of the input, up to 1 sample may be of an opposite logic value (glitch) without affecting the output. - value: 1 enum/CVAE: bit_size: 2 variants: @@ -1281,7 +1115,7 @@ enum/CxC: - name: QEI_3INP description: Controlled by 3-input QEI mode This value exists when TIMER support QEI feature. value: 5 -enum/DRB: +enum/RB: bit_size: 1 variants: - name: RESUME @@ -1425,7 +1259,7 @@ enum/FB: - name: SUSP_COUNT description: Suspends counting. value: 1 -enum/FIFCTL_CPV: +enum/CPV: bit_size: 1 variants: - name: CONSEC_PER @@ -1473,78 +1307,6 @@ enum/FP: - name: _8 description: The division factor is 8. value: 2 -enum/FRB: - bit_size: 1 - variants: - - name: RESUME - description: Resume counting. - value: 0 - - name: CVAE_ACTION - description: Perform the action as specified by the CVAE field. - value: 1 -enum/GEN_EVENT_IIDX_STAT: - bit_size: 8 - variants: - - name: NO_INTR - description: No interrupt pending. - value: 0 - - name: Z - description: 'Interrupt Source: Zero event (Z).' - value: 1 - - name: L - description: 'nterrupt Source: Load event (L).' - value: 2 - - name: CCD0 - description: 'Interrupt Source: Capture or compare down event (CCD0).' - value: 5 - - name: CCD1 - description: 'Interrupt Source: Capture or compare down event (CCD1).' - value: 6 - - name: CCD2 - description: 'Interrupt Source: Capture or compare down event (CCD2).' - value: 7 - - name: CCD3 - description: 'Interrupt Source: Capture or compare down event (CCD3).' - value: 8 - - name: CCU0 - description: 'Interrupt Source: Capture or compare up event (CCU0).' - value: 9 - - name: CCU1 - description: 'Interrupt Source: Capture or compare up event (CCU1).' - value: 10 - - name: CCU2 - description: 'Interrupt Source: Capture or compare up event (CCU2).' - value: 11 - - name: CCU3 - description: 'Interrupt Source: Capture or compare up event (CCU3).' - value: 12 - - name: CCD4 - description: 'Interrupt Source: Compare down event (CCD4).' - value: 13 - - name: CCD5 - description: 'Interrupt Source: Compare down event (CCD5).' - value: 14 - - name: CCU4 - description: 'Interrupt Source: Compare down event (CCU4).' - value: 15 - - name: CCU5 - description: 'Interrupt Source: Compare down event (CCU5).' - value: 16 - - name: F - description: 'Interrupt Source: Fault Event generated an interrupt. (F).' - value: 25 - - name: TOV - description: 'Interrupt Source: Trigger overflow (TOV).' - value: 26 - - name: REPC - description: 'Interrupt Source: Repeat Counter Zero (REPC).' - value: 27 - - name: DC - description: 'Interrupt Source: Direction Change (DC).' - value: 28 - - name: QEIERR - description: Interrupt Source:QEI Incorrect state transition error (QEIERR). - value: 29 enum/ISEL: bit_size: 4 variants: @@ -1578,7 +1340,7 @@ enum/ISEL: - name: COMP2 description: Comparator 2 output. value: 9 -enum/LCOND: +enum/LZCOND: bit_size: 3 variants: - name: CC_TRIG_NO_EFFECT @@ -1642,18 +1404,3 @@ enum/SWFRCACT: - name: CCP_LOW description: CCP output value is set low. value: 2 -enum/ZCOND: - bit_size: 3 - variants: - - name: CC_TRIG_NO_EFFECT - description: CCP edges have no effect. - value: 0 - - name: CC_TRIG_RISE - description: Rising edge of CCP or trigger assertion edge. - value: 1 - - name: CC_TRIG_FALL - description: Falling edge of CCP or trigger de-assertion edge. - value: 2 - - name: CC_TRIG_EDGE - description: Either edge of CCP or trigger change (assertion/de-assertion edge). - value: 3 From 03ef82e03549200aa913b202bf36027067fb5095 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 23:36:43 +0100 Subject: [PATCH 051/134] gpio: address the generic event masks by absolute DIO number GEN_EVENT1 masks DIO16 to DIO31 at bits 16 to 31, but both event blocks shared a 16-bit fieldset based at bit 0, so the upper half was reachable only at the wrong offset. --- data/registers/gpio_v1.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/data/registers/gpio_v1.yaml b/data/registers/gpio_v1.yaml index b410996..9295c30 100644 --- a/data/registers/gpio_v1.yaml +++ b/data/registers/gpio_v1.yaml @@ -422,14 +422,17 @@ fieldset/FPORT: bit_offset: 0 bit_size: 4 fieldset/GEN_EVENT: - description: Interrupt clear. + # Indexed by absolute DIO number, so that it agrees with CPU_INT and the DOUT/DIN masks. + # GEN_EVENT0 implements DIO0 to DIO15 and GEN_EVENT1 implements DIO16 to DIO31, each at the bit + # matching its own DIO number; the other half of the word is reserved in both. + description: Generic event mask, one bit per DIO. fields: - name: DIO - description: DIO0 event. + description: DIO event. bit_offset: 0 bit_size: 1 array: - len: 16 + len: 32 stride: 1 fieldset/GEN_EVENT_IIDX: description: Interrupt index. From 237c6ef2e63c78c2b2a78db485409ba6f6abf37e Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 23:38:08 +0100 Subject: [PATCH 052/134] gpio: share one fieldset for the per-DIO masks Ten registers carried a copy of the same 32-bit DIO array, and the two POLARITY halves and the two X4 views another two pairs. FILTEREN already shared one fieldset between its halves. --- data/registers/gpio_v1.yaml | 179 +++++++----------------------------- 1 file changed, 34 insertions(+), 145 deletions(-) diff --git a/data/registers/gpio_v1.yaml b/data/registers/gpio_v1.yaml index 9295c30..88c8855 100644 --- a/data/registers/gpio_v1.yaml +++ b/data/registers/gpio_v1.yaml @@ -8,27 +8,27 @@ block/CPU_INT: - name: IMASK description: Interrupt mask. byte_offset: 8 - fieldset: CPU_INT + fieldset: DIO - name: RIS description: Raw interrupt status. byte_offset: 16 access: Read - fieldset: CPU_INT + fieldset: DIO - name: MIS description: Masked interrupt status. byte_offset: 24 access: Read - fieldset: CPU_INT + fieldset: DIO - name: ISET description: Interrupt set. byte_offset: 32 access: Write - fieldset: CPU_INT + fieldset: DIO - name: ICLR description: Interrupt clear. byte_offset: 40 access: Write - fieldset: CPU_INT + fieldset: DIO block/GEN_EVENT: items: - name: IIDX @@ -39,27 +39,27 @@ block/GEN_EVENT: - name: IMASK description: Interrupt mask. byte_offset: 8 - fieldset: GEN_EVENT + fieldset: DIO - name: RIS description: Raw interrupt status. byte_offset: 16 access: Read - fieldset: GEN_EVENT + fieldset: DIO - name: MIS description: Masked interrupt status. byte_offset: 24 access: Read - fieldset: GEN_EVENT + fieldset: DIO - name: ISET description: Interrupt set. byte_offset: 32 access: Write - fieldset: GEN_EVENT + fieldset: DIO - name: ICLR description: Interrupt clear. byte_offset: 40 access: Write - fieldset: GEN_EVENT + fieldset: DIO block/GPIO: description: PERIPHERALREGION. items: @@ -113,40 +113,40 @@ block/GPIO: stride: 4 byte_offset: 4608 access: Write - fieldset: DOUT_X4 + fieldset: DIO_X4 - name: DOUT31_0 description: Data output 31 to 0. byte_offset: 4736 - fieldset: DOUT31_0 + fieldset: DIO - name: DOUTSET31_0 description: Data output set 31 to 0. byte_offset: 4752 access: Write - fieldset: DOUTSET31_0 + fieldset: DIO - name: DOUTCLR31_0 description: Data output clear 31 to 0. byte_offset: 4768 access: Write - fieldset: DOUTCLR31_0 + fieldset: DIO - name: DOUTTGL31_0 description: Data output toggle 31 to 0. byte_offset: 4784 access: Write - fieldset: DOUTTGL31_0 + fieldset: DIO - name: DOE31_0 description: Data output enable 31 to 0. byte_offset: 4800 - fieldset: DOE31_0 + fieldset: DIO - name: DOESET31_0 description: Data output enable set 31 to 0. byte_offset: 4816 access: Write - fieldset: DOESET31_0 + fieldset: DIO - name: DOECLR31_0 description: Data output enable clear 31 to 0. byte_offset: 4832 access: Write - fieldset: DOECLR31_0 + fieldset: DIO - name: DIN_Y_X4 description: Data input 3 to 0. array: @@ -154,20 +154,20 @@ block/GPIO: stride: 4 byte_offset: 4864 access: Read - fieldset: DIN_X4 + fieldset: DIO_X4 - name: DIN31_0 description: Data input 31 to 0. byte_offset: 4992 access: Read - fieldset: DIN31_0 + fieldset: DIO - name: POLARITY15_0 description: Polarity 15 to 0. byte_offset: 5008 - fieldset: POLARITY15_0 + fieldset: POLARITY - name: POLARITY31_16 description: Polarity 31 to 16. byte_offset: 5024 - fieldset: POLARITY31_16 + fieldset: POLARITY - name: CTL description: FAST WAKE GLOBAL EN. byte_offset: 5120 @@ -223,16 +223,6 @@ fieldset/CLKOVR: bit_offset: 1 bit_size: 1 enum: RUN_STOP -fieldset/CPU_INT: - description: Interrupt clear. - fields: - - name: DIO - description: DIO0 event. - bit_offset: 0 - bit_size: 1 - array: - len: 32 - stride: 1 fieldset/CPU_INT_IIDX: description: Interrupt index. fields: @@ -267,21 +257,24 @@ fieldset/DESC: description: Module identification contains a unique peripheral identification number. The assignments are maintained in a central database for all of the platform modules to ensure uniqueness. bit_offset: 16 bit_size: 16 -fieldset/DIN31_0: - description: Data input 31 to 0. +fieldset/DIO: + # Every one of these registers is a mask over the port's 32 DIO pins, indexed by DIO number. + # In the two generic event blocks only one half is implemented: GEN_EVENT0 has DIO0 to DIO15 and + # GEN_EVENT1 has DIO16 to DIO31, each at the bit matching its own DIO number. + description: A mask over the port's DIO pins. fields: - name: DIO - description: This bit reads the data input value of DIO0. + description: One bit per DIO pin. bit_offset: 0 bit_size: 1 array: len: 32 stride: 1 -fieldset/DIN_X4: - description: Data input 11 to 8. +fieldset/DIO_X4: + description: Four DIO pins, one per byte of the word. fields: - name: DIO - description: This bit reads the data input value of DIO8. + description: The value of one DIO pin. bit_offset: 0 bit_size: 1 array: @@ -297,86 +290,6 @@ fieldset/DMAMASK: array: len: 32 stride: 1 -fieldset/DOE31_0: - description: Data output enable 31 to 0. - fields: - - name: DIO - description: Enables data output for DIO0. - bit_offset: 0 - bit_size: 1 - array: - len: 32 - stride: 1 -fieldset/DOECLR31_0: - description: Data output enable clear 31 to 0. - fields: - - name: DIO - description: Writing 1 to this bit clears the DIO0 bit in the DOE31_0 register. Writing 0 has no effect. - bit_offset: 0 - bit_size: 1 - array: - len: 32 - stride: 1 -fieldset/DOESET31_0: - description: Data output enable set 31 to 0. - fields: - - name: DIO - description: Writing 1 to this bit sets the DIO0 bit in the DOE31_0 register. Writing 0 has no effect. - bit_offset: 0 - bit_size: 1 - array: - len: 32 - stride: 1 -fieldset/DOUT31_0: - description: Data output 31 to 0. - fields: - - name: DIO - description: This bit sets the value of the pin configured as DIO0 when the output is enabled through DOE31_0 register. - bit_offset: 0 - bit_size: 1 - array: - len: 32 - stride: 1 -fieldset/DOUTCLR31_0: - description: Data output clear 31 to 0. - fields: - - name: DIO - description: Writing 1 to this bit clears the DIO0 bit in the DOUT31_0 register. Writing 0 has no effect. - bit_offset: 0 - bit_size: 1 - array: - len: 32 - stride: 1 -fieldset/DOUTSET31_0: - description: Data output set 31 to 0. - fields: - - name: DIO - description: Writing 1 to this bit sets the DIO0 bit in the DOUT31_0 register. Writing 0 has no effect. - bit_offset: 0 - bit_size: 1 - array: - len: 32 - stride: 1 -fieldset/DOUTTGL31_0: - description: Data output toggle 31 to 0. - fields: - - name: DIO - description: This bit is used to toggle DIO0 output. - bit_offset: 0 - bit_size: 1 - array: - len: 32 - stride: 1 -fieldset/DOUT_X4: - description: Data output 11 to 8. - fields: - - name: DIO - description: This bit sets the value of the pin configured as DIO8 when the output is enabled through DOE31_0 register. - bit_offset: 0 - bit_size: 1 - array: - len: 4 - stride: 8 fieldset/EVT_MODE: description: Event Mode. fields: @@ -421,19 +334,6 @@ fieldset/FPORT: description: 0 = disconnected. 1-15 = connected to channelID = CHANID. bit_offset: 0 bit_size: 4 -fieldset/GEN_EVENT: - # Indexed by absolute DIO number, so that it agrees with CPU_INT and the DOUT/DIN masks. - # GEN_EVENT0 implements DIO0 to DIO15 and GEN_EVENT1 implements DIO16 to DIO31, each at the bit - # matching its own DIO number; the other half of the word is reserved in both. - description: Generic event mask, one bit per DIO. - fields: - - name: DIO - description: DIO event. - bit_offset: 0 - bit_size: 1 - array: - len: 32 - stride: 1 fieldset/GEN_EVENT_IIDX: description: Interrupt index. fields: @@ -449,22 +349,11 @@ fieldset/PDBGCTL: description: Free run control. bit_offset: 0 bit_size: 1 -fieldset/POLARITY15_0: - description: Polarity 15 to 0. - fields: - - name: DIO - description: Enables and configures edge detection polarity for DIO0. - bit_offset: 0 - bit_size: 2 - array: - len: 16 - stride: 2 - enum: POLARITY -fieldset/POLARITY31_16: - description: Polarity 31 to 16. +fieldset/POLARITY: + description: Edge detection polarity for sixteen DIO pins, two bits each. fields: - name: DIO - description: Enables and configures edge detection polarity for DIO16. + description: Enables and configures edge detection polarity for one DIO. bit_offset: 0 bit_size: 2 array: From 6c3fb8516dc6d40f50d0a30dacb1c2fa31cb3423 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 23:40:32 +0100 Subject: [PATCH 053/134] uart, i2c: array the LIN capture interrupts, share the controller and target fieldsets The FIFO status, PEC control and PEC status registers had a controller and a target copy with the same fields. EVT_MODE's DMA_TRIG0 and DMA_TRIG1 stay apart: hw_i2c.h puts DMA_TRIG1 at 0x1050 and DMA_TRIG0 at 0x1080, so the event lines really are in that order. --- data/registers/i2c_v1.yaml | 69 +++++++------------------------------ data/registers/uart_v1.yaml | 11 +++--- 2 files changed, 17 insertions(+), 63 deletions(-) diff --git a/data/registers/i2c_v1.yaml b/data/registers/i2c_v1.yaml index 924047f..ff5641d 100644 --- a/data/registers/i2c_v1.yaml +++ b/data/registers/i2c_v1.yaml @@ -43,16 +43,16 @@ block/CONTROLLER: description: I2C Controller FIFO Status Register. byte_offset: 44 access: Read - fieldset: CFIFOSR + fieldset: FIFOSR - name: CONTROLLER_I2CPECCTL description: I2C Controller PEC control register. byte_offset: 48 - fieldset: CONTROLLER_I2CPECCTL + fieldset: PECCTL - name: CONTROLLER_PECSR description: I2C Controller PEC status register. byte_offset: 52 access: Read - fieldset: CONTROLLER_PECSR + fieldset: PECSR block/CPU_INT: items: - name: IIDX @@ -252,16 +252,16 @@ block/TARGET: description: I2C Target FIFO Status Register. byte_offset: 32 access: Read - fieldset: TFIFOSR + fieldset: FIFOSR - name: TARGET_PECCTL description: I2C Target PEC control register. byte_offset: 36 - fieldset: TARGET_PECCTL + fieldset: PECCTL - name: TARGET_PECSR description: I2C Target PEC status register. byte_offset: 40 access: Read - fieldset: TARGET_PECSR + fieldset: PECSR fieldset/CBMON: description: I2C Controller Bus Monitor. fields: @@ -344,8 +344,8 @@ fieldset/CFIFOCTL: description: RX FIFO Flush Setting this bit will Flush the RX FIFO. Before clearing this bit to stop Flush the RXFIFOCNT should be checked to be 0 and indicating that the Flush has completed. bit_offset: 15 bit_size: 1 -fieldset/CFIFOSR: - description: I2C Controller FIFO Status Register. +fieldset/FIFOSR: + description: I2C FIFO Status Register. fields: - name: RXFIFOCNT description: Number of Bytes which could be read from the RX FIFO. @@ -394,8 +394,8 @@ fieldset/CLKSEL: description: Selects BUSCLK as clock source if enabled. bit_offset: 3 bit_size: 1 -fieldset/CONTROLLER_I2CPECCTL: - description: I2C Controller PEC control register. +fieldset/PECCTL: + description: I2C PEC control register. fields: - name: PECCNT description: PEC Count When this field is non zero, the number of I2C bytes are counted (Note that although the PEC is calculated on the I2C address it is not counted at a byte). When the byte count = PECCNT and the state machine is transmitting, the contents of the LSFR is loaded into the shift register instead of the byte received from the Tx FIFO. When the state machine is receiving, after the last bit of this byte is received the LSFR is checked and if it is non-zero, a PEC RX Error interrupt is generated. The I2C packet must be padded to include the PEC byte for both transmit and receive. In transmit mode the FIFO must be loaded with a dummy PEC byte. In receive mode the PEC byte will be passed to the Rx FIFO. In the normal Controller use case, FW would set PECEN=1 and PECCNT=SMB packet length (Not including Target Address byte, but including the PEC byte). FW would then configure DMA to allow the packet to complete unassisted and write MCTR to initiate the transaction. Note that when the byte count = PEC CNT, the byte count is reset to 0 and multiple PEC calculation can automatically occur within a single I2C transaction. Note that any write to the Controller_I2CPECCTL Register will clear the current PEC Byte Count in the Controller State Machine. @@ -405,8 +405,8 @@ fieldset/CONTROLLER_I2CPECCTL: description: PEC Enable This bit enables the SMB Packet Error Checking (PEC). When enabled the PEC is calculated on all bits except the Start, Stop, Ack and Nack. The PEC LSFR and the Byte Counter is set to 0 when the State Machine is in the IDLE state, which occur following a Stop or when a timeout occurs. The Counter is also set to 0 after the PEC byte is sent or received. Note that the NACK is automatically send following a PEC byte that results in a PEC error. The PEC Polynomial is x^8 + x^2 + x^1 + 1. bit_offset: 12 bit_size: 1 -fieldset/CONTROLLER_PECSR: - description: I2C Controller PEC status register. +fieldset/PECSR: + description: I2C PEC status register. fields: - name: PECBYTECNT description: PEC Byte Count. This is the current PEC Byte Count of the Controller State Machine. @@ -794,32 +794,6 @@ fieldset/TACKCTL: description: When set this bit will automatically turn on the Target ACKOEN field following the ACK/NACK of the received PEC byte. bit_offset: 4 bit_size: 1 -fieldset/TARGET_PECCTL: - description: I2C Target PEC control register. - fields: - - name: PECCNT - description: When this field is non zero, the number of I2C data bytes are counted. When the byte count = PECCNT and the state machine is transmitting, the contents of the LSFR is loaded into the shift register instead of the byte received from the Tx FIFO. When the state machine is receiving, after the last bit of this byte is received the LSFR is checked and if it is non-zero, a PEC RX Error interrupt is generated. The I2C packet must be padded to include the PEC byte for both transmit and receive. In transmit mode the FIFO must be loaded with a dummy PEC byte. In receive mode the PEC byte will be passed to the Rx FIFO. In the normal Target use case, FW would set PECEN=1 and PECCNT=0 and use the ACKOEN until the remaining SMB packet length is known. FW would then set the PECCNT to the remaining packet length (Including PEC bye). FW would then configure DMA to allow the packet to complete unassisted and exit NoAck mode. Note that when the byte count = PEC CNT, the byte count is reset to 0 and multiple PEC calculation can automatically occur within a single I2C transaction. - bit_offset: 0 - bit_size: 9 - - name: PECEN - description: PEC Enable This bit enables the SMB Packet Error Checking (PEC). When enabled the PEC is calculated on all bits except the Start, Stop, Ack and Nack. The PEC LSFR and the Byte Counter is set to 0 when the State Machine is in the IDLE state, which occur following a Stop or when a timeout occurs. The Counter is also set to 0 after the PEC byte is sent or received. Note that the NACK is automatically send following a PEC byte that results in a PEC error. The PEC Polynomial is x^8 + x^2 + x^1 + 1. - bit_offset: 12 - bit_size: 1 -fieldset/TARGET_PECSR: - description: I2C Target PEC status register. - fields: - - name: PECBYTECNT - description: This is the current PEC Byte Count of the Target State Machine. - bit_offset: 0 - bit_size: 9 - - name: PECSTS_CHECK - description: This status bit indicates if the PEC was checked in the transaction that occurred before the last Stop. Latched on Stop. - bit_offset: 16 - bit_size: 1 - - name: PECSTS_ERROR - description: This status bit indicates if a PEC check error occurred in the transaction that occurred before the last Stop. Latched on Stop. - bit_offset: 17 - bit_size: 1 fieldset/TCTR: description: I2C Target Control Register. fields: @@ -888,25 +862,6 @@ fieldset/TFIFOCTL: description: RX FIFO Flush Setting this bit will Flush the RX FIFO. Before clearing this bit to stop Flush the RXFIFOCNT should be checked to be 0 and indicating that the Flush has completed. bit_offset: 15 bit_size: 1 -fieldset/TFIFOSR: - description: I2C Target FIFO Status Register. - fields: - - name: RXFIFOCNT - description: Number of Bytes which could be read from the RX FIFO. - bit_offset: 0 - bit_size: 4 - - name: RXFLUSH - description: RX FIFO Flush When this bit is set a Flush operation for the RX FIFO is active. Clear the RXFLUSH bit in the control register to stop. - bit_offset: 7 - bit_size: 1 - - name: TXFIFOCNT - description: Number of Bytes which could be put into the TX FIFO. - bit_offset: 8 - bit_size: 4 - - name: TXFLUSH - description: TX FIFO Flush When this bit is set a Flush operation for the TX FIFO is active. Clear the TXFLUSH bit in the control register to stop. - bit_offset: 15 - bit_size: 1 fieldset/TIMEOUT_CNT: description: I2C Timeout Count Register. fields: diff --git a/data/registers/uart_v1.yaml b/data/registers/uart_v1.yaml index d53d994..4558aac 100644 --- a/data/registers/uart_v1.yaml +++ b/data/registers/uart_v1.yaml @@ -307,14 +307,13 @@ fieldset/CPU_INT: description: Clear Positive Edge on UARTxRXD Interrupt. bit_offset: 6 bit_size: 1 - - name: LINC0 - description: Clear LIN Capture 0 / Match Interrupt. + - name: LINC + description: 'LIN capture interrupt. Index 0 is the capture-or-match interrupt on the falling RXD edge, index 1 the capture interrupt on the rising edge.' bit_offset: 7 bit_size: 1 - - name: LINC1 - description: Clear LIN Capture 1 Interrupt. - bit_offset: 8 - bit_size: 1 + array: + len: 2 + stride: 1 - name: LINOVF description: Clear LIN Hardware Counter Overflow Interrupt. bit_offset: 9 From 3ae0b8033bab6673bdfbc48fec8ff91a129b77b5 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 23:42:22 +0100 Subject: [PATCH 054/134] adc: array the sample time compares, drop two enums which name no choice SAMPCNT is a five bit count whose enum listed only its two endpoints, and SC is a one bit start control; both variant descriptions move onto the field. INT_CFG and EVT1_CFG were the same event line mode select. PWRDN and SAMPMODE keep an enum each despite both reading AUTO/MANUAL: one picks a power down policy and the other a sample trigger, and sharing a type would let either value be written to the other field. --- data/registers/adc_v1.yaml | 81 ++++++++------------------------------ 1 file changed, 17 insertions(+), 64 deletions(-) diff --git a/data/registers/adc_v1.yaml b/data/registers/adc_v1.yaml index 901ed0e..0c087e6 100644 --- a/data/registers/adc_v1.yaml +++ b/data/registers/adc_v1.yaml @@ -59,22 +59,21 @@ block/ADC: description: Sample Clock Frequency Range Register. byte_offset: 4368 fieldset: CLKFREQ - - name: SCOMP0 - description: Sample Time Compare 0 Register. + - name: SCOMP + description: Sample Time Compare Register. + array: + len: 2 + stride: 4 byte_offset: 4372 - fieldset: SCOMP0 - - name: SCOMP1 - description: Sample Time Compare 1 Register. - byte_offset: 4376 - fieldset: SCOMP1 + fieldset: SCOMP - name: WCLOW description: Window Comparator Low Threshold Register. byte_offset: 4424 - fieldset: WCLOW + fieldset: WC - name: WCHIGH description: Window Comparator High Threshold Register. byte_offset: 4432 - fieldset: WCHIGH + fieldset: WC - name: MEMCTL description: Conversion Memory Control Register. array: @@ -309,10 +308,9 @@ fieldset/CTL1: bit_size: 1 enum: TRIGSRC - name: SC - description: Start of conversion. + description: Start of conversion. With SAMPMODE MANUAL the sample phase lasts as long as this bit is set, and clearing it starts the conversion phase; with AUTO, setting it triggers the timer based sample time and clearing it does nothing. bit_offset: 8 bit_size: 1 - enum: SC - name: CONSEQ description: Conversion sequence mode. bit_offset: 16 @@ -357,10 +355,9 @@ fieldset/CTL2: bit_offset: 10 bit_size: 1 - name: SAMPCNT - description: Number of ADC converted samples to be transferred on a DMA trigger. + description: Number of ADC converted samples to be transferred on a DMA trigger, up to 24. bit_offset: 11 bit_size: 5 - enum: SAMPCNT - name: STARTADD description: Sequencer start address. These bits select which MEMCTLx is used for single conversion or as first MEMCTL for sequence mode. The value of STARTADD is 0x00 to 0x17, corresponding to MEMRES0 to MEMRES23. bit_offset: 16 @@ -417,12 +414,12 @@ fieldset/EVT_MODE: description: Event line mode select for event corresponding to CPU_INT. bit_offset: 0 bit_size: 2 - enum: INT_CFG + enum: EVT_CFG - name: EVT1_CFG description: Event line mode select for event corresponding to GEN_EVENT. bit_offset: 2 bit_size: 2 - enum: EVT1_CFG + enum: EVT_CFG fieldset/FPORT: description: Publisher Configuration Register. fields: @@ -525,15 +522,8 @@ fieldset/RSTCTL: bit_offset: 24 bit_size: 8 enum: RESET_KEY -fieldset/SCOMP0: - description: Sample Time Compare 0 Register. - fields: - - name: VAL - description: 'Specifies the number of sample clocks. When VAL = 0 or 1, number of sample clocks = Sample clock divide value. When VAL > 1, number of sample clocks = VAL x Sample clock divide value. Note: Sample clock divide value is not the value written to SCLKDIV but the actual divide value (SCLKDIV = 2 implies divide value is 4). Example: VAL = 4, SCLKDIV = 3 implies 32 sample clock cycles.' - bit_offset: 0 - bit_size: 10 -fieldset/SCOMP1: - description: Sample Time Compare 1 Register. +fieldset/SCOMP: + description: Sample Time Compare Register. fields: - name: VAL description: 'Specifies the number of sample clocks. When VAL = 0 or 1, number of sample clocks = Sample clock divide value. When VAL > 1, number of sample clocks = VAL x Sample clock divide value. Note: Sample clock divide value is not the value written to SCLKDIV but the actual divide value (SCLKDIV = 2 implies divide value is 4). Example: VAL = 4, SCLKDIV = 3 implies 32 sample clock cycles.' @@ -557,15 +547,8 @@ fieldset/STATUS: description: Indicates reference buffer is powered up and ready. bit_offset: 1 bit_size: 1 -fieldset/WCHIGH: - description: Window Comparator High Threshold Register. - fields: - - name: DATA - description: If DF = 0, unsigned binary format has to be used. The threshold value has to be right aligned, with the MSB on the left. For 10-bits and 8-bits resolution, unused bit have to be 0s. If DF = 1, 2s-complement format has to be used. The value based on the resolution has to be left aligned with the LSB on the right. For 10-bits and 8-bits resolution, unused bit have to be 0s. - bit_offset: 0 - bit_size: 16 -fieldset/WCLOW: - description: Window Comparator Low Threshold Register. +fieldset/WC: + description: A window comparator threshold. fields: - name: DATA description: If DF = 0, unsigned binary format has to be used. The value based on the resolution has to be right aligned with the MSB on the left. For 10-bits and 8-bits resolution, unused bits have to be 0s. If DF = 1, 2s-complement format has to be used. The value based on the resolution has to be left aligned with the LSB on the right. For 10-bits and 8-bits resolution, unused bits have to be 0s. @@ -795,18 +778,6 @@ enum/DMA_TRIG_IIDX_STAT: - name: MEMRESIFG23 description: MEMRES23 data loaded interrupt. value: 32 -enum/EVT1_CFG: - bit_size: 2 - variants: - - name: DISABLE - description: The interrupt or event line is disabled. - value: 0 - - name: SOFTWARE - description: The interrupt or event line is in software mode. Software must clear the RIS. - value: 1 - - name: HARDWARE - description: The interrupt or event line is in hardware mode. The hardware (another module) clears automatically the associated RIS flag. - value: 2 enum/FRANGE: bit_size: 3 variants: @@ -852,7 +823,7 @@ enum/GEN_EVENT_IIDX_STAT: - name: MEMRESIFG0 description: MEMRES0 data loaded interrupt. value: 9 -enum/INT_CFG: +enum/EVT_CFG: bit_size: 2 variants: - name: DISABLE @@ -907,15 +878,6 @@ enum/SAMPCLK: - name: HFCLK description: 'HFCLK clock is the source of ADC sample clock. Note : HFCLK may not be available on all the devices.' value: 2 -enum/SAMPCNT: - bit_size: 5 - variants: - - name: MIN - description: Minimum value. - value: 0 - - name: MAX - description: Maximum value. - value: 24 enum/SAMPMODE: bit_size: 1 variants: @@ -925,15 +887,6 @@ enum/SAMPMODE: - name: MANUAL description: Software trigger is used as sample signal. value: 1 -enum/SC: - bit_size: 1 - variants: - - name: STOP - description: When SAMPMODE is set to MANUAL, clearing this bit will end the sample phase and the conversion phase will start. When SAMPMODE is set to AUTO, writing 0 has no effect. - value: 0 - - name: START - description: When SAMPMODE is set to MANUAL, setting this bit will start the sample phase. Sample phase will last as long as this bit is set. When SAMPMODE is set to AUTO, setting this bit will trigger the timer based sample time. - value: 1 enum/SCLKDIV: bit_size: 3 variants: From 48ee197c3109b76b1a3282cec3a19fd57d678f48 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 23:44:43 +0100 Subject: [PATCH 055/134] canfd: drop the block name from the ECC and subsystem register names Every register in those two blocks repeated it. The fieldsets keep theirs: that namespace is flat across the peripheral, and STAT, CTRL and IE are already taken. --- data/registers/canfd_v1.yaml | 66 ++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/data/registers/canfd_v1.yaml b/data/registers/canfd_v1.yaml index 25826de..89ea1ae 100644 --- a/data/registers/canfd_v1.yaml +++ b/data/registers/canfd_v1.yaml @@ -60,95 +60,95 @@ block/CPU_INT: fieldset: CPU_INT block/ECC: items: - - name: ERR_REV + - name: REV description: MCAN Error Aggregator Revision Register. byte_offset: 0 access: Read fieldset: ERR_REV - - name: ERR_VECTOR + - name: VECTOR description: MCAN ECC Vector Register. byte_offset: 8 fieldset: ERR_VECTOR - - name: ERR_STAT + - name: STAT description: MCAN Error Misc Status. byte_offset: 12 access: Read fieldset: ERR_STAT - - name: ERR_WRAP_REV + - name: WRAP_REV description: MCAN ECC Wrapper Revision Register. byte_offset: 16 access: Read fieldset: ERR_WRAP_REV - - name: ERR_CTRL + - name: CTRL description: MCAN ECC Control. byte_offset: 20 fieldset: ERR_CTRL - - name: ERR_ERR_CTRL1 + - name: ERR_CTRL1 description: MCAN ECC Error Control 1 Register. byte_offset: 24 fieldset: ERR_ERR_CTRL1 - - name: ERR_ERR_CTRL2 + - name: ERR_CTRL2 description: MCAN ECC Error Control 2 Register. byte_offset: 28 fieldset: ERR_ERR_CTRL2 - - name: ERR_ERR_STAT1 + - name: ERR_STAT1 description: MCAN ECC Error Status 1 Register. byte_offset: 32 fieldset: ERR_ERR_STAT1 - - name: ERR_ERR_STAT2 + - name: ERR_STAT2 description: MCAN ECC Error Status 2 Register. byte_offset: 36 access: Read fieldset: ERR_ERR_STAT2 - - name: ERR_ERR_STAT3 + - name: ERR_STAT3 description: MCAN ECC Error Status 3 Register. byte_offset: 40 fieldset: ERR_ERR_STAT3 - - name: ERR_SEC_EOI + - name: SEC_EOI description: MCAN Single Error Corrected End of Interrupt Register. byte_offset: 60 fieldset: ERR_SEC_EOI - - name: ERR_SEC_STATUS + - name: SEC_STATUS description: MCAN Single Error Corrected Interrupt Status Register. byte_offset: 64 fieldset: ERR_SEC_STATUS - - name: ERR_SEC_ENABLE_SET + - name: SEC_ENABLE_SET description: MCAN Single Error Corrected Interrupt Enable Set Register. byte_offset: 128 fieldset: ERR_SEC_ENABLE_SET - - name: ERR_SEC_ENABLE_CLR + - name: SEC_ENABLE_CLR description: MCAN Single Error Corrected Interrupt Enable Clear Register. byte_offset: 192 fieldset: ERR_SEC_ENABLE_CLR - - name: ERR_DED_EOI + - name: DED_EOI description: MCAN Double Error Detected End of Interrupt Register. byte_offset: 316 fieldset: ERR_DED_EOI - - name: ERR_DED_STATUS + - name: DED_STATUS description: MCAN Double Error Detected Interrupt Status Register. byte_offset: 320 fieldset: ERR_DED_STATUS - - name: ERR_DED_ENABLE_SET + - name: DED_ENABLE_SET description: MCAN Double Error Detected Interrupt Enable Set Register. byte_offset: 384 fieldset: ERR_DED_ENABLE_SET - - name: ERR_DED_ENABLE_CLR + - name: DED_ENABLE_CLR description: MCAN Double Error Detected Interrupt Enable Clear Register. byte_offset: 448 fieldset: ERR_DED_ENABLE_CLR - - name: ERR_AGGR_ENABLE_SET + - name: AGGR_ENABLE_SET description: MCAN Error Aggregator Enable Set Register. byte_offset: 512 fieldset: ERR_AGGR_ENABLE_SET - - name: ERR_AGGR_ENABLE_CLR + - name: AGGR_ENABLE_CLR description: MCAN Error Aggregator Enable Clear Register. byte_offset: 516 fieldset: ERR_AGGR_ENABLE_CLR - - name: ERR_AGGR_STATUS_SET + - name: AGGR_STATUS_SET description: MCAN Error Aggregator Status Set Register. byte_offset: 520 fieldset: ERR_AGGR_STATUS_SET - - name: ERR_AGGR_STATUS_CLR + - name: AGGR_STATUS_CLR description: MCAN Error Aggregator Status Clear Register. byte_offset: 524 fieldset: ERR_AGGR_STATUS_CLR @@ -400,50 +400,50 @@ block/PROCESSORS: block: ECC block/SUBSYS: items: - - name: SUBSYS_PID + - name: PID description: MCAN Subsystem Revision Register. byte_offset: 0 access: Read fieldset: SUBSYS_PID - - name: SUBSYS_CTRL + - name: CTRL description: MCAN Subsystem Control Register. byte_offset: 4 fieldset: SUBSYS_CTRL - - name: SUBSYS_STAT + - name: STAT description: MCAN Subsystem Status Register. byte_offset: 8 access: Read fieldset: SUBSYS_STAT - - name: SUBSYS_ICS + - name: ICS description: MCAN Subsystem Interrupt Clear Shadow Register. byte_offset: 12 fieldset: SUBSYS_ICS - - name: SUBSYS_IRS + - name: IRS description: MCAN Subsystem Interrupt Raw Satus Register. byte_offset: 16 fieldset: SUBSYS_IRS - - name: SUBSYS_IECS + - name: IECS description: MCAN Subsystem Interrupt Enable Clear Shadow Register. byte_offset: 20 fieldset: SUBSYS_IECS - - name: SUBSYS_IE + - name: IE description: MCAN Subsystem Interrupt Enable Register. byte_offset: 24 fieldset: SUBSYS_IE - - name: SUBSYS_IES + - name: IES description: MCAN Subsystem Interrupt Enable Status. byte_offset: 28 access: Read fieldset: SUBSYS_IES - - name: SUBSYS_EOI + - name: EOI description: MCAN Subsystem End of Interrupt. byte_offset: 32 fieldset: SUBSYS_EOI - - name: SUBSYS_EXT_TS_PRESCALER + - name: EXT_TS_PRESCALER description: MCAN Subsystem External Timestamp Prescaler 0. byte_offset: 36 fieldset: SUBSYS_EXT_TS_PRESCALER - - name: SUBSYS_EXT_TS_UNSERVICED_INTR_CNTR + - name: EXT_TS_UNSERVICED_INTR_CNTR description: MCAN Subsystem External Timestamp Unserviced Interrupts Counter. byte_offset: 40 access: Read From 8c0b7defd911f51e62201322bfb6189ff95cb2d8 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 23:46:13 +0100 Subject: [PATCH 056/134] canfd: share the repeated ECC and subsystem fieldsets The SEC and DED halves of the ECC aggregator have the same four registers each, the five subsystem interrupt registers the same single bit, and the two revision registers the same layout. ENDN and two ECC registers had an empty fieldset, so they now read as the plain words they are. DTSEG1/DTSEG2, NTSEG1/NTSEG2 and ECC_BIT1/ECC_BIT2 stay separate fields: the segments differ in width and the two ECC bits in role. --- data/registers/canfd_v1.yaml | 189 ++++++++--------------------------- 1 file changed, 44 insertions(+), 145 deletions(-) diff --git a/data/registers/canfd_v1.yaml b/data/registers/canfd_v1.yaml index 89ea1ae..7f9dfde 100644 --- a/data/registers/canfd_v1.yaml +++ b/data/registers/canfd_v1.yaml @@ -78,7 +78,7 @@ block/ECC: description: MCAN ECC Wrapper Revision Register. byte_offset: 16 access: Read - fieldset: ERR_WRAP_REV + fieldset: ERR_REV - name: CTRL description: MCAN ECC Control. byte_offset: 20 @@ -86,7 +86,6 @@ block/ECC: - name: ERR_CTRL1 description: MCAN ECC Error Control 1 Register. byte_offset: 24 - fieldset: ERR_ERR_CTRL1 - name: ERR_CTRL2 description: MCAN ECC Error Control 2 Register. byte_offset: 28 @@ -99,7 +98,6 @@ block/ECC: description: MCAN ECC Error Status 2 Register. byte_offset: 36 access: Read - fieldset: ERR_ERR_STAT2 - name: ERR_STAT3 description: MCAN ECC Error Status 3 Register. byte_offset: 40 @@ -107,35 +105,35 @@ block/ECC: - name: SEC_EOI description: MCAN Single Error Corrected End of Interrupt Register. byte_offset: 60 - fieldset: ERR_SEC_EOI + fieldset: ERR_EOI - name: SEC_STATUS description: MCAN Single Error Corrected Interrupt Status Register. byte_offset: 64 - fieldset: ERR_SEC_STATUS + fieldset: ERR_STATUS - name: SEC_ENABLE_SET description: MCAN Single Error Corrected Interrupt Enable Set Register. byte_offset: 128 - fieldset: ERR_SEC_ENABLE_SET + fieldset: ERR_ENABLE_SET - name: SEC_ENABLE_CLR description: MCAN Single Error Corrected Interrupt Enable Clear Register. byte_offset: 192 - fieldset: ERR_SEC_ENABLE_CLR + fieldset: ERR_ENABLE_CLR - name: DED_EOI description: MCAN Double Error Detected End of Interrupt Register. byte_offset: 316 - fieldset: ERR_DED_EOI + fieldset: ERR_EOI - name: DED_STATUS description: MCAN Double Error Detected Interrupt Status Register. byte_offset: 320 - fieldset: ERR_DED_STATUS + fieldset: ERR_STATUS - name: DED_ENABLE_SET description: MCAN Double Error Detected Interrupt Enable Set Register. byte_offset: 384 - fieldset: ERR_DED_ENABLE_SET + fieldset: ERR_ENABLE_SET - name: DED_ENABLE_CLR description: MCAN Double Error Detected Interrupt Enable Clear Register. byte_offset: 448 - fieldset: ERR_DED_ENABLE_CLR + fieldset: ERR_ENABLE_CLR - name: AGGR_ENABLE_SET description: MCAN Error Aggregator Enable Set Register. byte_offset: 512 @@ -147,11 +145,11 @@ block/ECC: - name: AGGR_STATUS_SET description: MCAN Error Aggregator Status Set Register. byte_offset: 520 - fieldset: ERR_AGGR_STATUS_SET + fieldset: ERR_AGGR_STATUS - name: AGGR_STATUS_CLR description: MCAN Error Aggregator Status Clear Register. byte_offset: 524 - fieldset: ERR_AGGR_STATUS_CLR + fieldset: ERR_AGGR_STATUS block/MCAN: items: - name: CREL @@ -163,7 +161,6 @@ block/MCAN: description: MCAN Endian Register. byte_offset: 4 access: Read - fieldset: ENDN - name: DBTP description: MCAN Data Bit Timing and Prescaler Register. byte_offset: 12 @@ -251,14 +248,13 @@ block/MCAN: byte_offset: 148 access: Read fieldset: HPMS - - name: NDAT1 - description: MCAN New Data 1. + - name: NDAT + description: 'MCAN New Data. Index 0 covers message buffers 0 to 31 and index 1 buffers 32 to 63.' + array: + len: 2 + stride: 4 byte_offset: 152 fieldset: NDAT - - name: NDAT2 - description: MCAN New Data 2. - byte_offset: 156 - fieldset: NDAT - name: RXF0C description: MCAN Rx FIFO 0 Configuration. byte_offset: 160 @@ -417,24 +413,24 @@ block/SUBSYS: - name: ICS description: MCAN Subsystem Interrupt Clear Shadow Register. byte_offset: 12 - fieldset: SUBSYS_ICS + fieldset: SUBSYS_INT - name: IRS description: MCAN Subsystem Interrupt Raw Satus Register. byte_offset: 16 - fieldset: SUBSYS_IRS + fieldset: SUBSYS_INT - name: IECS description: MCAN Subsystem Interrupt Enable Clear Shadow Register. byte_offset: 20 - fieldset: SUBSYS_IECS + fieldset: SUBSYS_INT - name: IE description: MCAN Subsystem Interrupt Enable Register. byte_offset: 24 - fieldset: SUBSYS_IE + fieldset: SUBSYS_INT - name: IES description: MCAN Subsystem Interrupt Enable Status. byte_offset: 28 access: Read - fieldset: SUBSYS_IES + fieldset: SUBSYS_INT - name: EOI description: MCAN Subsystem End of Interrupt. byte_offset: 32 @@ -524,14 +520,13 @@ fieldset/CCCR: fieldset/CPU_INT: description: Interrupt clear. fields: - - name: INTL0 - description: Clear MCAN Interrupt Line 0. + - name: INTL + description: The MCAN interrupt line. bit_offset: 0 bit_size: 1 - - name: INTL1 - description: Clear MCAN Interrupt Line 1. - bit_offset: 1 - bit_size: 1 + array: + len: 2 + stride: 1 - name: SEC description: Clear Message RAM SEC interrupt. bit_offset: 2 @@ -637,9 +632,6 @@ fieldset/ECR: description: 'CAN Error Logging. The counter is incremented each time when a CAN protocol error causes the Transmit Error Counter or the Receive Error Counter to be incremented. It is reset by read access to CEL. The counter stops at 0xFF; the next increment of TEC or REC sets interrupt flag IR.ELO. Note: When CCCR.ASM is set, the CAN protocol controller does not increment TEC and REC when a CAN protocol error is detected, but CEL is still incremented.' bit_offset: 16 bit_size: 8 -fieldset/ENDN: - description: MCAN Endian Register. - fields: [] fieldset/ERR_AGGR_ENABLE_CLR: description: MCAN Error Aggregator Enable Clear Register. fields: @@ -662,19 +654,8 @@ fieldset/ERR_AGGR_ENABLE_SET: description: Write 1 to enable timeout errors. Reads return the corresponding enable bit's current value. bit_offset: 1 bit_size: 1 -fieldset/ERR_AGGR_STATUS_CLR: - description: MCAN Error Aggregator Status Clear Register. - fields: - - name: AGGR_PARITY_ERR - description: Aggregator Parity Error Status 2-bit saturating counter of the number of parity errors that have occurred since last cleared. 0 No parity errors have occurred 1 One parity error has occurred 2 Two parity errors have occurred 3 Three parity errors have occurred A write of a non-zero value to this bit field decrements it by the value provided. - bit_offset: 0 - bit_size: 2 - - name: SVBUS_TIMEOUT - description: Aggregator Serial VBUS Timeout Error Status 2-bit saturating counter of the number of SVBUS timeout errors that have occurred since last cleared. 0 No timeout errors have occurred 1 One timeout error has occurred 2 Two timeout errors have occurred 3 Three timeout errors have occurred A write of a non-zero value to this bit field decrements it by the value provided. - bit_offset: 2 - bit_size: 2 -fieldset/ERR_AGGR_STATUS_SET: - description: MCAN Error Aggregator Status Set Register. +fieldset/ERR_AGGR_STATUS: + description: MCAN ECC aggregator status. fields: - name: AGGR_PARITY_ERR description: Aggregator Parity Error Status 2-bit saturating counter of the number of parity errors that have occurred since last cleared. 0 No parity errors have occurred 1 One parity error has occurred 2 Two parity errors have occurred 3 Three parity errors have occurred A write of a non-zero value to this bit field increments it by the value provided. @@ -719,37 +700,6 @@ fieldset/ERR_CTRL: description: Enables Serial VBUS timeout mechanism. bit_offset: 8 bit_size: 1 -fieldset/ERR_DED_ENABLE_CLR: - description: MCAN Double Error Detected Interrupt Enable Clear Register. - fields: - - name: MSGMEM_ENABLE_CLR - description: Message RAM DED Interrupt Pending Enable Clear. Writing a 1 to this bit disables the Message RAM DED error interrupts. Writing a 0 has no effect. Reads return the corresponding enable bit's current value. - bit_offset: 0 - bit_size: 1 -fieldset/ERR_DED_ENABLE_SET: - description: MCAN Double Error Detected Interrupt Enable Set Register. - fields: - - name: MSGMEM_ENABLE_SET - description: Message RAM DED Interrupt Pending Enable Set. Writing a 1 to this bit enables the Message RAM DED error interrupts. Writing a 0 has no effect. Reads return the corresponding enable bit's current value. - bit_offset: 0 - bit_size: 1 -fieldset/ERR_DED_EOI: - description: MCAN Double Error Detected End of Interrupt Register. - fields: - - name: EOI_WR - description: Write to this register indicates that software has acknowledged the pending interrupt and the next interrupt can be sent to the host. Note that a write to the MCANERR_ERR_STAT1.CLR_ECC_DED goes through the SVBUS and has a delayed completion. To avoid an additional interrupt, read the MCANERR_ERR_STAT1 register back prior to writing to this bit field. - bit_offset: 0 - bit_size: 1 -fieldset/ERR_DED_STATUS: - description: MCAN Double Error Detected Interrupt Status Register. - fields: - - name: MSGMEM_PEND - description: Message RAM DED Interrupt Pending 0 No DED interrupt is pending 1 DED interrupt is pending. - bit_offset: 0 - bit_size: 1 -fieldset/ERR_ERR_CTRL1: - description: MCAN ECC Error Control 1 Register. - fields: [] fieldset/ERR_ERR_CTRL2: description: MCAN ECC Error Control 2 Register. fields: @@ -800,9 +750,6 @@ fieldset/ERR_ERR_STAT1: description: ECC Error Bit Position. Indicates the bit position in the RAM data that is in error on an SEC error. Only valid on an SEC error. 0 Bit 0 is in error 1 Bit 1 is in error 2 Bit 2 is in error 3 Bit 3 is in error ... 31 Bit 31 is in error >32 Invalid. bit_offset: 16 bit_size: 16 -fieldset/ERR_ERR_STAT2: - description: MCAN ECC Error Status 2 Register. - fields: [] fieldset/ERR_ERR_STAT3: description: MCAN ECC Error Status 3 Register. fields: @@ -819,7 +766,7 @@ fieldset/ERR_ERR_STAT3: bit_offset: 9 bit_size: 1 fieldset/ERR_REV: - description: MCAN Error Aggregator Revision Register. + description: Revision of the ECC block or of its wrapper. fields: - name: REVMIN description: Minor Revision of the Error Aggregator. @@ -837,29 +784,29 @@ fieldset/ERR_REV: description: PID Register Scheme. bit_offset: 30 bit_size: 2 -fieldset/ERR_SEC_ENABLE_CLR: - description: MCAN Single Error Corrected Interrupt Enable Clear Register. +fieldset/ERR_ENABLE_CLR: + description: Disable the message memory error interrupt. fields: - name: MSGMEM_ENABLE_CLR description: Message RAM SEC Interrupt Pending Enable Clear. Writing a 1 to this bit disables the Message RAM SEC error interrupts. Writing a 0 has no effect. Reads return the corresponding enable bit's current value. bit_offset: 0 bit_size: 1 -fieldset/ERR_SEC_ENABLE_SET: - description: MCAN Single Error Corrected Interrupt Enable Set Register. +fieldset/ERR_ENABLE_SET: + description: Enable the message memory error interrupt. fields: - name: MSGMEM_ENABLE_SET description: Message RAM SEC Interrupt Pending Enable Set. Writing a 1 to this bit enables the Message RAM SEC error interrupts. Writing a 0 has no effect. Reads return the corresponding enable bit's current value. bit_offset: 0 bit_size: 1 -fieldset/ERR_SEC_EOI: - description: MCAN Single Error Corrected End of Interrupt Register. +fieldset/ERR_EOI: + description: End of interrupt for a message memory error. fields: - name: EOI_WR description: Write to this register indicates that software has acknowledged the pending interrupt and the next interrupt can be sent to the host. Note that a write to the MCANERR_ERR_STAT1.CLR_ECC_SEC goes through the SVBUS and has a delayed completion. To avoid an additional interrupt, read the MCANERR_ERR_STAT1 register back prior to writing to this bit field. bit_offset: 0 bit_size: 1 -fieldset/ERR_SEC_STATUS: - description: MCAN Single Error Corrected Interrupt Status Register. +fieldset/ERR_STATUS: + description: Pending status of a message memory error. fields: - name: MSGMEM_PEND description: Message RAM SEC Interrupt Pending 0 No SEC interrupt is pending 1 SEC interrupt is pending. @@ -891,25 +838,6 @@ fieldset/ERR_VECTOR: description: Read Completion Flag. bit_offset: 24 bit_size: 1 -fieldset/ERR_WRAP_REV: - description: MCAN ECC Wrapper Revision Register. - fields: - - name: REVMIN - description: Minor Revision of the Error Aggregator. - bit_offset: 0 - bit_size: 6 - - name: REVMAJ - description: Major Revision of the Error Aggregator. - bit_offset: 8 - bit_size: 3 - - name: MODULE_ID - description: Module Identification Number. - bit_offset: 16 - bit_size: 12 - - name: SCHEME - description: PID Register Scheme. - bit_offset: 30 - bit_size: 2 fieldset/EVT_MODE: description: Event Mode. fields: @@ -1090,14 +1018,13 @@ fieldset/IIDX: fieldset/ILE: description: MCAN Interrupt Line Enable. fields: - - name: EINT0 - description: Enable Interrupt Line 0 0 Interrupt Line 0 is disabled 1 Interrupt Line 0 is enabled. + - name: EINT + description: Enable the MCAN interrupt line. bit_offset: 0 bit_size: 1 - - name: EINT1 - description: Enable Interrupt Line 1 0 Interrupt Line 1 is disabled 1 Interrupt Line 1 is enabled. - bit_offset: 1 - bit_size: 1 + array: + len: 2 + stride: 1 fieldset/ILS: description: MCAN Interrupt Line Select. fields: @@ -1682,41 +1609,13 @@ fieldset/SUBSYS_EXT_TS_UNSERVICED_INTR_CNTR: description: External Timestamp Counter Unserviced Rollover Interrupts. If this value is > 1, an MCANSS_EOI write of '1' to bit 0 will issue another interrupt. The status of this bit field is affected by the MCANSS_IRS.EXT_TS_CNTR_OVFL bit field. bit_offset: 0 bit_size: 5 -fieldset/SUBSYS_ICS: - description: MCAN Subsystem Interrupt Clear Shadow Register. - fields: - - name: EXT_TS_CNTR_OVFL - description: External Timestamp Counter Overflow Interrupt Status Clear. Reads always return a 0. 0 Write of '0' has no effect 1 Write of '1' clears the MCANSS_IRS.EXT_TS_CNTR_OVFL bit. - bit_offset: 0 - bit_size: 1 -fieldset/SUBSYS_IE: - description: MCAN Subsystem Interrupt Enable Register. +fieldset/SUBSYS_INT: + description: Subsystem interrupt, one bit per source. fields: - name: EXT_TS_CNTR_OVFL description: External Timestamp Counter Overflow Interrupt Enable. A write of '0' has no effect. A write of '1' sets the MCANSS_IES.EXT_TS_CNTR_OVFL bit. bit_offset: 0 bit_size: 1 -fieldset/SUBSYS_IECS: - description: MCAN Subsystem Interrupt Enable Clear Shadow Register. - fields: - - name: EXT_TS_CNTR_OVFL - description: External Timestamp Counter Overflow Interrupt Enable Clear. Reads always return a 0. 0 Write of '0' has no effect 1 Write of '1' clears the MCANSS_IES.EXT_TS_CNTR_OVFL bit. - bit_offset: 0 - bit_size: 1 -fieldset/SUBSYS_IES: - description: MCAN Subsystem Interrupt Enable Status. - fields: - - name: EXT_TS_CNTR_OVFL - description: External Timestamp Counter Overflow Interrupt Enable Status. To set, use the CANSS_IE.EXT_TS_CNTR_OVFL bit. To clear, use the MCANSS_IECS.EXT_TS_CNTR_OVFL bit. 0 External timestamp counter overflow interrupt is not enabled 1 External timestamp counter overflow interrupt is enabled. - bit_offset: 0 - bit_size: 1 -fieldset/SUBSYS_IRS: - description: MCAN Subsystem Interrupt Raw Satus Register. - fields: - - name: EXT_TS_CNTR_OVFL - description: External Timestamp Counter Overflow Interrupt Status. This bit is set by HW or by a SW write of '1'. To clear, use the MCANSS_ICS.EXT_TS_CNTR_OVFL bit. 0 External timestamp counter has not overflowed 1 External timestamp counter has overflowed When this bit is set to '1' by HW or SW, the MCANSS_EXT_TS_UNSERVICED_INTR_CNTR.EXT_TS_INTR_CNTR bit field will increment by 1. - bit_offset: 0 - bit_size: 1 fieldset/SUBSYS_PID: description: MCAN Subsystem Revision Register. fields: From 651fa048f28d663911ee6ce317b6ac47db7221c4 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 23:48:56 +0100 Subject: [PATCH 057/134] sysctl: share one fieldset for the flash protection boundaries The IP and RX protection start and end registers all hold the same 64 byte aligned address; which boundary it is comes from the register name. The two NMI watchdog fault bits become one array on the families with two WWDTs. The clock source enums stay one per mux even where two list the same pair: SYSPLLREF and MFPCLKSRC both choose between SYSOSC and HFCLK, and sharing a type would let either be written to the other field. FWPROTMAINDATA and SHUTDNSTORE keep a fieldset each for the same reason - one 8-bit DATA field apiece is a coincidence, not a shared meaning. --- .../sysctl_g350x_g310x_g150x_g110x.yaml | 11 ++--- data/registers/sysctl_g351x_g151x.yaml | 46 +++++-------------- data/registers/sysctl_l122x_l222x.yaml | 35 +++----------- 3 files changed, 24 insertions(+), 68 deletions(-) diff --git a/data/registers/sysctl_g350x_g310x_g150x_g110x.yaml b/data/registers/sysctl_g350x_g310x_g150x_g110x.yaml index c12176d..ef9b045 100644 --- a/data/registers/sysctl_g350x_g310x_g150x_g110x.yaml +++ b/data/registers/sysctl_g350x_g310x_g150x_g110x.yaml @@ -576,14 +576,13 @@ fieldset/NMI: description: Clr the BORLVL NMI. bit_offset: 0 bit_size: 1 - - name: WWDT0 - description: Watch Dog 0 Fault. + - name: WWDT + description: Watch dog fault, one bit per WWDT instance. bit_offset: 1 bit_size: 1 - - name: WWDT1 - description: Watch Dog 0 Fault. - bit_offset: 2 - bit_size: 1 + array: + len: 2 + stride: 1 - name: LFCLKFAIL description: LFXT-EXLF Monitor Fail. bit_offset: 3 diff --git a/data/registers/sysctl_g351x_g151x.yaml b/data/registers/sysctl_g351x_g151x.yaml index 0437540..31e9f6d 100644 --- a/data/registers/sysctl_g351x_g151x.yaml +++ b/data/registers/sysctl_g351x_g151x.yaml @@ -225,19 +225,19 @@ block/SYSCTL: - name: FRXPROTMAINSTART description: Flash RX Protection Start Address. byte_offset: 12312 - fieldset: FRXPROTMAINSTART + fieldset: FPROTADDR - name: FRXPROTMAINEND description: Flash RX Protection End Address. byte_offset: 12316 - fieldset: FRXPROTMAINEND + fieldset: FPROTADDR - name: FIPPROTMAINSTART description: Flash IP Protection Start Address. byte_offset: 12320 - fieldset: FIPPROTMAINSTART + fieldset: FPROTADDR - name: FIPPROTMAINEND description: Flash IP Protection End Address. byte_offset: 12324 - fieldset: FIPPROTMAINEND + fieldset: FPROTADDR - name: FLBANKSWPPOLICY description: Flash Bank Swap Policy. byte_offset: 12344 @@ -404,18 +404,11 @@ fieldset/FCCCMD: bit_offset: 24 bit_size: 8 enum: FCCCMD_KEY -fieldset/FIPPROTMAINEND: - description: Flash IP Protection End Address. +fieldset/FPROTADDR: + description: A flash protection boundary address. fields: - name: ADDR - description: Flash IP Protection End Address 64B granularity. - bit_offset: 6 - bit_size: 16 -fieldset/FIPPROTMAINSTART: - description: Flash IP Protection Start Address. - fields: - - name: ADDR - description: Flash IP Protection Start Address 64B granularity. + description: The boundary address, at 64 byte granularity. bit_offset: 6 bit_size: 16 fieldset/FLBANKSWP: @@ -432,20 +425,6 @@ fieldset/FLBANKSWPPOLICY: description: '1: Disables Policy To Allow Flash Bank Swapping.' bit_offset: 0 bit_size: 1 -fieldset/FRXPROTMAINEND: - description: Flash RX Protection End Address. - fields: - - name: ADDR - description: Flash RX Protection End Address 64B granularity. - bit_offset: 6 - bit_size: 16 -fieldset/FRXPROTMAINSTART: - description: Flash RX Protection Start Address. - fields: - - name: ADDR - description: Flash RX Protection Start Address 64B granularity. - bit_offset: 6 - bit_size: 16 fieldset/FWENABLE: description: Security Firewall Enable Register. fields: @@ -695,14 +674,13 @@ fieldset/NMI: description: Clr the BORLVL NMI. bit_offset: 0 bit_size: 1 - - name: WWDT0 - description: Watch Dog 0 Fault. + - name: WWDT + description: Watch dog fault, one bit per WWDT instance. bit_offset: 1 bit_size: 1 - - name: WWDT1 - description: Watch Dog 0 Fault. - bit_offset: 2 - bit_size: 1 + array: + len: 2 + stride: 1 - name: LFCLKFAIL description: LFXT-EXLF Monitor Fail. bit_offset: 3 diff --git a/data/registers/sysctl_l122x_l222x.yaml b/data/registers/sysctl_l122x_l222x.yaml index e632615..3809069 100644 --- a/data/registers/sysctl_l122x_l222x.yaml +++ b/data/registers/sysctl_l122x_l222x.yaml @@ -197,19 +197,19 @@ block/SYSCTL: - name: FRXPROTMAINSTART description: Flash RX Protection Start Address. byte_offset: 12312 - fieldset: FRXPROTMAINSTART + fieldset: FPROTADDR - name: FRXPROTMAINEND description: Flash RX Protection End Address. byte_offset: 12316 - fieldset: FRXPROTMAINEND + fieldset: FPROTADDR - name: FIPPROTMAINSTART description: Flash IP Protection Start Address. byte_offset: 12320 - fieldset: FIPPROTMAINSTART + fieldset: FPROTADDR - name: FIPPROTMAINEND description: Flash IP Protection End Address. byte_offset: 12324 - fieldset: FIPPROTMAINEND + fieldset: FPROTADDR - name: FLBANKSWPPOLICY description: Flash Bank Swap Policy. byte_offset: 12344 @@ -364,18 +364,11 @@ fieldset/FCCCMD: bit_offset: 24 bit_size: 8 enum: FCCCMD_KEY -fieldset/FIPPROTMAINEND: - description: Flash IP Protection End Address. +fieldset/FPROTADDR: + description: A flash protection boundary address. fields: - name: ADDR - description: Flash IP Protection End Address 64B granularity. - bit_offset: 6 - bit_size: 16 -fieldset/FIPPROTMAINSTART: - description: Flash IP Protection Start Address. - fields: - - name: ADDR - description: Flash IP Protection Start Address 64B granularity. + description: The boundary address, at 64 byte granularity. bit_offset: 6 bit_size: 16 fieldset/FLBANKSWP: @@ -392,20 +385,6 @@ fieldset/FLBANKSWPPOLICY: description: '1: Disables Policy To Allow Flash Bank Swapping.' bit_offset: 0 bit_size: 1 -fieldset/FRXPROTMAINEND: - description: Flash RX Protection End Address. - fields: - - name: ADDR - description: Flash RX Protection End Address 64B granularity. - bit_offset: 6 - bit_size: 16 -fieldset/FRXPROTMAINSTART: - description: Flash RX Protection Start Address. - fields: - - name: ADDR - description: Flash RX Protection Start Address 64B granularity. - bit_offset: 6 - bit_size: 16 fieldset/FWENABLE: description: Security Firewall Enable Register. fields: From a949a638585e3340b4787aaad4ce0cca9f073482 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 23:51:10 +0100 Subject: [PATCH 058/134] mathacl: read the operands and results as plain words Each of the four carried a fieldset holding one DATA field across the whole register. The GPRCM reset bits lose their enums too, which no other block gives them. OP1 and OP2 are not an array: hw_mathacl.h puts OP2 at 0x1118 and OP1 at 0x111c. --- data/registers/mathacl_v1.yaml | 72 ---------------------------------- 1 file changed, 72 deletions(-) diff --git a/data/registers/mathacl_v1.yaml b/data/registers/mathacl_v1.yaml index 97b9fee..7e310ac 100644 --- a/data/registers/mathacl_v1.yaml +++ b/data/registers/mathacl_v1.yaml @@ -30,19 +30,15 @@ block/MATHACL: - name: OP2 description: Operand 2 register. byte_offset: 4376 - fieldset: OP2 - name: OP1 description: Operand 1 register. byte_offset: 4380 - fieldset: OP1 - name: RES1 description: Result 1 register. byte_offset: 4384 - fieldset: RES1 - name: RES2 description: Result 2 register. byte_offset: 4388 - fieldset: RES2 - name: STATUS description: Status Register. byte_offset: 4400 @@ -82,20 +78,6 @@ fieldset/CTL: description: 'Number of iterations, applicable if the function does the computations iteratively, for example sine/cosine/atan2/sqrt. Note: A value of 0 is interpreted as 31.' bit_offset: 24 bit_size: 5 -fieldset/OP1: - description: Operand 1 register. - fields: - - name: DATA - description: Operand 1 Register - bit_offset: 0 - bit_size: 32 -fieldset/OP2: - description: Operand 2 register. - fields: - - name: DATA - description: Operand 1 Register - bit_offset: 0 - bit_size: 32 fieldset/PWREN: description: Power enable. fields: @@ -108,20 +90,6 @@ fieldset/PWREN: bit_offset: 24 bit_size: 8 enum: PWREN_KEY -fieldset/RES1: - description: Result 1 register. - fields: - - name: DATA - description: Result 1 Register - bit_offset: 0 - bit_size: 32 -fieldset/RES2: - description: Result 2 register. - fields: - - name: DATA - description: Result 2 Register - bit_offset: 0 - bit_size: 32 fieldset/RSTCTL: description: Reset Control. fields: @@ -129,12 +97,10 @@ fieldset/RSTCTL: description: Assert reset to the peripheral. bit_offset: 0 bit_size: 1 - enum: RESETASSERT - name: RESETSTKYCLR description: Clear the RESETSTKY bit in the STAT register. bit_offset: 1 bit_size: 1 - enum: RESETSTKYCLR - name: KEY description: Unlock key B1h = KEY to allow write access to this register bit_offset: 24 @@ -147,7 +113,6 @@ fieldset/STAT: description: This bit indicates, if the peripheral was reset, since this bit was cleared by RESETSTKYCLR in the RSTCTL register. bit_offset: 16 bit_size: 1 - enum: RESETSTKY fieldset/STATUS: description: Status Register. fields: @@ -168,7 +133,6 @@ fieldset/STATUS: description: MATHACL busy bit. bit_offset: 8 bit_size: 1 - enum: BUSY fieldset/STATUSCLR: description: Status flag clear register. fields: @@ -184,15 +148,6 @@ fieldset/STATUSCLR: description: Write 1 to this bit to clear STATUS.ERR field. bit_offset: 2 bit_size: 1 -enum/BUSY: - bit_size: 1 - variants: - - name: DONE - description: Compute has completed. - value: 0 - - name: NOTDONE - description: Compute ongoing. - value: 1 enum/ERR: bit_size: 2 variants: @@ -342,33 +297,6 @@ enum/QVAL: - name: Q31 description: Q31 operands. value: 31 -enum/RESETASSERT: - bit_size: 1 - variants: - - name: NOP - description: Writing 0 has no effect. - value: 0 - - name: ASSERT - description: Assert reset. - value: 1 -enum/RESETSTKY: - bit_size: 1 - variants: - - name: NORES - description: The peripheral has not been reset since this bit was last cleared by RESETSTKYCLR in the RSTCTL register. - value: 0 - - name: RESET - description: The peripheral was reset since the last bit clear. - value: 1 -enum/RESETSTKYCLR: - bit_size: 1 - variants: - - name: NOP - description: Writing 0 has no effect. - value: 0 - - name: CLR - description: Clear reset sticky bit. - value: 1 enum/RESET_KEY: bit_size: 8 variants: From 81adfbc1e79e0594c189a442069f3cab7af1c3e9 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sat, 8 Aug 2026 23:53:15 +0100 Subject: [PATCH 059/134] flashctl, wwdt, beeper, unicomm uart: array the pairs, drop the enums naming a range Five flashctl enums listed only the smallest and largest value a count can take, and BANKID enumerated the same banks as BANK. The wwdt registers all repeated the peripheral's name, and its two window settings and the ECC values of one flash command are arrays. The unicomm UART gets the same LINC array as uart_v1. --- data/registers/beeper_v1.yaml | 10 ---- data/registers/flashctl_v1.yaml | 79 +++--------------------------- data/registers/unicommuart_v1.yaml | 11 ++--- data/registers/wwdt_v1.yaml | 20 ++++---- 4 files changed, 20 insertions(+), 100 deletions(-) diff --git a/data/registers/beeper_v1.yaml b/data/registers/beeper_v1.yaml index f48c838..b688240 100644 --- a/data/registers/beeper_v1.yaml +++ b/data/registers/beeper_v1.yaml @@ -12,21 +12,11 @@ fieldset/BEEPCFG: description: Beeper output enable bit_offset: 0 bit_size: 1 - enum: EN - name: FREQ description: Beeper Output Frequency Configuration bit_offset: 4 bit_size: 2 enum: FREQ -enum/EN: - bit_size: 1 - variants: - - name: DISABLE - description: Beeper Output Disabled - value: 0 - - name: ENABLE - description: Beeper Output Enabled - value: 1 enum/FREQ: bit_size: 2 variants: diff --git a/data/registers/flashctl_v1.yaml b/data/registers/flashctl_v1.yaml index 33268da..1d0235b 100644 --- a/data/registers/flashctl_v1.yaml +++ b/data/registers/flashctl_v1.yaml @@ -166,7 +166,6 @@ fieldset/BANKINFO0: description: 'Main region size in sectors Minimum: 0x8 (8) Maximum: 0x200 (512).' bit_offset: 0 bit_size: 12 - enum: BANKINFO_MAINSIZE fieldset/BANKINFO1: description: Bank Information Register 1. fields: @@ -174,17 +173,14 @@ fieldset/BANKINFO1: description: 'Non-main region size in sectors Minimum: 0x0 (0) Maximum: 0x10 (16).' bit_offset: 0 bit_size: 8 - enum: BANKINFO_NONMAINSIZE - name: TRIMSIZE description: 'Trim region size in sectors Minimum: 0x0 (0) Maximum: 0x10 (16).' bit_offset: 8 bit_size: 8 - enum: BANKINFO_TRIMSIZE - name: ENGRSIZE description: 'Engr region size in sectors Minimum: 0x0 (0) Maximum: 0x10 (16).' bit_offset: 16 bit_size: 8 - enum: BANKINFO_ENGRSIZE fieldset/CFGCMD: description: Command Configuration Register. fields: @@ -290,14 +286,13 @@ fieldset/CMDCTL: fieldset/CMDDATAECC: description: Command Data Register ECC 0. fields: - - name: VAL0 - description: ECC data for bits 63:0 of the data is placed here. + - name: VAL + description: 'ECC data for one 64 bit half of the data: index 0 covers bits 63:0 and index 1 bits 127:64.' bit_offset: 0 bit_size: 8 - - name: VAL1 - description: ECC data for bits 127:64 of the data is placed here. - bit_offset: 8 - bit_size: 8 + array: + len: 2 + stride: 8 fieldset/CMDDATAINDEX: description: Command Data Index Register. fields: @@ -378,7 +373,6 @@ fieldset/GBLINFO0: description: 'Number of banks instantiated Minimum: 1 Maximum: 5.' bit_offset: 16 bit_size: 3 - enum: NUMBANKS fieldset/GBLINFO1: description: Global Information Register 1. fields: @@ -435,7 +429,7 @@ fieldset/STATADDR: description: Current Bank ID A bank indicator is stored in this register which represents the current bank on which the state machine is operating. There is 1 bit per bank. bit_offset: 21 bit_size: 5 - enum: BANKID + enum: BANK fieldset/STATCMD: description: Command Status Register. fields: @@ -521,60 +515,6 @@ enum/BANK: - name: BANK4 description: Bank 4. value: 16 -enum/BANKID: - bit_size: 5 - variants: - - name: BANK0 - description: Bank 0. - value: 1 - - name: BANK1 - description: Bank 1. - value: 2 - - name: BANK2 - description: Bank 2. - value: 4 - - name: BANK3 - description: Bank 3. - value: 8 - - name: BANK4 - description: Bank 4. - value: 16 -enum/BANKINFO_ENGRSIZE: - bit_size: 8 - variants: - - name: MINSECTORS - description: Minimum value of [ENGRSIZE]. - value: 0 - - name: MAXSECTORS - description: Maximum value of [ENGRSIZE]. - value: 32 -enum/BANKINFO_MAINSIZE: - bit_size: 12 - variants: - - name: MINSECTORS - description: Minimum value of [MAINSIZE]. - value: 8 - - name: MAXSECTORS - description: Maximum value of [MAINSIZE]. - value: 512 -enum/BANKINFO_NONMAINSIZE: - bit_size: 8 - variants: - - name: MINSECTORS - description: Minimum value of [NONMAINSIZE]. - value: 0 - - name: MAXSECTORS - description: Maximum value of [NONMAINSIZE]. - value: 32 -enum/BANKINFO_TRIMSIZE: - bit_size: 8 - variants: - - name: MINSECTORS - description: Minimum value of [TRIMSIZE]. - value: 0 - - name: MAXSECTORS - description: Maximum value of [TRIMSIZE]. - value: 32 enum/COMMAND: bit_size: 3 variants: @@ -668,13 +608,6 @@ enum/MODE: - name: ERASEBNK description: Erase Bank. value: 15 -enum/NUMBANKS: - bit_size: 3 - variants: - - name: MINBANKS - value: 1 - - name: MAXBANKS - value: 5 enum/REDWIDTH: bit_size: 3 variants: diff --git a/data/registers/unicommuart_v1.yaml b/data/registers/unicommuart_v1.yaml index 0b06b36..d296309 100644 --- a/data/registers/unicommuart_v1.yaml +++ b/data/registers/unicommuart_v1.yaml @@ -242,14 +242,13 @@ fieldset/CPU_INT: description: Enable Positive Edge on UARTxRXD Interrupt. bit_offset: 6 bit_size: 1 - - name: LINC0 - description: Enable LIN Capture 0 / Match Interrupt. + - name: LINC + description: 'LIN capture interrupt. Index 0 is the capture-or-match interrupt on the falling RXD edge, index 1 the capture interrupt on the rising edge.' bit_offset: 7 bit_size: 1 - - name: LINC1 - description: Enable LIN Capture 1 Interrupt. - bit_offset: 8 - bit_size: 1 + array: + len: 2 + stride: 1 - name: LINOVF description: Enable LIN Hardware Counter Overflow Interrupt. bit_offset: 9 diff --git a/data/registers/wwdt_v1.yaml b/data/registers/wwdt_v1.yaml index 7fb6282..9f29986 100644 --- a/data/registers/wwdt_v1.yaml +++ b/data/registers/wwdt_v1.yaml @@ -73,19 +73,19 @@ block/WWDT: byte_offset: 4348 access: Read fieldset: DESC - - name: WWDTCTL0 + - name: CTL0 description: Window Watchdog Timer Control Register 0. byte_offset: 4352 fieldset: WWDTCTL0 - - name: WWDTCTL1 + - name: CTL1 description: Window Watchdog Timer Control Register 0. byte_offset: 4356 fieldset: WWDTCTL1 - - name: WWDTCNTRST + - name: CNTRST description: Window Watchdog Timer Counter Reset Register. byte_offset: 4360 fieldset: WWDTCNTRST - - name: WWDTSTAT + - name: STAT description: Window Watchdog Timer Status Register. byte_offset: 4364 access: Read @@ -198,15 +198,13 @@ fieldset/WWDTCTL0: bit_offset: 4 bit_size: 3 enum: PER - - name: WINDOW0 - description: Closed window period in percentage of the timer interval. WWDTCTL1.WINSEL determines the active window setting (WWDTCTL0.WINDOW0 or WWDTCTL0.WINDOW1). + - name: WINDOW + description: Closed window period in percentage of the timer interval. CTL1.WINSEL picks which of the two is active. bit_offset: 8 bit_size: 3 - enum: WINDOW - - name: WINDOW1 - description: Closed window period in percentage of the timer interval. WWDTCTL1.WINSEL determines the active window setting (WWDTCTL0.WINDOW0 or WWDTCTL0.WINDOW1). - bit_offset: 12 - bit_size: 3 + array: + len: 2 + stride: 4 enum: WINDOW - name: MODE description: Window Watchdog Timer Mode. From 01b535cce51b542986c2d3f12c73afa441f45088 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 00:02:10 +0100 Subject: [PATCH 060/134] registers: move the evidence for SVD deviations out of the YAML chiptool fmt strips comments, so a note recorded only in the YAML is one fmt away from being lost - and a deviation nobody can find the reason for gets corrected back to the SVD by the next person. The provenance now lives in data/registers/README.md, keyed by block and field. Notes about what a register means rather than where it came from move into the description text, which survives fmt and reaches the generated docs. --- data/registers/README.md | 56 ++++++++++++++++++++ data/registers/cpuss_v1.yaml | 3 +- data/registers/gpio_v1.yaml | 5 +- data/registers/iomux_v1.yaml | 3 +- data/registers/sysctl_c1105_c1106.yaml | 6 --- data/registers/sysctl_c110x.yaml | 6 --- data/registers/sysctl_l110x_l130x_l134x.yaml | 4 -- 7 files changed, 59 insertions(+), 24 deletions(-) create mode 100644 data/registers/README.md diff --git a/data/registers/README.md b/data/registers/README.md new file mode 100644 index 0000000..8f6db77 --- /dev/null +++ b/data/registers/README.md @@ -0,0 +1,56 @@ +# Register blocks + +These files are [chiptool](https://github.com/embassy-rs/chiptool) IR. They start life as +`chiptool extract-peripheral` output from the SVDs in `sources/svd/`, but **the checked-in YAML is +the source of truth**, not the SVD: it is hand-cleaned afterwards, and in the places listed below it +deliberately contradicts the SVD. The cleanup rules are in the [root README](../../README.md). + +Keep this file up to date rather than commenting the YAML. `chiptool fmt` silently strips comments, +so anything recorded only there is one `fmt` away from being lost โ€” which is also why a deviation +that is not written down here tends to get "corrected" back to the SVD by the next person. + +Notes about what a register *means*, as opposed to where its definition came from, belong in the +`description:` text instead. Those survive `fmt` and reach the generated PAC's documentation. + +## Deviations from the SVD + +Each entry says what the SVD claims, what this repo says instead, and what the evidence is. + +### `sysctl_c110x`, `sysctl_c1105_c1106` โ€” three unlock-protected control bits are at bit 0 + +| fieldset | field | +| --- | --- | +| `EXLFCTL` | `SETUSEEXLF` | +| `EXRSTPIN` | `DISABLE` | +| `SYSSTATUSCLR` | `ALLECC` | + +The C-series SVD puts each of these at bit 2. That contradicts SLAU893 and every other family's +SYSCTL, which agree on bit 0. Taking the SVD's word for it would produce a HAL whose writes to these +three registers never take effect. + +### `sysctl_l110x_l130x_l134x` โ€” `RSTCAUSE.BOOTSW` is 13 + +The SVD says 10, copied from SLAU847's field description for this SYSCTL variant. That description +contradicts the same TRM's "Reset Causes" table, which lists 10 as reserved and 13 as the +software-triggered BOOTRST. Every other family agrees with the table. `BOOTWWDT0` in the same enum +had already been corrected against that table for the same reason. + +### `cpuss_v1` โ€” the interrupt group's `MIS`, `ISET` and `ICLR` are 8 bits + +The SVD gives these three a single bit, where it gives `IMASK` and `RIS` eight. `hw_cpuss.h` masks +all five with `0xFF`, and the group's `IIDX` enumerates `INT0` to `INT7`, so a single bit would leave +only interrupt 0 reachable through three of the five registers. + +### `gpio_v1` โ€” the generic event masks are indexed by absolute DIO number + +The SVD bases both generic event blocks at bit 0 with sixteen bits each. `hw_gpio.h` puts +`GEN_EVENT0`'s `DIO0` to `DIO15` at bits 0 to 15 but `GEN_EVENT1`'s `DIO16` to `DIO31` at bits **16 +to 31**. Both blocks now share the same 32-bit fieldset as `CPU_INT` and the `DOUT`/`DIN` masks, so +one bit index means one DIO number everywhere; each event block implements only its own half. + +### `tim_v1` โ€” `CCD` and `CCU` are offset-list arrays + +Not a contradiction, but the reason a plain stride does not appear here: capture/compare channels 4 +and 5 are compare-only and sit *above* the `CCU` bits rather than next to `CCD0` to `CCD3` +(`CCD` at bits 4-7 and 12-13, `CCU` at 8-11 and 14-15, per `hw_gptimer.h`). The arrays use chiptool's +explicit `offsets:` list so that `ccd(n)` and `ccu(n)` still take a channel number. diff --git a/data/registers/cpuss_v1.yaml b/data/registers/cpuss_v1.yaml index 6c41270..12eea72 100644 --- a/data/registers/cpuss_v1.yaml +++ b/data/registers/cpuss_v1.yaml @@ -22,9 +22,8 @@ block/CPUSS: description: Prefetch/Cache control. byte_offset: 4864 fieldset: CTL -# The interrupt group is shared across all CPUSS variants. block/INT_GROUP: - description: Interrupt group. + description: Interrupt group. Shared across all CPUSS variants. items: - name: IIDX description: Interrupt index diff --git a/data/registers/gpio_v1.yaml b/data/registers/gpio_v1.yaml index 88c8855..a93580f 100644 --- a/data/registers/gpio_v1.yaml +++ b/data/registers/gpio_v1.yaml @@ -258,10 +258,7 @@ fieldset/DESC: bit_offset: 16 bit_size: 16 fieldset/DIO: - # Every one of these registers is a mask over the port's 32 DIO pins, indexed by DIO number. - # In the two generic event blocks only one half is implemented: GEN_EVENT0 has DIO0 to DIO15 and - # GEN_EVENT1 has DIO16 to DIO31, each at the bit matching its own DIO number. - description: A mask over the port's DIO pins. + description: 'A mask over the port''s 32 DIO pins, indexed by DIO number. The two generic event blocks implement one half each: GEN_EVENT0 has DIO0 to DIO15 and GEN_EVENT1 has DIO16 to DIO31, each at the bit matching its own DIO number.' fields: - name: DIO description: One bit per DIO pin. diff --git a/data/registers/iomux_v1.yaml b/data/registers/iomux_v1.yaml index ded82b5..c928367 100644 --- a/data/registers/iomux_v1.yaml +++ b/data/registers/iomux_v1.yaml @@ -2,8 +2,7 @@ block/IOMUX: description: PERIPHERALREGION. items: - name: PINCM - description: Pin Control Management Register in SECCFG region. - # Note: the HAL is responsible for checking entry in the PINCM array is valid to access. + description: Pin Control Management Register in SECCFG region. The array is sized for the largest device, so the caller is responsible for checking that an entry exists on the chip in hand before accessing it. array: len: 251 stride: 4 diff --git a/data/registers/sysctl_c1105_c1106.yaml b/data/registers/sysctl_c1105_c1106.yaml index 917b7d4..b383011 100644 --- a/data/registers/sysctl_c1105_c1106.yaml +++ b/data/registers/sysctl_c1105_c1106.yaml @@ -261,8 +261,6 @@ fieldset/EXLFCTL: fields: - name: SETUSEEXLF description: Use external LF CLK IN. - # The C-series SVD puts this at bit 2, which contradicts SLAU893 and every other family's - # SYSCTL. A write at bit 2 would never take effect. bit_offset: 0 bit_size: 1 - name: KEY @@ -274,8 +272,6 @@ fieldset/EXRSTPIN: fields: - name: DISABLE description: Disable External Reset. - # The C-series SVD puts this at bit 2, which contradicts SLAU893 and every other family's - # SYSCTL. A write at bit 2 would never take effect. bit_offset: 0 bit_size: 1 - name: KEY @@ -636,8 +632,6 @@ fieldset/SYSSTATUSCLR: fields: - name: ALLECC description: Clear ALL ECC related SYSSTATUS indicators. - # The C-series SVD puts this at bit 2, which contradicts SLAU893 and every other family's - # SYSCTL. A write at bit 2 would never take effect. bit_offset: 0 bit_size: 1 - name: KEY diff --git a/data/registers/sysctl_c110x.yaml b/data/registers/sysctl_c110x.yaml index f315179..77dece3 100644 --- a/data/registers/sysctl_c110x.yaml +++ b/data/registers/sysctl_c110x.yaml @@ -215,8 +215,6 @@ fieldset/EXLFCTL: fields: - name: SETUSEEXLF description: Use external LF CLK IN. - # The C-series SVD puts this at bit 2, which contradicts SLAU893 and every other family's - # SYSCTL. A write at bit 2 would never take effect. bit_offset: 0 bit_size: 1 - name: KEY @@ -228,8 +226,6 @@ fieldset/EXRSTPIN: fields: - name: DISABLE description: Disable External Reset. - # The C-series SVD puts this at bit 2, which contradicts SLAU893 and every other family's - # SYSCTL. A write at bit 2 would never take effect. bit_offset: 0 bit_size: 1 - name: KEY @@ -506,8 +502,6 @@ fieldset/SYSSTATUSCLR: fields: - name: ALLECC description: Clear ALL ECC related SYSSTATUS indicators. - # The C-series SVD puts this at bit 2, which contradicts SLAU893 and every other family's - # SYSCTL. A write at bit 2 would never take effect. bit_offset: 0 bit_size: 1 - name: KEY diff --git a/data/registers/sysctl_l110x_l130x_l134x.yaml b/data/registers/sysctl_l110x_l130x_l134x.yaml index e934785..69d4eaf 100644 --- a/data/registers/sysctl_l110x_l130x_l134x.yaml +++ b/data/registers/sysctl_l110x_l130x_l134x.yaml @@ -807,10 +807,6 @@ enum/ID: - name: BOOTEXNRST description: NRST triggered BOOTRST (<1s hold). value: 12 - # The SVD puts this at 10, copied from SLAU847's RSTCAUSE field description for this SYSCTL - # variant. That description contradicts the same TRM's "Reset Causes" table, which lists 10 as - # reserved and 13 as the software-triggered BOOTRST, and every other family agrees with the table. - # BOOTWWDT0 below was already corrected against that table for the same reason. - name: BOOTSW description: Software triggered BOOTRST. value: 13 From 1b3a1a3f86f87c2d724cb4d235b42d422929aa66 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 00:03:16 +0100 Subject: [PATCH 061/134] metapac-gen: only read .yaml out of data/registers The glob took every file in the directory, so the README added next to the blocks was parsed as IR and aborted the build. --- mspm0-metapac-gen/src/registers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mspm0-metapac-gen/src/registers.rs b/mspm0-metapac-gen/src/registers.rs index ee367cb..f11b09b 100644 --- a/mspm0-metapac-gen/src/registers.rs +++ b/mspm0-metapac-gen/src/registers.rs @@ -31,7 +31,7 @@ pub fn generate(out_dir: &Path) -> anyhow::Result<()> { let re = Regex::new("# *! *\\[.*\\]").unwrap(); - for f in glob::glob("data/registers/*").unwrap() { + for f in glob::glob("data/registers/*.yaml").unwrap() { let f = f.unwrap(); if !f.is_file() { From 29ff5b6d7a3182052615b859284c37a6cbaf6513 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 00:05:39 +0100 Subject: [PATCH 062/134] registers: say which blocks the transforms do not reproduce Re-running a transform over a checked-in block silently discards the hand work, and seven blocks have no transform at all. --- data/registers/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/data/registers/README.md b/data/registers/README.md index 8f6db77..444e172 100644 --- a/data/registers/README.md +++ b/data/registers/README.md @@ -12,6 +12,23 @@ that is not written down here tends to get "corrected" back to the SVD by the ne Notes about what a register *means*, as opposed to where its definition came from, belong in the `description:` text instead. Those survive `fmt` and reach the generated PAC's documentation. +## How much of this is reproducible + +Not all of it. `transforms/` gets a block most of the way there, but **re-running a transform does +not reproduce the checked-in YAML**, so it cannot be treated as a regeneration step: + +- Seven blocks have no transform at all and are maintained entirely by hand: `beeper_v1`, + `cpuss_v1`, `factoryregion_v1`, `sysctl_c1105_c1106`, `tim_btimer`, `unicomm_v1`, `vref_v1`. +- Where a transform does exist, the deviations below are not in it. Running the C110x SYSCTL + transform over `MSPM0C110X.svd` reproduces the checked-in block *except* for exactly the three + fields listed under it, which come out at the SVD's bit 2. +- The cleanup work โ€” arrays, shared fieldsets, deleted enums โ€” is hand-applied on top and is not + encoded in the transforms either. + +So a transform is a starting point for a *new* block, not something to re-run over an existing one: +doing that discards the hand work silently. Diff the output against the checked-in YAML instead, and +use the flatten-and-compare check described in the root README to see what actually moved. + ## Deviations from the SVD Each entry says what the SVD claims, what this repo says instead, and what the evidence is. From 4372752fde53c38ab6c977a80fb346b865748497 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 00:10:59 +0100 Subject: [PATCH 063/134] transforms: encode the sysctl bit and enum corrections The C110x transform already replaced SWDCFG.DISABLE to move it off the SVD's bit 2; the other three fields with the same problem now get the same treatment, and the L-series RSTCAUSE.BOOTSW value is corrected alongside the BOOTWWDT0 fix that was already there. Both transforms now reproduce their checked-in block exactly. TIM gains the CCD and CCU arrays, which MakeFieldArray can express with mode: Cursed. That does not make tim_v1 reproducible - the transform runs from a four channel TIMA0 - but it keeps the step from being lost. --- data/registers/README.md | 38 +++++++++++++++--------- transforms/SYSCTL_C110x.yaml | 36 ++++++++++++++++++++++ transforms/SYSCTL_L110x_L130x_L134x.yaml | 9 +++++- transforms/TIM.yaml | 14 +++++++++ 4 files changed, 82 insertions(+), 15 deletions(-) diff --git a/data/registers/README.md b/data/registers/README.md index 444e172..c6c3a4d 100644 --- a/data/registers/README.md +++ b/data/registers/README.md @@ -14,20 +14,30 @@ Notes about what a register *means*, as opposed to where its definition came fro ## How much of this is reproducible -Not all of it. `transforms/` gets a block most of the way there, but **re-running a transform does -not reproduce the checked-in YAML**, so it cannot be treated as a regeneration step: - -- Seven blocks have no transform at all and are maintained entirely by hand: `beeper_v1`, - `cpuss_v1`, `factoryregion_v1`, `sysctl_c1105_c1106`, `tim_btimer`, `unicomm_v1`, `vref_v1`. -- Where a transform does exist, the deviations below are not in it. Running the C110x SYSCTL - transform over `MSPM0C110X.svd` reproduces the checked-in block *except* for exactly the three - fields listed under it, which come out at the SVD's bit 2. -- The cleanup work โ€” arrays, shared fieldsets, deleted enums โ€” is hand-applied on top and is not - encoded in the transforms either. - -So a transform is a starting point for a *new* block, not something to re-run over an existing one: -doing that discards the hand work silently. Diff the output against the checked-in YAML instead, and -use the flatten-and-compare check described in the root README to see what actually moved. +Some of it. `transforms/` gets a block most of the way there, and two of the deviations below are now +encoded in it and verified end to end: + +| block | reproduces the checked-in YAML? | +| --- | --- | +| `sysctl_c110x` | **yes** โ€” `chiptool extract-peripheral --svd sources/svd/MSPM0C110X.svd --peripheral SYSCTL`, then `transforms/transform.sh` with `SYSCTL_C110x.yaml` | +| `sysctl_l110x_l130x_l134x` | **yes** โ€” same, from `MSPM0L130X.svd` | +| everything else with a transform | no, see below | +| `beeper_v1`, `cpuss_v1`, `factoryregion_v1`, `sysctl_c1105_c1106`, `tim_btimer`, `unicomm_v1`, `vref_v1` | no transform exists at all | + +Where a transform does not reproduce its block, the reason is usually that it is written against a +different source instance than the one the block was derived from โ€” `TIM.yaml` runs from `TIMA0` on +MSPM0G350X, which has four capture/compare channels and neither the `DC` nor the `QEIERR` event, so +its output cannot match `tim_v1` however the transform is written. Closing those gaps means +re-deriving the block, not just editing the transform. + +The rest of the cleanup โ€” shared fieldsets, deleted enums, stripped prefixes โ€” is hand-applied on top +and is not encoded anywhere. **Re-running a transform over an existing block therefore discards hand +work silently.** Diff the output against the checked-in YAML instead, and use the flatten-and-compare +check described in the root README to see what actually moved. + +The exception worth copying is `TIM.yaml`'s `CCD`/`CCU` step: `!MakeFieldArray` with `mode: Cursed` +produces exactly the offset lists the checked-in block carries, so that piece of the cleanup at least +survives a re-derivation. ## Deviations from the SVD diff --git a/transforms/SYSCTL_C110x.yaml b/transforms/SYSCTL_C110x.yaml index 2ebe324..0462562 100644 --- a/transforms/SYSCTL_C110x.yaml +++ b/transforms/SYSCTL_C110x.yaml @@ -512,6 +512,18 @@ transforms: bit_size: 8 enum: EXLFCTL_KEY + # EXLFCTL::SETUSEEXLF is at the wrong bit offset, so replace it + - !DeleteFields + fieldset: EXLFCTL + from: SETUSEEXLF + + - !AddFields + fieldset: EXLFCTL + fields: + - name: SETUSEEXLF + bit_offset: 0 + bit_size: 1 + # SHDNIOREL: Add key - !Add ir: @@ -546,6 +558,18 @@ transforms: bit_size: 8 enum: EXRSTPIN_KEY + # EXRSTPIN::DISABLE is at the wrong bit offset, so replace it + - !DeleteFields + fieldset: EXRSTPIN + from: DISABLE + + - !AddFields + fieldset: EXRSTPIN + fields: + - name: DISABLE + bit_offset: 0 + bit_size: 1 + # SYSSTATUSCLR: Add key - !Add ir: @@ -563,6 +587,18 @@ transforms: bit_size: 8 enum: SYSSTATUSCLR_KEY + # SYSSTATUSCLR::ALLECC is at the wrong bit offset, so replace it + - !DeleteFields + fieldset: SYSSTATUSCLR + from: ALLECC + + - !AddFields + fieldset: SYSSTATUSCLR + fields: + - name: ALLECC + bit_offset: 0 + bit_size: 1 + # SWDCFG: Add key - !Add ir: diff --git a/transforms/SYSCTL_L110x_L130x_L134x.yaml b/transforms/SYSCTL_L110x_L130x_L134x.yaml index 4142da2..2a5df00 100644 --- a/transforms/SYSCTL_L110x_L130x_L134x.yaml +++ b/transforms/SYSCTL_L110x_L130x_L134x.yaml @@ -76,15 +76,22 @@ transforms: # RSTCAUSE.ID.SYSWWDT0 should actually be called BOOTWWDT0 and have a value of 14 # RSTCAUSE.ID.SYSWWDT1 does not exist for L-series + # + # BOOTSW is at 10 in the SVD, which SLAU847's "Reset Causes" table contradicts. See + # data/registers/README.md. - !DeleteEnumVariants enum: ID - from: (SYSWWDT0|SYSWWDT1) + from: (SYSWWDT0|SYSWWDT1|BOOTSW) - !AddEnumVariants enum: ID variants: - name: BOOTWWDT0 + description: WWDT0 violation. value: 14 + - name: BOOTSW + description: Software triggered BOOTRST. + value: 13 # RESETCMD: Add KEY - !Add diff --git a/transforms/TIM.yaml b/transforms/TIM.yaml index 9a4f867..61a2e39 100644 --- a/transforms/TIM.yaml +++ b/transforms/TIM.yaml @@ -385,6 +385,20 @@ transforms: from: C0CCP(\d+) to: C0CCP + # Channels 4 and 5 are compare only and sit above the CCU bits rather than next to CCD0-3, so + # these need the offset list rather than a stride. See data/registers/README.md. + - !MakeFieldArray + fieldsets: (IMASK|RIS|MIS|ISET|ICLR) + from: CCD(\d+) + to: CCD + mode: Cursed + + - !MakeFieldArray + fieldsets: (IMASK|RIS|MIS|ISET|ICLR) + from: CCU(\d+) + to: CCU + mode: Cursed + # Ratio is useless - !DeleteEnums from: RATIO From 662f030e83626a21313faa5836694318491328ed Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 00:59:41 +0100 Subject: [PATCH 064/134] registers: fingerprint the generated Rust, not the YAML Flatten-and-compare works from the IR, so it cannot catch anything the generator does on the way to Rust. Note which tag to compare against, too: against an older one the C-series bit-0 corrections look like part of whatever release is being checked. --- data/registers/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/data/registers/README.md b/data/registers/README.md index c6c3a4d..1f7818c 100644 --- a/data/registers/README.md +++ b/data/registers/README.md @@ -43,6 +43,13 @@ survives a re-derivation. Each entry says what the SVD claims, what this repo says instead, and what the evidence is. +When checking a release for unintended movement, fingerprint the *generated* Rust rather than the +YAML โ€” read the register offsets back out of `wrapping_add` and the field positions out of each +accessor's `(shift, mask)`. That covers everything the generator does between IR and Rust, which +flatten-and-compare cannot see. Run it against the **immediately preceding tag**: against an older +one it reports the three C-series fields below moving from bit 2 to bit 0, which is a correction from +an earlier round rather than anything in the release under test. + ### `sysctl_c110x`, `sysctl_c1105_c1106` โ€” three unlock-protected control bits are at bit 0 | fieldset | field | From a21e4d344af05624907695012273779801bb1f8a Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 14:19:12 +0100 Subject: [PATCH 065/134] metapac-gen: format generated code with prettyplease Spawning rustfmt once per file was almost all of this binary's runtime: 33.6s to 2.7s for build-metapac. It also drops an undeclared dependency on rustfmt being installed. The autogenerated banner is prepended after formatting rather than being part of the parsed input, since syn keeps only doc comments. An inner doc attribute would survive but cannot be used here - most of these files are include!d, where an inner attribute is a hard error. --- Cargo.lock | 12 +++++ Cargo.toml | 2 + mspm0-metapac-gen/Cargo.toml | 2 + mspm0-metapac-gen/src/main.rs | 76 +++++++++++++----------------- mspm0-metapac-gen/src/registers.rs | 34 +++++-------- 5 files changed, 60 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e5f356..4e3b3a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -326,12 +326,14 @@ dependencies = [ "glob", "heck", "mspm0-data-types", + "prettyplease", "proc-macro2", "quote", "regex", "serde", "serde_json", "serde_yaml", + "syn 2.0.119", ] [[package]] @@ -361,6 +363,16 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + [[package]] name = "proc-macro2" version = "1.0.107" diff --git a/Cargo.toml b/Cargo.toml index 210216d..fd4bdaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,11 +17,13 @@ glob = "0.3.1" heck = "0.5.0" quote = "1.0.37" lopdf = "0.34.0" +prettyplease = "0.2.31" regex = "1.11.0" serde = "1.0.210" serde_json = "1.0.128" # Pinned to match chiptool, which requires this exact version. serde_yaml = "=0.9.34-deprecated" +syn = { version = "2.0.119", features = ["full", "parsing"] } proc-macro2 = "1.0.86" walkdir = "2.5.0" diff --git a/mspm0-metapac-gen/Cargo.toml b/mspm0-metapac-gen/Cargo.toml index 1edc267..1453c9c 100644 --- a/mspm0-metapac-gen/Cargo.toml +++ b/mspm0-metapac-gen/Cargo.toml @@ -8,8 +8,10 @@ anyhow.workspace = true chiptool.workspace = true heck.workspace = true glob.workspace = true +prettyplease.workspace = true quote.workspace = true proc-macro2.workspace = true +syn.workspace = true regex.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/mspm0-metapac-gen/src/main.rs b/mspm0-metapac-gen/src/main.rs index 805da40..97d9126 100644 --- a/mspm0-metapac-gen/src/main.rs +++ b/mspm0-metapac-gen/src/main.rs @@ -1,10 +1,8 @@ use std::{ collections::{BTreeMap, BTreeSet}, fmt::Write as _, - fs::{self, File}, - io::Write as _, + fs, path::{Path, PathBuf}, - process::Command, }; use anyhow::Context; @@ -49,7 +47,9 @@ fn main() -> anyhow::Result<()> { chips.insert(name, chip); } - // Must generate registers before the PAC to ensure that rustfmt can resolve imports. + // First because it creates `src/`, which krate::generate copies lib.rs into. It no longer has + // to precede the PAC for the formatter's sake: rustfmt followed `mod` declarations into files + // that did not exist yet, and prettyplease parses each file on its own. registers::generate(&out_dir)?; let mut metadata_dedup = BTreeMap::new(); @@ -87,21 +87,14 @@ fn generate_chip_pac( let chip_dir = chips.join(name); fs::create_dir_all(&chip_dir).unwrap(); - let path = chip_dir.join("pac.rs"); - let mut file = File::create(&path).unwrap(); + let interrupts = interrupt::generate(chip); + let peripherals = peripheral::generate(chip, all_peripheral_versions); - writeln!(&mut file, "{AUTOGENERATED_TEXT}").unwrap(); - writeln!(&mut file, "{}", interrupt::generate(chip)).unwrap(); - writeln!( - &mut file, - "{}", - peripheral::generate(chip, all_peripheral_versions) + write_rust( + chip_dir.join("pac.rs"), + "e!(#interrupts #peripherals).to_string(), ) .unwrap(); - - drop(file); - - rustfmt(path); } fn generate_chip_metadata( @@ -115,9 +108,6 @@ fn generate_chip_metadata( let dir = chips_dir.join(name); fs::create_dir_all(&dir).unwrap(); - let path = dir.join("metadata.rs"); - let mut file = File::create(&path).unwrap(); - let mut data = String::new(); write!( &mut data, @@ -141,8 +131,7 @@ fn generate_chip_metadata( let file = format!("metadata_{:04}.rs", n); let path = chips_dir.join(&file); - fs::write(&path, data).unwrap(); - rustfmt(path); + write_rust(&path, &data).unwrap(); file }); @@ -209,11 +198,10 @@ fn generate_chip_metadata( // Memory is the one part of the metadata which varies by part number rather than by family let memory = metadata::memory(chip); + let mut contents = String::new(); write!( - &mut file, + &mut contents, " - {AUTOGENERATED_TEXT} - include!(\"../{deduped_file}\"); static MEMORY: &[MemoryRegion] = {memory}; pub static METADATA: Metadata = Metadata {{ @@ -239,9 +227,7 @@ fn generate_chip_metadata( ) .unwrap(); - drop(file); - - rustfmt(path); + write_rust(dir.join("metadata.rs"), &contents).unwrap(); } fn generate_all_chips<'a>( @@ -262,11 +248,7 @@ fn generate_all_chips<'a>( ]; }; - let all_chips = out_dir.join("src/all_chips.rs"); - fs::write(&all_chips, list.to_string())?; - rustfmt(all_chips); - - Ok(()) + write_rust(out_dir.join("src/all_chips.rs"), &list.to_string()) } fn generate_all_peripheral_versions( @@ -286,18 +268,26 @@ fn generate_all_peripheral_versions( writeln!(&mut contents, "]),")?; } writeln!(&mut contents, "];")?; - let all_peripheral_versions = out_dir.join("src/all_peripheral_versions.rs"); - fs::write(&all_peripheral_versions, contents)?; - rustfmt(all_peripheral_versions); - Ok(()) + write_rust(out_dir.join("src/all_peripheral_versions.rs"), &contents) } -fn rustfmt<P: AsRef<Path>>(path: P) { - Command::new("rustfmt") - .arg(path.as_ref()) - .spawn() - .unwrap() - .wait() - .unwrap(); +/// Write generated Rust to `path`, formatted, with the autogenerated banner on top. +/// +/// prettyplease rather than rustfmt: this binary writes about five hundred files, and spawning +/// rustfmt once per file was 95% of its runtime โ€” 32 seconds of 34. Formatting in-process also +/// drops an undeclared dependency on rustfmt being installed. +/// +/// The banner is prepended after formatting because `syn` keeps only doc comments, so a `//` line +/// would not survive the round trip. Rust allows a comment ahead of the inner attributes that some +/// of these files open with. +pub fn write_rust(path: impl AsRef<Path>, contents: &str) -> anyhow::Result<()> { + let path = path.as_ref(); + let parsed = syn::parse_file(contents) + .with_context(|| format!("generated Rust for {} does not parse", path.display()))?; + + fs::write(path, format!("{AUTOGENERATED_TEXT}\n{}", prettyplease::unparse(&parsed))) + .with_context(|| format!("writing {}", path.display()))?; + + Ok(()) } diff --git a/mspm0-metapac-gen/src/registers.rs b/mspm0-metapac-gen/src/registers.rs index f11b09b..0a918e7 100644 --- a/mspm0-metapac-gen/src/registers.rs +++ b/mspm0-metapac-gen/src/registers.rs @@ -1,9 +1,4 @@ -use std::{ - fs::{self, File}, - io::Write, - path::Path, - str::FromStr, -}; +use std::{fs, path::Path, str::FromStr}; use anyhow::Context; use chiptool::{ @@ -13,7 +8,7 @@ use chiptool::{ use proc_macro2::TokenStream; use regex::Regex; -use crate::{rustfmt, AUTOGENERATED_TEXT}; +use crate::write_rust; pub fn generate(out_dir: &Path) -> anyhow::Result<()> { let common = out_dir.join("src/common.rs"); @@ -77,27 +72,20 @@ pub fn generate(out_dir: &Path) -> anyhow::Result<()> { .unwrap() .to_string_lossy() .replace(".yaml", ".rs"); - let path = out_dir.join(&name); - let mut file = File::create(&path).unwrap(); - - writeln!(file, "{AUTOGENERATED_TEXT}").unwrap(); - writeln!( - file, - r"#![allow(clippy::missing_safety_doc)] - #![allow(clippy::identity_op)] - #![allow(clippy::unnecessary_cast)] - #![allow(clippy::erasing_op)]" - ) - .unwrap(); + // chiptool emits inner attributes between items, which is only valid at the top of a file. let items = items.to_string().replace("] ", "]\n"); let items = re.replace_all(&items, ""); - writeln!(file, "{items}").unwrap(); - file.flush().unwrap(); - drop(file); + let contents = format!( + r"#![allow(clippy::missing_safety_doc)] + #![allow(clippy::identity_op)] + #![allow(clippy::unnecessary_cast)] + #![allow(clippy::erasing_op)] + {items}" + ); - rustfmt(path); + write_rust(out_dir.join(&name), &contents)?; } Ok(()) From 41657dbed57c9c65841a4db86ab60794246ffb6b Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 14:24:06 +0100 Subject: [PATCH 066/134] verify: a version must name a register block that exists Peripheral::version selects data/registers/<type>_<version>.yaml, so a version with no such file promises a block that is not there. RTC claimed legacy and b on 21 chips with neither curated; the perimap entries go, and the check now guards against them coming back. The companion check reports a peripheral whose name matched no prefix. Unknown is not inert - metapac-gen drops those from the metadata, which is how SPG0 went missing on five chips. It is a signal generator subsystem with its own SDK header, so it gets a type. --- mspm0-data-gen/src/generate.rs | 2 ++ mspm0-data-gen/src/perimap.rs | 10 +++--- mspm0-data-gen/src/verify.rs | 59 ++++++++++++++++++++++++++++++++++ mspm0-data-types/src/lib.rs | 19 +++++++++++ 4 files changed, 84 insertions(+), 6 deletions(-) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index d8dff60..a356b19 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -764,6 +764,8 @@ fn get_peripheral_type_version(chip_name: &str, name: &str) -> (PeripheralType, PeripheralType::Opa } else if name.starts_with("RTC") { PeripheralType::Rtc + } else if name.starts_with("SPG") { + PeripheralType::Spgss } else if name.starts_with("SPI") { PeripheralType::Spi } else if name.starts_with("TIMA") { diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index a3c1554..f7b82a9 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -34,12 +34,10 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ // the TRMs and with each other about CTL0 bits 1 and 2, which the YAML resolves in the TRMs' // favour. (".*:vref", "v1"), - // The RTC comes in variants which are not distinguished by a version number. - // SysConfig calls the standalone pre-LFSS peripheral the "legacy" RTC; the variants inside the - // LFSS are RTC_A when the LFSS has an independent VBAT supply and RTC_B when it is powered from - // VDD. - ("mspm0g..0x:rtc", "legacy"), - (".*:rtc", "b"), + // No RTC entry: a version names a register block, and none has been curated. The variants, + // for whoever writes them: sysconfig calls the standalone pre-LFSS peripheral the "legacy" + // RTC, and the ones inside the LFSS are RTC_A when the LFSS has an independent VBAT supply + // and RTC_B when it is powered from VDD. `mspm0g..0x` has the legacy one, everything else B. (".*:factoryregion", "v1"), // SLAU893 describes two C-series SYSCTLs, "SYSCTL_C1103_C1104" and "SYSCTL_C1105_C1106". The // latter is a superset: it adds the HFXT and LFXT crystal drivers, `MCLKCFG.FLASHWAIT` and the diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 6989b84..d48cfec 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -10,6 +10,8 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { core_peripherals, pin_names, gpio_no_duplicates, + peripheral_types_known, + register_blocks_exist, // Peripherals which don't actually exist no_gpamp_c110x_l151x, // Low power data which is only as complete as the data sources @@ -40,6 +42,63 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { .collect() } +/// Every register block `data/registers` provides, by file stem. +static REGISTER_BLOCKS: LazyLock<HashSet<String>> = LazyLock::new(|| { + glob::glob("data/registers/*.yaml") + .unwrap() + .flatten() + .filter_map(|path| Some(path.file_stem()?.to_string_lossy().into_owned())) + .collect() +}); + +/// A peripheral whose name matched no known prefix. +/// +/// `PeripheralType::Unknown` is not inert: `mspm0-metapac-gen` drops those peripherals from the +/// generated metadata, so the chip silently loses one. That is how a source bump which adds a +/// peripheral goes unnoticed, which is why this is an error rather than a note. +fn peripheral_types_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + let unknown = chip + .peripherals + .values() + .filter(|peripheral| peripheral.ty == PeripheralType::Unknown) + .map(|peripheral| peripheral.name.as_str()) + .collect::<Vec<_>>(); + + if !unknown.is_empty() { + bail!( + "{name}: no peripheral type for {}, so it will be dropped from the metadata. Add a \ + prefix to `peripheral_type_from_name` and a `PeripheralType` variant", + unknown.join(", ") + ); + } + + Ok(()) +} + +/// A version names a register block, so the block has to be there. +/// +/// `Peripheral::version` selects `data/registers/<type>_<version>.yaml`. A version with no such +/// file promises the consumer a register block which does not exist; either curate the block or +/// drop the `perimap` entry until it is written. +fn register_blocks_exist(chip: &Chip, name: &str) -> anyhow::Result<()> { + for peripheral in chip.peripherals.values() { + let Some(version) = &peripheral.version else { + continue; + }; + + let block = format!("{}_{version}", peripheral.ty); + + if !REGISTER_BLOCKS.contains(&block) { + bail!( + "{name}: {} claims version {version}, but data/registers/{block}.yaml does not exist", + peripheral.name + ); + } + } + + Ok(()) +} + /// Verify all core peripherals are present. fn core_peripherals(chip: &Chip, name: &str) -> anyhow::Result<()> { if !chip.peripherals.contains_key("CPUSS") { diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index cbb181b..52b737b 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -309,6 +309,9 @@ pub enum PeripheralType { Rtc, + /// Signal generator subsystem, whose instances sysconfig names `SPGx`. + Spgss, + Spi, /// System Controller @@ -376,6 +379,7 @@ impl fmt::Display for PeripheralType { PeripheralType::Npu => "npu", PeripheralType::Opa => "opa", PeripheralType::Rtc => "rtc", + PeripheralType::Spgss => "spgss", PeripheralType::Spi => "spi", PeripheralType::Sysctl => "sysctl", PeripheralType::Tim => "tim", @@ -415,9 +419,24 @@ pub struct Peripheral { #[serde(flatten, rename = "type")] pub ty: PeripheralType, + /// Which register block describes this peripheral. + /// + /// Names the `data/registers/<type>_<version>.yaml` the generated PAC imports, so a version + /// present here is a promise that the block exists โ€” `verify.rs` checks it. Instances of one + /// type normally share a version; where they do not, the version is what tells them apart, as + /// `btimer` does for the basic timers alongside `v1`. + /// + /// `None` when no register block has been curated for this peripheral yet. The peripheral is + /// still described โ€” address, pins, power domain and interrupts are all independent of this โ€” + /// but the PAC emits it as `pub const NAME: () = ();` rather than a typed block. #[serde(skip_serializing_if = "Option::is_none")] pub version: Option<String>, + /// Base address of the peripheral's register block. + /// + /// `None` for a peripheral which has no block of its own because it lives inside another, as + /// GPAMP does inside SYSCTL. Such a peripheral is omitted from the PAC entirely, and the HAL + /// reaches it through its host. #[serde(skip_serializing_if = "Option::is_none")] pub address: Option<u32>, From 571abaf7cf3d1af3773a977774094b4de947b861 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 14:28:40 +0100 Subject: [PATCH 067/134] beeper: finish splitting it out of SYSCTL The register was removed from the SYSCTL maps and beeper_v1.yaml written, but the peripheral was never wired up: no type, no GENERATE_PERIPHERALS entry, nothing synthesized. beeper_v1.rs shipped in every release imported by nothing, and the beeper was unreachable through the PAC. It is on six families rather than the two the block's description claimed, each with BEEPCFG at 0x1190 and the same two fields. Keyed on the family, not the SYSCTL version: l112x and l211x have it and share a version with l122x and l222x, which do not. --- data/registers/beeper_v1.yaml | 2 +- mspm0-data-gen/src/generate.rs | 47 +++++++++++++++++++++++++++++ mspm0-data-types/src/lib.rs | 7 +++++ mspm0-metapac-gen/src/peripheral.rs | 1 + 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/data/registers/beeper_v1.yaml b/data/registers/beeper_v1.yaml index b688240..238b30c 100644 --- a/data/registers/beeper_v1.yaml +++ b/data/registers/beeper_v1.yaml @@ -1,5 +1,5 @@ block/BEEPER: - description: The MSPM0C110x/MSPS003 beeper. This technically exists in the SYSCTL block, but is separated. + description: 'The beeper, addressed through SYSCTL: this is one register of that block, split out so the four SYSCTL variants which have it do not each carry a copy. Present on C110x, C1105/C1106, MSPS003, H321x, L112x and L211x.' items: - name: BEEPCFG description: BEEPER Configuration diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index a356b19..7c50438 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -695,6 +695,53 @@ fn generate_missing( } } + // The beeper is documented as one SYSCTL register and addressed through it, so sysconfig does + // not list it and its register block was carved out of the SYSCTL maps. Every family below + // puts BEEPCFG at 0x1190 with the same two fields, per its hw_sysctl_*.h. + // + // Keyed on the family and not on the SYSCTL version: mspm0l112x and mspm0l211x have the beeper + // and share `sysctl_l122x_l222x` with mspm0l122x and mspm0l222x, which do not. + const BEEPER_FAMILIES: &[&str] = &[ + "mspm0c110x", + "mspm0c1105_c1106", + "msps003fx", + "mspm0h321x", + "mspm0l112x", + "mspm0l211x", + ]; + + if BEEPER_FAMILIES.contains(&chip_name) { + let address = get_peripheral_addresses(chip_name, "SYSCTL", header, sysconfig)? + .context(format!("{chip_name}: BEEPER needs SYSCTL's address"))?; + + let version = PERIMAP + .get(&format!("{}:{}", chip_name, PeripheralType::Beeper)) + .map(|s| s.to_string()); + + peripherals.insert( + "BEEPER".to_string(), + Peripheral { + name: "BEEPER".to_string(), + ty: PeripheralType::Beeper, + version, + address: Some(address), + // It is part of SYSCTL, which is in PD0 on every family that has a beeper. + power_domain: PowerDomain::Pd0, + pins: vec![], + sys_fentries: None, + interrupts: Vec::new(), + block_async: None, + retained_through: None, + usable_through: None, + clocked_in_standby1: None, + timer: None, + clock_range_hz: None, + adc: None, + unicomm: None, + }, + ); + } + Ok(()) } diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 52b737b..348221d 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -259,6 +259,12 @@ pub enum PeripheralType { AesAdv, + /// The beeper, a square wave output driven from LFCLK. + /// + /// Documented as part of SYSCTL and addressed through it, but described here as a peripheral of + /// its own so it gets a register block rather than four registers' worth of SYSCTL variants. + Beeper, + Aes, Canfd, @@ -356,6 +362,7 @@ impl fmt::Display for PeripheralType { PeripheralType::Adc => "adc", PeripheralType::Aes => "aes", PeripheralType::AesAdv => "aesadv", + PeripheralType::Beeper => "beeper", PeripheralType::Canfd => "canfd", PeripheralType::Comp => "comp", PeripheralType::Cpuss => "cpuss", diff --git a/mspm0-metapac-gen/src/peripheral.rs b/mspm0-metapac-gen/src/peripheral.rs index bfd145f..60dffcc 100644 --- a/mspm0-metapac-gen/src/peripheral.rs +++ b/mspm0-metapac-gen/src/peripheral.rs @@ -10,6 +10,7 @@ use quote::quote; /// By name, then version (if any) const GENERATE_PERIPHERALS: &[PeripheralType] = &[ PeripheralType::Adc, + PeripheralType::Beeper, PeripheralType::Cpuss, PeripheralType::Dma, PeripheralType::Canfd, From 78a13096f7f9f096f49a1aa498da4f610a9adb13 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 14:37:29 +0100 Subject: [PATCH 068/134] power domains: state the corrections once, per series The chip-name lists enumerated where sysconfig happens to be wrong today, so a new family got the bug and not the fix - which is what had happened to CRC0 on l112x and l211x, where sysconfig gives the same PD_ULP_AON it gives on every family the list did cover. Those four chips move to PD1, and the datasheet row that follows from it is now recorded. Applying the table by construction is also what enforces the invariant, so the six verify checks asserting the same list go; they could no longer fail. The table is scoped by device series rather than global: MSPM33 and SimpleLink parts lay their domains out differently, and Series::of refuses an unrecognised family rather than silently applying MSPM0's. --- data/operating_modes/mspm0l112x.yaml | 1 + data/operating_modes/mspm0l211x.yaml | 1 + mspm0-data-gen/src/generate.rs | 134 ++++++++++++--------------- mspm0-data-gen/src/verify.rs | 96 +------------------ 4 files changed, 61 insertions(+), 171 deletions(-) diff --git a/data/operating_modes/mspm0l112x.yaml b/data/operating_modes/mspm0l112x.yaml index 10de054..6675f99 100644 --- a/data/operating_modes/mspm0l112x.yaml +++ b/data/operating_modes/mspm0l112x.yaml @@ -9,6 +9,7 @@ retained_through: AESADV: Standby CPUSS: Standby + CRC0: Standby DMA: Standby usable_through: diff --git a/data/operating_modes/mspm0l211x.yaml b/data/operating_modes/mspm0l211x.yaml index 3019f29..f1a750f 100644 --- a/data/operating_modes/mspm0l211x.yaml +++ b/data/operating_modes/mspm0l211x.yaml @@ -9,6 +9,7 @@ retained_through: AESADV: Standby CPUSS: Standby + CRC0: Standby DMA: Standby usable_through: diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 7c50438..61745b1 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -464,22 +464,74 @@ fn get_sys_fentries( Ok(Some(sys_fentries.parse::<usize>().unwrap())) } +/// The device series a family belongs to. +/// +/// Only exists to keep [`POWER_DOMAIN_FIXES`] from being applied to parts it was never checked +/// against. Adding MSPM33 or a SimpleLink part means adding a variant and its own table, not +/// extending the MSPM0 one โ€” their power domains are laid out differently. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum Series { + Mspm0, +} + +impl Series { + fn of(family: &str) -> anyhow::Result<Self> { + if family.starts_with("mspm0") || family.starts_with("msps003") { + return Ok(Series::Mspm0); + } + + bail!( + "{family}: unknown device series. Power domains are stated per series, so a new one \ + needs its own table rather than MSPM0's" + ) + } + + /// The domain a peripheral is in whatever sysconfig claims, if it is one of the known-wrong ones. + fn power_domain_fix(self, ty: PeripheralType) -> Option<PowerDomain> { + let fixes = match self { + Series::Mspm0 => POWER_DOMAIN_FIXES, + }; + + fixes + .iter() + .find(|(fixed, _)| *fixed == ty) + .map(|(_, domain)| *domain) + } +} + +/// Peripherals whose power domain sysconfig states wrongly, or not at all, on MSPM0. +/// +/// Each of these holds for every MSPM0 family, so they are applied unconditionally rather than +/// against a list of the families whose metadata happens to be wrong today โ€” a new family got the +/// bug and not the fix. Applying them by construction is also what enforces the invariant; there +/// were six checks in `verify.rs` asserting exactly this list, and they could no longer fail. +/// +/// GPAMP is here because sysconfig gives it no power domain at all, not because it gives a wrong +/// one. +const POWER_DOMAIN_FIXES: &[(PeripheralType, PowerDomain)] = &[ + (PeripheralType::AesAdv, PowerDomain::Pd1), + (PeripheralType::Cpuss, PowerDomain::Pd1), + (PeripheralType::Crc, PowerDomain::Pd1), + (PeripheralType::GpAmp, PowerDomain::Pd0), + (PeripheralType::Spi, PowerDomain::Pd1), + (PeripheralType::Trng, PowerDomain::Pd1), +]; + fn get_power_domain( peripheral: &sysconfig::Peripheral, ty: PeripheralType, chip_name: &str, ) -> anyhow::Result<PowerDomain> { + if let Some(domain) = Series::of(chip_name)?.power_domain_fix(ty) { + return Ok(domain); + } + let Some(power_domain) = peripheral .attributes .get("power_domain") // G151x uses all caps power domain while other chips use lowercase. .or_else(|| peripheral.attributes.get("POWER_DOMAIN")) else { - // GPAMP does not have a specified power domain from sysconfig. It is always in PD0. - if peripheral.name == "GPAMP" { - return Ok(PowerDomain::Pd0); - } - bail!("{chip_name}: {} has no power domain", peripheral.name) }; @@ -490,78 +542,8 @@ fn get_power_domain( ) }; - // A few notes on exceptions: - // - ADCx: - // The ADCs technically are in both PD0 and PD1 power domains. We pick PD0 since the - // ADC is in the more permissive power. - // - // - GPIOx: - // Same rationale as ADCs + // The ADCs and GPIOs are in both PD0 and PD1; we take the more permissive of the two. let domain = match power_domain { - // Fix mistakes in SYSCTL - "PD_ULP_AON" - if (chip_name == "msps003fx" - || chip_name == "mspm0c110x" - || chip_name == "mspm0c1105_c1106" - || chip_name == "mspm0h321x" - || chip_name == "mspm0l110x" - || chip_name == "mspm0l122x" - || chip_name == "mspm0l130x" - || chip_name == "mspm0l134x" - || chip_name == "mspm0l222x" - || chip_name == "mspm0l112x" - || chip_name == "mspm0l211x") - && ty == PeripheralType::Cpuss => - { - PowerDomain::Pd1 - } - "PD_ULP_AON" - if (chip_name == "mspm0l122x" - || chip_name == "mspm0l222x" - || chip_name == "mspm0l112x" - || chip_name == "mspm0l211x") - && ty == PeripheralType::AesAdv => - { - PowerDomain::Pd1 - } - "PD_ULP_AON" - if (chip_name == "msps003fx" - || chip_name == "mspm0c110x" - || chip_name == "mspm0c1105_c1106" - || chip_name == "mspm0h321x" - || chip_name == "mspm0l110x" - || chip_name == "mspm0l122x" - || chip_name == "mspm0l130x" - || chip_name == "mspm0l134x" - || chip_name == "mspm0l222x") - && ty == PeripheralType::Crc => - { - PowerDomain::Pd1 - } - "PD_ULP_AON" - if (chip_name == "msps003fx" - || chip_name == "mspm0c110x" - || chip_name == "mspm0c1105_c1106" - || chip_name == "mspm0h321x" - || chip_name == "mspm0l110x" - || chip_name == "mspm0l122x" - || chip_name == "mspm0l130x" - || chip_name == "mspm0l134x" - || chip_name == "mspm0l222x") - && ty == PeripheralType::Spi => - { - PowerDomain::Pd1 - } - "PD_ULP_AON" - if (chip_name == "mspm0l122x" || chip_name == "mspm0l222x") - && ty == PeripheralType::Trng => - { - PowerDomain::Pd1 - } - - // Q: GPAMP appears to be in PD0 but is None in most chips. - - // Normal "PD_ULP_AON" => PowerDomain::Pd0, "PD_ULP_AAON" => PowerDomain::Pd1, "PD_VRTC_AON" => PowerDomain::Backup, diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index d48cfec..f468c7d 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -1,6 +1,6 @@ use std::{collections::HashSet, sync::LazyLock}; -use anyhow::{bail, Context}; +use anyhow::bail; use mspm0_data_types::{Chip, PeripheralType, PowerDomain}; use regex::Regex; @@ -26,14 +26,6 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { clock_frequencies_consistent, retention_known, wakeup_pins_known, - // Power domains - verify_aesadv_power_domain, - verify_cpuss_power_domain, - verify_crc_power_domain, - verify_gpamp_power_domain, - verify_spi_power_domain, - verify_trng_power_domain, - // TODO: UART may be in either power domain, add checks if something is wrong ]; CHECKS @@ -558,92 +550,6 @@ fn gpio_no_duplicates(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } -fn verify_aesadv_power_domain(chip: &Chip, name: &str) -> anyhow::Result<()> { - // L122X puts AESADV in the wrong power domain - let Some(peripheral) = chip.peripherals.get("AESADV") else { - return Ok(()); - }; - - if peripheral.power_domain != PowerDomain::Pd1 { - bail!("{name}: AESADV is not in power domain PD1"); - } - - Ok(()) -} - -/// A few parts have errors in sysconfig metadata where CPUSS is in PD0. -/// -/// For all MSPM0 parts CPUSS is in PD1. -fn verify_cpuss_power_domain(chip: &Chip, name: &str) -> anyhow::Result<()> { - let peripheral = chip.peripherals.get("CPUSS").context("CPUSS not present")?; - - if peripheral.power_domain != PowerDomain::Pd1 { - bail!("{name}: CPUSS is not in power domain PD1"); - } - - Ok(()) -} - -fn verify_crc_power_domain(chip: &Chip, name: &str) -> anyhow::Result<()> { - let Some(peripheral) = chip - .peripherals - .get("CRC") - .or_else(|| chip.peripherals.get("CRCP0")) - else { - return Ok(()); - }; - - if peripheral.power_domain != PowerDomain::Pd1 { - bail!("{name}: CRC is not in power domain PD1"); - } - - Ok(()) -} - -fn verify_gpamp_power_domain(chip: &Chip, name: &str) -> anyhow::Result<()> { - // Sysconfig states GPAMP has no power domain, but it belongs to PD0. - let Some(peripheral) = chip.peripherals.get("GPAMP") else { - return Ok(()); - }; - - if peripheral.power_domain != PowerDomain::Pd0 { - bail!("{name}: GPAMP is not in power domain PD0"); - } - - Ok(()) -} - -/// SPI peripherals always belong to PD1. -fn verify_spi_power_domain(chip: &Chip, name: &str) -> anyhow::Result<()> { - // Maintainer note: This could change in the future. - static PATTERN: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"SPI(\d+)").unwrap()); - - for (peripheral_name, peripheral) in chip - .peripherals - .iter() - .filter(|(name, _)| PATTERN.is_match(name)) - { - if peripheral.power_domain != PowerDomain::Pd1 { - bail!("{name}: {peripheral_name} is not in power domain PD1"); - } - } - - Ok(()) -} - -/// TRNG peripherals always belong to PD1. -fn verify_trng_power_domain(chip: &Chip, name: &str) -> anyhow::Result<()> { - let Some(peripheral) = chip.peripherals.get("TRNG") else { - return Ok(()); - }; - - if peripheral.power_domain != PowerDomain::Pd1 { - bail!("{name}: TRNG is not in power domain PD1"); - } - - Ok(()) -} - /// Verify GPAMP does not exist on these chips: /// /// - C110X From 87b7ac729893add76d8d268f880e7ebe91d7b402 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 14:44:29 +0100 Subject: [PATCH 069/134] data-gen: one reader for the per-family data directories Five modules carried the same glob-and-deserialize loop with the directory and the error string swapped. The types and their provenance docs stay where they were; only the loop moves. int_group and operating_modes also lose a hand-rolled file-stem dance and a commented-out dbg!, and the five entry points are now free functions rather than three associated ones and two free. --- mspm0-data-gen/src/errata.rs | 24 ++++------------ mspm0-data-gen/src/int_group.rs | 41 +++++++++++---------------- mspm0-data-gen/src/main.rs | 8 +++--- mspm0-data-gen/src/operating_modes.rs | 29 ++++--------------- mspm0-data-gen/src/timers.rs | 24 ++++------------ mspm0-data-gen/src/util.rs | 32 ++++++++++++++++++++- mspm0-data-gen/src/wakeup.rs | 18 +++--------- 7 files changed, 73 insertions(+), 103 deletions(-) diff --git a/mspm0-data-gen/src/errata.rs b/mspm0-data-gen/src/errata.rs index fbeacff..389bd22 100644 --- a/mspm0-data-gen/src/errata.rs +++ b/mspm0-data-gen/src/errata.rs @@ -3,11 +3,12 @@ //! Which errata apply is not derivable from anything else in the sources, and it is the one kind of //! data whose absence is silent: a missing workaround leaves a device that mostly works. -use std::{collections::BTreeMap, fs}; +use std::collections::BTreeMap; -use anyhow::Context; use serde::Deserialize; +use crate::util; + /// The errata of one family, by TI's identifier. #[derive(Debug, Deserialize)] #[serde(transparent)] @@ -15,20 +16,7 @@ pub struct Errata { pub errata: Vec<String>, } -impl Errata { - /// Read every `data/errata/<family>.yaml`, keyed by family name. - pub fn parse() -> anyhow::Result<BTreeMap<String, Self>> { - let mut families = BTreeMap::new(); - - for path in glob::glob("data/errata/*.yaml").unwrap().flatten() { - let family = path.file_stem().unwrap().to_string_lossy().to_string(); - let content = fs::read_to_string(&path)?; - let errata = serde_yaml::from_str::<Self>(&content) - .context(format!("Error reading errata for {family}"))?; - - families.insert(family, errata); - } - - Ok(families) - } +/// Read every `data/errata/<family>.yaml`, keyed by family name. +pub fn parse() -> anyhow::Result<BTreeMap<String, Errata>> { + util::per_family("errata") } diff --git a/mspm0-data-gen/src/int_group.rs b/mspm0-data-gen/src/int_group.rs index 4021b21..1dab366 100644 --- a/mspm0-data-gen/src/int_group.rs +++ b/mspm0-data-gen/src/int_group.rs @@ -1,38 +1,29 @@ -use std::{collections::BTreeMap, fs}; +//! Reads `data/int_group/<family>.yaml`, which is hand-entered. +//! +//! Several peripherals can share one NVIC line; the handler tells them apart by reading the group's +//! `IIDX`. Which value means which peripheral is in the TRM's interrupt tables and nowhere +//! machine-readable, so it is transcribed here. + +use std::collections::BTreeMap; -use anyhow::Context; use serde::Deserialize; +use crate::util; + +/// The interrupt groups of one family, keyed by group name. #[derive(Debug, Default, Deserialize)] pub struct Groups { pub groups: BTreeMap<String, Vec<Interrupt>>, } -impl Groups { - pub fn parse() -> anyhow::Result<BTreeMap<String, Groups>> { - let mut map = BTreeMap::new(); - - for f in glob::glob("data/int_group/*.yaml")? { - let f = f?; - let content = fs::read_to_string(&f)?; - let groups = serde_yaml::from_str::<Groups>(&content)?; - - map.insert( - f.file_stem() - .context("File has no stem")? - .to_string_lossy() - .to_string(), - groups, - ); - // dbg!(groups); - } - - Ok(map) - } -} - +/// One peripheral within a group, and the `IIDX` value which selects it. #[derive(Debug, Deserialize)] pub struct Interrupt { pub name: String, pub iidx: u8, } + +/// Read every `data/int_group/<family>.yaml`, keyed by family name. +pub fn parse() -> anyhow::Result<BTreeMap<String, Groups>> { + util::per_family("int_group") +} diff --git a/mspm0-data-gen/src/main.rs b/mspm0-data-gen/src/main.rs index a4877d0..4c2c3f4 100644 --- a/mspm0-data-gen/src/main.rs +++ b/mspm0-data-gen/src/main.rs @@ -71,10 +71,10 @@ fn main() -> anyhow::Result<()> { stopwatch.section("Read interrupt group mappings"); - let int_groups = int_group::Groups::parse()?; - let operating_modes = operating_modes::OperatingModes::parse()?; - let timers = timers::Timers::parse()?; - let errata = errata::Errata::parse()?; + let int_groups = int_group::parse()?; + let operating_modes = operating_modes::parse()?; + let timers = timers::parse()?; + let errata = errata::parse()?; let wake = wakeup::parse()?; let parts = parts::PartsFile::read()?; diff --git a/mspm0-data-gen/src/operating_modes.rs b/mspm0-data-gen/src/operating_modes.rs index a3c283c..23a2d1f 100644 --- a/mspm0-data-gen/src/operating_modes.rs +++ b/mspm0-data-gen/src/operating_modes.rs @@ -5,12 +5,13 @@ //! This information isn't available in any machine-readable format, so it's extracted from the //! datasheet's "Supported Functionality by Operating Mode" table using `tools/operating_modes.py`. -use std::{collections::BTreeMap, fs}; +use std::collections::BTreeMap; -use anyhow::Context; use mspm0_data_types::PowerMode; use serde::Deserialize; +use crate::util; + #[derive(Debug, Default, Deserialize)] pub struct OperatingModes { /// Maps peripheral names to the deepest mode its configuration survives. @@ -21,25 +22,7 @@ pub struct OperatingModes { pub usable_through: BTreeMap<String, PowerMode>, } -impl OperatingModes { - pub fn parse() -> anyhow::Result<BTreeMap<String, OperatingModes>> { - let mut map = BTreeMap::new(); - - for f in glob::glob("data/operating_modes/*.yaml")? { - let f = f?; - let content = fs::read_to_string(&f)?; - let modes = serde_yaml::from_str::<OperatingModes>(&content) - .context(format!("Error reading {}", f.display()))?; - - map.insert( - f.file_stem() - .context("File has no stem")? - .to_string_lossy() - .to_string(), - modes, - ); - } - - Ok(map) - } +/// Read every `data/operating_modes/<family>.yaml`, keyed by family name. +pub fn parse() -> anyhow::Result<BTreeMap<String, OperatingModes>> { + util::per_family("operating_modes") } diff --git a/mspm0-data-gen/src/timers.rs b/mspm0-data-gen/src/timers.rs index 55e70a5..cd04001 100644 --- a/mspm0-data-gen/src/timers.rs +++ b/mspm0-data-gen/src/timers.rs @@ -5,12 +5,13 @@ //! The datasheet's TIMx configuration table is the only per-device source which does; see the //! tool's module docs for why the SVDs and the TRM are not used. -use std::{collections::BTreeMap, fs}; +use std::collections::BTreeMap; -use anyhow::Context; use mspm0_data_types::Timer; use serde::Deserialize; +use crate::util; + /// Timers of one family, keyed by instance name. #[derive(Debug, Deserialize)] #[serde(transparent)] @@ -18,20 +19,7 @@ pub struct Timers { pub timers: BTreeMap<String, Timer>, } -impl Timers { - /// Read every `data/timers/<family>.yaml`, keyed by family name. - pub fn parse() -> anyhow::Result<BTreeMap<String, Self>> { - let mut families = BTreeMap::new(); - - for path in glob::glob("data/timers/*.yaml").unwrap().flatten() { - let family = path.file_stem().unwrap().to_string_lossy().to_string(); - let content = fs::read_to_string(&path)?; - let timers = serde_yaml::from_str::<Self>(&content) - .context(format!("Error reading timers for {family}"))?; - - families.insert(family, timers); - } - - Ok(families) - } +/// Read every `data/timers/<family>.yaml`, keyed by family name. +pub fn parse() -> anyhow::Result<BTreeMap<String, Timers>> { + util::per_family("timers") } diff --git a/mspm0-data-gen/src/util.rs b/mspm0-data-gen/src/util.rs index 105a1b5..9e7d79e 100644 --- a/mspm0-data-gen/src/util.rs +++ b/mspm0-data-gen/src/util.rs @@ -1,7 +1,37 @@ -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; +use std::fs; use std::sync::{Mutex, OnceLock}; +use anyhow::Context; use regex::Regex; +use serde::de::DeserializeOwned; + +/// Read every `data/<dir>/<family>.yaml` into `T`, keyed by family name. +/// +/// The per-family directories under `data/` all load the same way โ€” one file per family, named +/// after it, deserialized whole โ€” so only the directory and the type differ. What each of them +/// means, and which tool writes it, is documented on the module that names the type. +pub fn per_family<T: DeserializeOwned>(dir: &str) -> anyhow::Result<BTreeMap<String, T>> { + let mut families = BTreeMap::new(); + + for path in glob::glob(&format!("data/{dir}/*.yaml"))?.flatten() { + let family = path + .file_stem() + .context(format!("{}: no file stem to take a family name from", path.display()))? + .to_string_lossy() + .into_owned(); + + let content = + fs::read_to_string(&path).context(format!("reading {}", path.display()))?; + + let value = serde_yaml::from_str::<T>(&content) + .context(format!("reading {}", path.display()))?; + + families.insert(family, value); + } + + Ok(families) +} pub struct RegexMap<'a, T> { map: &'a [(&'a str, T)], diff --git a/mspm0-data-gen/src/wakeup.rs b/mspm0-data-gen/src/wakeup.rs index 924bc2e..eba48f4 100644 --- a/mspm0-data-gen/src/wakeup.rs +++ b/mspm0-data-gen/src/wakeup.rs @@ -6,23 +6,13 @@ //! Unlike the other `data/` readers there is no intermediate type: the file's keys are exactly //! [`WakeTimes`]' fields, so it deserializes straight into the shape the JSON carries. -use std::{collections::BTreeMap, fs}; +use std::collections::BTreeMap; -use anyhow::Context; use mspm0_data_types::WakeTimes; +use crate::util; + /// Read every `data/wakeup/<family>.yaml`, keyed by family name. pub fn parse() -> anyhow::Result<BTreeMap<String, WakeTimes>> { - let mut families = BTreeMap::new(); - - for path in glob::glob("data/wakeup/*.yaml").unwrap().flatten() { - let family = path.file_stem().unwrap().to_string_lossy().to_string(); - let content = fs::read_to_string(&path)?; - let wake = serde_yaml::from_str::<WakeTimes>(&content) - .context(format!("Error reading wake-up times for {family}"))?; - - families.insert(family, wake); - } - - Ok(families) + util::per_family("wakeup") } From 57e8f6b250b69de22497b3d1a3cb6c5a41d7aad4 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 14:47:07 +0100 Subject: [PATCH 070/134] data-gen: table-drive the peripheral type prefixes A 37-branch if/else chain, two of whose arms clippy flagged as identical because TIMA, TIMB and TIMG all map to Tim. The table keeps first-match order, which AESADV before AES depends on. Splitting register_block_key out states the TIMB exception once. It was previously said twice in opposite directions: the chain collapsed TIMB into Tim and the code below re-split it back out. --- mspm0-data-gen/src/generate.rs | 166 +++++++++++++++------------------ 1 file changed, 75 insertions(+), 91 deletions(-) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 61745b1..23a76dc 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -735,101 +735,85 @@ fn maybe_rename(name: &str) -> String { name.to_string() } -fn get_peripheral_type_version(chip_name: &str, name: &str) -> (PeripheralType, Option<String>) { - if name.starts_with("SYSCTL") { - let version = PERIMAP - .get(&format!("{}:{}", chip_name, PeripheralType::Sysctl)) - .map(|s| s.to_string()); - return (PeripheralType::Sysctl, version); +/// Peripheral instance name prefixes, and the type each names. +/// +/// First match wins, so order matters where one prefix starts with another: `AESADV` has to come +/// before `AES`. TIMA, TIMB and TIMG are all `Tim` - which register block a timer instance uses is +/// a separate question, answered by [`register_block_key`]. +const PERIPHERAL_PREFIXES: &[(&str, PeripheralType)] = &[ + ("ADC", PeripheralType::Adc), + ("AESADV", PeripheralType::AesAdv), + ("AES", PeripheralType::Aes), + ("CANFD", PeripheralType::Canfd), + ("COMP", PeripheralType::Comp), + ("CPUSS", PeripheralType::Cpuss), + ("CRC", PeripheralType::Crc), + ("DAC", PeripheralType::Dac), + ("DEBUGSS", PeripheralType::Debugss), + ("DMA", PeripheralType::Dma), + ("EVENT", PeripheralType::Event), + ("FLASHCTL", PeripheralType::FlashCtl), + ("GPAMP", PeripheralType::GpAmp), + ("GPIO", PeripheralType::Gpio), + ("I2C", PeripheralType::I2c), + ("I2S", PeripheralType::I2s), + ("IOMUX", PeripheralType::Iomux), + ("IWDT", PeripheralType::Iwdt), + ("KEYSTORECTL", PeripheralType::KeystoreCtl), + ("LCD", PeripheralType::Lcd), + ("LFSS", PeripheralType::Lfss), + ("MATHACL", PeripheralType::Mathacl), + ("NPU", PeripheralType::Npu), + ("OPA", PeripheralType::Opa), + ("RTC", PeripheralType::Rtc), + ("SPG", PeripheralType::Spgss), + ("SPI", PeripheralType::Spi), + ("SYSCTL", PeripheralType::Sysctl), + ("TIMA", PeripheralType::Tim), + ("TIMB", PeripheralType::Tim), + ("TIMG", PeripheralType::Tim), + ("TRNG", PeripheralType::Trng), + ("UART", PeripheralType::Uart), + ("UC", PeripheralType::Unicomm), + ("USBFS", PeripheralType::Usbfs), + ("VREF", PeripheralType::Vref), + ("WUC", PeripheralType::Wuc), + ("WWDT", PeripheralType::Wwdt), +]; + +/// The type of a peripheral, from its instance name. +/// +/// `Unknown` for a name no prefix covers. That is not inert - such a peripheral is dropped from the +/// generated metadata - so `verify::peripheral_types_known` reports it rather than letting a source +/// bump quietly lose one. +fn peripheral_type_from_name(name: &str) -> PeripheralType { + PERIPHERAL_PREFIXES + .iter() + .find(|(prefix, _)| name.starts_with(prefix)) + .map(|(_, ty)| *ty) + .unwrap_or(PeripheralType::Unknown) +} + +/// The `data/registers` prefix a peripheral's register block is filed under. +/// +/// Normally the peripheral type, since a chip has one block per type. TIMB is the exception: a +/// basic timer has its own block but the same `Tim` type as TIMA and TIMG, so it needs a key of its +/// own to reach `perimap`. The version it selects then gets a module name from `VARIANT_MODULES` in +/// the metapac generator. +fn register_block_key(name: &str, ty: PeripheralType) -> Cow<'static, str> { + if ty == PeripheralType::Tim && name.starts_with("TIMB") { + return Cow::Borrowed("timb"); } - let ty = if name.starts_with("ADC") { - PeripheralType::Adc - } else if name.starts_with("AESADV") { - PeripheralType::AesAdv - } else if name.starts_with("AES") { - PeripheralType::Aes - } else if name.starts_with("CANFD") { - PeripheralType::Canfd - } else if name.starts_with("COMP") { - PeripheralType::Comp - } else if name.starts_with("CPUSS") { - PeripheralType::Cpuss - } else if name.starts_with("CRC") { - PeripheralType::Crc - } else if name.starts_with("DAC") { - PeripheralType::Dac - } else if name.starts_with("DEBUGSS") { - PeripheralType::Debugss - } else if name.starts_with("DMA") { - PeripheralType::Dma - } else if name.starts_with("EVENT") { - PeripheralType::Event - } else if name.starts_with("FLASHCTL") { - PeripheralType::FlashCtl - } else if name.starts_with("GPAMP") { - PeripheralType::GpAmp - } else if name.starts_with("GPIO") { - PeripheralType::Gpio - } else if name.starts_with("I2C") { - PeripheralType::I2c - } else if name.starts_with("I2S") { - PeripheralType::I2s - } else if name.starts_with("IOMUX") { - PeripheralType::Iomux - } else if name.starts_with("IWDT") { - PeripheralType::Iwdt - } else if name.starts_with("KEYSTORECTL") { - PeripheralType::KeystoreCtl - } else if name.starts_with("LCD") { - PeripheralType::Lcd - } else if name.starts_with("LFSS") { - PeripheralType::Lfss - } else if name.starts_with("MATHACL") { - PeripheralType::Mathacl - } else if name.starts_with("NPU") { - PeripheralType::Npu - } else if name.starts_with("OPA") { - PeripheralType::Opa - } else if name.starts_with("RTC") { - PeripheralType::Rtc - } else if name.starts_with("SPG") { - PeripheralType::Spgss - } else if name.starts_with("SPI") { - PeripheralType::Spi - } else if name.starts_with("TIMA") { - PeripheralType::Tim - } else if name.starts_with("TIMB") { - PeripheralType::Tim - } else if name.starts_with("TIMG") { - PeripheralType::Tim - } else if name.starts_with("TRNG") { - PeripheralType::Trng - } else if name.starts_with("UART") { - PeripheralType::Uart - } else if name.starts_with("UC") { - PeripheralType::Unicomm - } else if name.starts_with("USBFS") { - PeripheralType::Usbfs - } else if name.starts_with("VREF") { - PeripheralType::Vref - } else if name.starts_with("WUC") { - PeripheralType::Wuc - } else if name.starts_with("WWDT") { - PeripheralType::Wwdt - } else { - PeripheralType::Unknown - }; + Cow::Owned(ty.to_string()) +} + +fn get_peripheral_type_version(chip_name: &str, name: &str) -> (PeripheralType, Option<String>) { + let ty = peripheral_type_from_name(name); + let key = register_block_key(name, ty); - // TIMB is a basic timer and has its own register block, so the key names the instance kind - // rather than the peripheral type. TIMA and TIMG share one, and both keep the plain `tim` key. - let key = if ty == PeripheralType::Tim && name.starts_with("TIMB") { - "timb" - } else { - &ty.to_string() - }; let version = PERIMAP - .get(&format!("{}:{}", chip_name, key)) + .get(&format!("{chip_name}:{key}")) .map(|s| s.to_string()); (ty, version) From 47ed5230a156ddfdaa494dd3ed26f413a29832d8 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 14:48:56 +0100 Subject: [PATCH 071/134] data-gen: gather the sources into one struct generate and generate_family each took ten positional arguments, which clippy warned about on both, and the source types disagreed about their own shape - three wrappers keyed .files, one keyed .headers, and five were bare maps. Sources holds them all and narrows to a FamilySources, which also gives the msps003fx-shares-the-c110x-header rule and the family lookups one place to live instead of being spelled out at the call site. --- mspm0-data-gen/src/generate.rs | 86 +++++++++----------------------- mspm0-data-gen/src/header.rs | 5 +- mspm0-data-gen/src/main.rs | 27 +++++----- mspm0-data-gen/src/sources.rs | 91 ++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 77 deletions(-) create mode 100644 mspm0-data-gen/src/sources.rs diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 23a76dc..e20a916 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -10,88 +10,48 @@ use anyhow::{anyhow, bail, ensure, Context}; use mspm0_data_types::{ Adc, Chip, DmaChannel, Interrupt, Memory, MemoryKind, Package, PackagePin, Peripheral, PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, Timer, Unicomm, - WakeTimes, }; use regex::Regex; use crate::{ - clock_tree::{ClockTreeFile, ClockTrees}, - errata::Errata, - header::{Header, Headers}, + sources::{FamilySources, Sources}, + header::Header, int_group::Groups, operating_modes::OperatingModes, - parts::{PartFamily, PartMemory, PartsFile}, + parts::{PartFamily, PartMemory}, perimap::PERIMAP, - svd::{Svd, Svds}, - sysconfig::{self, PartPeripheralWrapper, Sysconfig, SysconfigFile}, + svd::Svd, + sysconfig::{self, PartPeripheralWrapper, SysconfigFile}, timers::Timers, verify, }; -pub fn generate( - parts: &PartsFile, - headers: &Headers, - sysconfig: &Sysconfig, - svds: &Svds, - operating_modes: &BTreeMap<String, OperatingModes>, - int_groups: &BTreeMap<String, Groups>, - timers: &BTreeMap<String, Timers>, - clock_trees: &ClockTrees, - errata: &BTreeMap<String, Errata>, - wake: &BTreeMap<String, WakeTimes>, -) -> anyhow::Result<()> { +pub fn generate(sources: &Sources) -> anyhow::Result<()> { fs::create_dir_all("./build/data/").unwrap(); - for family in parts.families.iter() { - let sysconfig = sysconfig - .files - .get(&family.family.to_uppercase()) - .context(format!("No sysconfig data available for {}", family.family))?; - - // MSPS003FX is the same as C110X except for package options and some pins. - let header_name = if family.family == "msps003fx" { - "mspm0c110x" - } else { - &family.family - }; + for family in sources.parts.families.iter() { + let family_sources = sources.family(&family.family)?; - let header = headers - .headers - .get(&header_name.to_lowercase()) - .context(format!("Could not lookup header for {}", header_name))?; - - let svd = svds.files.get(&family.family); - - generate_family( - family, - header, - sysconfig, - svd, - operating_modes.get(&family.family), - int_groups, - timers.get(&family.family), - clock_trees.files.get(&family.family), - errata.get(&family.family), - wake.get(&family.family).copied(), - ) - .context(format!("Error when generating family: {}", family.family))?; + generate_family(family, &family_sources) + .context(format!("Error when generating family: {}", family.family))?; } Ok(()) } -fn generate_family( - family: &PartFamily, - header: &Header, - sysconfig: &SysconfigFile, - svd: Option<&Svd>, - operating_modes: Option<&OperatingModes>, - int_groups: &BTreeMap<String, Groups>, - timers: Option<&Timers>, - clock_tree: Option<&ClockTreeFile>, - errata: Option<&Errata>, - wake: Option<WakeTimes>, -) -> anyhow::Result<()> { +fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Result<()> { + let FamilySources { + header, + sysconfig, + svd, + clock_tree, + operating_modes, + timers, + errata, + wake, + int_groups, + } = *sources; + // Data shared across all chips in a family. let packages = get_packages(&family.family, sysconfig)?; let iomux = generate_pincm(&family.family, sysconfig)?; diff --git a/mspm0-data-gen/src/header.rs b/mspm0-data-gen/src/header.rs index ba80964..5375b3e 100644 --- a/mspm0-data-gen/src/header.rs +++ b/mspm0-data-gen/src/header.rs @@ -6,7 +6,8 @@ use regex::Regex; #[derive(Debug)] pub struct Headers { - pub headers: BTreeMap<String, Header>, + /// Keyed by family name, lowercase (e.g. `mspm0g350x`). + pub files: BTreeMap<String, Header>, } impl Headers { @@ -31,7 +32,7 @@ impl Headers { headers.insert(name.to_string(), Header::read(name, &header)?); } - Ok(Self { headers }) + Ok(Self { files: headers }) } } diff --git a/mspm0-data-gen/src/main.rs b/mspm0-data-gen/src/main.rs index 4c2c3f4..cc7dfd8 100644 --- a/mspm0-data-gen/src/main.rs +++ b/mspm0-data-gen/src/main.rs @@ -9,6 +9,7 @@ mod perimap; mod svd; mod sysconfig; mod timers; +mod sources; mod util; mod verify; mod wakeup; @@ -80,19 +81,21 @@ fn main() -> anyhow::Result<()> { // TODO: Expanded family names (ex. C110X -> C1103 & C1104) + let sources = sources::Sources { + parts, + headers, + sysconfig, + svds, + clock_trees, + operating_modes, + int_groups, + timers, + errata, + wake, + }; + stopwatch.section("Generate data"); - generate::generate( - &parts, - &headers, - &sysconfig, - &svds, - &operating_modes, - &int_groups, - &timers, - &clock_trees, - &errata, - &wake, - )?; + generate::generate(&sources)?; stopwatch.stop(); diff --git a/mspm0-data-gen/src/sources.rs b/mspm0-data-gen/src/sources.rs new file mode 100644 index 0000000..dbdbdb8 --- /dev/null +++ b/mspm0-data-gen/src/sources.rs @@ -0,0 +1,91 @@ +//! Everything the generator reads, and the per-family view of it. +//! +//! The sources divide into two kinds: vendor data under `sources/`, which is parsed, and curated +//! YAML under `data/`, which is deserialized. Both are keyed by family, so the generator only ever +//! wants one family's worth at a time โ€” which is what [`FamilySources`] is. + +use std::collections::BTreeMap; + +use mspm0_data_types::WakeTimes; + +use crate::{ + clock_tree::{ClockTreeFile, ClockTrees}, + errata::Errata, + header::{Header, Headers}, + int_group::Groups, + operating_modes::OperatingModes, + parts::PartsFile, + svd::{Svd, Svds}, + sysconfig::{Sysconfig, SysconfigFile}, + timers::Timers, +}; + +/// Every source, whole. +pub struct Sources { + pub parts: PartsFile, + pub headers: Headers, + pub sysconfig: Sysconfig, + pub svds: Svds, + pub clock_trees: ClockTrees, + pub operating_modes: BTreeMap<String, OperatingModes>, + pub int_groups: BTreeMap<String, Groups>, + pub timers: BTreeMap<String, Timers>, + pub errata: BTreeMap<String, Errata>, + pub wake: BTreeMap<String, WakeTimes>, +} + +/// What one family is described by. +/// +/// Only `header` and `sysconfig` are required: a family with no SVD, no curated timer table or no +/// errata sheet is a gap `verify.rs` reports, not a reason to stop generating. +pub struct FamilySources<'a> { + pub header: &'a Header, + pub sysconfig: &'a SysconfigFile, + pub svd: Option<&'a Svd>, + pub clock_tree: Option<&'a ClockTreeFile>, + pub operating_modes: Option<&'a OperatingModes>, + pub timers: Option<&'a Timers>, + pub errata: Option<&'a Errata>, + pub wake: Option<WakeTimes>, + + /// Not narrowed to the family: `generate_irqs` looks groups up per chip, not per family. + pub int_groups: &'a BTreeMap<String, Groups>, +} + +impl Sources { + /// Narrow every source to one family. + pub fn family(&self, family: &str) -> anyhow::Result<FamilySources<'_>> { + use anyhow::Context; + + let sysconfig = self + .sysconfig + .files + .get(&family.to_uppercase()) + .context(format!("No sysconfig data available for {family}"))?; + + // MSPS003FX is the same die as C110X, differing only in package options and some pins. + let header_name = if family == "msps003fx" { + "mspm0c110x" + } else { + family + }; + + let header = self + .headers + .files + .get(header_name) + .context(format!("Could not lookup header for {header_name}"))?; + + Ok(FamilySources { + header, + sysconfig, + svd: self.svds.files.get(family), + clock_tree: self.clock_trees.files.get(family), + operating_modes: self.operating_modes.get(family), + timers: self.timers.get(family), + errata: self.errata.get(family), + wake: self.wake.get(family).copied(), + int_groups: &self.int_groups, + }) + } +} From 0701a2ad4016633ce1fbbcf57ce5ebcaac04375f Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 14:52:25 +0100 Subject: [PATCH 072/134] metapac-gen: build the metadata literal with quote! The Metadata struct was the last thing assembled by interpolating strings, so a typo in it was caught by the 216-chip build rather than by the type checker. Output is byte-identical. registers.rs also propagates instead of unwrapping: it returned a Result and then panicked on every step, which lost the file name on a malformed block. The one remaining expect parses a fixed path and cannot convert, since LexError is neither Send nor Sync. --- mspm0-metapac-gen/src/main.rs | 135 +++++++++++++++-------------- mspm0-metapac-gen/src/registers.rs | 40 +++------ 2 files changed, 85 insertions(+), 90 deletions(-) diff --git a/mspm0-metapac-gen/src/main.rs b/mspm0-metapac-gen/src/main.rs index 97d9126..fe3ae90 100644 --- a/mspm0-metapac-gen/src/main.rs +++ b/mspm0-metapac-gen/src/main.rs @@ -7,6 +7,7 @@ use std::{ use anyhow::Context; use mspm0_data_types::{Chip, Package}; +use proc_macro2::Literal; use quote::quote; mod interrupt; @@ -137,95 +138,101 @@ fn generate_chip_metadata( }); let family = &chip.family; - let nvic_priority_bits = &chip.nvic_priority_bits; - let max_mclk_hz = &chip.max_mclk_hz; - let max_ulpclk_hz = &chip.max_ulpclk_hz; - let sysosc_base_hz = &chip.sysosc_base_hz; - let flash_wait_hz = chip - .flash_wait_hz - .iter() - .map(|hz| hz.to_string()) - .collect::<Vec<_>>() - .join(", "); - let backup_domain = &chip.backup_domain; + let nvic_priority_bits = Literal::u8_unsuffixed(chip.nvic_priority_bits); + let max_mclk_hz = Literal::u32_unsuffixed(chip.max_mclk_hz); + let max_ulpclk_hz = Literal::u32_unsuffixed(chip.max_ulpclk_hz); + let sysosc_base_hz = Literal::u32_unsuffixed(chip.sysosc_base_hz); + let flash_wait_hz = chip.flash_wait_hz.iter().map(|hz| Literal::u32_unsuffixed(*hz)); + let backup_domain = chip.backup_domain; + let errata = chip.errata.iter(); + let tree = &chip.clock_tree; let hfclk_hz = match tree.hfclk_hz { - Some(range) => format!( - "Some(ClockRange {{ min_hz: {}, max_hz: {} }})", - range.min_hz, range.max_hz - ), - None => "None".to_string(), + Some(range) => { + let min_hz = Literal::u32_unsuffixed(range.min_hz); + let max_hz = Literal::u32_unsuffixed(range.max_hz); + quote!(Some(ClockRange { min_hz: #min_hz, max_hz: #max_hz })) + } + None => quote!(None), }; - let clock_tree = format!( - "ClockTree {{ hfxt: {}, hfclk_in: {}, hfclk_hz: {}, lfxt: {}, lfclk_in: {}, syspll: {}, \ - ulpclk_div: {}, stop1: {} }}", + let (hfxt, hfclk_in, lfxt, lfclk_in, syspll, ulpclk_div, stop1) = ( tree.hfxt, tree.hfclk_in, - hfclk_hz, tree.lfxt, tree.lfclk_in, tree.syspll, tree.ulpclk_div, tree.stop1, ); - - let errata = chip - .errata - .iter() - .map(|erratum| format!("{erratum:?}")) - .collect::<Vec<_>>() - .join(", "); + let clock_tree = quote! { + ClockTree { + hfxt: #hfxt, + hfclk_in: #hfclk_in, + hfclk_hz: #hfclk_hz, + lfxt: #lfxt, + lfclk_in: #lfclk_in, + syspll: #syspll, + ulpclk_div: #ulpclk_div, + stop1: #stop1, + } + }; let wake = &chip.wake_ns; let ns = |mode: Option<u32>| match mode { - Some(ns) => format!("Some({ns})"), - None => "None".to_string(), + Some(ns) => { + let ns = Literal::u32_unsuffixed(ns); + quote!(Some(#ns)) + } + None => quote!(None), + }; + let (sleep0, sleep1, sleep2) = (ns(wake.sleep0), ns(wake.sleep1), ns(wake.sleep2)); + let (stop0, wake_stop1, stop2) = (ns(wake.stop0), ns(wake.stop1), ns(wake.stop2)); + let (standby0, standby1, shutdown) = + (ns(wake.standby0), ns(wake.standby1), ns(wake.shutdown)); + let wake_ns = quote! { + WakeTimes { + sleep0: #sleep0, + sleep1: #sleep1, + sleep2: #sleep2, + stop0: #stop0, + stop1: #wake_stop1, + stop2: #stop2, + standby0: #standby0, + standby1: #standby1, + shutdown: #shutdown, + } }; - let wake_ns = format!( - "WakeTimes {{ sleep0: {}, sleep1: {}, sleep2: {}, stop0: {}, stop1: {}, stop2: {}, \ - standby0: {}, standby1: {}, shutdown: {} }}", - ns(wake.sleep0), - ns(wake.sleep1), - ns(wake.sleep2), - ns(wake.stop0), - ns(wake.stop1), - ns(wake.stop2), - ns(wake.standby0), - ns(wake.standby1), - ns(wake.shutdown), - ); // Memory is the one part of the metadata which varies by part number rather than by family let memory = metadata::memory(chip); + let include = format!("../{deduped_file}"); - let mut contents = String::new(); - write!( - &mut contents, - " - include!(\"../{deduped_file}\"); - static MEMORY: &[MemoryRegion] = {memory}; - pub static METADATA: Metadata = Metadata {{ - name: \"{name}\", - family: \"{family}\", + let contents = quote! { + include!(#include); + + static MEMORY: &[MemoryRegion] = #memory; + + pub static METADATA: Metadata = Metadata { + name: #name, + family: #family, memory: MEMORY, peripherals: PERIPHERALS, pins: PINS, - nvic_priority_bits: {nvic_priority_bits}, + nvic_priority_bits: #nvic_priority_bits, interrupts: INTERRUPTS, interrupt_groups: INTERRUPT_GROUPS, dma_channels: DMA_CHANNELS, - max_mclk_hz: {max_mclk_hz}, - max_ulpclk_hz: {max_ulpclk_hz}, - sysosc_base_hz: {sysosc_base_hz}, - flash_wait_hz: &[{flash_wait_hz}], - backup_domain: {backup_domain}, - clock_tree: {clock_tree}, - errata: &[{errata}], - wake_ns: {wake_ns}, - }}; - ", - ) - .unwrap(); + max_mclk_hz: #max_mclk_hz, + max_ulpclk_hz: #max_ulpclk_hz, + sysosc_base_hz: #sysosc_base_hz, + flash_wait_hz: &[#(#flash_wait_hz),*], + backup_domain: #backup_domain, + clock_tree: #clock_tree, + errata: &[#(#errata),*], + wake_ns: #wake_ns, + }; + } + .to_string(); write_rust(dir.join("metadata.rs"), &contents).unwrap(); } diff --git a/mspm0-metapac-gen/src/registers.rs b/mspm0-metapac-gen/src/registers.rs index 0a918e7..744c99c 100644 --- a/mspm0-metapac-gen/src/registers.rs +++ b/mspm0-metapac-gen/src/registers.rs @@ -15,41 +15,31 @@ pub fn generate(out_dir: &Path) -> anyhow::Result<()> { // Must create `src` directory which common is eventually written to. let out_dir = out_dir.join("src/peripherals"); - fs::create_dir_all(&out_dir).unwrap(); + fs::create_dir_all(&out_dir)?; // Common module - fs::write(common, generate::COMMON_MODULE).unwrap(); + fs::write(common, generate::COMMON_MODULE)?; let options = generate::Options::new().with_common_module(CommonModule::External( - TokenStream::from_str("crate::common").unwrap(), + // Not `?`: LexError is neither Send nor Sync, and this parses a fixed path anyway. + TokenStream::from_str("crate::common").expect("crate::common is a valid path"), )); - let re = Regex::new("# *! *\\[.*\\]").unwrap(); - - for f in glob::glob("data/registers/*.yaml").unwrap() { - let f = f.unwrap(); + let re = Regex::new("# *! *\\[.*\\]")?; + for f in glob::glob("data/registers/*.yaml")?.flatten() { if !f.is_file() { continue; } - if f.file_name() - .unwrap() - .to_string_lossy() - .starts_with("ignore.") - { - continue; - } - - let ctx = format!("{:?}", f.file_name()); + let ctx = || format!("register block {}", f.display()); - let mut ir: ir::IR = serde_yaml::from_str(&fs::read_to_string(&f).unwrap()) - .context(ctx.clone()) - .expect("Error reading registers"); + let mut ir: ir::IR = + serde_yaml::from_str(&fs::read_to_string(&f).with_context(ctx)?).with_context(ctx)?; transform::expand_extends::ExpandExtends {} .run(&mut ir) - .unwrap(); + .with_context(ctx)?; transform::map_names(&mut ir, |k, s| match k { transform::NameKind::Block => *s = s.to_string(), @@ -58,18 +48,16 @@ pub fn generate(out_dir: &Path) -> anyhow::Result<()> { _ => {} }); - transform::sort::Sort {}.run(&mut ir).unwrap(); + transform::sort::Sort {}.run(&mut ir).with_context(ctx)?; transform::sanitize::Sanitize::default() .run(&mut ir) - .unwrap(); + .with_context(ctx)?; - let items = generate::render(&ir, &options) - .context(ctx) - .expect("Failed to generate code for peripheral"); + let items = generate::render(&ir, &options).with_context(ctx)?; let name = f .file_name() - .unwrap() + .context("register block has no file name")? .to_string_lossy() .replace(".yaml", ".rs"); From 17a6c0c3fbb40b6698aedd6dc9275a82838c8eed Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 14:56:05 +0100 Subject: [PATCH 073/134] data-gen: clear the dead code and answer the stale TODOs The pin name could never contain a slash at the point it was split on one, the "ignore." file prefix both readers honoured was undocumented and unused, and three parameters went unread. Clippy is now clean. The TODOs that had answers get them: the perimap FIXMEs say what borrowing a SYSCTL costs and that l112x/l211x are already known to differ, and PeripheralType says what completeness means now that verify reports a type it does not cover. --- mspm0-data-gen/src/generate.rs | 51 +++++++++++++++------------------- mspm0-data-gen/src/perimap.rs | 6 ++-- mspm0-data-types/src/lib.rs | 3 +- mspm0-metapac-gen/src/main.rs | 14 ++-------- 4 files changed, 30 insertions(+), 44 deletions(-) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index e20a916..965305e 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -54,11 +54,11 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu // Data shared across all chips in a family. let packages = get_packages(&family.family, sysconfig)?; - let iomux = generate_pincm(&family.family, sysconfig)?; + let iomux = generate_pincm(sysconfig)?; let wakeup_pins = generate_wakeup_pins(sysconfig); let mut peripherals = generate_peripherals2(&family.family, header, sysconfig)?; let interrupts = generate_irqs(&family.family, header, int_groups)?; - let dma_channels = generate_dma_channels(&family.family, sysconfig)?; + let dma_channels = generate_dma_channels(sysconfig)?; let backup_domain = has_backup_domain(&family.family, sysconfig, &peripherals)?; let clock_tree = clock_tree .context(format!("{}: no clocktree.json", family.family))? @@ -184,15 +184,12 @@ fn get_packages(family: &str, sysconfig: &SysconfigFile) -> anyhow::Result<Vec<P Ok(packages) } -fn generate_pincm( - _chip_name: &str, - sysconfig: &SysconfigFile, -) -> anyhow::Result<BTreeMap<String, u32>> { +fn generate_pincm(sysconfig: &SysconfigFile) -> anyhow::Result<BTreeMap<String, u32>> { let mut pins = BTreeMap::new(); - // TODO: Remove this hack as we have replaced it. for device_pin in sysconfig.device_pins.values() { - // TODO: Does this cause any problems? + // Multi-bonded pins, as in generate_peripherals2: named for both functions, listed + // separately under each. if device_pin.name.contains('/') { continue; } @@ -284,7 +281,7 @@ fn generate_peripherals2( } let (ty, version) = get_peripheral_type_version(chip_name, &name); - let address = get_peripheral_addresses(chip_name, &name, header, sysconfig)?; + let address = get_peripheral_addresses(chip_name, &name, header)?; let power_domain = get_power_domain(peripheral, ty, chip_name)?; let sys_fentries = get_sys_fentries(peripheral, chip_name)?; @@ -338,9 +335,8 @@ fn generate_peripherals2( .context(format!("Device pin with id {device_pin_id}, used by {pin_name_and_signal} (id: {pin_id}) is not present"))?; let device_pin_name = &device_pin.name; - // Remove pin entries with a `/` as these represent multi-bonded pins. - // - // TODO: Does this cause any problems? + // Multi-bonded pins, which sysconfig names "PA1/NRST". The bank and + // pin they alias are listed separately, so skipping them loses nothing. if device_pin_name.contains('/') { continue; } @@ -349,11 +345,7 @@ fn generate_peripherals2( "PF was not valid integer for {device_pin_name}, {pin_name_and_signal}" ))?; - let pin = device_pin_name - .split_once('/') - .map(|(a, _)| a) - .unwrap_or_else(|| device_pin_name) - .to_string(); + let pin = device_pin_name.to_string(); if skip_peripheral_pin(device_pin_name, chip_name) { continue; @@ -591,7 +583,7 @@ fn generate_missing( // Resolving the address always is unfortunately required because or_insert_with_key cannot handle // fallible closures. let bank = format!("GPIO{bank}"); - let address = get_peripheral_addresses(chip_name, &bank, header, sysconfig)? + let address = get_peripheral_addresses(chip_name, &bank, header)? .context(format!("{bank} must have address"))?; let version = PERIMAP @@ -653,7 +645,7 @@ fn generate_missing( ]; if BEEPER_FAMILIES.contains(&chip_name) { - let address = get_peripheral_addresses(chip_name, "SYSCTL", header, sysconfig)? + let address = get_peripheral_addresses(chip_name, "SYSCTL", header)? .context(format!("{chip_name}: BEEPER needs SYSCTL's address"))?; let version = PERIMAP @@ -783,7 +775,6 @@ fn get_peripheral_addresses( chip_name: &str, name: &str, header: &Header, - _sysconfig: &SysconfigFile, ) -> anyhow::Result<Option<u32>> { let name = Cow::from(name); @@ -884,10 +875,7 @@ fn generate_irqs( Ok(interrupts) } -fn generate_dma_channels( - _chip_name: &str, - sysconfig: &SysconfigFile, -) -> anyhow::Result<BTreeMap<u32, DmaChannel>> { +fn generate_dma_channels(sysconfig: &SysconfigFile) -> anyhow::Result<BTreeMap<u32, DmaChannel>> { static PATTERN: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"DMA_CH(?<channel>\d+)").unwrap()); @@ -935,7 +923,11 @@ const UNICOMM_MODE_OFFSETS: &[(&str, u32)] = &[ ]; /// The register map each UNICOMM mode is described by, and how [`Unicomm`] reports it. -const UNICOMM_MODE_TYPES: &[(&str, PeripheralType, fn(&Unicomm) -> bool)] = &[ +/// A UNICOMM mode view: the suffix its instance name takes, the peripheral type it becomes, and +/// whether a given instance implements it. +type UnicommMode = (&'static str, PeripheralType, fn(&Unicomm) -> bool); + +const UNICOMM_MODE_TYPES: &[UnicommMode] = &[ ("UART", PeripheralType::UnicommUart, |m| m.uart), ("I2CC", PeripheralType::UnicommI2cc, |m| m.i2c_controller), ("I2CT", PeripheralType::UnicommI2ct, |m| m.i2c_target), @@ -1198,13 +1190,14 @@ fn apply_peripheral_interrupts( interrupts: &BTreeMap<i32, Interrupt>, ) { for (name, peripheral) in peripherals.iter_mut() { - let own = interrupts.values().filter_map(|interrupt| { - (&interrupt.name == name).then(|| PeripheralInterrupt { + let own = interrupts + .values() + .filter(|interrupt| &interrupt.name == name) + .map(|interrupt| PeripheralInterrupt { name: interrupt.name.clone(), num: interrupt.num, group_iidx: None, - }) - }); + }); let shared = interrupts.values().filter_map(|interrupt| { let (&iidx, _) = interrupt.group.iter().find(|(_, member)| *member == name)?; diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index f7b82a9..ade53e6 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -47,13 +47,15 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ ("msps003fx:sysctl", "c110x"), ("mspm0g..0x:sysctl", "g350x_g310x_g150x_g110x"), ("mspm0g..1x:sysctl", "g351x_g151x"), - // FIXME: When reference manual is updated for G511x/G518x, update this if needed. + // G511x/G518x have no reference manual of their own yet, so they borrow the G350x SYSCTL. + // Split them out when one is published. ("mspm0g5..x:sysctl", "g350x_g310x_g150x_g110x"), ("mspm0h321x:sysctl", "h321x"), ("mspm0l..0x:sysctl", "l110x_l130x_l134x"), ("mspm0l134x:sysctl", "l110x_l130x_l134x"), ("mspm0l.22x:sysctl", "l122x_l222x"), - // FIXME: When reference manual is updated for L112/L211x, update these if needed (split out). + // L112x/L211x likewise borrow the L122x SYSCTL until their own reference manual lands. + // They are known to differ already: both have a beeper, which l122x and l222x do not. ("mspm0l112x:sysctl", "l122x_l222x"), ("mspm0l211x:sysctl", "l122x_l222x"), ]); diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 348221d..a974eaf 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -247,7 +247,8 @@ pub struct PackagePin { pub signals: Vec<String>, } -// TODO: The rest +// Every peripheral sysconfig names on a supported device needs a variant here: one that falls +// through to `Unknown` is dropped from the generated metadata, which `verify.rs` reports. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "type")] pub enum PeripheralType { diff --git a/mspm0-metapac-gen/src/main.rs b/mspm0-metapac-gen/src/main.rs index fe3ae90..eef09ff 100644 --- a/mspm0-metapac-gen/src/main.rs +++ b/mspm0-metapac-gen/src/main.rs @@ -25,20 +25,10 @@ fn main() -> anyhow::Result<()> { let out_dir = PathBuf::from("build/mspm0-metapac"); fs::create_dir_all(&out_dir)?; - for f in glob::glob("build/data/*")? { - let f = f.unwrap(); - - if f.file_name() - .unwrap() - .to_string_lossy() - .starts_with("ignore.") - { - continue; - } - + for f in glob::glob("build/data/*.json")?.flatten() { let name = f .file_name() - .unwrap() + .context("chip data file has no name")? .to_string_lossy() .replace(".json", "") .to_lowercase(); From 01cd736a532979454a88ade7fe0dcae93fff75a8 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 15:22:17 +0100 Subject: [PATCH 074/134] vref: expose all three READY bits hw_vref.h puts READY0 at bit 0, READY1 at bit 2 and READY2 at bit 3, one per reference buffer, with bit 1 unused. The block carried a single READY at bit 0, so two were unreachable and the offsets are irregular enough that a caller could not have worked around it. The ENABLE description said a device may implement fewer buffers than the three the register addresses and that the datasheet says how many. The first half is right - SLAU846 Figure 23-1 is a superset diagram - but nothing published says which a device has, and the claim that the datasheet does sent the HAL looking for a fact that is not there. --- data/registers/vref_v1.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/data/registers/vref_v1.yaml b/data/registers/vref_v1.yaml index 1b53359..7824128 100644 --- a/data/registers/vref_v1.yaml +++ b/data/registers/vref_v1.yaml @@ -66,7 +66,7 @@ fieldset/CTL0: description: Control 0. fields: - name: ENABLE - description: Enables the corresponding VREF reference buffer. A device may implement fewer buffers than the three the register can address; the device datasheet says how many. + description: 'Enables reference buffer n. Each buffer feeds a different set of on-chip analog consumers rather than being interchangeable: hw_vref.h names buffer 1 COMP_VREF and buffer 2 ADC_VREF, and index 0 is the one TI''s own driverlib enables. SLAU846 Figure 23-1 is a superset diagram, so a device need not implement all three; nothing published says which it has.' bit_offset: 0 bit_size: 1 array: @@ -85,9 +85,11 @@ fieldset/CTL1: description: Control 1. fields: - name: READY - description: VREF output is ready. Once set, it stays set when VREF is disabled and later re-enabled. On a device with more than one reference buffer it only applies to the first one enabled. + description: 'Reference buffer n has settled. One bit per ENABLE, but not at the same offsets: READY0 is bit 0, READY1 bit 2 and READY2 bit 3, with bit 1 unused. Beware VREF_ERR_01, which leaves the bit set once the buffer has been enabled a first time since reset, so on an affected device it cannot report a later enable and the datasheet startup time is the only signal.' bit_offset: 0 bit_size: 1 + array: + offsets: [0, 2, 3] fieldset/CTL2: description: Control 2. fields: From 39a3eaed7a7dd32b93a66098301f1e6fc8dc9bf3 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 15:25:03 +0100 Subject: [PATCH 075/134] tools: extract the VREF startup time from the datasheets VREF_ERR_01 leaves CTL1.READY set once a buffer has been enabled since reset, so on the 93 affected devices the datasheet figure is the only way to know a later enable has settled. It spans 10us to 200us across the portfolio and follows neither the family nor the SYSCTL version, so one constant would either block far too long or return an unsettled reference. The four figures the HAL measured or read by hand come out identical, and the G5187 states the row twice - 20us bare and 200us with the 1uF cap on VREF+. The slower is recorded, as wakeup.py already does for a mode given under several conditions, with every condition quoted in the file. --- data/vref/mspm0c1105_c1106.yaml | 12 ++ data/vref/mspm0c110x.yaml | 12 ++ data/vref/mspm0g110x.yaml | 12 ++ data/vref/mspm0g150x.yaml | 12 ++ data/vref/mspm0g151x.yaml | 12 ++ data/vref/mspm0g310x.yaml | 12 ++ data/vref/mspm0g350x.yaml | 12 ++ data/vref/mspm0g351x.yaml | 12 ++ data/vref/mspm0g518x.yaml | 14 +++ data/vref/mspm0h321x.yaml | 12 ++ data/vref/mspm0l110x.yaml | 12 ++ data/vref/mspm0l112x.yaml | 12 ++ data/vref/mspm0l122x.yaml | 12 ++ data/vref/mspm0l130x.yaml | 12 ++ data/vref/mspm0l134x.yaml | 12 ++ data/vref/mspm0l211x.yaml | 12 ++ data/vref/mspm0l222x.yaml | 12 ++ data/vref/msps003fx.yaml | 12 ++ tools/vref.py | 192 ++++++++++++++++++++++++++++++++ 19 files changed, 410 insertions(+) create mode 100644 data/vref/mspm0c1105_c1106.yaml create mode 100644 data/vref/mspm0c110x.yaml create mode 100644 data/vref/mspm0g110x.yaml create mode 100644 data/vref/mspm0g150x.yaml create mode 100644 data/vref/mspm0g151x.yaml create mode 100644 data/vref/mspm0g310x.yaml create mode 100644 data/vref/mspm0g350x.yaml create mode 100644 data/vref/mspm0g351x.yaml create mode 100644 data/vref/mspm0g518x.yaml create mode 100644 data/vref/mspm0h321x.yaml create mode 100644 data/vref/mspm0l110x.yaml create mode 100644 data/vref/mspm0l112x.yaml create mode 100644 data/vref/mspm0l122x.yaml create mode 100644 data/vref/mspm0l130x.yaml create mode 100644 data/vref/mspm0l134x.yaml create mode 100644 data/vref/mspm0l211x.yaml create mode 100644 data/vref/mspm0l222x.yaml create mode 100644 data/vref/msps003fx.yaml create mode 100644 tools/vref.py diff --git a/data/vref/mspm0c1105_c1106.yaml b/data/vref/mspm0c1105_c1106.yaml new file mode 100644 index 0000000..7fc5d13 --- /dev/null +++ b/data/vref/mspm0c1105_c1106.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0C1106 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD = 2.8 V + +startup_ns: 30000 diff --git a/data/vref/mspm0c110x.yaml b/data/vref/mspm0c110x.yaml new file mode 100644 index 0000000..36504e2 --- /dev/null +++ b/data/vref/mspm0c110x.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0C1104 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD = 2.7 V + +startup_ns: 10000 diff --git a/data/vref/mspm0g110x.yaml b/data/vref/mspm0g110x.yaml new file mode 100644 index 0000000..e6a7971 --- /dev/null +++ b/data/vref/mspm0g110x.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0G1107 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD = 2.8 V, C = 1ยตF VREF + +startup_ns: 200000 diff --git a/data/vref/mspm0g150x.yaml b/data/vref/mspm0g150x.yaml new file mode 100644 index 0000000..6b552af --- /dev/null +++ b/data/vref/mspm0g150x.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0G1507 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD = 2.8 V, C = 1ยตF VREF + +startup_ns: 200000 diff --git a/data/vref/mspm0g151x.yaml b/data/vref/mspm0g151x.yaml new file mode 100644 index 0000000..c97598e --- /dev/null +++ b/data/vref/mspm0g151x.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0G1519 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD โ‰ฅ 2.7 V, C = 1ยตF VREF + +startup_ns: 200000 diff --git a/data/vref/mspm0g310x.yaml b/data/vref/mspm0g310x.yaml new file mode 100644 index 0000000..f625258 --- /dev/null +++ b/data/vref/mspm0g310x.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0G3107 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD = 2.8 V, C = 1ยตF VREF + +startup_ns: 200000 diff --git a/data/vref/mspm0g350x.yaml b/data/vref/mspm0g350x.yaml new file mode 100644 index 0000000..51af589 --- /dev/null +++ b/data/vref/mspm0g350x.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0G3507 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD = 2.8 V, C = 1ยตF VREF + +startup_ns: 200000 diff --git a/data/vref/mspm0g351x.yaml b/data/vref/mspm0g351x.yaml new file mode 100644 index 0000000..7f8bcd1 --- /dev/null +++ b/data/vref/mspm0g351x.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0G3519 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD โ‰ฅ 2.7 V, C = 1ยตF VREF + +startup_ns: 200000 diff --git a/data/vref/mspm0g518x.yaml b/data/vref/mspm0g518x.yaml new file mode 100644 index 0000000..57cca84 --- /dev/null +++ b/data/vref/mspm0g518x.yaml @@ -0,0 +1,14 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0G5187 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD โ‰ฅ 2.7 V, C = 1ยตF VREF +# +# slowest of: 20us at BUFCONFIG = {0, 1} , VDD โ‰ฅ 2.7 V; 200us at BUFCONFIG = {0, 1} , VDD โ‰ฅ 2.7 V, C = 1ยตF VREF + +startup_ns: 200000 diff --git a/data/vref/mspm0h321x.yaml b/data/vref/mspm0h321x.yaml new file mode 100644 index 0000000..9833e70 --- /dev/null +++ b/data/vref/mspm0h321x.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0H3216 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# VDD = 5 V + +startup_ns: 30000 diff --git a/data/vref/mspm0l110x.yaml b/data/vref/mspm0l110x.yaml new file mode 100644 index 0000000..5e1be63 --- /dev/null +++ b/data/vref/mspm0l110x.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0L1106 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD = 2.8 V + +startup_ns: 15000 diff --git a/data/vref/mspm0l112x.yaml b/data/vref/mspm0l112x.yaml new file mode 100644 index 0000000..0a2ba29 --- /dev/null +++ b/data/vref/mspm0l112x.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0L2117 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD = 2.8 V + +startup_ns: 30000 diff --git a/data/vref/mspm0l122x.yaml b/data/vref/mspm0l122x.yaml new file mode 100644 index 0000000..36d3a43 --- /dev/null +++ b/data/vref/mspm0l122x.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0L1228 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD = 2.8 V + +startup_ns: 15000 diff --git a/data/vref/mspm0l130x.yaml b/data/vref/mspm0l130x.yaml new file mode 100644 index 0000000..03cc6aa --- /dev/null +++ b/data/vref/mspm0l130x.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0L1306 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD = 2.8 V + +startup_ns: 15000 diff --git a/data/vref/mspm0l134x.yaml b/data/vref/mspm0l134x.yaml new file mode 100644 index 0000000..bedd12e --- /dev/null +++ b/data/vref/mspm0l134x.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0L1346 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD = 2.8 V + +startup_ns: 15000 diff --git a/data/vref/mspm0l211x.yaml b/data/vref/mspm0l211x.yaml new file mode 100644 index 0000000..0a2ba29 --- /dev/null +++ b/data/vref/mspm0l211x.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0L2117 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD = 2.8 V + +startup_ns: 30000 diff --git a/data/vref/mspm0l222x.yaml b/data/vref/mspm0l222x.yaml new file mode 100644 index 0000000..28eaf7f --- /dev/null +++ b/data/vref/mspm0l222x.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0L2228 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD = 2.8 V + +startup_ns: 15000 diff --git a/data/vref/msps003fx.yaml b/data/vref/msps003fx.yaml new file mode 100644 index 0000000..36504e2 --- /dev/null +++ b/data/vref/msps003fx.yaml @@ -0,0 +1,12 @@ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the MSPM0C1104 datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# BUFCONFIG = {0, 1} , VDD = 2.7 V + +startup_ns: 10000 diff --git a/tools/vref.py b/tools/vref.py new file mode 100644 index 0000000..a7ee15b --- /dev/null +++ b/tools/vref.py @@ -0,0 +1,192 @@ +"""Extract the VREF startup time from MSPM0 datasheets. + +`VREF.CTL0.ENABLE` starts a reference buffer and `CTL1.READY` is supposed to say when it has settled, +but `VREF_ERR_01` leaves that bit set once a buffer has been enabled a first time since reset. On an +affected device -- 93 of them -- the datasheet's `Tstartup` is the only way to know the reference is +usable, so TI's own workaround is to wait it out. This writes `data/vref/<family>.yaml`. + +Like `tools/wakeup.py`, this is an offline aid rather than part of the build. Re-run it after a data +source bump rather than editing the YAML by hand. + +Usage: + uv run tools/vref.py <datasheet.pdf> [...] # print what it finds + uv run tools/vref.py --write <dir-of-datasheets> # regenerate data/vref + +`uv run` installs the dependencies below on its own; a bare `python` needs them on the path. + +Two things about the row are worth knowing: + +- **The figure spans MIN, TYP and MAX**, as the wake-up times do, so it is a typical value rather than + a guaranteed ceiling. A consumer needing margin has to add its own. +- **The stated conditions differ between datasheets** -- `VDD = 2.8 V` on most, `2.7 V` on the C1104 -- + and one figure per device is therefore a simplification. The conditions are written into the + generated file's header so the simplification is visible rather than implied. +""" + +# /// script +# requires-python = ">=3.10" +# dependencies = ["pdfplumber", "pypdfium2", "pyyaml"] +# /// + +import re +import sys +from collections.abc import Iterator +from pathlib import Path + +try: + import pdfplumber + import pypdfium2 + import yaml +except ImportError as e: # pragma: no cover + raise SystemExit(f"{e.name} is missing; run this with `uv run` instead of `python`") + +CAPTION = "VREF startup time" + +LATTICE = {"vertical_strategy": "lines", "horizontal_strategy": "lines"} + +#: Time units the figure may be given in, as a multiplier to nanoseconds. The datasheets are +#: inconsistent about the case of the SI prefix, hence the lowercasing before lookup. +UNITS = {"ns": 1, "us": 1_000, "ยตs": 1_000, "ms": 1_000_000} + +NUMBER = re.compile(r"\d+(?:\.\d+)?") + + +def caption_pages(path: Path) -> list[int]: + """Indices of the pages carrying the VREF electrical table.""" + doc = pypdfium2.PdfDocument(str(path)) + try: + return [i for i in range(len(doc)) if CAPTION in doc[i].get_textpage().get_text_range()] + finally: + doc.close() + + +def read_tables(path: Path) -> Iterator[tuple[int, str]]: + """Yield `(startup time in ns, conditions)` for each VREF startup row found.""" + for page_index in caption_pages(path): + with pdfplumber.open(str(path)) as pdf: + for table in pdf.pages[page_index].extract_tables(LATTICE): + for row in table: + cells = [(c or "").replace("\n", " ").strip() for c in row] + if not any(CAPTION in cell for cell in cells): + continue + + found = read_row(cells) + if found is not None: + yield found + + +def read_row(cells: list[str]) -> tuple[int, str] | None: + """Read one `Tstartup` row into nanoseconds, or `None` if it does not parse. + + The row is symbol, description, conditions, the MIN/TYP/MAX columns and a unit. Only one of the + three figure columns is filled, so the first bare number after the caption is the figure wherever + the table puts it. + """ + unit = next((c for c in reversed(cells) if c and c.lower() in UNITS), "") + if not unit: + return None + + caption_at = next(i for i, cell in enumerate(cells) if CAPTION in cell) + + # Conditions may share the caption's cell or sit in the next one; either way they are the text + # between the caption and the first figure. + conditions = "" + value = None + for cell in cells[caption_at:]: + stripped = cell.replace(CAPTION, "").strip() + if NUMBER.fullmatch(stripped): + value = float(stripped) + break + if stripped: + conditions = f"{conditions} {stripped}".strip() + + if value is None: + return None + + return round(value * UNITS[unit.lower()]), undouble(" ".join(conditions.split())) + + +def undouble(text: str) -> str: + """Collapse a condition string the extractor read twice. + + The C1104's table returns its condition cell doubled -- `BUFCONFIG = {0, 1} , VDD = 2.7 V` twice + over -- where the other datasheets return it once. Only the text repeats; the figure beside it is + read correctly either way. + """ + words = text.split() + half = len(words) // 2 + if words and len(words) % 2 == 0 and words[:half] == words[half:]: + return " ".join(words[:half]) + + return text + + +HEADER = """\ +# How long this family's VREF takes to settle after CTL0.ENABLE, in nanoseconds. +# +# GENERATED by `tools/vref.py --write` from the `Tstartup` row of the {ds} datasheet. +# Re-run the tool rather than editing by hand. +# +# The figure's cell spans the datasheet's MIN, TYP and MAX columns, so it is typical rather than a +# guaranteed ceiling. It is stated under one set of conditions, quoted here, and recorded as one +# number per family because that is all the source gives: +# +# {conditions} +""" + + +def write(datasheets: str) -> int: + """Regenerate data/vref/*.yaml. Returns the number of problems reported.""" + parts = yaml.safe_load(Path("data/parts.yaml").read_text(encoding="utf-8")) + families = [(f["family"], f["datasheet_url"].rsplit("/", 1)[-1]) for f in parts["families"]] + + Path("data/vref").mkdir(parents=True, exist_ok=True) + problems = 0 + + for family, gpn in families: + pdf = Path(datasheets) / f"{gpn}_datasheet.pdf" + if not pdf.exists(): + print(f"{family}: no datasheet for {gpn} in {datasheets}") + problems += 1 + continue + + found = list(read_tables(pdf)) + if not found: + print(f"{family}: no VREF startup time in {pdf.name}") + problems += 1 + continue + + # A datasheet may state the row under several conditions -- the G5187 gives 20us bare and + # 200us with the 1uF capacitor on VREF+ -- and the slowest is the one worth planning around, + # since waiting too long only costs time. Every condition is quoted so the choice is visible. + ns, conditions = max(found, key=lambda f: f[0]) + if len({value for value, _ in found}) > 1: + others = "; ".join(f"{v / 1000:g}us at {c}" for v, c in sorted(found)) + conditions = f"{conditions}\n#\n# slowest of: {others}" + + path = Path(f"data/vref/{family}.yaml") + with open(path, "w", encoding="utf-8", newline="\n") as out: + out.write(HEADER.format(ds=gpn.upper(), conditions=conditions or "none stated")) + out.write(f"\nstartup_ns: {ns}\n") + print(f"{family}: {ns / 1000:g} us, from {pdf.name}") + + return problems + + +def main(argv: list[str]) -> None: + if "--write" in argv: + i = argv.index("--write") + raise SystemExit(1 if write(argv[i + 1] if i + 1 < len(argv) else ".") else 0) + + paths = [Path(a) for a in argv if not a.startswith("--")] + if not paths: + raise SystemExit(__doc__) + + for path in paths: + print(f"########## {path.name}") + for ns, conditions in read_tables(path): + print(f" {ns / 1000:>8g} us {conditions}") + + +if __name__ == "__main__": + main(sys.argv[1:]) From ef43b716f243e19b22536546f1147d56e1259ef4 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 15:31:10 +0100 Subject: [PATCH 076/134] vref: carry the startup time through to the PAC metadata Peripheral::vref, alongside the adc, timer and unicomm fields which already hang per-type facts off the instance rather than the chip. A device without VREF has no entry at all, which is a cleaner absence than a chip-level Option that is None for two different reasons. verify reports a VREF with no startup time, so a family whose datasheet gains or loses the row is not silently left without one. --- mspm0-data-gen/src/generate.rs | 27 +++++++++++++++++++++++++++ mspm0-data-gen/src/main.rs | 3 +++ mspm0-data-gen/src/sources.rs | 5 ++++- mspm0-data-gen/src/verify.rs | 23 +++++++++++++++++++++++ mspm0-data-gen/src/vref.rs | 17 +++++++++++++++++ mspm0-data-types/src/lib.rs | 26 ++++++++++++++++++++++++++ mspm0-metapac-gen/res/metadata.rs | 24 ++++++++++++++++++++++++ mspm0-metapac-gen/src/metadata.rs | 9 +++++++++ 8 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 mspm0-data-gen/src/vref.rs diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 965305e..4383b18 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -10,6 +10,7 @@ use anyhow::{anyhow, bail, ensure, Context}; use mspm0_data_types::{ Adc, Chip, DmaChannel, Interrupt, Memory, MemoryKind, Package, PackagePin, Peripheral, PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, Timer, Unicomm, + Vref, }; use regex::Regex; @@ -49,6 +50,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu timers, errata, wake, + vref, int_groups, } = *sources; @@ -73,6 +75,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu apply_clock_ranges(family, &mut peripherals); apply_adc(family, sysconfig, &mut peripherals)?; apply_unicomm(family, header, &mut peripherals)?; + apply_vref(vref, &mut peripherals); for part_number in family.part_numbers.iter() { // Filter for package types available on the part number. @@ -303,6 +306,7 @@ fn generate_peripherals2( clock_range_hz: None, adc: None, unicomm: None, + vref: None, }; // Lookup the pins @@ -537,6 +541,7 @@ fn generate_missing( clock_range_hz: None, adc: None, unicomm: None, + vref: None, }, ); @@ -569,6 +574,7 @@ fn generate_missing( clock_range_hz: None, adc: None, unicomm: None, + vref: None, }, ); @@ -609,6 +615,7 @@ fn generate_missing( clock_range_hz: None, adc: None, unicomm: None, + vref: None, }); let pin = device_pin @@ -672,6 +679,7 @@ fn generate_missing( clock_range_hz: None, adc: None, unicomm: None, + vref: None, }, ); } @@ -1023,6 +1031,7 @@ fn apply_unicomm( clock_range_hz: None, adc: None, unicomm: None, + vref: None, }); } } @@ -1034,6 +1043,24 @@ fn apply_unicomm( Ok(()) } +/// Attach the family's VREF startup time to its VREF instance. +/// +/// Left absent rather than defaulted when the family has no figure: a consumer which has to wait out +/// `VREF_ERR_01` needs to refuse rather than guess, and a guessed number is one that silently returns +/// an unsettled reference. +fn apply_vref(vref: Option<Vref>, peripherals: &mut BTreeMap<String, Peripheral>) { + let Some(vref) = vref else { + return; + }; + + for peripheral in peripherals + .values_mut() + .filter(|peripheral| peripheral.ty == PeripheralType::Vref) + { + peripheral.vref = Some(vref); + } +} + fn apply_adc( family: &PartFamily, sysconfig: &SysconfigFile, diff --git a/mspm0-data-gen/src/main.rs b/mspm0-data-gen/src/main.rs index cc7dfd8..0a9f0ac 100644 --- a/mspm0-data-gen/src/main.rs +++ b/mspm0-data-gen/src/main.rs @@ -11,6 +11,7 @@ mod sysconfig; mod timers; mod sources; mod util; +mod vref; mod verify; mod wakeup; @@ -77,6 +78,7 @@ fn main() -> anyhow::Result<()> { let timers = timers::parse()?; let errata = errata::parse()?; let wake = wakeup::parse()?; + let vref = vref::parse()?; let parts = parts::PartsFile::read()?; // TODO: Expanded family names (ex. C110X -> C1103 & C1104) @@ -92,6 +94,7 @@ fn main() -> anyhow::Result<()> { timers, errata, wake, + vref, }; stopwatch.section("Generate data"); diff --git a/mspm0-data-gen/src/sources.rs b/mspm0-data-gen/src/sources.rs index dbdbdb8..bd62eee 100644 --- a/mspm0-data-gen/src/sources.rs +++ b/mspm0-data-gen/src/sources.rs @@ -6,7 +6,7 @@ use std::collections::BTreeMap; -use mspm0_data_types::WakeTimes; +use mspm0_data_types::{Vref, WakeTimes}; use crate::{ clock_tree::{ClockTreeFile, ClockTrees}, @@ -32,6 +32,7 @@ pub struct Sources { pub timers: BTreeMap<String, Timers>, pub errata: BTreeMap<String, Errata>, pub wake: BTreeMap<String, WakeTimes>, + pub vref: BTreeMap<String, Vref>, } /// What one family is described by. @@ -47,6 +48,7 @@ pub struct FamilySources<'a> { pub timers: Option<&'a Timers>, pub errata: Option<&'a Errata>, pub wake: Option<WakeTimes>, + pub vref: Option<Vref>, /// Not narrowed to the family: `generate_irqs` looks groups up per chip, not per family. pub int_groups: &'a BTreeMap<String, Groups>, @@ -85,6 +87,7 @@ impl Sources { timers: self.timers.get(family), errata: self.errata.get(family), wake: self.wake.get(family).copied(), + vref: self.vref.get(family).copied(), int_groups: &self.int_groups, }) } diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index f468c7d..8e4e6c0 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -12,6 +12,7 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { gpio_no_duplicates, peripheral_types_known, register_blocks_exist, + vref_startup_known, // Peripherals which don't actually exist no_gpamp_c110x_l151x, // Low power data which is only as complete as the data sources @@ -91,6 +92,28 @@ fn register_blocks_exist(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// Report a VREF instance with no startup time. +/// +/// A device carrying `VREF_ERR_01` cannot trust `CTL1.READY` after the first enable since reset, so +/// without this figure a consumer has no way to know the reference has settled. +fn vref_startup_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + for peripheral in chip + .peripherals + .values() + .filter(|peripheral| peripheral.ty == PeripheralType::Vref) + { + if peripheral.vref.and_then(|vref| vref.startup_ns).is_none() { + bail!( + "{name}: {} has no startup time; data/vref/{}.yaml is missing or has no Tstartup row", + peripheral.name, + chip.family + ); + } + } + + Ok(()) +} + /// Verify all core peripherals are present. fn core_peripherals(chip: &Chip, name: &str) -> anyhow::Result<()> { if !chip.peripherals.contains_key("CPUSS") { diff --git a/mspm0-data-gen/src/vref.rs b/mspm0-data-gen/src/vref.rs new file mode 100644 index 0000000..51aa1b5 --- /dev/null +++ b/mspm0-data-gen/src/vref.rs @@ -0,0 +1,17 @@ +//! Reads `data/vref/<family>.yaml`, which `tools/vref.py` extracts from the datasheets. +//! +//! `CTL1.READY` says when a reference buffer has settled, but `VREF_ERR_01` leaves that bit set once +//! a buffer has been enabled a first time since reset. On the devices carrying it the datasheet's +//! `Tstartup` is the only way to know a later enable has taken effect, which is why a figure with no +//! machine-readable source is worth transcribing. + +use std::collections::BTreeMap; + +use mspm0_data_types::Vref; + +use crate::util; + +/// Read every `data/vref/<family>.yaml`, keyed by family name. +pub fn parse() -> anyhow::Result<BTreeMap<String, Vref>> { + util::per_family("vref") +} diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index a974eaf..1a3413e 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -528,6 +528,32 @@ pub struct Peripheral { /// `None` for peripherals which are not UNICOMM instances. #[serde(skip_serializing_if = "Option::is_none")] pub unicomm: Option<Unicomm>, + + /// The parts of the VREF instance which the `vref_v1` register block does not describe. + /// + /// `None` for peripherals which are not the VREF. + #[serde(skip_serializing_if = "Option::is_none")] + pub vref: Option<Vref>, +} + +/// The parts of the VREF instance which the single `vref_v1` register block does not describe. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Vref { + /// Time from `CTL0.ENABLE` to a settled reference, in nanoseconds. + /// + /// `CTL1.READY` reports this and is the better signal, but `VREF_ERR_01` leaves the bit set once + /// a buffer has been enabled a first time since reset, so on a device carrying that erratum it + /// cannot report a later enable and this figure is the only way to know. Check + /// [`Chip::errata`] rather than assuming either way. + /// + /// Typical rather than a guaranteed ceiling: the datasheet cell spans its MIN, TYP and MAX + /// columns. Where a datasheet states the row under several conditions this is the slowest of + /// them, which on the G5187 means the 200us figure with a 1uF capacitor on `VREF+` rather than + /// the 20us one without. + /// + /// `None` when the family's datasheet has no `Tstartup` row. + #[serde(skip_serializing_if = "Option::is_none")] + pub startup_ns: Option<u32>, } /// The parts of one ADC instance which the single `adc_v1` register block does not describe. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index c61f903..8209a71 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -239,6 +239,11 @@ pub struct Peripheral { /// /// `None` for peripherals which are not UNICOMM instances. pub unicomm: Option<Unicomm>, + + /// The parts of the VREF instance which the register block does not describe. + /// + /// `None` for peripherals which are not the VREF. + pub vref: Option<Vref>, } /// Which register maps a UNICOMM instance implements. @@ -369,6 +374,25 @@ pub enum PowerMode { Shutdown, } +/// The parts of the VREF instance which the register block does not describe. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct Vref { + /// Time from `CTL0.ENABLE` to a settled reference, in nanoseconds. + /// + /// `CTL1.READY` reports this and is the better signal, but `VREF_ERR_01` leaves the bit set once + /// a buffer has been enabled a first time since reset, so on a device carrying that erratum it + /// cannot report a later enable and this figure is the only way to know. Check `Metadata::errata` + /// rather than assuming either way. + /// + /// Typical rather than a guaranteed ceiling: the datasheet cell spans its MIN, TYP and MAX + /// columns. Where a datasheet states the row under several conditions this is the slowest, which + /// on the G5187 is the 200us figure with a 1uF capacitor on `VREF+` rather than the 20us one + /// without. + /// + /// `None` when the family's datasheet has no `Tstartup` row. + pub startup_ns: Option<u32>, +} + /// The interrupt raised by a peripheral. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub struct PeripheralInterrupt { diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index de575ae..f1d98bc 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -367,6 +367,14 @@ fn generate_peripheral( None => quote! { None }, }; + let vref = match peripheral.vref.and_then(|vref| vref.startup_ns) { + Some(ns) => { + let ns = Literal::u32_unsuffixed(ns); + quote! { Some(Vref { startup_ns: Some(#ns) }) } + } + None => quote! { None }, + }; + Some(quote! { Peripheral { name: #name, @@ -384,6 +392,7 @@ fn generate_peripheral( clock_range_hz: #clock_range_hz, adc: #adc, unicomm: #unicomm, + vref: #vref, } }) } From ab710d78768d4a28b9fdcc5bf6eca545e6963f13 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Sun, 9 Aug 2026 16:57:08 +0100 Subject: [PATCH 077/134] docs: list the VREF extraction alongside the other datasheet tools README had four tools where there are now five, and no mention of the data/vref directory or why a startup time needs transcribing at all. Also makes the affected-device count precise: 22 of the 43 part numbers carry VREF_ERR_01, which is the 93 chip+package features the HAL counted. --- README.md | 7 ++++++- tools/vref.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 11d35f8..76165d2 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,10 @@ These are the data sources currently used. ([`data/timers/`](./data/timers)) * How long the device takes to reach RUN from each sleep mode, from the wake-up timing table ([`data/wakeup/`](./data/wakeup)) + * How long VREF takes to settle after being enabled, from the `Tstartup` row + ([`data/vref/`](./data/vref)). `CTL1.READY` would answer it, but `VREF_ERR_01` leaves that bit + set after the first enable since reset, so on an affected device the datasheet figure is the + only signal * Which timers stay clocked in STANDBY1 (`standby1_timers` in [`parts.yaml`](./data/parts.yaml)) * MCLK and ULPCLK ceilings, the SYSOSC base frequency, the flash wait-state bands, `fADCCLK` and `TRNGCLKF` (all in [`parts.yaml`](./data/parts.yaml)) @@ -84,7 +88,8 @@ the `tools/` scripts read them from there. 3. If needed, add any chip specific register blocks like `sysctl`. 4. Check the peripheral mapping in [`perimap.rs`](./mspm0-data-gen/src/perimap.rs) to use the correct peripherals. 5. Fetch the documents with `./d download-docs`, then regenerate the extracted data: - `tools/operating_modes.py`, `tools/timers.py`, `tools/wakeup.py` and `tools/errata.py`, each with + `tools/operating_modes.py`, `tools/timers.py`, `tools/wakeup.py`, `tools/vref.py` and + `tools/errata.py`, each with `--write files`. 6. Run `./d gen` and read its output. `verify.rs` reports every per-chip gap it can detect, including a family with no timer, errata or operating-mode data. diff --git a/tools/vref.py b/tools/vref.py index a7ee15b..933e0b1 100644 --- a/tools/vref.py +++ b/tools/vref.py @@ -2,7 +2,7 @@ `VREF.CTL0.ENABLE` starts a reference buffer and `CTL1.READY` is supposed to say when it has settled, but `VREF_ERR_01` leaves that bit set once a buffer has been enabled a first time since reset. On an -affected device -- 93 of them -- the datasheet's `Tstartup` is the only way to know the reference is +affected device -- 22 of the 43 part numbers -- the datasheet's `Tstartup` is the only way to know it is usable, so TI's own workaround is to wait it out. This writes `data/vref/<family>.yaml`. Like `tools/wakeup.py`, this is an offline aid rather than part of the build. Re-run it after a data From 5124368ee62f6add74677df8550bf14fbcabae14 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 11:42:16 +0100 Subject: [PATCH 078/134] operating modes: report the STOP and STANDBY sub-mode usable_through required every policy column of a mode group to agree, so a peripheral usable in STANDBY0 but not STANDBY1 -- the windowed watchdog, on almost every family -- was rounded down to Stop. STOP0/1/2 and STANDBY0/1 each disable a superset of the one before, so they order cleanly. RUN and SLEEP stay whole: their sub-modes are clock-source policies rather than depths, and RUN2 runs the CPU with SYSOSC off where the deeper SLEEP0 has it on. Reading the sub-mode names out of the table's second header row also settles two rows the group heuristic got wrong. MSPM0H3216 has no SHUTDOWN column, so its STANDBY1 was being consumed as one and hid a DIS on nine peripherals, and a footnote marker rendered without parentheses cost the MSPM0C1106 GPIO row its STANDBY1 state. --- data/operating_modes/mspm0c1105_c1106.yaml | 40 ++--- data/operating_modes/mspm0c110x.yaml | 24 +-- data/operating_modes/mspm0g110x.yaml | 44 ++--- data/operating_modes/mspm0g150x.yaml | 52 +++--- data/operating_modes/mspm0g151x.yaml | 66 ++++---- data/operating_modes/mspm0g310x.yaml | 44 ++--- data/operating_modes/mspm0g350x.yaml | 52 +++--- data/operating_modes/mspm0g351x.yaml | 66 ++++---- data/operating_modes/mspm0g518x.yaml | 30 ++-- data/operating_modes/mspm0h321x.yaml | 38 ++--- data/operating_modes/mspm0l110x.yaml | 30 ++-- data/operating_modes/mspm0l112x.yaml | 36 ++--- data/operating_modes/mspm0l122x.yaml | 58 +++---- data/operating_modes/mspm0l130x.yaml | 34 ++-- data/operating_modes/mspm0l134x.yaml | 34 ++-- data/operating_modes/mspm0l211x.yaml | 38 ++--- data/operating_modes/mspm0l222x.yaml | 60 +++---- data/operating_modes/msps003fx.yaml | 24 +-- mspm0-data-gen/src/generate.rs | 2 +- mspm0-data-types/src/lib.rs | 42 ++++- mspm0-metapac-gen/res/metadata.rs | 28 +++- mspm0-metapac-gen/src/metadata.rs | 7 +- tools/operating_modes.py | 177 ++++++++++++++------- 23 files changed, 564 insertions(+), 462 deletions(-) diff --git a/data/operating_modes/mspm0c1105_c1106.yaml b/data/operating_modes/mspm0c1105_c1106.yaml index f52880a..c2fecdd 100644 --- a/data/operating_modes/mspm0c1105_c1106.yaml +++ b/data/operating_modes/mspm0c1105_c1106.yaml @@ -7,29 +7,29 @@ # `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: - CPUSS: Standby - CRC: Standby - DMA: Standby - SPI0: Standby + CPUSS: Standby1 + CRC: Standby1 + DMA: Standby1 + SPI0: Standby1 usable_through: - ADC0: Sleep - COMP0: Standby + ADC0: Stop0 + COMP0: Standby1 CPUSS: Run CRC: Sleep DMA: Sleep - GPIOA: Standby - GPIOB: Standby - I2C0: Stop - I2C1: Stop + GPIOA: Standby1 + GPIOB: Standby1 + I2C0: Standby0 + I2C1: Standby0 SPI0: Sleep - TIMA0: Stop - TIMG1: Stop - TIMG14: Standby - TIMG2: Stop - TIMG8: Standby - UART0: Stop - UART1: Stop - UART2: Stop - VREF: Sleep - WWDT0: Standby + TIMA0: Standby0 + TIMG1: Standby0 + TIMG14: Standby1 + TIMG2: Standby0 + TIMG8: Standby1 + UART0: Standby0 + UART1: Standby0 + UART2: Standby0 + VREF: Stop0 + WWDT0: Standby1 diff --git a/data/operating_modes/mspm0c110x.yaml b/data/operating_modes/mspm0c110x.yaml index a15cf6a..271086a 100644 --- a/data/operating_modes/mspm0c110x.yaml +++ b/data/operating_modes/mspm0c110x.yaml @@ -7,21 +7,21 @@ # `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: - CPUSS: Standby - CRC: Standby - DMA: Standby - SPI0: Standby + CPUSS: Standby1 + CRC: Standby1 + DMA: Standby1 + SPI0: Standby1 usable_through: - ADC0: Sleep + ADC0: Stop0 CPUSS: Run CRC: Sleep DMA: Sleep - GPIOA: Standby + GPIOA: Standby1 SPI0: Sleep - TIMA0: Standby - TIMG14: Standby - TIMG8: Standby - UART0: Standby - VREF: Sleep - WWDT0: Stop + TIMA0: Standby1 + TIMG14: Standby1 + TIMG8: Standby1 + UART0: Standby1 + VREF: Stop0 + WWDT0: Standby0 diff --git a/data/operating_modes/mspm0g110x.yaml b/data/operating_modes/mspm0g110x.yaml index da6ac42..a1662d7 100644 --- a/data/operating_modes/mspm0g110x.yaml +++ b/data/operating_modes/mspm0g110x.yaml @@ -7,43 +7,43 @@ # `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: - CPUSS: Standby - CRC: Standby - DMA: Standby - SPI0: Standby - SPI1: Standby + CPUSS: Standby1 + CRC: Standby1 + DMA: Standby1 + SPI0: Standby1 + SPI1: Standby1 TIMA0: Sleep TIMA1: Sleep TIMG12: Sleep TIMG6: Sleep TIMG7: Sleep - UART3: Standby + UART3: Standby1 usable_through: - ADC0: Sleep - ADC1: Sleep + ADC0: Stop1 + ADC1: Stop1 CPUSS: Run CRC: Sleep DMA: Sleep - GPAMP: Sleep - GPIOA: Standby - GPIOB: Standby - I2C0: Standby - I2C1: Standby - RTC: Standby + GPAMP: Stop1 + GPIOA: Standby1 + GPIOB: Standby1 + I2C0: Standby1 + I2C1: Standby1 + RTC: Standby1 SPI0: Sleep SPI1: Sleep TIMA0: Sleep TIMA1: Sleep - TIMG0: Standby + TIMG0: Standby1 TIMG12: Sleep TIMG6: Sleep TIMG7: Sleep - TIMG8: Standby - UART0: Standby - UART1: Standby - UART2: Standby + TIMG8: Standby1 + UART0: Standby1 + UART1: Standby1 + UART2: Standby1 UART3: Sleep - VREF: Standby - WWDT0: Stop - WWDT1: Stop + VREF: Standby1 + WWDT0: Standby0 + WWDT1: Standby0 diff --git a/data/operating_modes/mspm0g150x.yaml b/data/operating_modes/mspm0g150x.yaml index 0f9d991..a0863f9 100644 --- a/data/operating_modes/mspm0g150x.yaml +++ b/data/operating_modes/mspm0g150x.yaml @@ -8,52 +8,52 @@ retained_through: AES: Sleep - CPUSS: Standby - CRC: Standby - DMA: Standby + CPUSS: Standby1 + CRC: Standby1 + DMA: Standby1 MATHACL: Sleep - SPI0: Standby - SPI1: Standby + SPI0: Standby1 + SPI1: Standby1 TIMA0: Sleep TIMA1: Sleep TIMG12: Sleep TIMG6: Sleep TIMG7: Sleep TRNG: Sleep - UART3: Standby + UART3: Standby1 usable_through: - ADC0: Sleep - ADC1: Sleep + ADC0: Stop1 + ADC1: Stop1 AES: Sleep - COMP0: Standby - COMP1: Standby - COMP2: Standby + COMP0: Standby1 + COMP1: Standby1 + COMP2: Standby1 CPUSS: Run CRC: Sleep - DAC0: Sleep + DAC0: Stop1 DMA: Sleep - GPAMP: Sleep - GPIOA: Standby - GPIOB: Standby - I2C0: Standby - I2C1: Standby + GPAMP: Stop1 + GPIOA: Standby1 + GPIOB: Standby1 + I2C0: Standby1 + I2C1: Standby1 MATHACL: Sleep - RTC: Standby + RTC: Standby1 SPI0: Sleep SPI1: Sleep TIMA0: Sleep TIMA1: Sleep - TIMG0: Standby + TIMG0: Standby1 TIMG12: Sleep TIMG6: Sleep TIMG7: Sleep - TIMG8: Standby + TIMG8: Standby1 TRNG: Sleep - UART0: Standby - UART1: Standby - UART2: Standby + UART0: Standby1 + UART1: Standby1 + UART2: Standby1 UART3: Sleep - VREF: Standby - WWDT0: Stop - WWDT1: Stop + VREF: Standby1 + WWDT0: Standby0 + WWDT1: Standby0 diff --git a/data/operating_modes/mspm0g151x.yaml b/data/operating_modes/mspm0g151x.yaml index 5069424..9d00e5d 100644 --- a/data/operating_modes/mspm0g151x.yaml +++ b/data/operating_modes/mspm0g151x.yaml @@ -8,63 +8,63 @@ retained_through: AESADV: Sleep - CPUSS: Standby - CRCP0: Standby - DMA: Standby + CPUSS: Standby1 + CRCP0: Standby1 + DMA: Standby1 MATHACL: Sleep - SPI0: Standby - SPI1: Standby - SPI2: Standby + SPI0: Standby1 + SPI1: Standby1 + SPI2: Standby1 TIMA0: Sleep TIMA1: Sleep TIMG12: Sleep TIMG6: Sleep TIMG7: Sleep TRNG: Sleep - UART3: Standby - UART4: Standby - UART5: Standby - UART6: Standby + UART3: Standby1 + UART4: Standby1 + UART5: Standby1 + UART6: Standby1 usable_through: - ADC0: Sleep - ADC1: Sleep + ADC0: Stop1 + ADC1: Stop1 AESADV: Sleep - COMP0: Standby - COMP1: Standby - COMP2: Standby + COMP0: Standby1 + COMP1: Standby1 + COMP2: Standby1 CPUSS: Run CRCP0: Sleep - DAC0: Sleep + DAC0: Stop1 DMA: Sleep - GPIOA: Standby - GPIOB: Standby - GPIOC: Standby - I2C0: Standby - I2C1: Standby - I2C2: Standby + GPIOA: Standby1 + GPIOB: Standby1 + GPIOC: Standby1 + I2C0: Standby1 + I2C1: Standby1 + I2C2: Standby1 MATHACL: Sleep - RTC_B: Standby + RTC_B: Standby1 SPI0: Sleep SPI1: Sleep SPI2: Sleep TIMA0: Sleep TIMA1: Sleep - TIMG0: Standby + TIMG0: Standby1 TIMG12: Sleep - TIMG14: Standby + TIMG14: Standby1 TIMG6: Sleep TIMG7: Sleep - TIMG8: Standby - TIMG9: Standby + TIMG8: Standby1 + TIMG9: Standby1 TRNG: Sleep - UART0: Standby - UART1: Standby + UART0: Standby1 + UART1: Standby1 UART3: Sleep UART4: Sleep UART5: Sleep UART6: Sleep - UART7: Standby - VREF: Standby - WWDT0: Stop - WWDT1: Stop + UART7: Standby1 + VREF: Standby1 + WWDT0: Standby0 + WWDT1: Standby0 diff --git a/data/operating_modes/mspm0g310x.yaml b/data/operating_modes/mspm0g310x.yaml index 948af74..06cf6f1 100644 --- a/data/operating_modes/mspm0g310x.yaml +++ b/data/operating_modes/mspm0g310x.yaml @@ -9,46 +9,46 @@ retained_through: AES: Sleep CANFD0: Sleep - CPUSS: Standby - CRC: Standby - DMA: Standby - SPI0: Standby - SPI1: Standby + CPUSS: Standby1 + CRC: Standby1 + DMA: Standby1 + SPI0: Standby1 + SPI1: Standby1 TIMA0: Sleep TIMA1: Sleep TIMG12: Sleep TIMG6: Sleep TIMG7: Sleep TRNG: Sleep - UART3: Standby + UART3: Standby1 usable_through: - ADC0: Sleep - ADC1: Sleep + ADC0: Stop1 + ADC1: Stop1 AES: Sleep CPUSS: Run CRC: Sleep DMA: Sleep - GPAMP: Sleep - GPIOA: Standby - GPIOB: Standby - I2C0: Standby - I2C1: Standby - RTC: Standby + GPAMP: Stop1 + GPIOA: Standby1 + GPIOB: Standby1 + I2C0: Standby1 + I2C1: Standby1 + RTC: Standby1 SPI0: Sleep SPI1: Sleep TIMA0: Sleep TIMA1: Sleep - TIMG0: Standby + TIMG0: Standby1 TIMG12: Sleep TIMG6: Sleep TIMG7: Sleep - TIMG8: Standby + TIMG8: Standby1 TRNG: Sleep - UART0: Standby - UART1: Standby - UART2: Standby + UART0: Standby1 + UART1: Standby1 + UART2: Standby1 UART3: Sleep - VREF: Standby - WWDT0: Stop - WWDT1: Stop + VREF: Standby1 + WWDT0: Standby0 + WWDT1: Standby0 diff --git a/data/operating_modes/mspm0g350x.yaml b/data/operating_modes/mspm0g350x.yaml index a3ee21e..8c4d305 100644 --- a/data/operating_modes/mspm0g350x.yaml +++ b/data/operating_modes/mspm0g350x.yaml @@ -9,52 +9,52 @@ retained_through: AES: Sleep CANFD0: Sleep - CPUSS: Standby - CRC: Standby - DMA: Standby + CPUSS: Standby1 + CRC: Standby1 + DMA: Standby1 MATHACL: Sleep - SPI0: Standby - SPI1: Standby + SPI0: Standby1 + SPI1: Standby1 TIMA0: Sleep TIMA1: Sleep TIMG12: Sleep TIMG6: Sleep TIMG7: Sleep TRNG: Sleep - UART3: Standby + UART3: Standby1 usable_through: - ADC0: Sleep - ADC1: Sleep + ADC0: Stop1 + ADC1: Stop1 AES: Sleep - COMP0: Standby - COMP1: Standby - COMP2: Standby + COMP0: Standby1 + COMP1: Standby1 + COMP2: Standby1 CPUSS: Run CRC: Sleep - DAC0: Sleep + DAC0: Stop1 DMA: Sleep - GPAMP: Sleep - GPIOA: Standby - GPIOB: Standby - I2C0: Standby - I2C1: Standby + GPAMP: Stop1 + GPIOA: Standby1 + GPIOB: Standby1 + I2C0: Standby1 + I2C1: Standby1 MATHACL: Sleep - RTC: Standby + RTC: Standby1 SPI0: Sleep SPI1: Sleep TIMA0: Sleep TIMA1: Sleep - TIMG0: Standby + TIMG0: Standby1 TIMG12: Sleep TIMG6: Sleep TIMG7: Sleep - TIMG8: Standby + TIMG8: Standby1 TRNG: Sleep - UART0: Standby - UART1: Standby - UART2: Standby + UART0: Standby1 + UART1: Standby1 + UART2: Standby1 UART3: Sleep - VREF: Standby - WWDT0: Stop - WWDT1: Stop + VREF: Standby1 + WWDT0: Standby0 + WWDT1: Standby0 diff --git a/data/operating_modes/mspm0g351x.yaml b/data/operating_modes/mspm0g351x.yaml index 5f38bcf..8170e2b 100644 --- a/data/operating_modes/mspm0g351x.yaml +++ b/data/operating_modes/mspm0g351x.yaml @@ -10,63 +10,63 @@ retained_through: AESADV: Sleep CANFD0: Sleep CANFD1: Sleep - CPUSS: Standby - CRCP0: Standby - DMA: Standby + CPUSS: Standby1 + CRCP0: Standby1 + DMA: Standby1 MATHACL: Sleep - SPI0: Standby - SPI1: Standby - SPI2: Standby + SPI0: Standby1 + SPI1: Standby1 + SPI2: Standby1 TIMA0: Sleep TIMA1: Sleep TIMG12: Sleep TIMG6: Sleep TIMG7: Sleep TRNG: Sleep - UART3: Standby - UART4: Standby - UART5: Standby - UART6: Standby + UART3: Standby1 + UART4: Standby1 + UART5: Standby1 + UART6: Standby1 usable_through: - ADC0: Sleep - ADC1: Sleep + ADC0: Stop1 + ADC1: Stop1 AESADV: Sleep - COMP0: Standby - COMP1: Standby - COMP2: Standby + COMP0: Standby1 + COMP1: Standby1 + COMP2: Standby1 CPUSS: Run CRCP0: Sleep - DAC0: Sleep + DAC0: Stop1 DMA: Sleep - GPIOA: Standby - GPIOB: Standby - GPIOC: Standby - I2C0: Standby - I2C1: Standby - I2C2: Standby + GPIOA: Standby1 + GPIOB: Standby1 + GPIOC: Standby1 + I2C0: Standby1 + I2C1: Standby1 + I2C2: Standby1 MATHACL: Sleep - RTC_B: Standby + RTC_B: Standby1 SPI0: Sleep SPI1: Sleep SPI2: Sleep TIMA0: Sleep TIMA1: Sleep - TIMG0: Standby + TIMG0: Standby1 TIMG12: Sleep - TIMG14: Standby + TIMG14: Standby1 TIMG6: Sleep TIMG7: Sleep - TIMG8: Standby - TIMG9: Standby + TIMG8: Standby1 + TIMG9: Standby1 TRNG: Sleep - UART0: Standby - UART1: Standby + UART0: Standby1 + UART1: Standby1 UART3: Sleep UART4: Sleep UART5: Sleep UART6: Sleep - UART7: Standby - VREF: Standby - WWDT0: Stop - WWDT1: Stop + UART7: Standby1 + VREF: Standby1 + WWDT0: Standby0 + WWDT1: Standby0 diff --git a/data/operating_modes/mspm0g518x.yaml b/data/operating_modes/mspm0g518x.yaml index 0518937..4e531e4 100644 --- a/data/operating_modes/mspm0g518x.yaml +++ b/data/operating_modes/mspm0g518x.yaml @@ -8,9 +8,9 @@ retained_through: AESADV: Sleep - CPUSS: Standby - CRCP0: Standby - DMA: Standby + CPUSS: Standby1 + CRCP0: Standby1 + DMA: Standby1 I2S0: Sleep NPU: Sleep TIMA0: Sleep @@ -21,25 +21,25 @@ retained_through: USBFS0: Sleep usable_through: - ADC0: Sleep + ADC0: Stop1 AESADV: Sleep - COMP0: Standby + COMP0: Standby1 CPUSS: Run CRCP0: Sleep DMA: Sleep - GPIOA: Standby - GPIOB: Standby - IWDT: Standby + GPIOA: Standby1 + GPIOB: Standby1 + IWDT: Standby1 NPU: Sleep - RTC_B: Standby + RTC_B: Standby1 TIMA0: Sleep - TIMB0: Standby - TIMG0: Standby + TIMB0: Standby1 + TIMG0: Standby1 TIMG6: Sleep TIMG7: Sleep - UC0: Standby - UC1: Standby + UC0: Standby1 + UC1: Standby1 UC2: Sleep UC3: Sleep - WWDT0: Stop - WWDT1: Stop + WWDT0: Standby0 + WWDT1: Standby0 diff --git a/data/operating_modes/mspm0h321x.yaml b/data/operating_modes/mspm0h321x.yaml index e81bb2d..e67c4b9 100644 --- a/data/operating_modes/mspm0h321x.yaml +++ b/data/operating_modes/mspm0h321x.yaml @@ -7,28 +7,28 @@ # `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: - CPUSS: Standby - CRC: Standby - DMA: Standby - SPI0: Standby + CPUSS: Standby1 + CRC: Standby1 + DMA: Standby1 + SPI0: Standby1 usable_through: - ADC0: Sleep + ADC0: Stop0 CPUSS: Run CRC: Sleep DMA: Sleep - GPIOA: Standby - GPIOB: Standby - I2C0: Standby - I2C1: Standby + GPIOA: Standby1 + GPIOB: Standby1 + I2C0: Standby0 + I2C1: Standby0 SPI0: Sleep - TIMA0: Standby - TIMG1: Standby - TIMG14: Standby - TIMG2: Standby - TIMG8: Standby - UART0: Standby - UART1: Standby - UART2: Standby - VREF: Sleep - WWDT0: Standby + TIMA0: Standby0 + TIMG1: Standby0 + TIMG14: Standby1 + TIMG2: Standby0 + TIMG8: Standby0 + UART0: Standby0 + UART1: Standby0 + UART2: Standby0 + VREF: Stop0 + WWDT0: Standby1 diff --git a/data/operating_modes/mspm0l110x.yaml b/data/operating_modes/mspm0l110x.yaml index 7117bd3..f758289 100644 --- a/data/operating_modes/mspm0l110x.yaml +++ b/data/operating_modes/mspm0l110x.yaml @@ -7,24 +7,24 @@ # `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: - CPUSS: Standby - CRC: Standby - DMA: Standby - SPI0: Standby + CPUSS: Standby1 + CRC: Standby1 + DMA: Standby1 + SPI0: Standby1 usable_through: - ADC0: Sleep + ADC0: Stop1 CPUSS: Run CRC: Sleep DMA: Sleep - GPAMP: Sleep - GPIOA: Standby - I2C0: Standby + GPAMP: Stop1 + GPIOA: Standby1 + I2C0: Standby1 SPI0: Sleep - TIMG0: Standby - TIMG1: Standby - TIMG2: Standby - TIMG4: Standby - UART0: Standby - UART1: Standby - WWDT0: Stop + TIMG0: Standby1 + TIMG1: Standby1 + TIMG2: Standby1 + TIMG4: Standby1 + UART0: Standby1 + UART1: Standby1 + WWDT0: Standby0 diff --git a/data/operating_modes/mspm0l112x.yaml b/data/operating_modes/mspm0l112x.yaml index 6675f99..34fe591 100644 --- a/data/operating_modes/mspm0l112x.yaml +++ b/data/operating_modes/mspm0l112x.yaml @@ -7,30 +7,30 @@ # `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: - AESADV: Standby - CPUSS: Standby - CRC0: Standby - DMA: Standby + AESADV: Standby1 + CPUSS: Standby1 + CRC0: Standby1 + DMA: Standby1 usable_through: - ADC0: Sleep + ADC0: Stop0 AESADV: Sleep - COMP0: Standby + COMP0: Standby1 CPUSS: Run CRC0: Sleep DMA: Sleep - GPIOA: Standby - GPIOB: Standby - IWDT: Standby - RTC_B: Standby + GPIOA: Standby1 + GPIOB: Standby1 + IWDT: Standby1 + RTC_B: Standby1 TIMA0: Sleep - TIMG1: Standby - TIMG14: Standby - TIMG2: Standby + TIMG1: Standby1 + TIMG14: Standby1 + TIMG2: Standby1 UC11: Sleep - UC4: Standby - UC6: Standby - UC7: Standby + UC4: Standby1 + UC6: Standby1 + UC7: Standby1 UC8: Sleep - VREF: Sleep - WWDT0: Stop + VREF: Stop0 + WWDT0: Standby0 diff --git a/data/operating_modes/mspm0l122x.yaml b/data/operating_modes/mspm0l122x.yaml index 0221b92..44d2acb 100644 --- a/data/operating_modes/mspm0l122x.yaml +++ b/data/operating_modes/mspm0l122x.yaml @@ -7,41 +7,41 @@ # `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: - AESADV: Standby - CPUSS: Standby - DMA: Standby - SPI0: Standby - SPI1: Standby + AESADV: Standby1 + CPUSS: Standby1 + DMA: Standby1 + SPI0: Standby1 + SPI1: Standby1 TRNG: Sleep # Not derivable from the table: these rows give one value spanning every mode, # which says the peripheral is available but not whether its configuration # survives. Entered by hand and preserved across regeneration. - CRCP0: Standby + CRCP0: Standby1 usable_through: - ADC0: Sleep - AESADV: Standby - COMP0: Standby + ADC0: Stop1 + AESADV: Standby1 + COMP0: Standby1 CPUSS: Run DMA: Sleep - GPIOA: Standby - GPIOB: Standby - GPIOC: Standby - I2C0: Standby - I2C1: Standby - SPI0: Standby - SPI1: Standby - TIMA0: Standby - TIMG0: Standby - TIMG12: Standby - TIMG4: Standby - TIMG5: Standby - TIMG8: Standby - TRNG: Standby - UART0: Standby - UART1: Standby - UART2: Standby - UART3: Standby - UART4: Standby - WWDT0: Stop + GPIOA: Standby1 + GPIOB: Standby1 + GPIOC: Standby1 + I2C0: Standby1 + I2C1: Standby1 + SPI0: Standby1 + SPI1: Standby1 + TIMA0: Standby1 + TIMG0: Standby1 + TIMG12: Standby1 + TIMG4: Standby1 + TIMG5: Standby1 + TIMG8: Standby1 + TRNG: Standby1 + UART0: Standby1 + UART1: Standby1 + UART2: Standby1 + UART3: Standby1 + UART4: Standby1 + WWDT0: Standby0 diff --git a/data/operating_modes/mspm0l130x.yaml b/data/operating_modes/mspm0l130x.yaml index 4e1e3b5..faab4c0 100644 --- a/data/operating_modes/mspm0l130x.yaml +++ b/data/operating_modes/mspm0l130x.yaml @@ -7,26 +7,26 @@ # `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: - CPUSS: Standby - CRC: Standby - DMA: Standby - SPI0: Standby + CPUSS: Standby1 + CRC: Standby1 + DMA: Standby1 + SPI0: Standby1 usable_through: - ADC0: Sleep - COMP0: Standby + ADC0: Stop1 + COMP0: Standby1 CPUSS: Run CRC: Sleep DMA: Sleep - GPAMP: Sleep - GPIOA: Standby - I2C0: Standby - I2C1: Standby + GPAMP: Stop1 + GPIOA: Standby1 + I2C0: Standby1 + I2C1: Standby1 SPI0: Sleep - TIMG0: Standby - TIMG1: Standby - TIMG2: Standby - TIMG4: Standby - UART0: Standby - UART1: Standby - WWDT0: Stop + TIMG0: Standby1 + TIMG1: Standby1 + TIMG2: Standby1 + TIMG4: Standby1 + UART0: Standby1 + UART1: Standby1 + WWDT0: Standby0 diff --git a/data/operating_modes/mspm0l134x.yaml b/data/operating_modes/mspm0l134x.yaml index bd317c2..0ade9e6 100644 --- a/data/operating_modes/mspm0l134x.yaml +++ b/data/operating_modes/mspm0l134x.yaml @@ -7,26 +7,26 @@ # `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: - CPUSS: Standby - CRC: Standby - DMA: Standby - SPI0: Standby + CPUSS: Standby1 + CRC: Standby1 + DMA: Standby1 + SPI0: Standby1 usable_through: - ADC0: Sleep - COMP0: Standby + ADC0: Stop1 + COMP0: Standby1 CPUSS: Run CRC: Sleep DMA: Sleep - GPAMP: Sleep - GPIOA: Standby - I2C0: Standby - I2C1: Standby + GPAMP: Stop1 + GPIOA: Standby1 + I2C0: Standby1 + I2C1: Standby1 SPI0: Sleep - TIMG0: Standby - TIMG1: Standby - TIMG2: Standby - TIMG4: Standby - UART0: Standby - UART1: Standby - WWDT0: Stop + TIMG0: Standby1 + TIMG1: Standby1 + TIMG2: Standby1 + TIMG4: Standby1 + UART0: Standby1 + UART1: Standby1 + WWDT0: Standby0 diff --git a/data/operating_modes/mspm0l211x.yaml b/data/operating_modes/mspm0l211x.yaml index f1a750f..5d5da77 100644 --- a/data/operating_modes/mspm0l211x.yaml +++ b/data/operating_modes/mspm0l211x.yaml @@ -7,31 +7,31 @@ # `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: - AESADV: Standby - CPUSS: Standby - CRC0: Standby - DMA: Standby + AESADV: Standby1 + CPUSS: Standby1 + CRC0: Standby1 + DMA: Standby1 usable_through: - ADC0: Sleep + ADC0: Stop0 AESADV: Sleep - COMP0: Standby + COMP0: Standby1 CPUSS: Run CRC0: Sleep DMA: Sleep - GPIOA: Standby - GPIOB: Standby - IWDT: Standby - LCD: Standby - RTC_B: Standby + GPIOA: Standby1 + GPIOB: Standby1 + IWDT: Standby1 + LCD: Standby1 + RTC_B: Standby1 TIMA0: Sleep - TIMG1: Standby - TIMG14: Standby - TIMG2: Standby + TIMG1: Standby1 + TIMG14: Standby1 + TIMG2: Standby1 UC11: Sleep - UC4: Standby - UC6: Standby - UC7: Standby + UC4: Standby1 + UC6: Standby1 + UC7: Standby1 UC8: Sleep - VREF: Sleep - WWDT0: Stop + VREF: Stop0 + WWDT0: Standby0 diff --git a/data/operating_modes/mspm0l222x.yaml b/data/operating_modes/mspm0l222x.yaml index 438e829..6d8b0b6 100644 --- a/data/operating_modes/mspm0l222x.yaml +++ b/data/operating_modes/mspm0l222x.yaml @@ -7,42 +7,42 @@ # `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: - AESADV: Standby - CPUSS: Standby - DMA: Standby - SPI0: Standby - SPI1: Standby + AESADV: Standby1 + CPUSS: Standby1 + DMA: Standby1 + SPI0: Standby1 + SPI1: Standby1 TRNG: Sleep # Not derivable from the table: these rows give one value spanning every mode, # which says the peripheral is available but not whether its configuration # survives. Entered by hand and preserved across regeneration. - CRCP0: Standby + CRCP0: Standby1 usable_through: - ADC0: Sleep - AESADV: Standby - COMP0: Standby + ADC0: Stop1 + AESADV: Standby1 + COMP0: Standby1 CPUSS: Run DMA: Sleep - GPIOA: Standby - GPIOB: Standby - GPIOC: Standby - I2C0: Standby - I2C1: Standby - LCD: Standby - SPI0: Standby - SPI1: Standby - TIMA0: Standby - TIMG0: Standby - TIMG12: Standby - TIMG4: Standby - TIMG5: Standby - TIMG8: Standby - TRNG: Standby - UART0: Standby - UART1: Standby - UART2: Standby - UART3: Standby - UART4: Standby - WWDT0: Stop + GPIOA: Standby1 + GPIOB: Standby1 + GPIOC: Standby1 + I2C0: Standby1 + I2C1: Standby1 + LCD: Standby1 + SPI0: Standby1 + SPI1: Standby1 + TIMA0: Standby1 + TIMG0: Standby1 + TIMG12: Standby1 + TIMG4: Standby1 + TIMG5: Standby1 + TIMG8: Standby1 + TRNG: Standby1 + UART0: Standby1 + UART1: Standby1 + UART2: Standby1 + UART3: Standby1 + UART4: Standby1 + WWDT0: Standby0 diff --git a/data/operating_modes/msps003fx.yaml b/data/operating_modes/msps003fx.yaml index a15cf6a..271086a 100644 --- a/data/operating_modes/msps003fx.yaml +++ b/data/operating_modes/msps003fx.yaml @@ -7,21 +7,21 @@ # `retained_through` covers PD1 peripherals only, since nothing else is automatically disabled. retained_through: - CPUSS: Standby - CRC: Standby - DMA: Standby - SPI0: Standby + CPUSS: Standby1 + CRC: Standby1 + DMA: Standby1 + SPI0: Standby1 usable_through: - ADC0: Sleep + ADC0: Stop0 CPUSS: Run CRC: Sleep DMA: Sleep - GPIOA: Standby + GPIOA: Standby1 SPI0: Sleep - TIMA0: Standby - TIMG14: Standby - TIMG8: Standby - UART0: Standby - VREF: Sleep - WWDT0: Stop + TIMA0: Standby1 + TIMG14: Standby1 + TIMG8: Standby1 + UART0: Standby1 + VREF: Stop0 + WWDT0: Standby0 diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 4383b18..e39b26e 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -1137,7 +1137,7 @@ fn convert_memory(memory: &PartMemory) -> anyhow::Result<Memory> { // second RAM bank differ, and they say so in parts.yaml. let retained_through = memory.retained_through.unwrap_or(match kind { MemoryKind::Flash => PowerMode::Shutdown, - MemoryKind::Ram => PowerMode::Standby, + MemoryKind::Ram => PowerMode::Standby1, }); Ok(Memory { diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 1a3413e..60b33b7 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -473,10 +473,13 @@ pub struct Peripheral { /// The deepest mode through which this peripheral keeps its configuration. /// - /// [`PowerMode::Standby`] means the configuration survives everything short of SHUTDOWN; - /// [`PowerMode::Sleep`] means it is already gone in STOP, so the peripheral must be fully + /// [`PowerMode::Standby1`] means the configuration survives everything short of SHUTDOWN; + /// [`PowerMode::Sleep`] means it is already gone by STOP0, so the peripheral must be fully /// reconfigured on wake. All PD1 peripherals need re-enabling after STOP or STANDBY regardless. /// + /// [`PowerMode::Sleep`] is the shallowest value this takes. PD1 is powered in RUN and SLEEP + /// alike, so a row already `OFF` by STOP0 says nothing finer than "not through STOP". + /// /// `None` when `power_domain` is not [`PowerDomain::Pd1`]. #[serde(skip_serializing_if = "Option::is_none")] pub retained_through: Option<PowerMode>, @@ -488,6 +491,11 @@ pub struct Peripheral { /// /// `None` when the row does not resolve to a single mode: either its values differ between the /// policies within a mode group, or one value spans every column. + /// + /// [`PowerMode::Standby1`] here does **not** mean a timer can wake the core. This is the + /// table's peripheral row; whether a timer is still clocked is a separate row and is reported + /// by [`Peripheral::clocked_in_standby1`]. The two coincide on 13 of the 18 families and + /// diverge on the L-series. #[serde(skip_serializing_if = "Option::is_none")] pub usable_through: Option<PowerMode>, @@ -678,14 +686,34 @@ pub struct Unicomm { /// An operating mode, ordered from shallowest to deepest. /// /// Ordering is meaningful and is what makes retention comparable: a peripheral retained through -/// [`PowerMode::Standby`] is also retained in every shallower mode, so a consumer can ask -/// `retained_through >= PowerMode::Stop` rather than enumerating cases. +/// [`PowerMode::Standby1`] is also retained in every shallower mode, so a consumer can ask +/// `retained_through >= PowerMode::Stop0` rather than enumerating cases. +/// +/// STOP and STANDBY are split per sub-mode because each disables a superset of the one before it. +/// **RUN and SLEEP are deliberately not**, and adding their sub-modes would break the ordering: +/// RUN1 and RUN2 are clock-source policies rather than depths, so RUN2 runs the CPU with SYSOSC +/// off while the deeper SLEEP0 turns it back on. Across the datasheets, every row whose usability +/// switches back on does so at `RUN2 -> SLEEP0` or `SLEEP2 -> STOP0` โ€” MCAN, USB, I2S, OPA and the +/// MFCLK-fed peripherals โ€” and none within STOP or within STANDBY. +/// +/// Not every family has every sub-mode: the C-series, MSPM0H321x and MSPM0L2117 tables have no +/// STOP1 column, so [`PowerMode::Stop1`] never appears for those and [`PowerMode::Stop2`] is the +/// next mode after [`PowerMode::Stop0`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub enum PowerMode { Run, Sleep, - Stop, - Standby, + Stop0, + Stop1, + Stop2, + + /// PD0 is still clocked here, which is what separates it from [`PowerMode::Standby1`]. + Standby0, + + /// PD0 is unclocked apart from the handful of timers marked + /// [`Peripheral::clocked_in_standby1`], so those are the only peripherals which can wake the + /// core from here. + Standby1, /// Nothing but the `SHUTDNSTORE` bytes in SYSCTL survives this, so it appears only for /// non-volatile memory. @@ -758,7 +786,7 @@ pub struct Memory { /// The deepest mode through which the contents of this partition survive. /// /// Flash is non-volatile, so it is [`PowerMode::Shutdown`]. SRAM is normally - /// [`PowerMode::Standby`], since only the `SHUTDNSTORE` bytes in SYSCTL survive SHUTDOWN. The + /// [`PowerMode::Standby1`], since only the `SHUTDNSTORE` bytes in SYSCTL survive SHUTDOWN. The /// upper SRAM bank of the parts which have one is the exception. pub retained_through: PowerMode, } diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 8209a71..150b742 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -189,8 +189,9 @@ pub struct Peripheral { /// The deepest mode through which this peripheral keeps its configuration. /// - /// `Standby` means the configuration survives everything short of SHUTDOWN; `Sleep` means it is - /// already gone in STOP, so the peripheral must be fully reconfigured on wake. + /// `Standby1` means the configuration survives everything short of SHUTDOWN; `Sleep` means it + /// is already gone by STOP0, so the peripheral must be fully reconfigured on wake. `Sleep` is + /// the shallowest value this takes, since PD1 is powered in RUN and SLEEP alike. /// /// All PD1 peripherals need re-enabling after STOP or STANDBY regardless (TRM ยง2.2.6.1). /// @@ -360,14 +361,27 @@ pub struct Timer { /// An operating mode, ordered from shallowest to deepest. /// /// Ordering is meaningful and is what makes retention comparable: something retained through -/// `PowerMode::Standby` is also retained in every shallower mode, so a consumer can ask -/// `retained_through >= PowerMode::Stop` rather than enumerating cases. +/// `PowerMode::Standby1` is also retained in every shallower mode, so a consumer can ask +/// `retained_through >= PowerMode::Stop0` rather than enumerating cases. +/// +/// STOP and STANDBY are split per sub-mode because each disables a superset of the one before it. +/// RUN and SLEEP are not: their sub-modes are clock-source policies rather than depths, and RUN2 +/// runs the CPU with SYSOSC off where the deeper SLEEP0 has it on, so ordering them would be a +/// lie. Not every family has every sub-mode โ€” several have no STOP1 at all. #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)] pub enum PowerMode { Run, Sleep, - Stop, - Standby, + Stop0, + Stop1, + Stop2, + + /// PD0 is still clocked here, which is what separates it from `PowerMode::Standby1`. + Standby0, + + /// PD0 is unclocked apart from the handful of timers marked `clocked_in_standby1`, so those + /// are the only peripherals which can wake the core from here. + Standby1, /// Nothing but the `SHUTDNSTORE` bytes in SYSCTL survives this, so it appears only for /// non-volatile memory. @@ -422,7 +436,7 @@ pub struct MemoryRegion { /// The deepest mode through which the contents of this region survive. /// - /// Flash is non-volatile, so it is `Shutdown`. SRAM is normally `Standby`, since only the + /// Flash is non-volatile, so it is `Shutdown`. SRAM is normally `Standby1`, since only the /// `SHUTDNSTORE` bytes in SYSCTL survive SHUTDOWN. pub retained_through: PowerMode, } diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index f1d98bc..76b6b67 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -180,8 +180,11 @@ fn power_mode(mode: PowerMode) -> TokenStream { match mode { PowerMode::Run => quote! { PowerMode::Run }, PowerMode::Sleep => quote! { PowerMode::Sleep }, - PowerMode::Stop => quote! { PowerMode::Stop }, - PowerMode::Standby => quote! { PowerMode::Standby }, + PowerMode::Stop0 => quote! { PowerMode::Stop0 }, + PowerMode::Stop1 => quote! { PowerMode::Stop1 }, + PowerMode::Stop2 => quote! { PowerMode::Stop2 }, + PowerMode::Standby0 => quote! { PowerMode::Standby0 }, + PowerMode::Standby1 => quote! { PowerMode::Standby1 }, PowerMode::Shutdown => quote! { PowerMode::Shutdown }, } } diff --git a/tools/operating_modes.py b/tools/operating_modes.py index 3eaa4f5..fae60b5 100644 --- a/tools/operating_modes.py +++ b/tools/operating_modes.py @@ -18,6 +18,17 @@ lines; reading extracted text instead silently loses the spans. row.cells[i] is None <=> column i is covered by a merge that began earlier + +STOP and STANDBY are reported per sub-mode, RUN and SLEEP as a whole. That asymmetry is in the +hardware, not a shortcut: STOP0/1/2 and STANDBY0/1 each disable a superset of the one before, but +RUN1 and RUN2 are *clock-source policies* rather than depths -- RUN2 runs the CPU with SYSOSC off, +and SLEEP0 turns it back on. Across all 16 datasheets every row whose usability switches back on +does so at `RUN2 -> SLEEP0` or `SLEEP2 -> STOP0`, and none within STOP or within STANDBY, so a +single threshold is only meaningful once RUN and SLEEP are each collapsed. + +Which sub-modes exist varies: most families have STOP0/1/2, while the C-series, H3216 and L2117 +tables have no STOP1 column at all. The sub-mode names are read from the table's second header row +rather than counted off, so a missing one cannot shift the rest. """ # /// script @@ -29,7 +40,7 @@ import json import re import sys -from collections.abc import Iterator +from collections.abc import Iterable, Iterator from pathlib import Path try: @@ -39,10 +50,10 @@ except ImportError as e: # pragma: no cover raise SystemExit(f"{e.name} is missing; run this with `uv run` instead of `python`") -ColumnGroups = dict[int, str] # column index -> mode group +ColumnModes = dict[int, str] # column index -> PowerMode variant name States = dict[int, str | None] # column index -> state, merges resolved Rows = list[tuple[str, States]] # (row label, states) -ByGroup = dict[str, set[str]] # mode group -> states seen in it +ByMode = dict[str, set[str]] # PowerMode variant -> states seen in it Overrides = dict[str, dict[str, dict[str, str]]] # family -> field -> name -> value CAPTION = "Supported Functionality by Operating Mode" @@ -52,12 +63,25 @@ #: Mode groups in order of increasing depth. The datasheet names these as merged header cells. GROUPS = ["RUN", "SLEEP", "STOP", "STANDBY", "SHUTDOWN"] +#: Groups reported per sub-mode rather than collapsed, and the `PowerMode` stem each maps onto. +SPLIT = {"STOP": "Stop", "STANDBY": "Standby"} + +#: `PowerMode` variants a column can map to, deepest last. `Stop1` is absent on several families. +MODES = ["Run", "Sleep", "Stop0", "Stop1", "Stop2", "Standby0", "Standby1", "Shutdown"] + +#: A sub-mode name from the table's second header row. That row is rotated, and pdfplumber returns +#: rotated text back to front ("0POTS"), so the cell is reversed before matching. +SUBMODE = re.compile(r"^(RUN|SLEEP|STOP|STANDBY)(\d)$") + #: Per the table's legend: EN and OPT mean the function is usable, DIS/OFF/NS do not. `NS` is "not #: automatically disabled in the specified mode, but its use is not supported", which is the case a #: boolean would hide. Only OFF loses configuration. USABLE = ("EN", "OPT") -STATE = re.compile(r"^(EN|DIS|OPT|NS|OFF)\b") +#: A cell's state, ignoring any footnote marker after it. Most are parenthesised ("OPT(2)"), but the +#: MSPM0C1106 GPIOA/B row renders one bare ("OPT2"), which a `\b` here would reject -- and rejecting +#: it silently costs that row its STANDBY1 column. +STATE = re.compile(r"^(EN|DIS|OPT|NS|OFF)(?![A-Za-z])") #: Row labels which do not use the peripheral's own name. ALIASES = { @@ -84,11 +108,11 @@ def caption_pages(path: Path) -> list[int]: doc.close() -def read_tables(path: Path) -> Iterator[tuple[ColumnGroups, Rows]]: - """Yield (column_groups, rows) for each page of the operating-mode table. +def read_tables(path: Path) -> Iterator[tuple[ColumnModes, Rows]]: + """Yield (column_modes, rows) for each page of the operating-mode table. - `column_groups` maps a column index to its mode group; `rows` is a list of (label, states) where - `states` is one entry per mode column, merges already resolved. + `column_modes` maps a column index to the `PowerMode` variant it reports; `rows` is a list of + (label, states) where `states` is one entry per mode column, merges already resolved. """ pages = caption_pages(path) if not pages: @@ -101,26 +125,22 @@ def read_tables(path: Path) -> Iterator[tuple[ColumnGroups, Rows]]: continue data = table.extract() - # The header row names each group in the column it starts at; the columns it spans - # follow as `None` cells. Anything past the last named group is SHUTDOWN, which some - # datasheets leave unlabelled. + # The group row names each group in the column it starts at; the columns it spans + # follow as `None` cells. It is used only to find where the mode columns begin -- + # which mode each one reports comes from the sub-mode row below it. header = [(c or "").replace("\n", "").strip() for c in data[0]] - column_group: ColumnGroups = {} - current: str | None = None + columns: list[int] = [] + seen_group = False for i, cell in enumerate(header): if i < 2: continue # group label and row label columns - name = next((g for g in GROUPS if cell.upper().startswith(g)), None) - if name: - current = name - elif cell and current == "STANDBY": - current = "SHUTDOWN" # trailing unlabelled column - if current: - column_group[i] = current - # A trailing column after STANDBY with no header of its own is SHUTDOWN. - tail = max(column_group, default=1) - if column_group.get(tail) == "STANDBY" and tail == len(header) - 1: - column_group[tail] = "SHUTDOWN" + seen_group |= any(cell.upper().startswith(g) for g in GROUPS) + if seen_group: + columns.append(i) + + column_mode = split_submodes(columns, data[1] if len(data) > 1 else []) + if column_mode is None: + continue rows: Rows = [] for ri, row in enumerate(table.rows[1:], start=1): @@ -133,7 +153,7 @@ def read_tables(path: Path) -> Iterator[tuple[ColumnGroups, Rows]]: states: States = {} held: str | None = None - for ci in sorted(column_group): + for ci in sorted(column_mode): if ci >= len(row.cells): continue if row.cells[ci] is not None: @@ -144,51 +164,88 @@ def read_tables(path: Path) -> Iterator[tuple[ColumnGroups, Rows]]: states[ci] = held rows.append((label, states)) - yield column_group, rows + yield column_mode, rows + + +def split_submodes(columns: Iterable[int], subheader: list) -> ColumnModes | None: + """Map each mode column to a `PowerMode` variant, or `None` if the sub-mode row does not parse. + + The sub-mode row is the authority, not the merged group row above it. RUN and SLEEP collapse to + one variant each; STOP and STANDBY keep the index the table gives them, read rather than counted + because several families have no STOP1. + + Deciding SHUTDOWN here is what makes MSPM0H3216 come out right. Its table stops at STANDBY1 with + no SHUTDOWN column at all, and a trailing-column heuristic reads that last column as SHUTDOWN and + loses the deeper half of STANDBY. + """ + labels = [(c or "").replace("\n", "").strip()[::-1].upper() for c in subheader] + last = max(columns, default=-1) + + out: ColumnModes = {} + for ci in columns: + label = labels[ci] if ci < len(labels) else "" + m = SUBMODE.match(label) + if m is None: + # SHUTDOWN has no index, and its cell is routinely clipped ("SHUTDOW", "DOWN", "NWO"). + # Anywhere but the last column, an unreadable label means the row did not parse. + if ci != last: + return None + out[ci] = "Shutdown" + continue + + group, index = m.group(1), m.group(2) + out[ci] = f"{SPLIT[group]}{index}" if group in SPLIT else group.capitalize() + + return out -def group_states(column_group: ColumnGroups, states: States) -> ByGroup: - """States seen in each mode group, as {group: set of states}.""" - out: ByGroup = {} - for ci, group in column_group.items(): +def mode_states(column_mode: ColumnModes, states: States) -> ByMode: + """States seen in each mode, as {mode: set of states}.""" + out: ByMode = {} + for ci, mode in column_mode.items(): s = states.get(ci) if s: - out.setdefault(group, set()).add(s) + out.setdefault(mode, set()).add(s) return out -def retained_through(by_group: ByGroup) -> str | None: +def present(by_mode: ByMode) -> list[str]: + """The modes this table has a column for, shallowest first, excluding SHUTDOWN.""" + return [m for m in MODES if m != "Shutdown" and m in by_mode] + + +def retained_through(by_mode: ByMode) -> str | None: """Deepest mode a PD1 peripheral's configuration survives, or None if the table is silent. - `OFF` is the only state which loses configuration; `EN`, `OPT` and `DIS` all keep it. STOP and - STANDBY are read separately, so losing it only in STANDBY reports `Stop` rather than `Sleep`. + `OFF` is the only state which loses configuration; `EN`, `OPT` and `DIS` all keep it. Losing it + only in the deeper half of STANDBY now reports `Standby0` where this used to have to say `Stop`. - A blank group is answered conservatively: retention in a deeper mode implies it in a shallower one, - but not the reverse. + Sleep is the floor rather than Run: PD1 is powered in both, so a row which is already `OFF` by + the first STOP sub-mode says nothing finer than "not through STOP". """ - stop = by_group.get("STOP", set()) - standby = by_group.get("STANDBY", set()) - if not stop and not standby: + sleeps = [m for m in present(by_mode) if m not in ("Run", "Sleep")] + if not sleeps: return None - if "OFF" in stop: - return "Sleep" - if "OFF" in standby: - return "Stop" if stop else "Sleep" - return "Standby" if standby else "Stop" + deepest = "Sleep" + for mode in sleeps: + if "OFF" in by_mode[mode]: + break + deepest = mode + return deepest + +def usable_through(by_mode: ByMode) -> str | None: + """Deepest mode in which every policy is usable, or None if the table is silent. -def usable_through(by_group: ByGroup) -> str | None: - """Deepest mode group in which every policy is usable, or None if the table is silent.""" + Walks only the modes the table has a column for, so a family without STOP1 still reaches STOP2. + """ deepest: str | None = None - for group in ("RUN", "SLEEP", "STOP", "STANDBY"): - seen = by_group.get(group) - if not seen: - break - if not seen <= set(USABLE): + for mode in present(by_mode): + if not by_mode[mode] <= set(USABLE): break - deepest = group - return {"RUN": "Run", "SLEEP": "Sleep", "STOP": "Stop", "STANDBY": "Standby"}.get(deepest) + deepest = mode + return deepest def expand(label: str) -> list[str]: @@ -281,10 +338,10 @@ def write(datasheets: str) -> int: retained: dict[str, str] = {} usable: dict[str, str] = {} - for column_group, rows in read_tables(pdf): + for column_mode, rows in read_tables(pdf): for label, states in rows: - by_group = group_states(column_group, states) - r, u = retained_through(by_group), usable_through(by_group) + by_mode = mode_states(column_mode, states) + r, u = retained_through(by_mode), usable_through(by_mode) for name in expand(label): if name not in known.get(family, ()): continue @@ -346,11 +403,11 @@ def main(argv: list[str]) -> None: for path in paths: print(f"########## {path.name}") - for column_group, rows in read_tables(path): - columns = sorted(column_group) - print(" " + " ".join(f"{column_group[c][:4]:<5}" for c in columns)) + for column_mode, rows in read_tables(path): + columns = sorted(column_mode) + print(" " + " ".join(f"{column_mode[c]:<9}" for c in columns)) for label, states in rows: - print(f" {' '.join(f'{states.get(c) or chr(45):<5}' for c in columns)} {label}") + print(f" {' '.join(f'{states.get(c) or chr(45):<9}' for c in columns)} {label}") print() From ed461a71db62b1316fd2a42ca339bf42027a3c91 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 12:12:51 +0100 Subject: [PATCH 079/134] registers: tighten the prose Nothing about the blocks or the deviations changed. The reproducibility note had lost its heading, and a run of headings all leaned on the same dash. --- data/registers/README.md | 81 +++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 47 deletions(-) diff --git a/data/registers/README.md b/data/registers/README.md index 1f7818c..b4744fb 100644 --- a/data/registers/README.md +++ b/data/registers/README.md @@ -1,56 +1,43 @@ # Register blocks These files are [chiptool](https://github.com/embassy-rs/chiptool) IR. They start life as -`chiptool extract-peripheral` output from the SVDs in `sources/svd/`, but **the checked-in YAML is -the source of truth**, not the SVD: it is hand-cleaned afterwards, and in the places listed below it +`chiptool extract-peripheral` output from the SVDs in `sources/svd/`. The checked-in YAML is the +source of truth, not the SVD. It is hand-cleaned afterwards, and in the places listed below it deliberately contradicts the SVD. The cleanup rules are in the [root README](../../README.md). -Keep this file up to date rather than commenting the YAML. `chiptool fmt` silently strips comments, -so anything recorded only there is one `fmt` away from being lost โ€” which is also why a deviation -that is not written down here tends to get "corrected" back to the SVD by the next person. +Keep this file up to date rather than commenting the YAML. `chiptool fmt` silently strips comments. -Notes about what a register *means*, as opposed to where its definition came from, belong in the -`description:` text instead. Those survive `fmt` and reach the generated PAC's documentation. +Notes about what a register means belong in its `description:` text instead. Those survive `fmt` and +reach the generated PAC's documentation. ## How much of this is reproducible -Some of it. `transforms/` gets a block most of the way there, and two of the deviations below are now -encoded in it and verified end to end: +Not all of it, so re-running a transform over an existing block discards hand work silently. + +`transforms/` gets a block most of the way there. Two of the deviations below are encoded in it and +verified end to end: | block | reproduces the checked-in YAML? | | --- | --- | -| `sysctl_c110x` | **yes** โ€” `chiptool extract-peripheral --svd sources/svd/MSPM0C110X.svd --peripheral SYSCTL`, then `transforms/transform.sh` with `SYSCTL_C110x.yaml` | -| `sysctl_l110x_l130x_l134x` | **yes** โ€” same, from `MSPM0L130X.svd` | +| `sysctl_c110x` | yes: `chiptool extract-peripheral --svd sources/svd/MSPM0C110X.svd --peripheral SYSCTL`, then `transforms/transform.sh` with `SYSCTL_C110x.yaml` | +| `sysctl_l110x_l130x_l134x` | yes: same, from `MSPM0L130X.svd` | | everything else with a transform | no, see below | | `beeper_v1`, `cpuss_v1`, `factoryregion_v1`, `sysctl_c1105_c1106`, `tim_btimer`, `unicomm_v1`, `vref_v1` | no transform exists at all | -Where a transform does not reproduce its block, the reason is usually that it is written against a -different source instance than the one the block was derived from โ€” `TIM.yaml` runs from `TIMA0` on -MSPM0G350X, which has four capture/compare channels and neither the `DC` nor the `QEIERR` event, so -its output cannot match `tim_v1` however the transform is written. Closing those gaps means -re-deriving the block, not just editing the transform. - -The rest of the cleanup โ€” shared fieldsets, deleted enums, stripped prefixes โ€” is hand-applied on top -and is not encoded anywhere. **Re-running a transform over an existing block therefore discards hand -work silently.** Diff the output against the checked-in YAML instead, and use the flatten-and-compare -check described in the root README to see what actually moved. - -The exception worth copying is `TIM.yaml`'s `CCD`/`CCU` step: `!MakeFieldArray` with `mode: Cursed` -produces exactly the offset lists the checked-in block carries, so that piece of the cleanup at least -survives a re-derivation. +A transform that does not reproduce its block is usually written against a different source instance. +`TIM.yaml` runs from `TIMA0` on MSPM0G350X, which has four capture/compare channels and neither the +`DC` nor the `QEIERR` event. Its output cannot match `tim_v1` however the transform is written. ## Deviations from the SVD Each entry says what the SVD claims, what this repo says instead, and what the evidence is. -When checking a release for unintended movement, fingerprint the *generated* Rust rather than the -YAML โ€” read the register offsets back out of `wrapping_add` and the field positions out of each -accessor's `(shift, mask)`. That covers everything the generator does between IR and Rust, which -flatten-and-compare cannot see. Run it against the **immediately preceding tag**: against an older -one it reports the three C-series fields below moving from bit 2 to bit 0, which is a correction from -an earlier round rather than anything in the release under test. +When checking a release for unintended movement, fingerprint the generated Rust rather than the YAML. +Read the register offsets back out of `wrapping_add` and the field positions out of each accessor's +`(shift, mask)`. That covers everything the generator does between IR and Rust, and +flatten-and-compare sees none of it. -### `sysctl_c110x`, `sysctl_c1105_c1106` โ€” three unlock-protected control bits are at bit 0 +### `sysctl_c110x`, `sysctl_c1105_c1106`: three unlock-protected control bits are at bit 0 | fieldset | field | | --- | --- | @@ -58,33 +45,33 @@ an earlier round rather than anything in the release under test. | `EXRSTPIN` | `DISABLE` | | `SYSSTATUSCLR` | `ALLECC` | -The C-series SVD puts each of these at bit 2. That contradicts SLAU893 and every other family's -SYSCTL, which agree on bit 0. Taking the SVD's word for it would produce a HAL whose writes to these -three registers never take effect. +The C-series SVD puts each of these at bit 2. SLAU893 and every other family's SYSCTL agree on bit 0. +Taking the SVD's word for it would produce a HAL whose writes to these three registers never take +effect. -### `sysctl_l110x_l130x_l134x` โ€” `RSTCAUSE.BOOTSW` is 13 +### `sysctl_l110x_l130x_l134x`: `RSTCAUSE.BOOTSW` is 13 The SVD says 10, copied from SLAU847's field description for this SYSCTL variant. That description contradicts the same TRM's "Reset Causes" table, which lists 10 as reserved and 13 as the software-triggered BOOTRST. Every other family agrees with the table. `BOOTWWDT0` in the same enum had already been corrected against that table for the same reason. -### `cpuss_v1` โ€” the interrupt group's `MIS`, `ISET` and `ICLR` are 8 bits +### `cpuss_v1`: the interrupt group's `MIS`, `ISET` and `ICLR` are 8 bits The SVD gives these three a single bit, where it gives `IMASK` and `RIS` eight. `hw_cpuss.h` masks -all five with `0xFF`, and the group's `IIDX` enumerates `INT0` to `INT7`, so a single bit would leave +all five with `0xFF`, and the group's `IIDX` enumerates `INT0` to `INT7`. A single bit would leave only interrupt 0 reachable through three of the five registers. -### `gpio_v1` โ€” the generic event masks are indexed by absolute DIO number +### `gpio_v1`: the generic event masks are indexed by absolute DIO number The SVD bases both generic event blocks at bit 0 with sixteen bits each. `hw_gpio.h` puts -`GEN_EVENT0`'s `DIO0` to `DIO15` at bits 0 to 15 but `GEN_EVENT1`'s `DIO16` to `DIO31` at bits **16 -to 31**. Both blocks now share the same 32-bit fieldset as `CPU_INT` and the `DOUT`/`DIN` masks, so -one bit index means one DIO number everywhere; each event block implements only its own half. +`GEN_EVENT0`'s `DIO0` to `DIO15` at bits 0 to 15, but `GEN_EVENT1`'s `DIO16` to `DIO31` at bits 16 to +31. Both blocks now share the same 32-bit fieldset as `CPU_INT` and the `DOUT`/`DIN` masks, so one +bit index means one DIO number everywhere. Each event block implements only its own half. -### `tim_v1` โ€” `CCD` and `CCU` are offset-list arrays +### `tim_v1`: `CCD` and `CCU` are offset-list arrays -Not a contradiction, but the reason a plain stride does not appear here: capture/compare channels 4 -and 5 are compare-only and sit *above* the `CCU` bits rather than next to `CCD0` to `CCD3` -(`CCD` at bits 4-7 and 12-13, `CCU` at 8-11 and 14-15, per `hw_gptimer.h`). The arrays use chiptool's -explicit `offsets:` list so that `ccd(n)` and `ccu(n)` still take a channel number. +Not a contradiction. Capture/compare channels 4 and 5 are compare-only and sit above the `CCU` bits +rather than next to `CCD0` to `CCD3` (`CCD` at bits 4-7 and 12-13, `CCU` at 8-11 and 14-15, per +`hw_gptimer.h`). A plain stride cannot describe that, so the arrays use chiptool's explicit +`offsets:` list, which keeps `ccd(n)` and `ccu(n)` taking a channel number. From 4d6741c1f99be10fc04a71d73d079aa610ce5c09 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 12:12:51 +0100 Subject: [PATCH 080/134] docs: replace the stale clocktree justification MSPM0C110x and MSPM0C1105/C1106 stopped sharing a SYSCTL when the second block landed, so the example for why clocktree.json is needed no longer holds. Nothing read out of that file varies within a shared SYSCTL version any more; only the curated stop1 and ulpclk_div do, so the caveat moves to them with the L-series pair as the example. The perimap section also gains the rule that a version names a register block which has to exist. --- README.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 76165d2..5a5ee00 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,8 @@ These are the data sources currently used. * How many counters a basic timer instance has (`SYS_NUM_COUNTERS`), and a cross-check of the datasheet's capture/compare channel counts (`SYS_NUM_CC`). * SysConfig `clocktree.json` - * Which crystal drivers, external clock inputs and SYSPLL a family has. This is not derivable from - the SYSCTL register block: MSPM0C110x and MSPM0C1105/C1106 share one, but only the latter has a - crystal driver. + * Which crystal drivers, external clock inputs and SYSPLL a family has. A node is present exactly + when the family has that hardware. * mspm0-sdk header files * Interrupt number, name * Peripheral addresses @@ -64,8 +63,8 @@ These are the data sources currently used. ([`data/wakeup/`](./data/wakeup)) * How long VREF takes to settle after being enabled, from the `Tstartup` row ([`data/vref/`](./data/vref)). `CTL1.READY` would answer it, but `VREF_ERR_01` leaves that bit - set after the first enable since reset, so on an affected device the datasheet figure is the - only signal + set after the first enable since reset. On an affected device the datasheet figure is the only + signal * Which timers stay clocked in STANDBY1 (`standby1_timers` in [`parts.yaml`](./data/parts.yaml)) * MCLK and ULPCLK ceilings, the SYSOSC base frequency, the flash wait-state bands, `fADCCLK` and `TRNGCLKF` (all in [`parts.yaml`](./data/parts.yaml)) @@ -74,7 +73,8 @@ These are the data sources currently used. * Manually entered * IIDX values for interrupts within a `INT_GROUP` * Whether the device has `MCLKCFG.UDIV` and the STOP1 sub-mode (`clock_tree` in - [`parts.yaml`](./data/parts.yaml)) + [`parts.yaml`](./data/parts.yaml)). Neither can be keyed off the SYSCTL version: MSPM0L112x and + MSPM0L211x share `sysctl_l122x_l222x` with MSPM0L122x and MSPM0L222x, but have no STOP1 Run `./d download-docs` to fetch the datasheets, errata sheets and reference manuals into `./files/`; the `tools/` scripts read them from there. @@ -155,8 +155,11 @@ When parsing a chip, for each peripheral a "key" string is constructed using thi ``` `PERIPHERAL_NAME` is the peripheral type, so every instance of a type on a chip gets the same -version. Where one type covers instances with different register blocks the key has to say which, -as `timb` does for the basic timers alongside `tim` โ€” see `get_peripheral_type_version`. Such a +version. Where one type covers instances with different register blocks, the key has to say which: +`timb` does that for the basic timers alongside `tim`, in `get_peripheral_type_version`. Such a version also needs an entry in `VARIANT_MODULES` in -[`peripheral.rs`](./mspm0-metapac-gen/src/peripheral.rs) to give it a module name of its own, -otherwise the metapac generator panics on the two blocks colliding. +[`peripheral.rs`](./mspm0-metapac-gen/src/peripheral.rs) to give it a module name of its own. +Without one, the metapac generator panics on the two blocks colliding. + +A version also means the register block exists. `verify.rs` fails if a perimap entry names a YAML +that has not been written, so do not add the entry ahead of the block. From 27ac0c213c3b8aa9692985e2c9bf1f7a3e176aa8 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 15:17:13 +0100 Subject: [PATCH 081/134] tools: extract the ADC internal channels from the datasheets Which ADC channels sample the temperature sensor, an OPA, GPAMP or DAC output, the internal reference or a supply monitor is per instance and per family, and the machine-readable sources overstate it. The SDK keys the mapping by SDK family, a superset of its devices: it routes the OPA outputs on G110x/G310x, which have no OPA and whose datasheets leave the channel empty, omits the L122x/L222x VREF channel, and lists a C110x VREF channel its own channel-range comment excludes. The sysconfig attributes repeat the superset mistake. The datasheets' channel-mapping tables are right on all five points, so they are the source. A cell which is neither a pin nor a known internal signal is reported rather than guessed, so a new family cannot silently misclassify. --- data/adc_channels/mspm0c1105_c1106.yaml | 10 + data/adc_channels/mspm0c110x.yaml | 9 + data/adc_channels/mspm0g110x.yaml | 14 ++ data/adc_channels/mspm0g150x.yaml | 17 ++ data/adc_channels/mspm0g151x.yaml | 12 ++ data/adc_channels/mspm0g310x.yaml | 14 ++ data/adc_channels/mspm0g350x.yaml | 17 ++ data/adc_channels/mspm0g351x.yaml | 12 ++ data/adc_channels/mspm0g518x.yaml | 11 + data/adc_channels/mspm0h321x.yaml | 10 + data/adc_channels/mspm0l110x.yaml | 10 + data/adc_channels/mspm0l112x.yaml | 10 + data/adc_channels/mspm0l122x.yaml | 11 + data/adc_channels/mspm0l130x.yaml | 12 ++ data/adc_channels/mspm0l134x.yaml | 12 ++ data/adc_channels/mspm0l211x.yaml | 10 + data/adc_channels/mspm0l222x.yaml | 11 + data/adc_channels/msps003fx.yaml | 9 + tools/adc_channels.py | 266 ++++++++++++++++++++++++ 19 files changed, 477 insertions(+) create mode 100644 data/adc_channels/mspm0c1105_c1106.yaml create mode 100644 data/adc_channels/mspm0c110x.yaml create mode 100644 data/adc_channels/mspm0g110x.yaml create mode 100644 data/adc_channels/mspm0g150x.yaml create mode 100644 data/adc_channels/mspm0g151x.yaml create mode 100644 data/adc_channels/mspm0g310x.yaml create mode 100644 data/adc_channels/mspm0g350x.yaml create mode 100644 data/adc_channels/mspm0g351x.yaml create mode 100644 data/adc_channels/mspm0g518x.yaml create mode 100644 data/adc_channels/mspm0h321x.yaml create mode 100644 data/adc_channels/mspm0l110x.yaml create mode 100644 data/adc_channels/mspm0l112x.yaml create mode 100644 data/adc_channels/mspm0l122x.yaml create mode 100644 data/adc_channels/mspm0l130x.yaml create mode 100644 data/adc_channels/mspm0l134x.yaml create mode 100644 data/adc_channels/mspm0l211x.yaml create mode 100644 data/adc_channels/mspm0l222x.yaml create mode 100644 data/adc_channels/msps003fx.yaml create mode 100644 tools/adc_channels.py diff --git a/data/adc_channels/mspm0c1105_c1106.yaml b/data/adc_channels/mspm0c1105_c1106.yaml new file mode 100644 index 0000000..b50040a --- /dev/null +++ b/data/adc_channels/mspm0c1105_c1106.yaml @@ -0,0 +1,10 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0C1106 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 28: TemperatureSensor + 29: Vref + 31: SupplyMonitor diff --git a/data/adc_channels/mspm0c110x.yaml b/data/adc_channels/mspm0c110x.yaml new file mode 100644 index 0000000..00a9aeb --- /dev/null +++ b/data/adc_channels/mspm0c110x.yaml @@ -0,0 +1,9 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0C1104 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 11: TemperatureSensor + 15: SupplyMonitor diff --git a/data/adc_channels/mspm0g110x.yaml b/data/adc_channels/mspm0g110x.yaml new file mode 100644 index 0000000..c65f4de --- /dev/null +++ b/data/adc_channels/mspm0g110x.yaml @@ -0,0 +1,14 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0G1107 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 11: TemperatureSensor + 14: Gpamp + 15: SupplyMonitor +ADC1: + 12: TemperatureSensor + 14: Gpamp + 15: SupplyMonitor diff --git a/data/adc_channels/mspm0g150x.yaml b/data/adc_channels/mspm0g150x.yaml new file mode 100644 index 0000000..e148f71 --- /dev/null +++ b/data/adc_channels/mspm0g150x.yaml @@ -0,0 +1,17 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0G1507 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 11: TemperatureSensor + 13: Opa0 + 14: Gpamp + 15: SupplyMonitor +ADC1: + 0: Dac0 + 12: TemperatureSensor + 13: Opa1 + 14: Gpamp + 15: SupplyMonitor diff --git a/data/adc_channels/mspm0g151x.yaml b/data/adc_channels/mspm0g151x.yaml new file mode 100644 index 0000000..bdd8595 --- /dev/null +++ b/data/adc_channels/mspm0g151x.yaml @@ -0,0 +1,12 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0G1519 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 11: TemperatureSensor + 15: SupplyMonitor +ADC1: + 0: Dac0 + 15: SupplyMonitor diff --git a/data/adc_channels/mspm0g310x.yaml b/data/adc_channels/mspm0g310x.yaml new file mode 100644 index 0000000..d627be3 --- /dev/null +++ b/data/adc_channels/mspm0g310x.yaml @@ -0,0 +1,14 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0G3107 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 11: TemperatureSensor + 14: Gpamp + 15: SupplyMonitor +ADC1: + 12: TemperatureSensor + 14: Gpamp + 15: SupplyMonitor diff --git a/data/adc_channels/mspm0g350x.yaml b/data/adc_channels/mspm0g350x.yaml new file mode 100644 index 0000000..79b1fdd --- /dev/null +++ b/data/adc_channels/mspm0g350x.yaml @@ -0,0 +1,17 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0G3507 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 11: TemperatureSensor + 13: Opa0 + 14: Gpamp + 15: SupplyMonitor +ADC1: + 0: Dac0 + 12: TemperatureSensor + 13: Opa1 + 14: Gpamp + 15: SupplyMonitor diff --git a/data/adc_channels/mspm0g351x.yaml b/data/adc_channels/mspm0g351x.yaml new file mode 100644 index 0000000..f1dbb62 --- /dev/null +++ b/data/adc_channels/mspm0g351x.yaml @@ -0,0 +1,12 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0G3519 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 11: TemperatureSensor + 15: SupplyMonitor +ADC1: + 0: Dac0 + 15: SupplyMonitor diff --git a/data/adc_channels/mspm0g518x.yaml b/data/adc_channels/mspm0g518x.yaml new file mode 100644 index 0000000..2af510e --- /dev/null +++ b/data/adc_channels/mspm0g518x.yaml @@ -0,0 +1,11 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0G5187 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 28: Vref + 29: TemperatureSensor + 30: VusbMonitor + 31: SupplyMonitor diff --git a/data/adc_channels/mspm0h321x.yaml b/data/adc_channels/mspm0h321x.yaml new file mode 100644 index 0000000..f46dcbd --- /dev/null +++ b/data/adc_channels/mspm0h321x.yaml @@ -0,0 +1,10 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0H3216 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 28: TemperatureSensor + 29: Vref + 31: SupplyMonitor diff --git a/data/adc_channels/mspm0l110x.yaml b/data/adc_channels/mspm0l110x.yaml new file mode 100644 index 0000000..9442682 --- /dev/null +++ b/data/adc_channels/mspm0l110x.yaml @@ -0,0 +1,10 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0L1106 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 11: TemperatureSensor + 14: Gpamp + 15: SupplyMonitor diff --git a/data/adc_channels/mspm0l112x.yaml b/data/adc_channels/mspm0l112x.yaml new file mode 100644 index 0000000..f4c8876 --- /dev/null +++ b/data/adc_channels/mspm0l112x.yaml @@ -0,0 +1,10 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0L2117 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 28: TemperatureSensor + 29: Vref + 31: SupplyMonitor diff --git a/data/adc_channels/mspm0l122x.yaml b/data/adc_channels/mspm0l122x.yaml new file mode 100644 index 0000000..ed74857 --- /dev/null +++ b/data/adc_channels/mspm0l122x.yaml @@ -0,0 +1,11 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0L1228 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 28: Vref + 29: TemperatureSensor + 30: VbatMonitor + 31: SupplyMonitor diff --git a/data/adc_channels/mspm0l130x.yaml b/data/adc_channels/mspm0l130x.yaml new file mode 100644 index 0000000..b9b4cfb --- /dev/null +++ b/data/adc_channels/mspm0l130x.yaml @@ -0,0 +1,12 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0L1306 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 11: TemperatureSensor + 12: Opa0 + 13: Opa1 + 14: Gpamp + 15: SupplyMonitor diff --git a/data/adc_channels/mspm0l134x.yaml b/data/adc_channels/mspm0l134x.yaml new file mode 100644 index 0000000..4e4e6a0 --- /dev/null +++ b/data/adc_channels/mspm0l134x.yaml @@ -0,0 +1,12 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0L1346 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 11: TemperatureSensor + 12: Opa0 + 13: Opa1 + 14: Gpamp + 15: SupplyMonitor diff --git a/data/adc_channels/mspm0l211x.yaml b/data/adc_channels/mspm0l211x.yaml new file mode 100644 index 0000000..f4c8876 --- /dev/null +++ b/data/adc_channels/mspm0l211x.yaml @@ -0,0 +1,10 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0L2117 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 28: TemperatureSensor + 29: Vref + 31: SupplyMonitor diff --git a/data/adc_channels/mspm0l222x.yaml b/data/adc_channels/mspm0l222x.yaml new file mode 100644 index 0000000..899d309 --- /dev/null +++ b/data/adc_channels/mspm0l222x.yaml @@ -0,0 +1,11 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0L2228 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 28: Vref + 29: TemperatureSensor + 30: VbatMonitor + 31: SupplyMonitor diff --git a/data/adc_channels/msps003fx.yaml b/data/adc_channels/msps003fx.yaml new file mode 100644 index 0000000..00a9aeb --- /dev/null +++ b/data/adc_channels/msps003fx.yaml @@ -0,0 +1,9 @@ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the MSPM0C1104 +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +ADC0: + 11: TemperatureSensor + 15: SupplyMonitor diff --git a/tools/adc_channels.py b/tools/adc_channels.py new file mode 100644 index 0000000..e938789 --- /dev/null +++ b/tools/adc_channels.py @@ -0,0 +1,266 @@ +"""Extract which internal signals the ADC channels sample from MSPM0 datasheets. + +Some ADC channel numbers do not go to a package pin: they sample the temperature sensor, an OPA or +GPAMP output, the internal reference, or a supply monitor. Which channel carries which signal is +published only in the datasheet's "ADC Channel Mapping" table, and differs per family and per ADC +instance. This writes `data/adc_channels/<family>.yaml`. + +Like `tools/vref.py`, this is an offline aid rather than part of the build. Re-run it after a data +source bump rather than editing the YAML by hand. + +Usage: + uv run tools/adc_channels.py <datasheet.pdf> [...] # print what it finds + uv run tools/adc_channels.py --write <dir-of-datasheets> # regenerate data/adc_channels + +`uv run` installs the dependencies below on its own; a bare `python` needs them on the path. + +The obvious machine-readable source is worse. The SDK's +`source/ti/driverlib/.meta/adc12/ADC12_internalConnections.js` holds the same mapping, but keyed by +SDK family, and an SDK family is a superset of its devices: it gives MSPM0G110x and MSPM0G310x the +OPA routes of the G150x/G350x they share a key with, when those parts have no OPA and their +datasheets leave channel 13 empty. It also omits channel 28 (VREF) on MSPM0L122x/L222x, and lists +channel 12 as "Internal VREF" on MSPM0C110x where the datasheet shows `-` and TI's own channel-range +comment in the same file excludes 12. The sysconfig `SYS_OA*_CHANNELS` attributes repeat the +superset mistake. The datasheets are per part and correct on all five points, so they are the +source. + +Reading the table: + +- A cell is a package pin (`A5`, `A1_12`), empty, `Reserved`, or an internal signal. The datasheet's + own rule is footnote (1): "Italicized signal names are purely internal to the SoC". Italics do not + survive table extraction, so the internal names are matched against a fixed list instead, and a + cell which is neither a pin nor a known name is reported as a problem rather than skipped -- a new + datasheet's new signal name has to be classified by a human. +- A shared cell (`A1_0 / DAC_OUT`) is a pin and an internal signal on one channel; the pin half is + dropped. `VREF+`/`VREF-` name the external reference pins, not the internal reference, and are + dropped with the pins. +""" + +# /// script +# requires-python = ">=3.10" +# dependencies = ["pdfplumber", "pypdfium2", "pyyaml"] +# /// + +import re +import sys +from pathlib import Path + +try: + import pdfplumber + import pypdfium2 + import yaml +except ImportError as e: # pragma: no cover + raise SystemExit(f"{e.name} is missing; run this with `uv run` instead of `python`") + +CAPTION = re.compile(r"ADC\d* Channel Mapping") + +LATTICE = {"vertical_strategy": "lines", "horizontal_strategy": "lines"} + +#: Internal signal names as the datasheets print them, lowercased, with whitespace around any `/` +#: removed. The values are the `AdcInternalSource` variants of `mspm0-data-types`. +INTERNAL = { + "temperature sensor": "TemperatureSensor", + "opa0 output": "Opa0", + "opa1 output": "Opa1", + "gpamp output": "Gpamp", + "dac_out": "Dac0", + "vref": "Vref", + "vrefint": "Vref", + "supply monitor": "SupplyMonitor", + "supply/battery monitor": "SupplyMonitor", + "vbat monitor": "VbatMonitor", + "vusb monitor": "VusbMonitor", +} + +#: A package pin, `A5` or `A1_12`, or the external reference pins a channel can share a pin with. +EXTERNAL = re.compile(r"A\d+(_\d+)?|VREF[+-]") + +FOOTNOTE = re.compile(r"\(\d+\)") + +SUBHEADER = re.compile(r"ADC\d+") + + +class Problem(Exception): + pass + + +def caption_pages(path: Path) -> list[int]: + """Indices of the pages carrying the ADC channel-mapping table.""" + doc = pypdfium2.PdfDocument(str(path)) + try: + return [i for i in range(len(doc)) if CAPTION.search(doc[i].get_textpage().get_text_range())] + finally: + doc.close() + + +def read_tables(path: Path) -> dict[str, dict[int, str]]: + """Instance name to channel number to internal source, from every channel-mapping table.""" + channels: dict[str, dict[int, str]] = {} + with pdfplumber.open(str(path)) as pdf: + for page_index in caption_pages(path): + for table in pdf.pages[page_index].extract_tables(LATTICE): + read_table(table, channels) + + return channels + + +def read_table(table: list[list[str | None]], channels: dict[str, dict[int, str]]) -> None: + """Fold one extracted table into `channels`. A table without the expected header is not the + channel-mapping table and is ignored; the caption page carries others.""" + width = max(len(row) for row in table) + rows = [ + [(cell or "").replace("\n", " ").strip() for cell in row] + [""] * (width - len(row)) + for row in table + ] + + header = next( + ( + row + for row in rows + if any(c.upper().startswith("CHANNEL") for c in row) + and any("SIGNAL NAME" in c.upper() for c in row) + ), + None, + ) + if header is None: + return + + channel_columns = [i for i, c in enumerate(header) if c.upper().startswith("CHANNEL")] + + # Signal columns run from each channel column to the next. Their instance is named either by a + # sub-header row of ADCn labels (the dual-ADC layouts) or inside the header cell itself, as + # "Signal Name (ADC0)"; a table naming neither has one ADC, which every datasheet calls ADC0. + instance_of = {} + subheader = next( + ( + row + for row in rows + if row is not header + and not any(row[i].isdigit() for i in channel_columns if i < len(row)) + and any(SUBHEADER.fullmatch(c) for c in row) + ), + None, + ) + for start, end in zip(channel_columns, channel_columns[1:] + [len(header)]): + signal_columns = list(range(start + 1, end)) + for column in signal_columns: + if subheader is not None and SUBHEADER.fullmatch(subheader[column]): + instance_of[column] = subheader[column] + elif named := SUBHEADER.search(header[column]): + instance_of[column] = named.group(0) + elif len(signal_columns) == 1: + instance_of[column] = "ADC0" + else: + raise Problem(f"cannot name the ADC instance of column {column}") + + for row in rows: + for start, end in zip(channel_columns, channel_columns[1:] + [len(header)]): + if not row[start].isdigit(): + continue + + channel = int(row[start]) + for column in range(start + 1, end): + source = read_cell(row[column]) + if source is None: + continue + + instance = instance_of[column] + already = channels.setdefault(instance, {}).setdefault(channel, source) + if already != source: + raise Problem(f"{instance} channel {channel} read as {already} and {source}") + + +def read_cell(cell: str) -> str | None: + """The internal source a cell names, or `None` for pins, `Reserved` and empty cells.""" + text = " ".join(FOOTNOTE.sub(" ", cell).split()) + if text in ("", "-", "โ€“", "Reserved"): + return None + + key = re.sub(r"\s*/\s*", "/", text).lower() + if key in INTERNAL: + return INTERNAL[key] + + parts = [p for p in re.split(r"\s*/\s*", text) if p] + internal = [INTERNAL[p.lower()] for p in parts if p.lower() in INTERNAL] + external = [p for p in parts if EXTERNAL.fullmatch(p)] + if len(internal) + len(external) != len(parts): + unknown = [p for p in parts if p not in external and p.lower() not in INTERNAL] + raise Problem(f"unrecognised signal {unknown} in cell {cell!r}") + if len(internal) > 1: + raise Problem(f"two internal signals in cell {cell!r}") + + return internal[0] if internal else None + + +HEADER = """\ +# Which internal signals this family's ADC channels sample. +# +# GENERATED by `tools/adc_channels.py --write` from the ADC channel-mapping table of the {ds} +# datasheet. Re-run the tool rather than editing by hand. +# +# Channels not listed here go to package pins or nowhere. +""" + + +def write(datasheets: str) -> int: + """Regenerate data/adc_channels/*.yaml. Returns the number of problems reported.""" + parts = yaml.safe_load(Path("data/parts.yaml").read_text(encoding="utf-8")) + families = [(f["family"], f["datasheet_url"].rsplit("/", 1)[-1]) for f in parts["families"]] + + Path("data/adc_channels").mkdir(parents=True, exist_ok=True) + problems = 0 + + for family, gpn in families: + pdf = Path(datasheets) / f"{gpn}_datasheet.pdf" + if not pdf.exists(): + print(f"{family}: no datasheet for {gpn} in {datasheets}") + problems += 1 + continue + + try: + channels = read_tables(pdf) + except Problem as problem: + print(f"{family}: {problem} in {pdf.name}") + problems += 1 + continue + + if not channels: + print(f"{family}: no ADC channel-mapping table in {pdf.name}") + problems += 1 + continue + + path = Path(f"data/adc_channels/{family}.yaml") + with open(path, "w", encoding="utf-8", newline="\n") as out: + out.write(HEADER.format(ds=gpn.upper())) + for instance, mapping in sorted(channels.items()): + out.write(f"{instance}:\n") + for channel, source in sorted(mapping.items()): + out.write(f" {channel}: {source}\n") + + found = ", ".join( + f"{instance} {{{', '.join(f'{ch}: {src}' for ch, src in sorted(mapping.items()))}}}" + for instance, mapping in sorted(channels.items()) + ) + print(f"{family}: {found}") + + return problems + + +def main(argv: list[str]) -> None: + if "--write" in argv: + i = argv.index("--write") + raise SystemExit(1 if write(argv[i + 1] if i + 1 < len(argv) else ".") else 0) + + paths = [Path(a) for a in argv if not a.startswith("--")] + if not paths: + raise SystemExit(__doc__) + + for path in paths: + print(f"########## {path.name}") + for instance, mapping in sorted(read_tables(path).items()): + for channel, source in sorted(mapping.items()): + print(f" {instance} channel {channel:>2}: {source}") + + +if __name__ == "__main__": + main(sys.argv[1:]) From 755d87cff5500968a5985f74fb55c600a35e0fcb Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 15:17:23 +0100 Subject: [PATCH 082/134] adc: carry the internal channels through to the PAC metadata Adc::internal_channels, on the struct the ADC instances already carry, so the OPA-to-ADC routing the HAL keeps in a hand-held family table comes from the metadata instead. The routing does not follow the instance name - OPA0 is channel 13 on mspm0g350x and channel 12 on mspm0l130x - so a consumer table was always one new family away from being wrong. verify fails a route naming an instance the chip does not have, which is the difference between the datasheets and the SDK's family-superset tables, and reports an ADC with no channel data as a lost extraction, since every datasheet so far has the table. --- mspm0-data-gen/src/adc_channels.rs | 21 +++++++++ mspm0-data-gen/src/generate.rs | 25 ++++++++--- mspm0-data-gen/src/main.rs | 7 ++- mspm0-data-gen/src/sources.rs | 4 ++ mspm0-data-gen/src/util.rs | 12 ++--- mspm0-data-gen/src/verify.rs | 71 +++++++++++++++++++++++++++++- mspm0-data-types/src/lib.rs | 48 +++++++++++++++++++- mspm0-metapac-gen/res/metadata.rs | 48 ++++++++++++++++++++ mspm0-metapac-gen/src/metadata.rs | 32 ++++++++++++-- 9 files changed, 250 insertions(+), 18 deletions(-) create mode 100644 mspm0-data-gen/src/adc_channels.rs diff --git a/mspm0-data-gen/src/adc_channels.rs b/mspm0-data-gen/src/adc_channels.rs new file mode 100644 index 0000000..d66d0b8 --- /dev/null +++ b/mspm0-data-gen/src/adc_channels.rs @@ -0,0 +1,21 @@ +//! Reads `data/adc_channels/<family>.yaml`, which `tools/adc_channels.py` extracts from the +//! datasheets' "ADC Channel Mapping" tables. +//! +//! The SDK's `ADC12_internalConnections.js` holds the same mapping but keyed by SDK family, which +//! is a superset of its devices: it gives mspm0g110x and mspm0g310x OPA routes those parts do not +//! have, and the sysconfig `SYS_OA*_CHANNELS` attributes repeat the mistake. The datasheets are per +//! part, so they are the source; the tool's docs list the discrepancies found. + +use std::collections::BTreeMap; + +use mspm0_data_types::AdcInternalSource; + +use crate::util; + +/// One family's mapping: ADC instance name to channel number to internal source. +pub type AdcChannels = BTreeMap<String, BTreeMap<u8, AdcInternalSource>>; + +/// Read every `data/adc_channels/<family>.yaml`, keyed by family name. +pub fn parse() -> anyhow::Result<BTreeMap<String, AdcChannels>> { + util::per_family("adc_channels") +} diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index e39b26e..5e0e5d4 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -15,12 +15,13 @@ use mspm0_data_types::{ use regex::Regex; use crate::{ - sources::{FamilySources, Sources}, + adc_channels::AdcChannels, header::Header, int_group::Groups, operating_modes::OperatingModes, parts::{PartFamily, PartMemory}, perimap::PERIMAP, + sources::{FamilySources, Sources}, svd::Svd, sysconfig::{self, PartPeripheralWrapper, SysconfigFile}, timers::Timers, @@ -44,6 +45,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu let FamilySources { header, sysconfig, + adc_channels, svd, clock_tree, operating_modes, @@ -73,7 +75,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu apply_standby1_timers(family, &mut peripherals)?; apply_timers(family, sysconfig, timers, &mut peripherals)?; apply_clock_ranges(family, &mut peripherals); - apply_adc(family, sysconfig, &mut peripherals)?; + apply_adc(family, sysconfig, adc_channels, &mut peripherals)?; apply_unicomm(family, header, &mut peripherals)?; apply_vref(vref, &mut peripherals); @@ -541,7 +543,7 @@ fn generate_missing( clock_range_hz: None, adc: None, unicomm: None, - vref: None, + vref: None, }, ); @@ -574,7 +576,7 @@ fn generate_missing( clock_range_hz: None, adc: None, unicomm: None, - vref: None, + vref: None, }, ); @@ -615,7 +617,7 @@ fn generate_missing( clock_range_hz: None, adc: None, unicomm: None, - vref: None, + vref: None, }); let pin = device_pin @@ -1064,6 +1066,7 @@ fn apply_vref(vref: Option<Vref>, peripherals: &mut BTreeMap<String, Peripheral> fn apply_adc( family: &PartFamily, sysconfig: &SysconfigFile, + adc_channels: Option<&AdcChannels>, peripherals: &mut BTreeMap<String, Peripheral>, ) -> anyhow::Result<()> { let chip_name = &family.family; @@ -1100,7 +1103,17 @@ fn apply_adc( .get(name) .context(format!("{chip_name}: {name} has no MEMCTL count"))?; - peripheral.adc = Some(Adc { memctl, vrsel }); + // Absent data is a gap verify.rs reports, like the other datasheet extractions. + let internal_channels = adc_channels + .and_then(|family| family.get(name)) + .cloned() + .unwrap_or_default(); + + peripheral.adc = Some(Adc { + memctl, + vrsel, + internal_channels, + }); } Ok(()) diff --git a/mspm0-data-gen/src/main.rs b/mspm0-data-gen/src/main.rs index 0a9f0ac..670a74d 100644 --- a/mspm0-data-gen/src/main.rs +++ b/mspm0-data-gen/src/main.rs @@ -1,3 +1,4 @@ +mod adc_channels; mod clock_tree; mod errata; mod generate; @@ -6,13 +7,13 @@ mod int_group; mod operating_modes; mod parts; mod perimap; +mod sources; mod svd; mod sysconfig; mod timers; -mod sources; mod util; -mod vref; mod verify; +mod vref; mod wakeup; use std::{path::PathBuf, time::Instant}; @@ -73,6 +74,7 @@ fn main() -> anyhow::Result<()> { stopwatch.section("Read interrupt group mappings"); + let adc_channels = adc_channels::parse()?; let int_groups = int_group::parse()?; let operating_modes = operating_modes::parse()?; let timers = timers::parse()?; @@ -86,6 +88,7 @@ fn main() -> anyhow::Result<()> { let sources = sources::Sources { parts, headers, + adc_channels, sysconfig, svds, clock_trees, diff --git a/mspm0-data-gen/src/sources.rs b/mspm0-data-gen/src/sources.rs index bd62eee..ce2059a 100644 --- a/mspm0-data-gen/src/sources.rs +++ b/mspm0-data-gen/src/sources.rs @@ -9,6 +9,7 @@ use std::collections::BTreeMap; use mspm0_data_types::{Vref, WakeTimes}; use crate::{ + adc_channels::AdcChannels, clock_tree::{ClockTreeFile, ClockTrees}, errata::Errata, header::{Header, Headers}, @@ -24,6 +25,7 @@ use crate::{ pub struct Sources { pub parts: PartsFile, pub headers: Headers, + pub adc_channels: BTreeMap<String, AdcChannels>, pub sysconfig: Sysconfig, pub svds: Svds, pub clock_trees: ClockTrees, @@ -42,6 +44,7 @@ pub struct Sources { pub struct FamilySources<'a> { pub header: &'a Header, pub sysconfig: &'a SysconfigFile, + pub adc_channels: Option<&'a AdcChannels>, pub svd: Option<&'a Svd>, pub clock_tree: Option<&'a ClockTreeFile>, pub operating_modes: Option<&'a OperatingModes>, @@ -81,6 +84,7 @@ impl Sources { Ok(FamilySources { header, sysconfig, + adc_channels: self.adc_channels.get(family), svd: self.svds.files.get(family), clock_tree: self.clock_trees.files.get(family), operating_modes: self.operating_modes.get(family), diff --git a/mspm0-data-gen/src/util.rs b/mspm0-data-gen/src/util.rs index 9e7d79e..ad9bb57 100644 --- a/mspm0-data-gen/src/util.rs +++ b/mspm0-data-gen/src/util.rs @@ -17,15 +17,17 @@ pub fn per_family<T: DeserializeOwned>(dir: &str) -> anyhow::Result<BTreeMap<Str for path in glob::glob(&format!("data/{dir}/*.yaml"))?.flatten() { let family = path .file_stem() - .context(format!("{}: no file stem to take a family name from", path.display()))? + .context(format!( + "{}: no file stem to take a family name from", + path.display() + ))? .to_string_lossy() .into_owned(); - let content = - fs::read_to_string(&path).context(format!("reading {}", path.display()))?; + let content = fs::read_to_string(&path).context(format!("reading {}", path.display()))?; - let value = serde_yaml::from_str::<T>(&content) - .context(format!("reading {}", path.display()))?; + let value = + serde_yaml::from_str::<T>(&content).context(format!("reading {}", path.display()))?; families.insert(family, value); } diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 8e4e6c0..f25c508 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -1,7 +1,7 @@ use std::{collections::HashSet, sync::LazyLock}; use anyhow::bail; -use mspm0_data_types::{Chip, PeripheralType, PowerDomain}; +use mspm0_data_types::{AdcInternalSource, Chip, PeripheralType, PowerDomain}; use regex::Regex; /// Run every check, returning one error per failure. @@ -13,6 +13,8 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { peripheral_types_known, register_blocks_exist, vref_startup_known, + adc_channels_known, + adc_internal_sources_exist, // Peripherals which don't actually exist no_gpamp_c110x_l151x, // Low power data which is only as complete as the data sources @@ -114,6 +116,73 @@ fn vref_startup_known(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// Report an ADC instance with no internal-channel data. +/// +/// Every datasheet so far has the channel-mapping table, so an empty map means +/// `data/adc_channels/<family>.yaml` is missing or the extraction lost the instance, not a device +/// without internal channels. +fn adc_channels_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + for peripheral in chip + .peripherals + .values() + .filter(|peripheral| peripheral.ty == PeripheralType::Adc) + { + let empty = peripheral + .adc + .as_ref() + .is_none_or(|adc| adc.internal_channels.is_empty()); + if empty { + bail!( + "{name}: {} has no internal channels; data/adc_channels/{}.yaml is missing or lost \ + the instance", + peripheral.name, + chip.family + ); + } + } + + Ok(()) +} + +/// An internal channel naming a peripheral instance requires that instance to exist. +/// +/// This is the check that separates the datasheets from the SDK's family-superset tables, which +/// route the OPA outputs on families that have no OPA. +fn adc_internal_sources_exist(chip: &Chip, name: &str) -> anyhow::Result<()> { + for peripheral in chip + .peripherals + .values() + .filter(|peripheral| peripheral.ty == PeripheralType::Adc) + { + let Some(adc) = &peripheral.adc else { + continue; + }; + + for (channel, source) in &adc.internal_channels { + let instance = match source { + AdcInternalSource::Opa0 => "OPA0", + AdcInternalSource::Opa1 => "OPA1", + AdcInternalSource::Gpamp => "GPAMP", + AdcInternalSource::Dac0 => "DAC0", + AdcInternalSource::Vref => "VREF", + AdcInternalSource::TemperatureSensor + | AdcInternalSource::SupplyMonitor + | AdcInternalSource::VbatMonitor + | AdcInternalSource::VusbMonitor => continue, + }; + + if !chip.peripherals.contains_key(instance) { + bail!( + "{name}: {} channel {channel} samples {instance}, which the chip does not have", + peripheral.name + ); + } + } + } + + Ok(()) +} + /// Verify all core peripherals are present. fn core_peripherals(chip: &Chip, name: &str) -> anyhow::Result<()> { if !chip.peripherals.contains_key("CPUSS") { diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 60b33b7..c6df1b4 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -565,13 +565,59 @@ pub struct Vref { } /// The parts of one ADC instance which the single `adc_v1` register block does not describe. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Adc { /// Number of configurable conversion channels (`MEMCTL`). pub memctl: u8, /// Number of options `CTL2.VRSEL` accepts. pub vrsel: u8, + + /// Channels hard-wired to an internal signal rather than a package pin. + /// + /// From the datasheet's "ADC Channel Mapping" table, read by `tools/adc_channels.py`. Channels + /// not listed go to package pins or nowhere. The routing differs per instance and per family -- + /// the OPA0 output is ADC0 channel 13 on mspm0g350x and channel 12 on mspm0l130x -- so a + /// consumer must not key it on the instance name. + /// + /// Empty when the family's datasheet has no such table, which none so far lacks. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub internal_channels: BTreeMap<u8, AdcInternalSource>, +} + +/// An internal signal an ADC channel samples instead of a package pin. +/// +/// Variants exist for the signals the current datasheets route; a new family may add more. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +pub enum AdcInternalSource { + /// The temperature sensor. Its single-point calibration value is in `FACTORYREGION`. + TemperatureSensor, + + /// The OPA0 output. + Opa0, + + /// The OPA1 output. + Opa1, + + /// The GPAMP output. + Gpamp, + + /// The DAC0 output. The channel is shared with a package pin, which cannot sample external + /// signals while the DAC drives it. + Dac0, + + /// The internal voltage reference, `VREF` or `VREFINT` in the datasheets. Not the `VREF+`/ + /// `VREF-` pins, which are external and stay in the pin data. + Vref, + + /// The supply monitor, "Supply/Battery Monitor" in most datasheets. + SupplyMonitor, + + /// The VBAT backup-supply monitor. + VbatMonitor, + + /// The VUSB supply monitor. + VusbMonitor, } /// An inclusive frequency range, in Hz. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 150b742..688e3bf 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -294,6 +294,54 @@ pub struct Adc { /// Number of options `CTL2.VRSEL` accepts. pub vrsel: u8, + + /// Channels hard-wired to an internal signal rather than a package pin, sorted by channel. + /// + /// From the datasheet's "ADC Channel Mapping" table. Channels not listed go to package pins or + /// nowhere. The routing differs per instance and per family -- the OPA0 output is ADC0 + /// channel 13 on mspm0g350x and channel 12 on mspm0l130x -- so do not key it on the instance + /// name. + pub internal_channels: &'static [AdcInternalChannel], +} + +/// One ADC channel and the internal signal it samples. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct AdcInternalChannel { + pub channel: u8, + pub source: AdcInternalSource, +} + +/// An internal signal an ADC channel samples instead of a package pin. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum AdcInternalSource { + /// The temperature sensor. Its single-point calibration value is in `FACTORYREGION`. + TemperatureSensor, + + /// The OPA0 output. + Opa0, + + /// The OPA1 output. + Opa1, + + /// The GPAMP output. + Gpamp, + + /// The DAC0 output. The channel is shared with a package pin, which cannot sample external + /// signals while the DAC drives it. + Dac0, + + /// The internal voltage reference, `VREF` or `VREFINT` in the datasheets. Not the `VREF+`/ + /// `VREF-` pins, which are external and stay in the pin data. + Vref, + + /// The supply monitor, "Supply/Battery Monitor" in most datasheets. + SupplyMonitor, + + /// The VBAT backup-supply monitor. + VbatMonitor, + + /// The VUSB supply monitor. + VusbMonitor, } /// The capabilities of one timer instance. diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 76b6b67..79a3b65 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -1,7 +1,8 @@ use std::{collections::HashSet, sync::LazyLock}; use mspm0_data_types::{ - Chip, MemoryKind, Package, Peripheral, PeripheralType, PowerDomain, PowerMode, + AdcInternalSource, Chip, MemoryKind, Package, Peripheral, PeripheralType, PowerDomain, + PowerMode, }; use proc_macro2::{Literal, TokenStream}; use quote::quote; @@ -189,6 +190,20 @@ fn power_mode(mode: PowerMode) -> TokenStream { } } +fn adc_internal_source(source: AdcInternalSource) -> TokenStream { + match source { + AdcInternalSource::TemperatureSensor => quote! { AdcInternalSource::TemperatureSensor }, + AdcInternalSource::Opa0 => quote! { AdcInternalSource::Opa0 }, + AdcInternalSource::Opa1 => quote! { AdcInternalSource::Opa1 }, + AdcInternalSource::Gpamp => quote! { AdcInternalSource::Gpamp }, + AdcInternalSource::Dac0 => quote! { AdcInternalSource::Dac0 }, + AdcInternalSource::Vref => quote! { AdcInternalSource::Vref }, + AdcInternalSource::SupplyMonitor => quote! { AdcInternalSource::SupplyMonitor }, + AdcInternalSource::VbatMonitor => quote! { AdcInternalSource::VbatMonitor }, + AdcInternalSource::VusbMonitor => quote! { AdcInternalSource::VusbMonitor }, + } +} + fn skip_peripheral(ty: PeripheralType) -> bool { matches!(ty, PeripheralType::Unknown) } @@ -345,11 +360,22 @@ fn generate_peripheral( None => quote! { None }, }; - let adc = match peripheral.adc { + let adc = match &peripheral.adc { Some(adc) => { let memctl = Literal::u8_unsuffixed(adc.memctl); let vrsel = Literal::u8_unsuffixed(adc.vrsel); - quote! { Some(Adc { memctl: #memctl, vrsel: #vrsel }) } + let internal_channels = adc.internal_channels.iter().map(|(channel, source)| { + let channel = Literal::u8_unsuffixed(*channel); + let source = adc_internal_source(*source); + quote! { AdcInternalChannel { channel: #channel, source: #source } } + }); + quote! { + Some(Adc { + memctl: #memctl, + vrsel: #vrsel, + internal_channels: &[#(#internal_channels),*], + }) + } } None => quote! { None }, }; From 05f6a00e4a780195d8efaa85d3f3580f262d3998 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 15:17:29 +0100 Subject: [PATCH 083/134] docs: list the ADC channel extraction alongside the other datasheet tools README's datasheet bullet list and the new-chip checklist both name the extractions one short. --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5a5ee00..f28cdc7 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,10 @@ These are the data sources currently used. ([`data/vref/`](./data/vref)). `CTL1.READY` would answer it, but `VREF_ERR_01` leaves that bit set after the first enable since reset. On an affected device the datasheet figure is the only signal + * Which ADC channels sample an internal signal โ€” the temperature sensor, an OPA, GPAMP or DAC + output, the internal reference, a supply monitor โ€” from the ADC channel-mapping table + ([`data/adc_channels/`](./data/adc_channels)). The SDK carries the same mapping keyed by SDK + family, which overstates: it routes OPA outputs on parts that have no OPA * Which timers stay clocked in STANDBY1 (`standby1_timers` in [`parts.yaml`](./data/parts.yaml)) * MCLK and ULPCLK ceilings, the SYSOSC base frequency, the flash wait-state bands, `fADCCLK` and `TRNGCLKF` (all in [`parts.yaml`](./data/parts.yaml)) @@ -88,8 +92,8 @@ the `tools/` scripts read them from there. 3. If needed, add any chip specific register blocks like `sysctl`. 4. Check the peripheral mapping in [`perimap.rs`](./mspm0-data-gen/src/perimap.rs) to use the correct peripherals. 5. Fetch the documents with `./d download-docs`, then regenerate the extracted data: - `tools/operating_modes.py`, `tools/timers.py`, `tools/wakeup.py`, `tools/vref.py` and - `tools/errata.py`, each with + `tools/operating_modes.py`, `tools/timers.py`, `tools/wakeup.py`, `tools/vref.py`, + `tools/adc_channels.py` and `tools/errata.py`, each with `--write files`. 6. Run `./d gen` and read its output. `verify.rs` reports every per-chip gap it can detect, including a family with no timer, errata or operating-mode data. From 97115679a86c575f1c07bceb1d5007ce91809b09 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 15:17:45 +0100 Subject: [PATCH 084/134] metapac-gen: rustfmt main.rs --- mspm0-metapac-gen/src/main.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/mspm0-metapac-gen/src/main.rs b/mspm0-metapac-gen/src/main.rs index eef09ff..6d0a4a3 100644 --- a/mspm0-metapac-gen/src/main.rs +++ b/mspm0-metapac-gen/src/main.rs @@ -132,7 +132,10 @@ fn generate_chip_metadata( let max_mclk_hz = Literal::u32_unsuffixed(chip.max_mclk_hz); let max_ulpclk_hz = Literal::u32_unsuffixed(chip.max_ulpclk_hz); let sysosc_base_hz = Literal::u32_unsuffixed(chip.sysosc_base_hz); - let flash_wait_hz = chip.flash_wait_hz.iter().map(|hz| Literal::u32_unsuffixed(*hz)); + let flash_wait_hz = chip + .flash_wait_hz + .iter() + .map(|hz| Literal::u32_unsuffixed(*hz)); let backup_domain = chip.backup_domain; let errata = chip.errata.iter(); @@ -177,8 +180,7 @@ fn generate_chip_metadata( }; let (sleep0, sleep1, sleep2) = (ns(wake.sleep0), ns(wake.sleep1), ns(wake.sleep2)); let (stop0, wake_stop1, stop2) = (ns(wake.stop0), ns(wake.stop1), ns(wake.stop2)); - let (standby0, standby1, shutdown) = - (ns(wake.standby0), ns(wake.standby1), ns(wake.shutdown)); + let (standby0, standby1, shutdown) = (ns(wake.standby0), ns(wake.standby1), ns(wake.shutdown)); let wake_ns = quote! { WakeTimes { sleep0: #sleep0, @@ -283,8 +285,11 @@ pub fn write_rust(path: impl AsRef<Path>, contents: &str) -> anyhow::Result<()> let parsed = syn::parse_file(contents) .with_context(|| format!("generated Rust for {} does not parse", path.display()))?; - fs::write(path, format!("{AUTOGENERATED_TEXT}\n{}", prettyplease::unparse(&parsed))) - .with_context(|| format!("writing {}", path.display()))?; + fs::write( + path, + format!("{AUTOGENERATED_TEXT}\n{}", prettyplease::unparse(&parsed)), + ) + .with_context(|| format!("writing {}", path.display()))?; Ok(()) } From e09000094b9fbde1670ff29c80cfce9d3238bd30 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 16:31:23 +0100 Subject: [PATCH 085/134] uart: extract the extended-feature table from the datasheets Nothing machine-readable can be the source: sysconfig's SYS_LIN_EN is 1 on main UARTs across the C, G1x0x/G3x0x, H321x and L1x0x families against their datasheets, SVDs and driverlib, and two families have no SVD. The features are stored instead of TI's Extend/Main label because the UNICOMM UARTs do not nest: on the G5187, UC1 has LIN without smart card and UC3 the reverse. --- data/uart/mspm0c1105_c1106.yaml | 22 +++ data/uart/mspm0c110x.yaml | 10 + data/uart/mspm0g110x.yaml | 28 +++ data/uart/mspm0g150x.yaml | 28 +++ data/uart/mspm0g151x.yaml | 46 +++++ data/uart/mspm0g310x.yaml | 28 +++ data/uart/mspm0g350x.yaml | 28 +++ data/uart/mspm0g351x.yaml | 46 +++++ data/uart/mspm0g518x.yaml | 22 +++ data/uart/mspm0h321x.yaml | 22 +++ data/uart/mspm0l110x.yaml | 16 ++ data/uart/mspm0l112x.yaml | 22 +++ data/uart/mspm0l122x.yaml | 34 ++++ data/uart/mspm0l130x.yaml | 16 ++ data/uart/mspm0l134x.yaml | 16 ++ data/uart/mspm0l211x.yaml | 22 +++ data/uart/mspm0l222x.yaml | 34 ++++ data/uart/msps003fx.yaml | 10 + tools/uart.py | 316 ++++++++++++++++++++++++++++++++ 19 files changed, 766 insertions(+) create mode 100644 data/uart/mspm0c1105_c1106.yaml create mode 100644 data/uart/mspm0c110x.yaml create mode 100644 data/uart/mspm0g110x.yaml create mode 100644 data/uart/mspm0g150x.yaml create mode 100644 data/uart/mspm0g151x.yaml create mode 100644 data/uart/mspm0g310x.yaml create mode 100644 data/uart/mspm0g350x.yaml create mode 100644 data/uart/mspm0g351x.yaml create mode 100644 data/uart/mspm0g518x.yaml create mode 100644 data/uart/mspm0h321x.yaml create mode 100644 data/uart/mspm0l110x.yaml create mode 100644 data/uart/mspm0l112x.yaml create mode 100644 data/uart/mspm0l122x.yaml create mode 100644 data/uart/mspm0l130x.yaml create mode 100644 data/uart/mspm0l134x.yaml create mode 100644 data/uart/mspm0l211x.yaml create mode 100644 data/uart/mspm0l222x.yaml create mode 100644 data/uart/msps003fx.yaml create mode 100644 tools/uart.py diff --git a/data/uart/mspm0c1105_c1106.yaml b/data/uart/mspm0c1105_c1106.yaml new file mode 100644 index 0000000..5809799 --- /dev/null +++ b/data/uart/mspm0c1105_c1106.yaml @@ -0,0 +1,22 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0C1106 datasheet. +# Re-run the tool rather than editing by hand. +UART0: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UART1: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART2: + lin: false + dali: false + irda: false + iso7816: false + manchester: false diff --git a/data/uart/mspm0c110x.yaml b/data/uart/mspm0c110x.yaml new file mode 100644 index 0000000..ab1f8a2 --- /dev/null +++ b/data/uart/mspm0c110x.yaml @@ -0,0 +1,10 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0C1104 datasheet. +# Re-run the tool rather than editing by hand. +UART0: + lin: true + dali: true + irda: true + iso7816: true + manchester: true diff --git a/data/uart/mspm0g110x.yaml b/data/uart/mspm0g110x.yaml new file mode 100644 index 0000000..f9790c5 --- /dev/null +++ b/data/uart/mspm0g110x.yaml @@ -0,0 +1,28 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0G1107 datasheet. +# Re-run the tool rather than editing by hand. +UART0: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UART1: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART2: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART3: + lin: false + dali: false + irda: false + iso7816: false + manchester: false diff --git a/data/uart/mspm0g150x.yaml b/data/uart/mspm0g150x.yaml new file mode 100644 index 0000000..596ce2a --- /dev/null +++ b/data/uart/mspm0g150x.yaml @@ -0,0 +1,28 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0G1507 datasheet. +# Re-run the tool rather than editing by hand. +UART0: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UART1: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART2: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART3: + lin: false + dali: false + irda: false + iso7816: false + manchester: false diff --git a/data/uart/mspm0g151x.yaml b/data/uart/mspm0g151x.yaml new file mode 100644 index 0000000..1cb4394 --- /dev/null +++ b/data/uart/mspm0g151x.yaml @@ -0,0 +1,46 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0G1519 datasheet. +# Re-run the tool rather than editing by hand. +UART0: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UART1: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART3: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART4: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART5: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART6: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART7: + lin: true + dali: true + irda: true + iso7816: true + manchester: true diff --git a/data/uart/mspm0g310x.yaml b/data/uart/mspm0g310x.yaml new file mode 100644 index 0000000..220b7a6 --- /dev/null +++ b/data/uart/mspm0g310x.yaml @@ -0,0 +1,28 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0G3107 datasheet. +# Re-run the tool rather than editing by hand. +UART0: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UART1: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART2: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART3: + lin: false + dali: false + irda: false + iso7816: false + manchester: false diff --git a/data/uart/mspm0g350x.yaml b/data/uart/mspm0g350x.yaml new file mode 100644 index 0000000..6a7b604 --- /dev/null +++ b/data/uart/mspm0g350x.yaml @@ -0,0 +1,28 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0G3507 datasheet. +# Re-run the tool rather than editing by hand. +UART0: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UART1: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART2: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART3: + lin: false + dali: false + irda: false + iso7816: false + manchester: false diff --git a/data/uart/mspm0g351x.yaml b/data/uart/mspm0g351x.yaml new file mode 100644 index 0000000..53fe24a --- /dev/null +++ b/data/uart/mspm0g351x.yaml @@ -0,0 +1,46 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0G3519 datasheet. +# Re-run the tool rather than editing by hand. +UART0: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UART1: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART3: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART4: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART5: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART6: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART7: + lin: true + dali: true + irda: true + iso7816: true + manchester: true diff --git a/data/uart/mspm0g518x.yaml b/data/uart/mspm0g518x.yaml new file mode 100644 index 0000000..67ec8f0 --- /dev/null +++ b/data/uart/mspm0g518x.yaml @@ -0,0 +1,22 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0G5187 datasheet. +# Re-run the tool rather than editing by hand. +UC0_UART: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UC1_UART: + lin: true + dali: false + irda: false + iso7816: false + manchester: false +UC3_UART: + lin: false + dali: false + irda: false + iso7816: true + manchester: false diff --git a/data/uart/mspm0h321x.yaml b/data/uart/mspm0h321x.yaml new file mode 100644 index 0000000..83c5cda --- /dev/null +++ b/data/uart/mspm0h321x.yaml @@ -0,0 +1,22 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0H3216 datasheet. +# Re-run the tool rather than editing by hand. +UART0: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UART1: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART2: + lin: false + dali: false + irda: false + iso7816: false + manchester: false diff --git a/data/uart/mspm0l110x.yaml b/data/uart/mspm0l110x.yaml new file mode 100644 index 0000000..e486941 --- /dev/null +++ b/data/uart/mspm0l110x.yaml @@ -0,0 +1,16 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0L1106 datasheet. +# Re-run the tool rather than editing by hand. +UART0: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UART1: + lin: false + dali: false + irda: false + iso7816: false + manchester: false diff --git a/data/uart/mspm0l112x.yaml b/data/uart/mspm0l112x.yaml new file mode 100644 index 0000000..123e86f --- /dev/null +++ b/data/uart/mspm0l112x.yaml @@ -0,0 +1,22 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0L2117 datasheet. +# Re-run the tool rather than editing by hand. +UC11_UART: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UC4_UART: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UC8_UART: + lin: false + dali: false + irda: false + iso7816: false + manchester: false diff --git a/data/uart/mspm0l122x.yaml b/data/uart/mspm0l122x.yaml new file mode 100644 index 0000000..790801f --- /dev/null +++ b/data/uart/mspm0l122x.yaml @@ -0,0 +1,34 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0L1228 datasheet. +# Re-run the tool rather than editing by hand. +UART0: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UART1: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UART2: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART3: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART4: + lin: false + dali: false + irda: false + iso7816: false + manchester: false diff --git a/data/uart/mspm0l130x.yaml b/data/uart/mspm0l130x.yaml new file mode 100644 index 0000000..85dff6c --- /dev/null +++ b/data/uart/mspm0l130x.yaml @@ -0,0 +1,16 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0L1306 datasheet. +# Re-run the tool rather than editing by hand. +UART0: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UART1: + lin: false + dali: false + irda: false + iso7816: false + manchester: false diff --git a/data/uart/mspm0l134x.yaml b/data/uart/mspm0l134x.yaml new file mode 100644 index 0000000..c6917b0 --- /dev/null +++ b/data/uart/mspm0l134x.yaml @@ -0,0 +1,16 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0L1346 datasheet. +# Re-run the tool rather than editing by hand. +UART0: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UART1: + lin: false + dali: false + irda: false + iso7816: false + manchester: false diff --git a/data/uart/mspm0l211x.yaml b/data/uart/mspm0l211x.yaml new file mode 100644 index 0000000..123e86f --- /dev/null +++ b/data/uart/mspm0l211x.yaml @@ -0,0 +1,22 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0L2117 datasheet. +# Re-run the tool rather than editing by hand. +UC11_UART: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UC4_UART: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UC8_UART: + lin: false + dali: false + irda: false + iso7816: false + manchester: false diff --git a/data/uart/mspm0l222x.yaml b/data/uart/mspm0l222x.yaml new file mode 100644 index 0000000..ea7724c --- /dev/null +++ b/data/uart/mspm0l222x.yaml @@ -0,0 +1,34 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0L2228 datasheet. +# Re-run the tool rather than editing by hand. +UART0: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UART1: + lin: true + dali: true + irda: true + iso7816: true + manchester: true +UART2: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART3: + lin: false + dali: false + irda: false + iso7816: false + manchester: false +UART4: + lin: false + dali: false + irda: false + iso7816: false + manchester: false diff --git a/data/uart/msps003fx.yaml b/data/uart/msps003fx.yaml new file mode 100644 index 0000000..ab1f8a2 --- /dev/null +++ b/data/uart/msps003fx.yaml @@ -0,0 +1,10 @@ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the MSPM0C1104 datasheet. +# Re-run the tool rather than editing by hand. +UART0: + lin: true + dali: true + irda: true + iso7816: true + manchester: true diff --git a/tools/uart.py b/tools/uart.py new file mode 100644 index 0000000..fb31489 --- /dev/null +++ b/tools/uart.py @@ -0,0 +1,316 @@ +"""Extract which extended-UART features each UART instance has from MSPM0 datasheets. + +Every legacy UART instance shares the `uart_v1` register block and every UNICOMM UART function +shares `unicommuart_v1`, so nothing in the generated PAC says which instance implements the +extended features: LIN, DALI, IrDA, ISO7816 smart card and Manchester coding. The table that does +is the datasheet's "UART Features" table, whose column headers also carry TI's Extend/Main naming. +This writes `data/uart/<family>.yaml`. + +Like `tools/timers.py`, this is an offline aid rather than part of the build. Re-run it after a +data source bump rather than editing the YAML by hand. + +Usage: + uv run tools/uart.py <datasheet.pdf> [...] # print what it finds + uv run tools/uart.py --write <dir-of-datasheets> # regenerate data/uart + +`uv run` installs the dependencies below on its own; a bare `python` needs them on the path. + +The machine-readable alternatives were checked and lost: + +- sysconfig's `SYS_LIN_EN` is `1` on the *main* UARTs of MSPM0G350X and its siblings, which their + own datasheet, SVD and driverlib all contradict; on MSPM0G351X the same attribute is `0` there. + Its per-instance `SYS_UARTADV` does agree with every datasheet, and `generate.rs` cross-checks + against it, but one demonstrably wrong attribute in the same block is why none of them is the + source. +- The SVDs give the main instances a reduced register set which agrees with the datasheets, but + they are the least reliable source in this repo and two families have none. + +The UNICOMM tables differ from the legacy ones in more than shape. The MSPM0G5187 splits its UART +functions three ways -- UC0 "(Advanced)", UC1 "(Basic w/ LIN)", UC3 "(Basic)" -- and the features do +not nest: UC1 has LIN but no smart card, UC3 smart card but no LIN. A single extend/main flag +cannot carry that, which is why the YAML records the features themselves. The same table folds DALI +and Manchester into one "UART-DALI-MANCHESTER" row, as does TI's own `SYS_UART_DALI_MENC_EN` +attribute, so that row sets both flags. +""" + +# /// script +# requires-python = ">=3.10" +# dependencies = ["pdfplumber", "pypdfium2", "pyyaml"] +# /// + +import glob +import json +import re +import sys +from pathlib import Path + +try: + import pdfplumber + import pypdfium2 + import yaml +except ImportError as e: # pragma: no cover + raise SystemExit(f"{e.name} is missing; run this with `uv run` instead of `python`") + +Uart = dict[str, bool] +Uarts = dict[str, Uart] + +CAPTION = re.compile( + r"UART Features|UART FEATURES|UNICOMM-UART feature support|UART \(UNICOMM\) Features" +) + +LATTICE = {"vertical_strategy": "lines", "horizontal_strategy": "lines"} + +FEATURES = ("lin", "dali", "irda", "iso7816", "manchester") + +#: Feature-row label -> the flags the row sets, after `key()` has collapsed case and whitespace. +#: The legacy and MSPM0L2117 tables label rows with the left-column description; the MSPM0G5187 +#: labels them with a feature tag. An empty tuple is a row known not to matter here; a label in +#: neither form is a problem, so a new datasheet's new row has to be classified by a human. +ROWS = { + "support lin mode": ("lin",), + "support dali": ("dali",), + "support irda": ("irda",), + "support iso7816 smart card": ("iso7816",), + "support manchester coding": ("manchester",), + "uart-lin": ("lin",), + "uart-dali-manchester": ("dali", "manchester"), + "uart-irda": ("irda",), + "uart-smartcard": ("iso7816",), + # Rows the data does not carry: operating-mode facts are `data/operating_modes/`, the FIFO + # depth is sysconfig's `SYS_FENTRIES`, and the rest is common to every instance. + "active in stop and standby mode": (), + "active in stop and standby modes": (), + "separate transmit and receive fifos": (), + "support hardware flow control": (), + "support 9-bit configuration": (), + "fifo depth": (), + "fifo entry depth": (), + "-": (), + "uart-rx-timeout": (), + "uart-idleline-multiproc": (), + "uart-flow-control": (), + "uart-multidrop-9-bit": (), + "uart-ext-driver": (), + "uart-fifo": (), + "uart-dma": (), +} + +#: Cell contents meaning "this instance does not have it". +ABSENT = {"", "-", "โ€“", "โ€”", "no"} + +INSTANCE = re.compile(r"UART\d+|UC\d+") + + +class Problem(Exception): + pass + + +def key(label: str) -> str: + """Normalise a row label: lowercase, and no whitespace splitting a wrapped tag.""" + label = " ".join(label.split()) + return label.replace("- ", "-").lower() if label.upper().startswith("UART-") else label.lower() + + +def present(cell: str) -> bool: + """Whether a yes/no cell says the instance has the feature.""" + text = cell.strip().lower() + if text not in ABSENT and text != "yes": + raise Problem(f"feature cell is neither yes nor no: {cell!r}") + return text == "yes" + + +def instances(header: str) -> list[str]: + """The instances a legacy column header names. + + The headers group and abbreviate: "UART1 and 2 (Main)", "UART0, UART7 (Extend, low-power)", + "UART3-UART6 (Main)". The parenthetical label is dropped -- the feature rows carry what it + abbreviates. + """ + names: list[str] = [] + for token in re.split(r",|\band\b", header.split("(")[0].replace("\n", " ")): + token = token.strip() + if not token: + continue + if match := re.fullmatch(r"UART(\d+)\s*-\s*UART(\d+)", token): + names += [f"UART{n}" for n in range(int(match.group(1)), int(match.group(2)) + 1)] + elif re.fullmatch(r"UART\d+", token): + names.append(token) + elif token.isdigit(): # "UART1 and 2" + names.append(f"UART{token}") + else: + raise Problem(f"cannot read column header {header!r}") + return names + + +def caption_pages(path: Path) -> list[int]: + """Indices of the pages carrying the table's caption.""" + doc = pypdfium2.PdfDocument(str(path)) + try: + return [i for i in range(len(doc)) if CAPTION.search(doc[i].get_textpage().get_text_range())] + finally: + doc.close() + + +def read_tables(path: Path) -> Uarts: + """Instance name to feature flags, merged over every page of the table. + + The MSPM0C1106 and MSPM0H3216 tables span two pages and repeat their header on the second, so + the pages accumulate into one mapping and completeness is checked at the end. + """ + uarts: Uarts = {} + with pdfplumber.open(str(path)) as pdf: + for page_index in caption_pages(path): + for table in pdf.pages[page_index].extract_tables(LATTICE): + read_table(table, uarts) + + for name, uart in uarts.items(): + if missing := [feature for feature in FEATURES if feature not in uart]: + raise Problem(f"the {name} column has no {', '.join(missing)} row") + + return uarts + + +def read_table(table: list[list[str | None]], uarts: Uarts) -> None: + """Fold one extracted table into `uarts`. + + Three shapes exist. The legacy tables put "UART Features" over the row labels and the instances + in the remaining header cells. The MSPM0L2117 writes "Supported Features" and names instances + "UC4.UART" -- sharing its header text with the I2C and SPI tables alongside, which the `.UART` + requirement rejects. The MSPM0G5187 adds a feature-tag column and names the instances on a + second header row, under a first that only carries TI's Advanced/Basic labels. + """ + if not table or not table[0]: + return + rows = [[(cell or "").strip() for cell in row] for row in table] + corner = key(rows[0][0]) + + if corner == "uart features": + columns = { + index: instances(header) for index, header in enumerate(rows[0]) if index and header + } + label_column, body = 0, rows[1:] + elif corner == "supported features": + columns = { + index: [name.replace(".UART", "_UART") for name in header.replace(" ", "").split(",")] + for index, header in enumerate(rows[0]) + if index and header + } + if not all(name.endswith("_UART") for names in columns.values() for name in names): + return # the I2C or SPI table + label_column, body = 0, rows[1:] + elif corner.startswith("unicomm-uart features"): + columns = { + index: [f"{header}_UART"] + for index, header in enumerate(rows[1]) + if INSTANCE.fullmatch(header) + } + label_column, body = 0, rows[2:] + else: + return + + for row in body: + label = key(row[label_column]) + if INSTANCE.fullmatch(row[label_column].split(".")[0].split("(")[0].strip()): + continue # a repeated header row + flags = ROWS.get(label) + if flags is None: + raise Problem(f"unrecognised feature row {row[label_column]!r}") + + for index, names in columns.items(): + for flag in flags: + value = present(row[index]) + for name in names: + already = uarts.setdefault(name, {}).setdefault(flag, value) + if already != value: + raise Problem(f"{name} {flag} read as both {already} and {value}") + + +HEADER = """\ +# Which extended-UART features each UART instance of this family implements. +# +# GENERATED by `tools/uart.py --write` from the UART Features table of the {ds} datasheet. +# Re-run the tool rather than editing by hand. +""" + + +def write(datasheets: str) -> int: + """Regenerate data/uart/*.yaml. Returns the number of problems reported.""" + parts = yaml.safe_load(Path("data/parts.yaml").read_text(encoding="utf-8")) + families = [(f["family"], f["datasheet_url"].rsplit("/", 1)[-1]) for f in parts["families"]] + + known: dict[str, set[str]] = {} + for path in glob.glob("build/data/*.json"): + chip = json.loads(Path(path).read_text(encoding="utf-8")) + known.setdefault(chip["family"], set()).update( + name + for name, p in chip["peripherals"].items() + if p["type"] in ("Uart", "UnicommUart") + ) + if not known: + raise SystemExit("build/data is empty -- run ./d gen first") + + Path("data/uart").mkdir(parents=True, exist_ok=True) + problems = 0 + + for family, gpn in families: + pdf = Path(datasheets) / f"{gpn}_datasheet.pdf" + if not pdf.exists(): + print(f"{family}: no datasheet for {gpn} in {datasheets}") + problems += 1 + continue + + try: + uarts = read_tables(pdf) + except Problem as problem: + print(f"{family}: {problem} in {pdf.name}") + problems += 1 + continue + + # The datasheet covers several families at once, so it can describe instances this family + # does not have. Those are dropped; instances it has and the table misses are reported. + wanted = known.get(family, set()) + uarts = {name: uart for name, uart in uarts.items() if name in wanted} + for name in sorted(wanted - set(uarts)): + print(f"{family}: {name} is a UART of this family but the {gpn} table has no column") + problems += 1 + + if not uarts: + print(f"{family}: no UART Features table in {pdf.name}") + problems += 1 + continue + + path = Path(f"data/uart/{family}.yaml") + with open(path, "w", encoding="utf-8", newline="\n") as out: + out.write(HEADER.format(ds=gpn.upper())) + for name in sorted(uarts): + out.write(f"{name}:\n") + for feature in FEATURES: + out.write(f" {feature}: {str(uarts[name][feature]).lower()}\n") + + found = ", ".join( + f"{name} {{{', '.join(feature for feature in FEATURES if uarts[name][feature]) or 'none'}}}" + for name in sorted(uarts) + ) + print(f"{family}: {found}") + + return problems + + +def main(argv: list[str]) -> None: + if "--write" in argv: + i = argv.index("--write") + raise SystemExit(1 if write(argv[i + 1] if i + 1 < len(argv) else ".") else 0) + + paths = [Path(a) for a in argv if not a.startswith("--")] + if not paths: + raise SystemExit(__doc__) + + for path in paths: + print(f"########## {path.name}") + for name, uart in sorted(read_tables(path).items()): + flags = ", ".join(feature for feature in FEATURES if uart[feature]) or "none" + print(f" {name:10} {flags}") + + +if __name__ == "__main__": + main(sys.argv[1:]) From fbe28281a9d23cb8cafa98c48925d289c5d616a3 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 16:31:32 +0100 Subject: [PATCH 086/134] uart: carry the extended features through to the PAC metadata Peripheral::uart holds five per-instance flags on every UART and UNICOMM UART function; one register block serves all instances, so this is the only per-part statement of which one implements LIN, DALI, IrDA, ISO7816 or Manchester. Generation cross-checks sysconfig's SYS_UARTADV and the UNICOMM SYS_UART_*_EN attributes; verify reports an instance with no data. --- mspm0-data-gen/src/generate.rs | 83 ++++++++++++++++++++++++++++++- mspm0-data-gen/src/main.rs | 3 ++ mspm0-data-gen/src/sources.rs | 4 ++ mspm0-data-gen/src/uart.rs | 21 ++++++++ mspm0-data-gen/src/verify.rs | 25 ++++++++++ mspm0-data-types/src/lib.rs | 36 ++++++++++++++ mspm0-metapac-gen/res/metadata.rs | 33 ++++++++++++ mspm0-metapac-gen/src/metadata.rs | 18 +++++++ 8 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 mspm0-data-gen/src/uart.rs diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 5e0e5d4..ad31e2c 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -9,8 +9,8 @@ use std::{ use anyhow::{anyhow, bail, ensure, Context}; use mspm0_data_types::{ Adc, Chip, DmaChannel, Interrupt, Memory, MemoryKind, Package, PackagePin, Peripheral, - PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, Timer, Unicomm, - Vref, + PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, Timer, Uart, + Unicomm, Vref, }; use regex::Regex; @@ -25,6 +25,7 @@ use crate::{ svd::Svd, sysconfig::{self, PartPeripheralWrapper, SysconfigFile}, timers::Timers, + uart::Uarts, verify, }; @@ -50,6 +51,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu clock_tree, operating_modes, timers, + uart, errata, wake, vref, @@ -77,6 +79,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu apply_clock_ranges(family, &mut peripherals); apply_adc(family, sysconfig, adc_channels, &mut peripherals)?; apply_unicomm(family, header, &mut peripherals)?; + apply_uart(family, sysconfig, uart, &mut peripherals)?; apply_vref(vref, &mut peripherals); for part_number in family.part_numbers.iter() { @@ -308,6 +311,7 @@ fn generate_peripherals2( clock_range_hz: None, adc: None, unicomm: None, + uart: None, vref: None, }; @@ -543,6 +547,7 @@ fn generate_missing( clock_range_hz: None, adc: None, unicomm: None, + uart: None, vref: None, }, ); @@ -576,6 +581,7 @@ fn generate_missing( clock_range_hz: None, adc: None, unicomm: None, + uart: None, vref: None, }, ); @@ -617,6 +623,7 @@ fn generate_missing( clock_range_hz: None, adc: None, unicomm: None, + uart: None, vref: None, }); @@ -681,6 +688,7 @@ fn generate_missing( clock_range_hz: None, adc: None, unicomm: None, + uart: None, vref: None, }, ); @@ -1033,6 +1041,7 @@ fn apply_unicomm( clock_range_hz: None, adc: None, unicomm: None, + uart: None, vref: None, }); } @@ -1063,6 +1072,76 @@ fn apply_vref(vref: Option<Vref>, peripherals: &mut BTreeMap<String, Peripheral> } } +/// Attach each UART instance's extended-feature flags from `data/uart`. +fn apply_uart( + family: &PartFamily, + sysconfig: &SysconfigFile, + uarts: Option<&Uarts>, + peripherals: &mut BTreeMap<String, Peripheral>, +) -> anyhow::Result<()> { + for (name, peripheral) in peripherals.iter_mut() { + if !matches!( + peripheral.ty, + PeripheralType::Uart | PeripheralType::UnicommUart + ) { + continue; + } + + // Absent data is a gap verify.rs reports, like the other datasheet extractions. + peripheral.uart = uarts.and_then(|family| family.get(name)).copied(); + } + + let Some(uarts) = uarts else { + return Ok(()); + }; + + // Cross-check against sysconfig, which is per instance and agrees with every datasheet read so + // far, so a disagreement means the table was misread rather than that the two sources describe + // different things. `SYS_UARTADV` marks the legacy extend instances; the UNICOMM hosts state + // the features themselves, with DALI and Manchester folded into one attribute just as the + // MSPM0G5187 table folds them into one row. `SYS_LIN_EN` is deliberately not consulted: it is + // `1` on the main UARTs of mspm0g350x and its siblings, against their datasheet and SVD. + for peripheral in sysconfig.peripherals.values() { + if let Some(adv) = peripheral.attributes.get("SYS_UARTADV") { + let Some(uart) = uarts.get(&peripheral.name) else { + continue; + }; + let extend = uart.lin || uart.dali || uart.irda || uart.iso7816 || uart.manchester; + ensure!( + adv.as_str() == Some(if extend { "true" } else { "false" }), + "{}: data/uart says {} {} extend but sysconfig SYS_UARTADV disagrees", + family.family, + peripheral.name, + if extend { "is" } else { "is not" }, + ); + } + + let checks: [(&str, fn(&Uart) -> bool); 5] = [ + ("SYS_UART_LIN_EN", |uart| uart.lin), + ("SYS_UART_IRDA_EN", |uart| uart.irda), + ("SYS_UART_SMARTCARD_EN", |uart| uart.iso7816), + ("SYS_UART_DALI_MENC_EN", |uart| uart.dali), + ("SYS_UART_DALI_MENC_EN", |uart| uart.manchester), + ]; + for (attribute, check) in checks { + let Some(value) = peripheral.attributes.get(attribute) else { + continue; + }; + let Some(uart) = uarts.get(&format!("{}_UART", peripheral.name)) else { + continue; + }; + ensure!( + value.as_str() == Some(if check(uart) { "true" } else { "false" }), + "{}: data/uart and sysconfig {attribute} disagree about {}_UART", + family.family, + peripheral.name, + ); + } + } + + Ok(()) +} + fn apply_adc( family: &PartFamily, sysconfig: &SysconfigFile, diff --git a/mspm0-data-gen/src/main.rs b/mspm0-data-gen/src/main.rs index 670a74d..d8cd77c 100644 --- a/mspm0-data-gen/src/main.rs +++ b/mspm0-data-gen/src/main.rs @@ -11,6 +11,7 @@ mod sources; mod svd; mod sysconfig; mod timers; +mod uart; mod util; mod verify; mod vref; @@ -78,6 +79,7 @@ fn main() -> anyhow::Result<()> { let int_groups = int_group::parse()?; let operating_modes = operating_modes::parse()?; let timers = timers::parse()?; + let uart = uart::parse()?; let errata = errata::parse()?; let wake = wakeup::parse()?; let vref = vref::parse()?; @@ -95,6 +97,7 @@ fn main() -> anyhow::Result<()> { operating_modes, int_groups, timers, + uart, errata, wake, vref, diff --git a/mspm0-data-gen/src/sources.rs b/mspm0-data-gen/src/sources.rs index ce2059a..f311dc8 100644 --- a/mspm0-data-gen/src/sources.rs +++ b/mspm0-data-gen/src/sources.rs @@ -19,6 +19,7 @@ use crate::{ svd::{Svd, Svds}, sysconfig::{Sysconfig, SysconfigFile}, timers::Timers, + uart::Uarts, }; /// Every source, whole. @@ -32,6 +33,7 @@ pub struct Sources { pub operating_modes: BTreeMap<String, OperatingModes>, pub int_groups: BTreeMap<String, Groups>, pub timers: BTreeMap<String, Timers>, + pub uart: BTreeMap<String, Uarts>, pub errata: BTreeMap<String, Errata>, pub wake: BTreeMap<String, WakeTimes>, pub vref: BTreeMap<String, Vref>, @@ -49,6 +51,7 @@ pub struct FamilySources<'a> { pub clock_tree: Option<&'a ClockTreeFile>, pub operating_modes: Option<&'a OperatingModes>, pub timers: Option<&'a Timers>, + pub uart: Option<&'a Uarts>, pub errata: Option<&'a Errata>, pub wake: Option<WakeTimes>, pub vref: Option<Vref>, @@ -89,6 +92,7 @@ impl Sources { clock_tree: self.clock_trees.files.get(family), operating_modes: self.operating_modes.get(family), timers: self.timers.get(family), + uart: self.uart.get(family), errata: self.errata.get(family), wake: self.wake.get(family).copied(), vref: self.vref.get(family).copied(), diff --git a/mspm0-data-gen/src/uart.rs b/mspm0-data-gen/src/uart.rs new file mode 100644 index 0000000..5b0818c --- /dev/null +++ b/mspm0-data-gen/src/uart.rs @@ -0,0 +1,21 @@ +//! Reads `data/uart/<family>.yaml`, which `tools/uart.py` extracts from the datasheets' "UART +//! Features" tables. +//! +//! The datasheets are the source rather than sysconfig: `SYS_LIN_EN` is `1` on the main UARTs of +//! mspm0g350x and its siblings, which their own datasheet, SVD and driverlib all contradict. +//! sysconfig's `SYS_UARTADV` does agree with every datasheet, and `apply_uart` cross-checks +//! against it. The tool's docs have the details. + +use std::collections::BTreeMap; + +use mspm0_data_types::Uart; + +use crate::util; + +/// One family's mapping: UART instance name (`UART0`, `UC4_UART`) to its features. +pub type Uarts = BTreeMap<String, Uart>; + +/// Read every `data/uart/<family>.yaml`, keyed by family name. +pub fn parse() -> anyhow::Result<BTreeMap<String, Uarts>> { + util::per_family("uart") +} diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index f25c508..ae94c56 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -15,6 +15,7 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { vref_startup_known, adc_channels_known, adc_internal_sources_exist, + uart_features_known, // Peripherals which don't actually exist no_gpamp_c110x_l151x, // Low power data which is only as complete as the data sources @@ -183,6 +184,30 @@ fn adc_internal_sources_exist(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// Every UART instance should say which extended features it has. +/// +/// Every datasheet so far has the "UART Features" table, so an instance without the data means +/// `data/uart/<family>.yaml` is missing or lost the instance. +fn uart_features_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + for peripheral in chip.peripherals.values().filter(|peripheral| { + matches!( + peripheral.ty, + PeripheralType::Uart | PeripheralType::UnicommUart + ) + }) { + if peripheral.uart.is_none() { + bail!( + "{name}: {} has no extended-feature data; data/uart/{}.yaml is missing or lost \ + the instance", + peripheral.name, + chip.family + ); + } + } + + Ok(()) +} + /// Verify all core peripherals are present. fn core_peripherals(chip: &Chip, name: &str) -> anyhow::Result<()> { if !chip.peripherals.contains_key("CPUSS") { diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index c6df1b4..ea6d8c3 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -537,6 +537,12 @@ pub struct Peripheral { #[serde(skip_serializing_if = "Option::is_none")] pub unicomm: Option<Unicomm>, + /// Which extended-UART features this UART implements. + /// + /// `None` for peripherals which are neither a UART nor a UNICOMM UART function. + #[serde(skip_serializing_if = "Option::is_none")] + pub uart: Option<Uart>, + /// The parts of the VREF instance which the `vref_v1` register block does not describe. /// /// `None` for peripherals which are not the VREF. @@ -729,6 +735,36 @@ pub struct Unicomm { pub spi: bool, } +/// Which extended-UART features a UART instance implements. +/// +/// Every legacy UART shares the `uart_v1` register block and every UNICOMM UART function shares +/// `unicommuart_v1`, so the block does not say which instance implements these. The datasheet's +/// "UART Features" table does, read by `tools/uart.py` into `data/uart/`. +/// +/// TI's Extend/Main naming is deliberately not recorded: the UNICOMM UARTs do not nest that way. +/// On MSPM0G518x `UC1` has LIN but no smart card and `UC3` smart card but no LIN, so the features +/// stand alone. A legacy extend instance has all five; a legacy main instance has none. +/// +/// The features do not follow the instance name -- UART1 is main on mspm0l130x and extend on +/// mspm0l122x -- so a consumer must not key them on it. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Uart { + /// LIN mode: the `LINCTL`/`LINCNT`/`LINC0`/`LINC1` registers and their interrupts. + pub lin: bool, + + /// DALI (IEC 62386) support. + pub dali: bool, + + /// IrDA encoding and decoding. + pub irda: bool, + + /// ISO 7816 smart card mode. + pub iso7816: bool, + + /// The Manchester codec (`CTL0.MENC`). + pub manchester: bool, +} + /// An operating mode, ordered from shallowest to deepest. /// /// Ordering is meaningful and is what makes retention comparable: a peripheral retained through diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 688e3bf..9830f4b 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -241,12 +241,45 @@ pub struct Peripheral { /// `None` for peripherals which are not UNICOMM instances. pub unicomm: Option<Unicomm>, + /// Which extended-UART features this UART implements. + /// + /// `None` for peripherals which are neither a UART nor a UNICOMM UART function. + pub uart: Option<Uart>, + /// The parts of the VREF instance which the register block does not describe. /// /// `None` for peripherals which are not the VREF. pub vref: Option<Vref>, } +/// Which extended-UART features a UART instance implements. +/// +/// Every legacy UART shares one register block and every UNICOMM UART function shares another, so +/// the block does not say which instance implements these; the datasheet's "UART Features" table +/// does. TI's Extend/Main naming is deliberately not recorded: the UNICOMM UARTs do not nest that +/// way. On MSPM0G518x `UC1` has LIN but no smart card and `UC3` smart card but no LIN, so the +/// features stand alone. A legacy extend instance has all five; a legacy main instance has none. +/// +/// The features do not follow the instance name โ€” UART1 is main on mspm0l130x and extend on +/// mspm0l122x โ€” so do not key them on it. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct Uart { + /// LIN mode: the `LINCTL`/`LINCNT`/`LINC0`/`LINC1` registers and their interrupts. + pub lin: bool, + + /// DALI (IEC 62386) support. + pub dali: bool, + + /// IrDA encoding and decoding. + pub irda: bool, + + /// ISO 7816 smart card mode. + pub iso7816: bool, + + /// The Manchester codec (`CTL0.MENC`). + pub manchester: bool, +} + /// Which register maps a UNICOMM instance implements. /// /// UNICOMM is one peripheral which is a UART, an SPI, an I2C controller or an I2C target depending diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 79a3b65..524b73e 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -396,6 +396,23 @@ fn generate_peripheral( None => quote! { None }, }; + let uart = match peripheral.uart { + Some(uart) => { + let (lin, dali, irda) = (uart.lin, uart.dali, uart.irda); + let (iso7816, manchester) = (uart.iso7816, uart.manchester); + quote! { + Some(Uart { + lin: #lin, + dali: #dali, + irda: #irda, + iso7816: #iso7816, + manchester: #manchester, + }) + } + } + None => quote! { None }, + }; + let vref = match peripheral.vref.and_then(|vref| vref.startup_ns) { Some(ns) => { let ns = Literal::u32_unsuffixed(ns); @@ -421,6 +438,7 @@ fn generate_peripheral( clock_range_hz: #clock_range_hz, adc: #adc, unicomm: #unicomm, + uart: #uart, vref: #vref, } }) From be6e5e39ab94bb0625022e858e458a54730b9cd2 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 16:31:38 +0100 Subject: [PATCH 087/134] registers: EXCLKSRC position 3 is MFPCLK on the C series too Both C-series headers define EXCLKSRC_MFPCLK = 3 and the MFCLK spelling exists nowhere in the SDK; only the SVDs use it. The MFPCLKEN description carried the same typo. --- data/registers/sysctl_c1105_c1106.yaml | 4 ++-- data/registers/sysctl_c110x.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/registers/sysctl_c1105_c1106.yaml b/data/registers/sysctl_c1105_c1106.yaml index b383011..c508420 100644 --- a/data/registers/sysctl_c1105_c1106.yaml +++ b/data/registers/sysctl_c1105_c1106.yaml @@ -349,7 +349,7 @@ fieldset/GENCLKEN: bit_offset: 0 bit_size: 1 - name: MFPCLKEN - description: Enable the MFCLK. + description: Enable the MFPCLK. bit_offset: 4 bit_size: 1 fieldset/HFCLKCLKCFG: @@ -734,7 +734,7 @@ enum/EXCLKSRC: value: 1 - name: LFCLK value: 2 - - name: MFCLK + - name: MFPCLK description: 'NOTE: This must be divided in post divider.' value: 3 - name: HFCLK diff --git a/data/registers/sysctl_c110x.yaml b/data/registers/sysctl_c110x.yaml index 77dece3..cd506b5 100644 --- a/data/registers/sysctl_c110x.yaml +++ b/data/registers/sysctl_c110x.yaml @@ -299,7 +299,7 @@ fieldset/GENCLKEN: bit_offset: 0 bit_size: 1 - name: MFPCLKEN - description: Enable the MFCLK. + description: Enable the MFPCLK. bit_offset: 4 bit_size: 1 fieldset/HSCLKEN: @@ -595,7 +595,7 @@ enum/EXCLKSRC: value: 1 - name: LFCLK value: 2 - - name: MFCLK + - name: MFPCLK description: 'NOTE: This must be divided in post divider.' value: 3 - name: HFCLK From 7380a36e8b044c029b7c7d5666b114787d7b3029 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 16:31:49 +0100 Subject: [PATCH 088/134] registers: split the G518x SYSCTL from the block it borrowed The borrowed G350x block was missing the whole 0x3000 flash-protection region and described a PMUOPAMP and a CANCLKSRC the part does not have. TI's per-family header and SVD agree the G518x SYSCTL is the G351x layout minus the SRAM bank-1 registers, plus the USB FLL: its control registers, the HSCLK override, the USBFLLERR interrupt, the ready bits, and EXCLKSRC position 6, which the G350x/G351x SVDs leave reserved. Derived from sysctl_g351x_g151x and proven against the G518X SVD by flattening both to offset -> field bit-ranges; the only surviving diffs are the KEY fields every sysctl block here adds. SYSSTATUS bit 24 is NPUREADY as in the header, not TINIEREADY as in the SVD. --- data/registers/sysctl_g518x.yaml | 1809 ++++++++++++++++++++++++++++++ mspm0-data-gen/src/perimap.rs | 7 +- 2 files changed, 1813 insertions(+), 3 deletions(-) create mode 100644 data/registers/sysctl_g518x.yaml diff --git a/data/registers/sysctl_g518x.yaml b/data/registers/sysctl_g518x.yaml new file mode 100644 index 0000000..3cbda64 --- /dev/null +++ b/data/registers/sysctl_g518x.yaml @@ -0,0 +1,1809 @@ +block/SYSCTL: + description: mem_map. + items: + - name: IIDX + description: SYSCTL interrupt index. + byte_offset: 4128 + access: Read + fieldset: IIDX + - name: IMASK + description: SYSCTL interrupt mask. + byte_offset: 4136 + fieldset: INT + - name: RIS + description: SYSCTL raw interrupt status. + byte_offset: 4144 + access: Read + fieldset: INT + - name: MIS + description: SYSCTL masked interrupt status. + byte_offset: 4152 + access: Read + fieldset: INT + - name: ISET + description: SYSCTL interrupt set. + byte_offset: 4160 + access: Write + fieldset: INT + - name: ICLR + description: SYSCTL interrupt clear. + byte_offset: 4168 + access: Write + fieldset: INT + - name: NMIIIDX + description: NMI interrupt index. + byte_offset: 4176 + access: Read + fieldset: NMIIIDX + - name: NMIRIS + description: NMI raw interrupt status. + byte_offset: 4192 + access: Read + fieldset: NMI + - name: NMIISET + description: NMI interrupt set. + byte_offset: 4208 + access: Write + fieldset: NMI + - name: NMIICLR + description: NMI interrupt clear. + byte_offset: 4216 + access: Write + fieldset: NMI + - name: SYSOSCCFG + description: SYSOSC configuration. + byte_offset: 4352 + fieldset: SYSOSCCFG + - name: MCLKCFG + description: Main clock (MCLK) configuration. + byte_offset: 4356 + fieldset: MCLKCFG + - name: HSCLKEN + description: High-speed clock (HSCLK) source enable/disable. + byte_offset: 4360 + fieldset: HSCLKEN + - name: HSCLKCFG + description: High-speed clock (HSCLK) source selection. + byte_offset: 4364 + fieldset: HSCLKCFG + - name: HFCLKCLKCFG + description: High-frequency clock (HFCLK) configuration. + byte_offset: 4368 + fieldset: HFCLKCLKCFG + - name: LFCLKCFG + description: Low frequency crystal oscillator (LFXT) configuration. + byte_offset: 4372 + fieldset: LFCLKCFG + - name: SYSPLLCFG0 + description: SYSPLL reference and output configuration. + byte_offset: 4384 + fieldset: SYSPLLCFG0 + - name: SYSPLLCFG1 + description: SYSPLL reference and feedback divider. + byte_offset: 4388 + fieldset: SYSPLLCFG1 + - name: SYSPLLPARAM0 + description: SYSPLL PARAM0 (load from FACTORY region). + byte_offset: 4392 + fieldset: SYSPLLPARAM0 + - name: SYSPLLPARAM1 + description: SYSPLL PARAM1 (load from FACTORY region). + byte_offset: 4396 + fieldset: SYSPLLPARAM1 + - name: GENCLKCFG + description: General clock configuration. + byte_offset: 4408 + fieldset: GENCLKCFG + - name: GENCLKEN + description: General clock enable control. + byte_offset: 4412 + fieldset: GENCLKEN + - name: PMODECFG + description: Power mode configuration. + byte_offset: 4416 + fieldset: PMODECFG + - name: FCC + description: Frequency clock counter (FCC) count. + byte_offset: 4432 + access: Read + fieldset: FCC + - name: SYSOSCTRIMUSER + description: SYSOSC user-specified trim. + byte_offset: 4464 + fieldset: SYSOSCTRIMUSER + - name: SRAMBOUNDARY + description: SRAM Write Boundary. + byte_offset: 4472 + fieldset: SRAMBOUNDARY + - name: SYSTEMCFG + description: System configuration. + byte_offset: 4480 + fieldset: SYSTEMCFG + - name: WRITELOCK + description: SYSCTL register write lockout. + byte_offset: 4608 + fieldset: WRITELOCK + - name: CLKSTATUS + description: Clock module (CKM) status. + byte_offset: 4612 + access: Read + fieldset: CLKSTATUS + - name: SYSSTATUS + description: System status information. + byte_offset: 4616 + access: Read + fieldset: SYSSTATUS + - name: DEDERRADDR + description: Memory DED Address. + byte_offset: 4620 + access: Read + - name: RSTCAUSE + description: Reset cause. + byte_offset: 4640 + access: Read + fieldset: RSTCAUSE + - name: RESETLEVEL + description: Reset level for application-triggered reset command. + byte_offset: 4864 + fieldset: RESETLEVEL + - name: RESETCMD + description: Execute an application-triggered reset command. + byte_offset: 4868 + access: Write + fieldset: RESETCMD + - name: BORTHRESHOLD + description: BOR threshold selection. + byte_offset: 4872 + fieldset: BORTHRESHOLD + - name: BORCLRCMD + description: Set the BOR threshold. + byte_offset: 4876 + access: Write + fieldset: BORCLRCMD + - name: SYSOSCFCLCTL + description: SYSOSC frequency correction loop (FCL) ROSC enable. + byte_offset: 4880 + access: Write + fieldset: SYSOSCFCLCTL + - name: LFXTCTL + description: LFXT and LFCLK control. + byte_offset: 4884 + access: Write + fieldset: LFXTCTL + - name: EXLFCTL + description: LFCLK_IN and LFCLK control. + byte_offset: 4888 + access: Write + fieldset: EXLFCTL + - name: SHDNIOREL + description: SHUTDOWN IO release control. + byte_offset: 4892 + access: Write + fieldset: SHDNIOREL + - name: EXRSTPIN + description: Disable the reset function of the NRST pin. + byte_offset: 4896 + access: Write + fieldset: EXRSTPIN + - name: SYSSTATUSCLR + description: Clear sticky bits of SYSSTATUS. + byte_offset: 4900 + access: Write + fieldset: SYSSTATUSCLR + - name: SWDCFG + description: Disable the SWD function on the SWD pins. + byte_offset: 4904 + access: Write + fieldset: SWDCFG + - name: FCCCMD + description: Frequency clock counter start capture. + byte_offset: 4908 + access: Write + fieldset: FCCCMD + - name: USBFLLCTL + description: USB FLL control. + byte_offset: 4928 + fieldset: USBFLLCTL + - name: USBFLLADJ + description: USB FLL manual adjustment. + byte_offset: 4932 + fieldset: USBFLLADJ + - name: USBFLLSTAT + description: USB FLL status. + byte_offset: 4936 + access: Read + fieldset: USBFLLSTAT + - name: SHUTDNSTORE + description: Shutdown storage memory (byte 0). + array: + len: 4 + stride: 4 + byte_offset: 5120 + fieldset: SHUTDNSTORE + - name: FWEPROTMAIN + description: 1 Sector Write-Erase per bit starting at address 0x0 of flash. + byte_offset: 12288 + - name: FWPROTMAINDATA + description: Read-Write Protection for first 4 Sectors of Data Bank. + byte_offset: 12308 + fieldset: FWPROTMAINDATA + - name: FRXPROTMAINSTART + description: Flash RX Protection Start Address. + byte_offset: 12312 + fieldset: FPROTADDR + - name: FRXPROTMAINEND + description: Flash RX Protection End Address. + byte_offset: 12316 + fieldset: FPROTADDR + - name: FIPPROTMAINSTART + description: Flash IP Protection Start Address. + byte_offset: 12320 + fieldset: FPROTADDR + - name: FIPPROTMAINEND + description: Flash IP Protection End Address. + byte_offset: 12324 + fieldset: FPROTADDR + - name: FLBANKSWPPOLICY + description: Flash Bank Swap Policy. + byte_offset: 12344 + access: Write + fieldset: FLBANKSWPPOLICY + - name: FLBANKSWP + description: Flash MAIN bank address swap. + byte_offset: 12348 + access: Write + fieldset: FLBANKSWP + - name: FWENABLE + description: Security Firewall Enable Register. + byte_offset: 12356 + access: Write + fieldset: FWENABLE + - name: SECSTATUS + description: Security Configuration status. + byte_offset: 12360 + access: Read + fieldset: SECSTATUS + - name: INITDONE + description: INITCODE PASS. + byte_offset: 12384 + access: Write + fieldset: INITDONE +fieldset/BORCLRCMD: + description: Set the BOR threshold. + fields: + - name: GO + description: GO clears any prior BOR violation status indications and attempts to change the active BOR mode to that specified in the LEVEL field of the BORTHRESHOLD register. + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: BORCLRCMD_KEY +fieldset/BORTHRESHOLD: + description: BOR threshold selection. + fields: + - name: LEVEL + description: LEVEL specifies the desired BOR threshold and BOR mode. + bit_offset: 0 + bit_size: 2 +fieldset/CLKSTATUS: + description: Clock module (CKM) status. + fields: + - name: SYSOSCFREQ + description: SYSOSCFREQ indicates the current SYSOSC operating frequency. + bit_offset: 0 + bit_size: 2 + enum: SYSOSCFREQ + - name: HSCLKMUX + description: HSCLKMUX indicates if MCLK is currently sourced from the high-speed clock (HSCLK). + bit_offset: 4 + bit_size: 1 + enum: HSCLKMUX + - name: LFCLKMUX + description: LFCLKMUX indicates if LFCLK is sourced from the internal LFOSC, the low frequency crystal (LFXT), or the LFCLK_IN digital clock input. + bit_offset: 6 + bit_size: 2 + enum: LFCLKMUX + - name: HFCLKGOOD + description: HFCLKGOOD indicates that the HFCLK started correctly. When the HFXT is started or HFCLK_IN is selected as the HFCLK source, this bit will be set by hardware if a valid HFCLK is detected, and cleared if HFCLK is not operating within the expected range. + bit_offset: 8 + bit_size: 1 + - name: SYSPLLGOOD + description: SYSPLLGOOD indicates if the SYSPLL started correctly. When the SYSPLL is started, SYSPLLGOOD is cleared by hardware. After the startup settling time has expired, the SYSPLL status is tested. If the SYSPLL started successfully the SYSPLLGOOD bit is set, else it is left cleared. + bit_offset: 9 + bit_size: 1 + - name: LFXTGOOD + description: LFXTGOOD indicates if the LFXT started correctly. When the LFXT is started, LFXTGOOD is cleared by hardware. After the startup settling time has expired, the LFXT status is tested. If the LFXT started successfully the LFXTGOOD bit is set, else it is left cleared. + bit_offset: 10 + bit_size: 1 + - name: LFOSCGOOD + description: LFOSCGOOD indicates when the LFOSC startup has completed and the LFOSC is ready for use. + bit_offset: 11 + bit_size: 1 + - name: HSCLKSOFF + description: HSCLKSOFF is set when the high speed clock sources (SYSPLL, HFCLK) are disabled or dead. It is the logical AND of HFCLKOFF and SYSPLLOFF. + bit_offset: 12 + bit_size: 1 + - name: HFCLKOFF + description: HFCLKOFF indicates if the HFCLK is disabled or was dead at startup. When the HFCLK is started, HFCLKOFF is cleared by hardware. Following startup of the HFCLK, if the HFCLK startup monitor determines that the HFCLK was not started correctly, HFCLKOFF is set. + bit_offset: 13 + bit_size: 1 + - name: SYSPLLOFF + description: SYSPLLOFF indicates if the SYSPLL is disabled or was dead at startup. When the SYSPLL is started, SYSPLLOFF is cleared by hardware. Following startup of the SYSPLL, if the SYSPLL startup monitor determines that the SYSPLL was not started correctly, SYSPLLOFF is set. + bit_offset: 14 + bit_size: 1 + - name: CURHSCLKSEL + description: CURHSCLKSEL indicates the current clock source for HSCLK. + bit_offset: 16 + bit_size: 1 + enum: CURHSCLKSEL + - name: CURMCLKSEL + description: CURMCLKSEL indicates if MCLK is currently sourced from LFCLK. + bit_offset: 17 + bit_size: 1 + - name: HSCLKDEAD + description: HSCLKDEAD is set by hardware if the selected source for HSCLK was started but did not start successfully. + bit_offset: 20 + bit_size: 1 + - name: HSCLKGOOD + description: HSCLKGOOD is set by hardware if the selected clock source for HSCLK started successfully. + bit_offset: 21 + bit_size: 1 + - name: LFCLKFAIL + description: LFCLKFAIL indicates when the continous LFCLK monitor detects a LFXT or LFCLK_IN clock stuck failure. + bit_offset: 23 + bit_size: 1 + - name: FCLMODE + description: FCLMODE indicates if the SYSOSC frequency correction loop (FCL) is enabled. + bit_offset: 24 + bit_size: 1 + - name: FCCDONE + description: FCCDONE indicates when a frequency clock counter capture is complete. + bit_offset: 25 + bit_size: 1 + - name: HFCLKBLKUPD + description: HFCLKBLKUPD indicates when writes to the HFCLKCLKCFG register are blocked. + bit_offset: 28 + bit_size: 1 + - name: SYSPLLBLKUPD + description: SYSPLLBLKUPD indicates when writes to SYSPLLCFG0/1 and SYSPLLPARAM0/1 are blocked. + bit_offset: 29 + bit_size: 1 + - name: ANACLKERR + description: ANACLKERR is set when the device clock configuration does not support an enabled analog peripheral mode and the analog peripheral may not be functioning as expected. + bit_offset: 31 + bit_size: 1 +fieldset/EXLFCTL: + description: LFCLK_IN and LFCLK control. + fields: + - name: SETUSEEXLF + description: Set SETUSEEXLF to switch LFCLK to the LFCLK_IN digital clock input. Once set, SETUSEEXLF remains set until the next BOOTRST. + bit_offset: 0 + bit_size: 1 +fieldset/EXRSTPIN: + description: Disable the reset function of the NRST pin. + fields: + - name: DISABLE + description: Set DISABLE to disable the reset function of the NRST pin. Once set, this configuration is locked until the next POR. + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: EXRSTPIN_KEY +fieldset/FCC: + description: Frequency clock counter (FCC) count. + fields: + - name: DATA + description: Frequency clock counter (FCC) count value. + bit_offset: 0 + bit_size: 22 +fieldset/FCCCMD: + description: Frequency clock counter start capture. + fields: + - name: GO + description: Set GO to start a capture with the frequency clock counter (FCC). + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: FCCCMD_KEY +fieldset/FPROTADDR: + description: A flash protection boundary address. + fields: + - name: ADDR + description: The boundary address, at 64 byte granularity. + bit_offset: 6 + bit_size: 16 +fieldset/FLBANKSWP: + description: Flash MAIN bank address swap. + fields: + - name: USEUPPER + description: '1: Use Upper Bank as Logical 0.' + bit_offset: 0 + bit_size: 1 +fieldset/FLBANKSWPPOLICY: + description: Flash Bank Swap Policy. + fields: + - name: DISABLE + description: '1: Disables Policy To Allow Flash Bank Swapping.' + bit_offset: 0 + bit_size: 1 +fieldset/FWENABLE: + description: Security Firewall Enable Register. + fields: + - name: FLRXPROT + description: '1: Flash Read Execute Protection Active.' + bit_offset: 4 + bit_size: 1 + - name: FLIPPROT + description: '1: Flash Read IP ProtectionActive.' + bit_offset: 6 + bit_size: 1 + - name: SRAMBOUNDARYLOCK + description: '1: Blocks Writes from Changing SRAMBOUNDARY MMR.' + bit_offset: 8 + bit_size: 1 +fieldset/FWPROTMAINDATA: + description: Read-Write Protection for first 4 Sectors of Data Bank. + fields: + - name: DATA + description: '00: Both RW allowed 01: Read Only 10: No Read No Write 11: No Read No Write - Not Used.' + bit_offset: 0 + bit_size: 8 +fieldset/GENCLKCFG: + description: General clock configuration. + fields: + - name: EXCLKSRC + description: EXCLKSRC selects the source for the CLK_OUT external clock output block. ULPCLK and MFPCLK require the CLK_OUT divider (EXCLKDIVEN) to be enabled. + bit_offset: 0 + bit_size: 3 + enum: EXCLKSRC + - name: EXCLKDIVVAL + description: EXCLKDIVVAL selects the divider value for the divider in the CLK_OUT external clock output block. + bit_offset: 4 + bit_size: 3 + enum: EXCLKDIVVAL + - name: EXCLKDIVEN + description: EXCLKDIVEN enables or disables the divider function of the CLK_OUT external clock output block. + bit_offset: 7 + bit_size: 1 + - name: USBCLKSRC + description: USBCLKSRC selects the USBCLK source. + bit_offset: 10 + bit_size: 1 + enum: USBCLKSRC + - name: MFPCLKSRC + description: MFPCLKSRC selects the MFPCLK (middle frequency precision clock) source. + bit_offset: 9 + bit_size: 1 + enum: MFPCLKSRC + - name: HFCLK4MFPCLKDIV + description: HFCLK4MFPCLKDIV selects the divider applied to HFCLK when HFCLK is used as the MFPCLK source. Integer dividers from /1 to /16 may be selected. + bit_offset: 12 + bit_size: 4 + enum: HFCLK4MFPCLKDIV + - name: FCCSELCLK + description: FCCSELCLK selectes the frequency clock counter (FCC) clock source. + bit_offset: 16 + bit_size: 4 + enum: FCCSELCLK + - name: FCCTRIGSRC + description: FCCTRIGSRC selects the frequency clock counter (FCC) trigger source. + bit_offset: 20 + bit_size: 1 + enum: FCCTRIGSRC + - name: FCCLVLTRIG + description: FCCLVLTRIG selects the frequency clock counter (FCC) trigger mode. + bit_offset: 21 + bit_size: 1 + enum: FCCLVLTRIG + - name: ANACPUMPCFG + description: ANACPUMPCFG selects the analog mux charge pump (VBOOST) enable method. + bit_offset: 22 + bit_size: 2 + enum: ANACPUMPCFG + - name: FCCTRIGCNT + description: FCCTRIGCNT specifies the number of trigger clock periods in the trigger window. FCCTRIGCNT=0h (one trigger clock period) up to 1Fh (32 trigger clock periods) may be specified. + bit_offset: 24 + bit_size: 5 + - name: FCCLFCLKSRC + description: FCCLFCLKSRC selects between the system LFCLK and an externally sourced LFCLK. + bit_offset: 29 + bit_size: 1 +fieldset/GENCLKEN: + description: General clock enable control. + fields: + - name: EXCLKEN + description: EXCLKEN enables the CLK_OUT external clock output block. + bit_offset: 0 + bit_size: 1 + - name: MFPCLKEN + description: MFPCLKEN enables the middle frequency precision clock (MFPCLK). + bit_offset: 4 + bit_size: 1 +fieldset/HFCLKCLKCFG: + description: High-frequency clock (HFCLK) configuration. + fields: + - name: HFXTTIME + description: HFXT startup time in 64us resolution, from 0 (minimum startup time, approximately zero) to 255 (maximum, approximately 16.32ms). If the HFCLK startup monitor is enabled (HFCLKFLTCHK), HFXT is checked after this time expires. + bit_offset: 0 + bit_size: 8 + - name: HFXTRSEL + description: HFXT Range Select. + bit_offset: 12 + bit_size: 2 + enum: HFXTRSEL + - name: HFCLKFLTCHK + description: HFCLKFLTCHK enables or disables the HFCLK startup monitor. + bit_offset: 28 + bit_size: 1 +fieldset/HSCLKCFG: + description: High-speed clock (HSCLK) source selection. + fields: + - name: HSCLKSEL + description: HSCLKSEL selects the HSCLK source (SYSPLL or HFCLK). + bit_offset: 0 + bit_size: 1 + enum: HSCLKSEL + - name: USBFLL4HSCLK + description: USBFLL4HSCLK selects the USBFLL as the HSCLK source, overriding HSCLKSEL. + bit_offset: 1 + bit_size: 1 + enum: USBFLL4HSCLK +fieldset/HSCLKEN: + description: High-speed clock (HSCLK) source enable/disable. + fields: + - name: HFXTEN + description: HFXTEN enables or disables the high frequency crystal oscillator (HFXT). + bit_offset: 0 + bit_size: 1 + - name: SYSPLLEN + description: SYSPLLEN enables or disables the system phase-lock loop (SYSPLL). + bit_offset: 8 + bit_size: 1 + - name: USEEXTHFCLK + description: USEEXTHFCLK selects the HFCLK_IN digital clock input to be the source for HFCLK. When disabled, HFXT is the HFCLK source and HFXTEN may be set. Do not set HFXTEN and USEEXTHFCLK simultaneously. + bit_offset: 16 + bit_size: 1 +fieldset/IIDX: + description: SYSCTL interrupt index. + fields: + - name: STAT + description: The SYSCTL interrupt index (IIDX) register generates a value corresponding to the highest priority pending interrupt source. This value may be used as an address offset for fast, deterministic handling in the interrupt service routine. A read of the IIDX register will clear the corresponding interrupt status in the RIS and MIS registers. + bit_offset: 0 + bit_size: 4 + enum: IIDX_STAT +fieldset/INITDONE: + description: INITCODE PASS. + fields: + - name: PASS + description: INITCODE writes 1 for PASS, left unwritten a timeout will occur if not blocked. + bit_offset: 0 + bit_size: 1 +fieldset/INT: + description: SYSCTL interrupt clear. + fields: + - name: LFOSCGOOD + description: Clear the LFOSCGOOD interrupt. + bit_offset: 0 + bit_size: 1 + - name: ANACLKERR + description: Analog Clocking Consistency Error. + bit_offset: 1 + bit_size: 1 + - name: FLASHSEC + description: Flash Single Error Correct. + bit_offset: 2 + bit_size: 1 + - name: SRAMSEC + description: SRAM Single Error Correct. + bit_offset: 3 + bit_size: 1 + - name: LFXTGOOD + description: LFXT GOOD. + bit_offset: 4 + bit_size: 1 + - name: HFCLKGOOD + description: HFCLK GOOD. + bit_offset: 5 + bit_size: 1 + - name: SYSPLLGOOD + description: SYSPLL GOOD. + bit_offset: 6 + bit_size: 1 + - name: HSCLKGOOD + description: HSCLK GOOD. + bit_offset: 7 + bit_size: 1 + - name: USBFLLERR + description: USB FLL error. + bit_offset: 8 + bit_size: 1 +fieldset/LFCLKCFG: + description: Low frequency crystal oscillator (LFXT) configuration. + fields: + - name: XT1DRIVE + description: XT1DRIVE selects the low frequency crystal oscillator (LFXT) drive strength. + bit_offset: 0 + bit_size: 2 + enum: XT1DRIVE + - name: MONITOR + description: MONITOR enables or disables the LFCLK monitor, which continuously checks LFXT or LFCLK_IN for a clock stuck fault. + bit_offset: 4 + bit_size: 1 + - name: LOWCAP + description: LOWCAP controls the low-power LFXT mode. When the LFXT load capacitance is less than 3pf, LOWCAP may be set for reduced power consumption. + bit_offset: 8 + bit_size: 1 +fieldset/LFXTCTL: + description: LFXT and LFCLK control. + fields: + - name: STARTLFXT + description: Set STARTLFXT to start the low frequency crystal oscillator (LFXT). Once set, STARTLFXT remains set until the next BOOTRST. + bit_offset: 0 + bit_size: 1 + - name: SETUSELFXT + description: Set SETUSELFXT to switch LFCLK to LFXT. Once set, SETUSELFXT remains set until the next BOOTRST. + bit_offset: 1 + bit_size: 1 +fieldset/MCLKCFG: + description: Main clock (MCLK) configuration. + fields: + - name: MDIV + description: MDIV may be used to divide the MCLK frequency when MCLK is sourced from SYSOSC. MDIV=0h corresponds to /1 (no divider). MDIV=1h corresponds to /2 (divide-by-2). MDIV=Fh corresponds to /16 (divide-by-16). MDIV may be set between /1 and /16 on an integer basis. + bit_offset: 0 + bit_size: 4 + - name: UDIV + description: UDIV specifies the ULPCLK divider when MCLK is sourced from HSCLK. UDIV has no effect when MCLK is sourced from SYSOSC or LFCLK. + bit_offset: 4 + bit_size: 2 + enum: UDIV + - name: FLASHWAIT + description: FLASHWAIT specifies the number of flash wait states when MCLK is sourced from HSCLK. FLASHWAIT has no effect when MCLK is sourced from SYSOSC or LFCLK. + bit_offset: 8 + bit_size: 4 + enum: FLASHWAIT + - name: USEMFTICK + description: USEMFTICK specifies whether the 4MHz constant-rate clock (MFCLK) to peripherals is enabled or disabled. When enabled, MDIV must be disabled (set to 0h=/1). + bit_offset: 12 + bit_size: 1 + - name: USEHSCLK + description: USEHSCLK, together with USELFCLK, sets the MCLK source policy. Set USEHSCLK to use HSCLK (HFCLK or SYSPLL) as the MCLK source in RUN and SLEEP modes. + bit_offset: 16 + bit_size: 1 + - name: USELFCLK + description: USELFCLK sets the MCLK source policy. Set USELFCLK to use LFCLK as the MCLK source. Note that setting USELFCLK does not disable SYSOSC, and SYSOSC remains available for direct use by analog modules. + bit_offset: 20 + bit_size: 1 + - name: STOPCLKSTBY + description: STOPCLKSTBY sets the STANDBY mode policy (STANDBY0 or STANDBY1). When set, ULPCLK and LFCLK are disabled to all peripherals in STANDBY mode, with the exception of TIMG0 and TIMG1 which continue to run. Wake-up is only possible via an asynchronous fast clock request. + bit_offset: 21 + bit_size: 1 + - name: MCLKDEADCHK + description: MCLKDEADCHK enables or disables the continuous MCLK dead check monitor. LFCLK must be running before MCLKDEADCHK is enabled. + bit_offset: 22 + bit_size: 1 +fieldset/NMI: + description: NMI interrupt clear. + fields: + - name: BORLVL + description: Clr the BORLVL NMI. + bit_offset: 0 + bit_size: 1 + - name: WWDT + description: Watch dog fault, one bit per WWDT instance. + bit_offset: 1 + bit_size: 1 + array: + len: 2 + stride: 1 + - name: LFCLKFAIL + description: LFXT-EXLF Monitor Fail. + bit_offset: 3 + bit_size: 1 + - name: FLASHDED + description: Flash Double Error Detect. + bit_offset: 4 + bit_size: 1 + - name: SRAMDED + description: SRAM Double Error Detect. + bit_offset: 5 + bit_size: 1 +fieldset/NMIIIDX: + description: NMI interrupt index. + fields: + - name: STAT + description: The NMI interrupt index (NMIIIDX) register generates a value corresponding to the highest priority pending NMI source. This value may be used as an address offset for fast, deterministic handling in the NMI service routine. A read of the NMIIIDX register will clear the corresponding interrupt status in the NMIRIS register. + bit_offset: 0 + bit_size: 4 + enum: NMIIIDX_STAT +fieldset/PMODECFG: + description: Power mode configuration. + fields: + - name: DSLEEP + description: DSLEEP selects the operating mode to enter upon a DEEPSLEEP request from the CPU. + bit_offset: 0 + bit_size: 2 + enum: DSLEEP +fieldset/RESETCMD: + description: Execute an application-triggered reset command. + fields: + - name: GO + description: Execute the reset specified in RESETLEVEL.LEVEL. Must be written together with the KEY. + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: RESETCMD_KEY +fieldset/RESETLEVEL: + description: Reset level for application-triggered reset command. + fields: + - name: LEVEL + description: LEVEL is used to specify the type of reset to be issued when RESETCMD is set to generate a software triggered reset. + bit_offset: 0 + bit_size: 3 + enum: RESETLEVEL_LEVEL +fieldset/RSTCAUSE: + description: Reset cause. + fields: + - name: ID + description: ID is a read-to-clear field which indicates the lowest level reset cause since the last read. + bit_offset: 0 + bit_size: 5 + enum: ID +fieldset/SECSTATUS: + description: Security Configuration status. + fields: + - name: INITDONE + description: '1: CSC has been completed.' + bit_offset: 0 + bit_size: 1 + - name: CSCEXISTS + description: '1: CSC Exists in the system.' + bit_offset: 2 + bit_size: 1 + - name: FLRXPROT + description: '1: Flash Read Execute Protection Active.' + bit_offset: 4 + bit_size: 1 + - name: FLIPPROT + description: '1: Flash IP Protection Active.' + bit_offset: 6 + bit_size: 1 + - name: SRAMBOUNDARYLOCK + description: '1: SRAM Boundary MMR Locked.' + bit_offset: 8 + bit_size: 1 + - name: FLBANKSWPPOLICY + description: '1: Upper and Lower Banks allowed to be swapped.' + bit_offset: 10 + bit_size: 1 + - name: FLBANKSWP + description: '1: Upper and Lower Banks have been swapped.' + bit_offset: 12 + bit_size: 1 +fieldset/SHDNIOREL: + description: SHUTDOWN IO release control. + fields: + - name: RELEASE + description: Set RELEASE to release the IO after a SHUTDOWN mode exit. + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: SHDNIOREL_KEY +fieldset/SHUTDNSTORE: + description: Shutdown storage memory (byte 0). + fields: + - name: DATA + description: Shutdown storage byte 0. + bit_offset: 0 + bit_size: 8 +fieldset/SRAMBOUNDARY: + description: SRAM Write Boundary. + fields: + - name: ADDR + description: 'SRAM boundary configuration. The value configured into this acts such that: SRAM accesses to addresses less than or equal value will be RW only. SRAM accesses to addresses greater than value will be RX only. Value of 0 is not valid (system will have no stack). If set to 0, the system acts as if the entire SRAM is RWX. Any non-zero value can be configured, including a value = SRAM size.' + bit_offset: 5 + bit_size: 15 +fieldset/SWDCFG: + description: Disable the SWD function on the SWD pins. + fields: + - name: DISABLE + description: Set DISABLE to disable the SWD function on SWD pins, allowing the SWD pins to be used as GPIO. + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: SWDCFG_KEY +fieldset/SYSOSCCFG: + description: SYSOSC configuration. + fields: + - name: FREQ + description: Target operating frequency for the system oscillator (SYSOSC). + bit_offset: 0 + bit_size: 2 + enum: SYSOSCCFG_FREQ + - name: USE4MHZSTOP + description: USE4MHZSTOP sets the SYSOSC stop mode frequency policy. When entering STOP mode, the SYSOSC frequency may be automatically switched to 4MHz to reduce SYSOSC power consumption. + bit_offset: 8 + bit_size: 1 + - name: DISABLESTOP + description: DISABLESTOP sets the SYSOSC stop mode enable/disable policy. When operating in STOP mode, the SYSOSC may be automatically disabled. When set, ULPCLK will run from LFCLK in STOP mode and SYSOSC will be disabled to reduce power consumption. + bit_offset: 9 + bit_size: 1 + - name: DISABLE + description: DISABLE sets the SYSOSC enable/disable policy. SYSOSC may be powered off in RUN, SLEEP, and STOP modes to reduce power consumption. When SYSOSC is disabled, MCLK and ULPCLK are sourced from LFCLK. + bit_offset: 10 + bit_size: 1 + - name: BLOCKASYNCALL + description: BLOCKASYNCALL may be used to mask block all asynchronous fast clock requests, preventing hardware from dynamically changing the active clock configuration when operating in a given mode. + bit_offset: 16 + bit_size: 1 + - name: FASTCPUEVENT + description: FASTCPUEVENT may be used to assert a fast clock request when an interrupt is asserted to the CPU, reducing interrupt latency. + bit_offset: 17 + bit_size: 1 +fieldset/SYSOSCFCLCTL: + description: SYSOSC frequency correction loop (FCL) ROSC enable. + fields: + - name: SETUSEFCL + description: Set SETUSEFCL to enable the frequency correction loop in SYSOSC. Once enabled, this state is locked until the next BOOTRST. + bit_offset: 0 + bit_size: 1 + - name: SETUSEEXRES + description: Set SETUSEEXRES to specify that an external resistor will be used for the FCL. An appropriate resistor must be populated on the ROSC pin. This state is locked until the next BOOTRST. + bit_offset: 1 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: SYSOSCFCLCTL_KEY +fieldset/SYSOSCTRIMUSER: + description: SYSOSC user-specified trim. + fields: + - name: FREQ + description: FREQ specifies the target user-trimmed frequency for SYSOSC. + bit_offset: 0 + bit_size: 2 + enum: SYSOSCTRIMUSER_FREQ + - name: CAP + description: CAP specifies the SYSOSC capacitor trim. This value changes with the target frequency. + bit_offset: 4 + bit_size: 3 + - name: RESCOARSE + description: RESCOARSE specifies the resister coarse trim. This value changes with the target frequency. + bit_offset: 8 + bit_size: 6 + - name: RESFINE + description: RESFINE specifies the resister fine trim. This value changes with the target frequency. + bit_offset: 16 + bit_size: 4 + - name: RDIV + description: RDIV specifies the frequency correction loop (FCL) resistor trim. This value changes with the target frequency. + bit_offset: 20 + bit_size: 9 +fieldset/SYSPLLCFG0: + description: SYSPLL reference and output configuration. + fields: + - name: SYSPLLREF + description: SYSPLLREF selects the system PLL (SYSPLL) reference clock source. + bit_offset: 0 + bit_size: 1 + enum: SYSPLLREF + - name: MCLK2XVCO + description: MCLK2XVCO selects the SYSPLL output which is sent to the HSCLK mux for use by MCLK. + bit_offset: 1 + bit_size: 1 + - name: ENABLECLK0 + description: ENABLECLK0 enables or disables the SYSPLLCLK0 output. + bit_offset: 4 + bit_size: 1 + - name: ENABLECLK1 + description: ENABLECLK1 enables or disables the SYSPLLCLK1 output. + bit_offset: 5 + bit_size: 1 + - name: ENABLECLK2X + description: ENABLECLK2X enables or disables the SYSPLLCLK2X output. + bit_offset: 6 + bit_size: 1 + - name: RDIVCLK0 + description: RDIVCLK0 sets the final divider for the SYSPLLCLK0 output. + bit_offset: 8 + bit_size: 4 + enum: RDIVCLK0 + - name: RDIVCLK1 + description: RDIVCLK1 sets the final divider for the SYSPLLCLK1 output. + bit_offset: 12 + bit_size: 4 + enum: RDIVCLK1 + - name: RDIVCLK2X + description: RDIVCLK2X sets the final divider for the SYSPLLCLK2X output. + bit_offset: 16 + bit_size: 4 + enum: RDIVCLK2X +fieldset/SYSPLLCFG1: + description: SYSPLL reference and feedback divider. + fields: + - name: PDIV + description: PDIV selects the SYSPLL reference clock prescale divider. + bit_offset: 0 + bit_size: 2 + enum: PDIV + - name: QDIV + description: QDIV selects the SYSPLL feedback path divider. + bit_offset: 8 + bit_size: 7 + enum: QDIV +fieldset/SYSPLLPARAM0: + description: SYSPLL PARAM0 (load from FACTORY region). + fields: + - name: STARTTIME + description: Startup time from enable to locked clock, in 1us resolution. + bit_offset: 0 + bit_size: 6 + - name: STARTTIMELP + description: Startup time from low power mode exit to locked clock, in 1us resolution. + bit_offset: 8 + bit_size: 6 + - name: CPCURRENT + description: Charge pump current. + bit_offset: 16 + bit_size: 6 + - name: CAPBVAL + description: Override value for Cap B. + bit_offset: 24 + bit_size: 5 + - name: CAPBOVERRIDE + description: CAPBOVERRIDE controls the override for Cap B. + bit_offset: 31 + bit_size: 1 +fieldset/SYSPLLPARAM1: + description: SYSPLL PARAM1 (load from FACTORY region). + fields: + - name: LPFCAPA + description: Loop filter Cap A. + bit_offset: 0 + bit_size: 5 + - name: LPFRESA + description: Loop filter Res A. + bit_offset: 8 + bit_size: 10 + - name: LPFRESC + description: Loop filter Res C. + bit_offset: 24 + bit_size: 8 +fieldset/SYSSTATUS: + description: System status information. + fields: + - name: FLASHDED + description: FLASHDED indicates if a flash ECC double bit error was detected (DED). + bit_offset: 0 + bit_size: 1 + - name: FLASHSEC + description: FLASHSEC indicates if a flash ECC single bit error was detected and corrected (SEC). + bit_offset: 1 + bit_size: 1 + - name: BORCURTHRESHOLD + description: BORCURTHRESHOLD indicates the active brown-out reset supply monitor configuration. + bit_offset: 2 + bit_size: 2 + enum: BORCURTHRESHOLD + - name: BORLVL + description: BORLVL indicates if a BOR event occured and the BOR threshold was switched to BOR0 by hardware. + bit_offset: 4 + bit_size: 1 + - name: ANACPUMPGOOD + description: ANACPUMPGOOD is set by hardware when the VBOOST analog mux charge pump is ready. + bit_offset: 5 + bit_size: 1 + - name: PMUIREFGOOD + description: PMUIREFGOOD is set by hardware when the PMU current reference is ready. + bit_offset: 6 + bit_size: 1 + - name: EXTRSTPINDIS + description: EXTRSTPINDIS indicates when user has disabled the use of external reset pin. + bit_offset: 12 + bit_size: 1 + - name: SWDCFGDIS + description: SWDCFGDIS indicates when user has disabled the use of SWD Port. + bit_offset: 13 + bit_size: 1 + - name: SHDNIOLOCK + description: SHDNIOLOCK indicates when IO is locked due to SHUTDOWN. + bit_offset: 14 + bit_size: 1 + - name: NPUREADY + description: NPUREADY indicates when the NPU is ready. + bit_offset: 24 + bit_size: 1 + - name: USBFS0READY + description: USBFS0READY indicates when the USBFS0 peripheral is ready. + bit_offset: 25 + bit_size: 1 + - name: REBOOTATTEMPTS + description: REBOOTATTEMPTS indicates the number of boot attempts taken before the user application starts. + bit_offset: 30 + bit_size: 2 +fieldset/SYSSTATUSCLR: + description: Clear sticky bits of SYSSTATUS. + fields: + - name: ALLECC + description: Set ALLECC to clear all ECC related SYSSTATUS indicators. + bit_offset: 0 + bit_size: 1 + - name: KEY + bit_offset: 24 + bit_size: 8 + enum: SYSSTATUSCLR_KEY +fieldset/SYSTEMCFG: + description: System configuration. + fields: + - name: WWDTLP0RSTDIS + description: WWDTLP0RSTDIS specifies whether a WWDT Error Event will trigger a BOOTRST or an NMI. + bit_offset: 0 + bit_size: 1 + - name: WWDTLP1RSTDIS + description: WWDTLP1RSTDIS specifies whether a WWDT Error Event will trigger a SYSRST or an NMI. + bit_offset: 1 + bit_size: 1 + - name: FLASHECCRSTDIS + description: FLASHECCRSTDIS specifies whether a flash ECC double error detect (DED) will trigger a SYSRST or an NMI. + bit_offset: 2 + bit_size: 1 +fieldset/USBFLLADJ: + description: USB FLL manual adjustment. + fields: + - name: FRQADJ + description: Frequency lock loop adjust. + bit_offset: 0 + bit_size: 6 + - name: FRQMOD + description: Frequency lock loop modulator value. + bit_offset: 8 + bit_size: 3 +fieldset/USBFLLCTL: + description: USB FLL control. + fields: + - name: CLKEN + description: USB oscillator enable. + bit_offset: 0 + bit_size: 1 + - name: FLLEN + description: USB oscillator frequency lock loop enable. + bit_offset: 1 + bit_size: 1 + - name: REFSEL + description: Reference clock selection for the frequency lock loop. + bit_offset: 8 + bit_size: 1 + enum: REFSEL +fieldset/USBFLLSTAT: + description: USB FLL status. + fields: + - name: CLKRDY + description: USB oscillator ready indication. + bit_offset: 0 + bit_size: 1 + - name: FLLLOCK + description: USB oscillator frequency lock loop lock indication. + bit_offset: 1 + bit_size: 1 + - name: REFRDY + description: Reference clock for the frequency lock loop ready indication. + bit_offset: 8 + bit_size: 1 + - name: FRQADJ + description: Current frequency lock loop adjust. + bit_offset: 16 + bit_size: 6 + - name: FRQMOD + description: Current frequency lock loop modulator value. + bit_offset: 24 + bit_size: 3 +fieldset/WRITELOCK: + description: SYSCTL register write lockout. + fields: + - name: ACTIVE + description: ACTIVE controls whether critical SYSCTL registers are write protected or not. + bit_offset: 0 + bit_size: 1 +enum/ANACPUMPCFG: + bit_size: 2 + variants: + - name: ONDEMAND + description: VBOOST is enabled on request from a COMP, GPAMP, or OPA. + value: 0 + - name: ONACTIVE + description: VBOOST is enabled when the device is in RUN or SLEEP mode, or when a COMP/GPAMP/OPA is enabled. + value: 1 + - name: ONALWAYS + description: VBOOST is always enabled. + value: 2 +enum/BORCLRCMD_KEY: + bit_size: 8 + variants: + - name: KEY + value: 199 +enum/BORCURTHRESHOLD: + bit_size: 2 + variants: + - name: BORMIN + description: Default minimum threshold; a BOR0- violation triggers a BOR. + value: 0 + - name: BORLEVEL1 + description: A BOR1- violation generates a BORLVL interrupt. + value: 1 + - name: BORLEVEL2 + description: A BOR2- violation generates a BORLVL interrupt. + value: 2 + - name: BORLEVEL3 + description: A BOR3- violation generates a BORLVL interrupt. + value: 3 +enum/CURHSCLKSEL: + bit_size: 1 + variants: + - name: SYSPLL + description: HSCLK is currently sourced from the SYSPLL. + value: 0 + - name: HFCLK + description: HSCLK is currently sourced from the HFCLK. + value: 1 +enum/DSLEEP: + bit_size: 2 + variants: + - name: STOP + description: STOP mode is entered. + value: 0 + - name: STANDBY + description: STANDBY mode is entered. + value: 1 + - name: SHUTDOWN + description: SHUTDOWN mode is entered. + value: 2 +enum/EXCLKDIVVAL: + bit_size: 3 + variants: + - name: DIV2 + description: CLK_OUT source is divided by 2. + value: 0 + - name: DIV4 + description: CLK_OUT source is divided by 4. + value: 1 + - name: DIV6 + description: CLK_OUT source is divided by 6. + value: 2 + - name: DIV8 + description: CLK_OUT source is divided by 8. + value: 3 + - name: DIV10 + description: CLK_OUT source is divided by 10. + value: 4 + - name: DIV12 + description: CLK_OUT source is divided by 12. + value: 5 + - name: DIV14 + description: CLK_OUT source is divided by 14. + value: 6 + - name: DIV16 + description: CLK_OUT source is divided by 16. + value: 7 +enum/EXCLKSRC: + bit_size: 3 + variants: + - name: SYSOSC + description: CLK_OUT is SYSOSC. + value: 0 + - name: ULPCLK + description: CLK_OUT is ULPCLK (EXCLKDIVEN must be enabled). + value: 1 + - name: LFCLK + description: CLK_OUT is LFCLK. + value: 2 + - name: MFPCLK + description: CLK_OUT is MFPCLK (EXCLKDIVEN must be enabled). + value: 3 + - name: HFCLK + description: CLK_OUT is HFCLK. + value: 4 + - name: SYSPLLOUT1 + description: CLK_OUT is SYSPLLCLK1 (SYSPLLCLK1 must be <=48MHz). + value: 5 + - name: USBFLL + description: CLK_OUT is USBFLL (at 60MHz it must be divided for external output). + value: 6 +enum/EXRSTPIN_KEY: + bit_size: 8 + variants: + - name: KEY + value: 30 +enum/FCCCMD_KEY: + bit_size: 8 + variants: + - name: KEY + value: 14 +enum/FCCLVLTRIG: + bit_size: 1 + variants: + - name: RISE2RISE + description: Rising edge to rising edge triggered. + value: 0 + - name: LEVEL + description: Level triggered. + value: 1 +enum/FCCSELCLK: + bit_size: 4 + variants: + - name: MCLK + description: FCC clock is MCLK. + value: 0 + - name: SYSOSC + description: FCC clock is SYSOSC. + value: 1 + - name: HFCLK + description: FCC clock is HFCLK. + value: 2 + - name: EXTCLK + description: FCC clock is the CLK_OUT selection. + value: 3 + - name: SYSPLLCLK0 + description: FCC clock is SYSPLLCLK0. + value: 4 + - name: SYSPLLCLK1 + description: FCC clock is SYSPLLCLK1. + value: 5 + - name: SYSPLLCLK2X + description: FCC clock is SYSPLLCLK2X. + value: 6 + - name: FCCIN + description: FCC clock is the FCCIN external input. + value: 7 +enum/FCCTRIGSRC: + bit_size: 1 + variants: + - name: EXTPIN + description: FCC trigger is the external pin. + value: 0 + - name: LFCLK + description: FCC trigger is the LFCLK. + value: 1 +enum/FLASHWAIT: + bit_size: 4 + variants: + - name: WAIT0 + description: No flash wait states are applied. + value: 0 + - name: WAIT1 + description: One flash wait state is applied. + value: 1 + - name: WAIT2 + description: 2 flash wait states are applied. + value: 2 +enum/HFCLK4MFPCLKDIV: + bit_size: 4 + variants: + - name: DIV1 + description: HFCLK is not divided before being used for MFPCLK. + value: 0 + - name: DIV2 + description: HFCLK is divided by 2 before being used for MFPCLK. + value: 1 + - name: DIV3 + description: HFCLK is divided by 3 before being used for MFPCLK. + value: 2 + - name: DIV4 + description: HFCLK is divided by 4 before being used for MFPCLK. + value: 3 + - name: DIV5 + description: HFCLK is divided by 5 before being used for MFPCLK. + value: 4 + - name: DIV6 + description: HFCLK is divided by 6 before being used for MFPCLK. + value: 5 + - name: DIV7 + description: HFCLK is divided by 7 before being used for MFPCLK. + value: 6 + - name: DIV8 + description: HFCLK is divided by 8 before being used for MFPCLK. + value: 7 + - name: DIV9 + description: HFCLK is divided by 9 before being used for MFPCLK. + value: 8 + - name: DIV10 + description: HFCLK is divided by 10 before being used for MFPCLK. + value: 9 + - name: DIV11 + description: HFCLK is divided by 11 before being used for MFPCLK. + value: 10 + - name: DIV12 + description: HFCLK is divided by 12 before being used for MFPCLK. + value: 11 + - name: DIV13 + description: HFCLK is divided by 13 before being used for MFPCLK. + value: 12 + - name: DIV14 + description: HFCLK is divided by 14 before being used for MFPCLK. + value: 13 + - name: DIV15 + description: HFCLK is divided by 15 before being used for MFPCLK. + value: 14 + - name: DIV16 + description: HFCLK is divided by 16 before being used for MFPCLK. + value: 15 +enum/HFXTRSEL: + bit_size: 2 + variants: + - name: RANGE4TO8 + description: 4MHz <= HFXT frequency <= 8MHz. + value: 0 + - name: RANGE8TO16 + description: 8MHz < HFXT frequency <= 16MHz. + value: 1 + - name: RANGE16TO32 + description: 16MHz < HFXT frequency <= 32MHz. + value: 2 + - name: RANGE32TO48 + description: 32MHz < HFXT frequency <= 48MHz. + value: 3 +enum/HSCLKMUX: + bit_size: 1 + variants: + - name: SYSOSC + description: MCLK is not sourced from HSCLK. + value: 0 + - name: HSCLK + description: MCLK is sourced from HSCLK. + value: 1 +enum/HSCLKSEL: + bit_size: 1 + variants: + - name: SYSPLL + description: HSCLK is sourced from the SYSPLL. + value: 0 + - name: HFCLKCLK + description: HSCLK is sourced from the HFCLK. + value: 1 +enum/ID: + bit_size: 5 + variants: + - name: NORST + description: No reset since last read. + value: 0 + - name: PORHWFAIL + description: POR- violation, SHUTDNSTOREx or PMU trim parity fault. + value: 1 + - name: POREXNRST + description: NRST triggered POR (>1s hold). + value: 2 + - name: PORSW + description: Software triggered POR. + value: 3 + - name: BORSUPPLY + description: BOR0- violation. + value: 4 + - name: BORWAKESHUTDN + description: SHUTDOWN mode exit. + value: 5 + - name: BOOTCLKFAIL + description: Fatal clock failure. + value: 9 + - name: BOOTEXNRST + description: NRST triggered BOOTRST (<1s hold). + value: 12 + - name: BOOTSW + description: Software triggered BOOTRST. + value: 13 + - name: BOOTWWDT0 + description: WWDT0 violation. + value: 14 + - name: SYSBSLEXIT + description: BSL exit. + value: 16 + - name: SYSBSLENTRY + description: BSL entry. + value: 17 + - name: SYSWWDT1 + description: WWDT1 violation. + value: 19 + - name: SYSCPULOCK + description: CPULOCK violation. + value: 21 + - name: SYSDBG + description: Debug triggered SYSRST. + value: 26 + - name: SYSSW + description: Software triggered SYSRST. + value: 27 + - name: CPUDBG + description: Debug triggered CPURST. + value: 28 + - name: CPUSW + description: Software triggered CPURST. + value: 29 +enum/IIDX_STAT: + bit_size: 4 + variants: + - name: NO_INTR + description: No interrupt pending. + value: 0 + - name: LFOSCGOOD + description: LFOSCGOOD interrupt pending. + value: 1 + - name: ANACLKERR + value: 2 + - name: FLASHSEC + value: 3 + - name: SRAMSEC + value: 4 + - name: LFXTGOOD + value: 5 + - name: HFCLKGOOD + value: 6 + - name: SYSPLLGOOD + value: 7 + - name: HSCLKGOOD + value: 8 +enum/LFCLKMUX: + bit_size: 2 + variants: + - name: LFOSC + description: LFCLK is sourced from the internal LFOSC. + value: 0 + - name: LFXT + description: LFCLK is sourced from the LFXT (crystal). + value: 1 + - name: EXLF + description: LFCLK is sourced from LFCLK_IN (external digital clock input). + value: 2 +enum/MFPCLKSRC: + bit_size: 1 + variants: + - name: SYSOSC + description: MFPCLK is sourced from SYSOSC. + value: 0 + - name: HFCLK + description: MFPCLK is sourced from HFCLK. + value: 1 +enum/NMIIIDX_STAT: + bit_size: 4 + variants: + - name: NO_INTR + description: No NMI pending. + value: 0 + - name: BORLVL + description: BOR Threshold NMI pending. + value: 1 + - name: WWDT0 + value: 2 + - name: WWDT1 + value: 3 + - name: LFCLKFAIL + value: 4 + - name: FLASHDED + value: 5 + - name: SRAMDED + value: 6 +enum/PDIV: + bit_size: 2 + variants: + - name: REFDIV1 + description: SYSPLLREF is not divided. + value: 0 + - name: REFDIV2 + description: SYSPLLREF is divided by 2. + value: 1 + - name: REFDIV4 + description: SYSPLLREF is divided by 4. + value: 2 + - name: REFDIV8 + description: SYSPLLREF is divided by 8. + value: 3 +enum/QDIV: + bit_size: 7 + variants: + - name: INVALID + description: Divide-by-one is not a valid QDIV option. + value: 0 + - name: QDIVMIN + description: Feedback path is divided by 2. + value: 1 + - name: QDIVMAX + description: Feedback path is divided by 127 (0x7E). + value: 126 +enum/RDIVCLK0: + bit_size: 4 + variants: + - name: CLK0DIV2 + description: SYSPLLCLK0 is divided by 2. + value: 0 + - name: CLK0DIV4 + description: SYSPLLCLK0 is divided by 4. + value: 1 + - name: CLK0DIV6 + description: SYSPLLCLK0 is divided by 6. + value: 2 + - name: CLK0DIV8 + description: SYSPLLCLK0 is divided by 8. + value: 3 + - name: CLK0DIV10 + description: SYSPLLCLK0 is divided by 10. + value: 4 + - name: CLK0DIV12 + description: SYSPLLCLK0 is divided by 12. + value: 5 + - name: CLK0DIV14 + description: SYSPLLCLK0 is divided by 14. + value: 6 + - name: CLK0DIV16 + description: SYSPLLCLK0 is divided by 16. + value: 7 + - name: CLK0DIV18 + description: SYSPLLCLK0 is divided by 18. + value: 8 + - name: CLK0DIV20 + description: SYSPLLCLK0 is divided by 20. + value: 9 + - name: CLK0DIV22 + description: SYSPLLCLK0 is divided by 22. + value: 10 + - name: CLK0DIV24 + description: SYSPLLCLK0 is divided by 24. + value: 11 + - name: CLK0DIV26 + description: SYSPLLCLK0 is divided by 26. + value: 12 + - name: CLK0DIV28 + description: SYSPLLCLK0 is divided by 28. + value: 13 + - name: CLK0DIV30 + description: SYSPLLCLK0 is divided by 30. + value: 14 + - name: CLK0DIV32 + description: SYSPLLCLK0 is divided by 32. + value: 15 +enum/RDIVCLK1: + bit_size: 4 + variants: + - name: CLK1DIV2 + description: SYSPLLCLK1 is divided by 2. + value: 0 + - name: CLK1DIV4 + description: SYSPLLCLK1 is divided by 4. + value: 1 + - name: CLK1DIV6 + description: SYSPLLCLK1 is divided by 6. + value: 2 + - name: CLK1DIV8 + description: SYSPLLCLK1 is divided by 8. + value: 3 + - name: CLK1DIV10 + description: SYSPLLCLK1 is divided by 10. + value: 4 + - name: CLK1DIV12 + description: SYSPLLCLK1 is divided by 12. + value: 5 + - name: CLK1DIV14 + description: SYSPLLCLK1 is divided by 14. + value: 6 + - name: CLK1DIV16 + description: SYSPLLCLK1 is divided by 16. + value: 7 + - name: CLK1DIV18 + description: SYSPLLCLK1 is divided by 18. + value: 8 + - name: CLK1DIV20 + description: SYSPLLCLK1 is divided by 20. + value: 9 + - name: CLK1DIV22 + description: SYSPLLCLK1 is divided by 22. + value: 10 + - name: CLK1DIV24 + description: SYSPLLCLK1 is divided by 24. + value: 11 + - name: CLK1DIV26 + description: SYSPLLCLK1 is divided by 26. + value: 12 + - name: CLK1DIV28 + description: SYSPLLCLK1 is divided by 28. + value: 13 + - name: CLK1DIV30 + description: SYSPLLCLK1 is divided by 30. + value: 14 + - name: CLK1DIV32 + description: SYSPLLCLK1 is divided by 32. + value: 15 +enum/RDIVCLK2X: + bit_size: 4 + variants: + - name: CLK2XDIV1 + description: SYSPLLCLK1 is divided by 1. + value: 0 + - name: CLK2XDIV2 + description: SYSPLLCLK1 is divided by 2. + value: 1 + - name: CLK2XDIV3 + description: SYSPLLCLK1 is divided by 3. + value: 2 + - name: CLK2XDIV4 + description: SYSPLLCLK1 is divided by 4. + value: 3 + - name: CLK2XDIV5 + description: SYSPLLCLK1 is divided by 5. + value: 4 + - name: CLK2XDIV6 + description: SYSPLLCLK1 is divided by 6. + value: 5 + - name: CLK2XDIV7 + description: SYSPLLCLK1 is divided by 7. + value: 6 + - name: CLK2XDIV8 + description: SYSPLLCLK1 is divided by 8. + value: 7 + - name: CLK2XDIV9 + description: SYSPLLCLK1 is divided by 9. + value: 8 + - name: CLK2XDIV10 + description: SYSPLLCLK1 is divided by 10. + value: 9 + - name: CLK2XDIV11 + description: SYSPLLCLK1 is divided by 11. + value: 10 + - name: CLK2XDIV12 + description: SYSPLLCLK1 is divided by 12. + value: 11 + - name: CLK2XDIV13 + description: SYSPLLCLK1 is divided by 13. + value: 12 + - name: CLK2XDIV14 + description: SYSPLLCLK1 is divided by 14. + value: 13 + - name: CLK2XDIV15 + description: SYSPLLCLK1 is divided by 15. + value: 14 + - name: CLK2XDIV16 + description: SYSPLLCLK1 is divided by 16. + value: 15 +enum/RESETCMD_KEY: + bit_size: 8 + variants: + - name: KEY + value: 228 +enum/RESETLEVEL_LEVEL: + bit_size: 3 + variants: + - name: CPU + description: Issue a SYSRST (CPU plus peripherals only). + value: 0 + - name: BOOT + description: Issue a BOOTRST (CPU, peripherals, and boot configuration routine). + value: 1 + - name: BOOTLOADERENTRY + description: Issue a SYSRST and enter the boot strap loader (BSL). + value: 2 + - name: POR + description: Issue a power-on reset (POR). + value: 3 + - name: BOOTLOADEREXIT + description: Issue a SYSRST and exit the boot strap loader (BSL). + value: 4 +enum/REFSEL: + bit_size: 1 + variants: + - name: USBSOF + description: Use the USB start-of-frame as the reference clock. + value: 0 + - name: LFCLK + description: Use LFCLK as the reference clock. + value: 1 +enum/SHDNIOREL_KEY: + bit_size: 8 + variants: + - name: KEY + value: 145 +enum/SWDCFG_KEY: + bit_size: 8 + variants: + - name: KEY + value: 98 +enum/SYSOSCCFG_FREQ: + bit_size: 2 + variants: + - name: SYSOSCBASE + description: Base frequency (32MHz). + value: 0 + - name: SYSOSC4M + description: Low frequency (4MHz). + value: 1 + - name: SYSOSCUSER + description: User-trimmed frequency (16 or 24 MHz). + value: 2 +enum/SYSOSCFCLCTL_KEY: + bit_size: 8 + variants: + - name: KEY + value: 42 +enum/SYSOSCFREQ: + bit_size: 2 + variants: + - name: SYSOSC32M + description: SYSOSC is at base frequency (32MHz). + value: 0 + - name: SYSOSC4M + description: SYSOSC is at low frequency (4MHz). + value: 1 + - name: SYSOSCUSER + description: SYSOSC is at the user-trimmed frequency (16 or 24MHz). + value: 2 + - name: SYSOSCTURBO + description: Reserved. + value: 3 +enum/SYSOSCTRIMUSER_FREQ: + bit_size: 2 + variants: + - name: SYSOSC16M + description: 16MHz user frequency. + value: 1 + - name: SYSOSC24M + description: 24MHz user frequency. + value: 2 +enum/SYSPLLREF: + bit_size: 1 + variants: + - name: SYSOSC + description: SYSPLL reference is SYSOSC. + value: 0 + - name: HFCLK + description: SYSPLL reference is HFCLK. + value: 1 +enum/SYSSTATUSCLR_KEY: + bit_size: 8 + variants: + - name: KEY + value: 206 +enum/UDIV: + bit_size: 2 + variants: + - name: NODIVIDE + description: ULPCLK is not divided and is equal to MCLK. + value: 0 + - name: DIVIDE2 + description: ULPCLK is MCLK/2 (divided-by-2). + value: 1 +enum/USBCLKSRC: + bit_size: 1 + variants: + - name: USBFLLCLK + description: USBCLK source is USBFLL. + value: 0 + - name: SYSPLLOUT1 + description: USBCLK source is SYSPLLCLK1. + value: 1 +enum/USBFLL4HSCLK: + bit_size: 1 + variants: + - name: SYSPLL_HFCLK + description: HSCLK is sourced from HSCLKCFG.HSCLKSEL. + value: 0 + - name: USBFLL + description: HSCLK is sourced from the USBFLL. + value: 1 +enum/XT1DRIVE: + bit_size: 2 + variants: + - name: LOWESTDRV + description: Lowest drive and current. + value: 0 + - name: LOWERDRV + description: Lower drive and current. + value: 1 + - name: HIGHERDRV + description: Higher drive and current. + value: 2 + - name: HIGHESTDRV + description: Highest drive and current. + value: 3 diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index ade53e6..f9d693c 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -47,9 +47,10 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ ("msps003fx:sysctl", "c110x"), ("mspm0g..0x:sysctl", "g350x_g310x_g150x_g110x"), ("mspm0g..1x:sysctl", "g351x_g151x"), - // G511x/G518x have no reference manual of their own yet, so they borrow the G350x SYSCTL. - // Split them out when one is published. - ("mspm0g5..x:sysctl", "g350x_g310x_g150x_g110x"), + // Derived from the G351x block: same flash-protection and security region, minus the SRAM + // bank-1 registers and CANCLKSRC, plus the USB FLL. TI's own header and SVD for the family + // describe it; only the TRM still lags. + ("mspm0g518x:sysctl", "g518x"), ("mspm0h321x:sysctl", "h321x"), ("mspm0l..0x:sysctl", "l110x_l130x_l134x"), ("mspm0l134x:sysctl", "l110x_l130x_l134x"), From f33ab04e85b79b827e982d6d47abb86912563985 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 16:31:54 +0100 Subject: [PATCH 089/134] docs: list the UART extraction alongside the other datasheet tools --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f28cdc7..6f5dc74 100644 --- a/README.md +++ b/README.md @@ -65,10 +65,14 @@ These are the data sources currently used. ([`data/vref/`](./data/vref)). `CTL1.READY` would answer it, but `VREF_ERR_01` leaves that bit set after the first enable since reset. On an affected device the datasheet figure is the only signal - * Which ADC channels sample an internal signal โ€” the temperature sensor, an OPA, GPAMP or DAC - output, the internal reference, a supply monitor โ€” from the ADC channel-mapping table - ([`data/adc_channels/`](./data/adc_channels)). The SDK carries the same mapping keyed by SDK - family, which overstates: it routes OPA outputs on parts that have no OPA + * Which ADC channels sample an internal signal instead of a pin (the temperature sensor, the + OPA, GPAMP and DAC outputs, the internal reference, the supply monitors), from the ADC + channel-mapping table ([`data/adc_channels/`](./data/adc_channels)). The SDK states the same + mapping per SDK family, and a family overstates its parts: it routes the OPA outputs on + MSPM0G110x, which has no OPA + * Which extended-UART features (LIN, DALI, IrDA, ISO7816, Manchester) each UART instance + implements, from the "UART Features" table ([`data/uart/`](./data/uart)). One register block + serves every instance, so this is the only place the difference is stated per part * Which timers stay clocked in STANDBY1 (`standby1_timers` in [`parts.yaml`](./data/parts.yaml)) * MCLK and ULPCLK ceilings, the SYSOSC base frequency, the flash wait-state bands, `fADCCLK` and `TRNGCLKF` (all in [`parts.yaml`](./data/parts.yaml)) @@ -93,7 +97,7 @@ the `tools/` scripts read them from there. 4. Check the peripheral mapping in [`perimap.rs`](./mspm0-data-gen/src/perimap.rs) to use the correct peripherals. 5. Fetch the documents with `./d download-docs`, then regenerate the extracted data: `tools/operating_modes.py`, `tools/timers.py`, `tools/wakeup.py`, `tools/vref.py`, - `tools/adc_channels.py` and `tools/errata.py`, each with + `tools/adc_channels.py`, `tools/uart.py` and `tools/errata.py`, each with `--write files`. 6. Run `./d gen` and read its output. `verify.rs` reports every per-chip gap it can detect, including a family with no timer, errata or operating-mode data. From c4d73737182efb1b2fd715ff28ac21130472dec2 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 17:03:54 +0100 Subject: [PATCH 090/134] registers: add the COMP and CRC blocks Two comparator versions: dacout adds only CTL1.DACOUTEN, on the four families whose headers state COMP_SYS_DACOUT_EN. IIDX.STAT is three bits against every TI source's 0x3 mask; their own value lists reach 4h and the sibling IPs size the field properly. Three CRC versions, keyed by the headers' per-device parameters: 16 drops POLYSIZE on the CRC-16-only devices, p adds the programmable polynomial of the CRCP instances. SLAU893's CRC chapter describes CRCP0 for devices whose own headers use plain CRC_Regs, so the headers won. --- data/registers/comp_dacout.yaml | 565 ++++++++++++++++++++++++++++ data/registers/comp_v1.yaml | 561 +++++++++++++++++++++++++++ data/registers/crc_16.yaml | 150 ++++++++ data/registers/crc_p.yaml | 163 ++++++++ data/registers/crc_v1.yaml | 160 ++++++++ mspm0-data-gen/src/perimap.rs | 20 + mspm0-metapac-gen/src/peripheral.rs | 2 + 7 files changed, 1621 insertions(+) create mode 100644 data/registers/comp_dacout.yaml create mode 100644 data/registers/comp_v1.yaml create mode 100644 data/registers/crc_16.yaml create mode 100644 data/registers/crc_p.yaml create mode 100644 data/registers/crc_v1.yaml diff --git a/data/registers/comp_dacout.yaml b/data/registers/comp_dacout.yaml new file mode 100644 index 0000000..2c6d60c --- /dev/null +++ b/data/registers/comp_dacout.yaml @@ -0,0 +1,565 @@ +block/COMP: + description: PERIPHERALREGION. + items: + - name: FSUB + description: Subscriber Configuration Register. + array: + len: 2 + stride: 4 + byte_offset: 1024 + fieldset: FPORT + - name: FPUB + description: Publisher Configuration Register. + byte_offset: 1092 + fieldset: FPORT + - name: GPRCM + array: + len: 1 + stride: 24 + byte_offset: 2048 + block: GPRCM + - name: CPU_INT + array: + len: 1 + stride: 44 + byte_offset: 4128 + block: CPU_INT + - name: GEN_EVENT + array: + len: 1 + stride: 44 + byte_offset: 4176 + block: GEN_EVENT + - name: EVT_MODE + description: Event Mode. + byte_offset: 4320 + access: Read + fieldset: EVT_MODE + - name: DESC + description: Module Description. + byte_offset: 4348 + access: Read + fieldset: DESC + - name: CTL0 + description: Control 0. + byte_offset: 4352 + fieldset: CTL0 + - name: CTL1 + description: Control 1. + byte_offset: 4356 + fieldset: CTL1 + - name: CTL2 + description: Control 2. + byte_offset: 4360 + fieldset: CTL2 + - name: CTL3 + description: Control 3. + byte_offset: 4364 + fieldset: CTL3 + - name: STAT + description: Status. + byte_offset: 4384 + access: Read + fieldset: STAT +block/CPU_INT: + items: + - name: IIDX + description: Interrupt index. + byte_offset: 0 + access: Read + fieldset: IIDX + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: INT + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: INT + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: INT + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: INT + - name: ICLR + description: Interrupt clear. + byte_offset: 40 + access: Write + fieldset: INT +block/GEN_EVENT: + items: + - name: IIDX + description: Interrupt index. + byte_offset: 0 + access: Read + fieldset: IIDX + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: INT + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: INT + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: INT + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: INT + - name: ICLR + description: Interrupt clear. + byte_offset: 40 + access: Write + fieldset: INT +block/GPRCM: + items: + - name: PWREN + description: Power enable. + byte_offset: 0 + fieldset: PWREN + - name: RSTCTL + description: Reset Control. + byte_offset: 4 + access: Write + fieldset: RSTCTL + - name: CLKCFG + description: Peripheral Clock Configuration Register. + byte_offset: 8 + fieldset: CLKCFG + - name: GPRCM_STAT + description: Status Register. + byte_offset: 20 + access: Read + fieldset: GPRCM_STAT +fieldset/CLKCFG: + description: Peripheral Clock Configuration Register. + fields: + - name: BLOCKASYNC + description: Async Clock Request is blocked from starting SYSOSC or forcing bus clock to 32MHz. + bit_offset: 8 + bit_size: 1 + - name: KEY + description: KEY to Allow State Change A9h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: CLKCFG_KEY +fieldset/CTL0: + description: Control 0. + fields: + - name: IPSEL + description: Channel input selected for the positive terminal of the comparator if IPEN is set to 1. + bit_offset: 0 + bit_size: 3 + - name: IPEN + description: Channel input enable for the positive terminal of the comparator. + bit_offset: 15 + bit_size: 1 + - name: IMSEL + description: Channel input selected for the negative terminal of the comparator if IMEN is set to 1. + bit_offset: 16 + bit_size: 3 + - name: IMEN + description: Channel input enable for the negative terminal of the comparator. + bit_offset: 31 + bit_size: 1 +fieldset/CTL1: + description: Control 1. + fields: + - name: ENABLE + description: This bit turns on the comparator. When the comparator is turned off it consumes no power. + bit_offset: 0 + bit_size: 1 + - name: MODE + description: This bit selects the comparator operating mode. + bit_offset: 1 + bit_size: 1 + enum: MODE + - name: EXCH + description: This bit exchanges the comparator inputs and inverts the comparator output. + bit_offset: 2 + bit_size: 1 + - name: SHORT + description: This bit shorts the positive and negative input terminals of the comparator. + bit_offset: 3 + bit_size: 1 + - name: IES + description: This bit selected the interrupt edge for COMPIFG and COMPINVIFG. + bit_offset: 4 + bit_size: 1 + enum: IES + - name: HYST + description: These bits select the hysteresis setting of the comparator. + bit_offset: 5 + bit_size: 2 + enum: HYST + - name: OUTPOL + description: This bit selects the comparator output polarity. + bit_offset: 7 + bit_size: 1 + enum: OUTPOL + - name: FLTEN + description: This bit enables the analog filter at comparator output. + bit_offset: 8 + bit_size: 1 + - name: FLTDLY + description: These bits select the comparator output filter delay. See the device-specific data sheet for specific values on comparator propagation delay for different filter delay settings. + bit_offset: 9 + bit_size: 2 + enum: FLTDLY + - name: DACOUTEN + description: This bit enables DAC8 output to be connected to the pin. + bit_offset: 11 + bit_size: 1 + - name: WINCOMPEN + description: This bit enables window comparator operation of comparator. + bit_offset: 12 + bit_size: 1 +fieldset/CTL2: + description: Control 2. + fields: + - name: REFMODE + description: This bit requests ULP_REF bandgap operation in fast mode(static) or low power mode (sampled). The local reference buffer and 8-bit DAC inside comparator module are also configured accordingly. Fast mode operation offers higher accuracy but consumes higher current. Low power operation consumes lower current but with relaxed reference voltage accuracy. Comparator requests for reference voltage from ULP_REF only when REFLVL > 0. + bit_offset: 0 + bit_size: 1 + enum: REFMODE + - name: REFSRC + description: These bits select the reference source for the comparator. + bit_offset: 3 + bit_size: 3 + enum: REFSRC + - name: REFSEL + description: This bit selects if the selected reference voltage is applied to positive or negative terminal of the comparator. + bit_offset: 7 + bit_size: 1 + enum: REFSEL + - name: BLANKSRC + description: These bits select the blanking source for the comparator. + bit_offset: 8 + bit_size: 3 + enum: BLANKSRC + - name: DACCTL + description: This bit determines if the comparator output or DACSW bit controls the selection between DACCODE0 and DACCODE1. + bit_offset: 16 + bit_size: 1 + enum: DACCTL + - name: DACSW + description: This bit selects between DACCODE0 and DACCODE1 to 8-bit DAC when DACCTL bit is 1. + bit_offset: 17 + bit_size: 1 + enum: DACSW + - name: SAMPMODE + description: Enable sampled mode of comparator. + bit_offset: 24 + bit_size: 1 +fieldset/CTL3: + description: Control 3. + fields: + - name: DACCODE + description: The two 8-bit DAC codes; DACCTL and DACSW select which one drives the DAC. When the code is 0x0 the DAC output is the selected reference voltage x 1/256 V, and when it is 0xFF, x 255/256 V. + bit_offset: 0 + bit_size: 8 + array: + len: 2 + stride: 16 +fieldset/DESC: + description: Module Description. + fields: + - name: MINREV + description: Minor rev of the IP. + bit_offset: 0 + bit_size: 4 + - name: MAJREV + description: Major rev of the IP. + bit_offset: 4 + bit_size: 4 + - name: FEATUREVER + description: Feature Set for the module *instance*. + bit_offset: 12 + bit_size: 4 + - name: MODULEID + description: Module identification contains a unique peripheral identification number. The assignments are maintained in a central database for all of the platform modules to ensure uniqueness. + bit_offset: 16 + bit_size: 16 +fieldset/EVT_MODE: + description: Event Mode. + fields: + - name: CPU + description: Event line mode select for event corresponding to CPU_INT. + bit_offset: 0 + bit_size: 2 + enum: INT_CFG + - name: GEN_EVENT + description: Event line mode select for event corresponding to GEN_EVENT. + bit_offset: 2 + bit_size: 2 + enum: INT_CFG +fieldset/FPORT: + description: Event subscriber/publisher channel selection. + fields: + - name: CHANID + description: 0 = disconnected. 1-15 = connected to channelID = CHANID. + bit_offset: 0 + bit_size: 4 +fieldset/GPRCM_STAT: + description: Status Register. + fields: + - name: RESETSTKY + description: This bit indicates, if the peripheral was reset, since this bit was cleared by RESETSTKYCLR in the RSTCTL register. + bit_offset: 16 + bit_size: 1 +fieldset/IIDX: + description: Interrupt index. + fields: + - name: STAT + # Three bits, not the two every TI source states: the SVDs, hw_comp.h and the TRMs all mask + # STAT 0x3 while their own value lists reach 4h (OUTRDYIFG). Sibling IPs size it properly + # (WWDT 0x1F, ADC 0x3FF), so the 0x3 is a defect in the COMP IP description alone. + description: Interrupt index status. + bit_offset: 0 + bit_size: 3 + enum: IIDX_STAT +fieldset/INT: + description: Interrupt. + fields: + - name: COMPIFG + description: Masks COMPIFG. + bit_offset: 1 + bit_size: 1 + - name: COMPINVIFG + description: Masks COMPINVIFG. + bit_offset: 2 + bit_size: 1 + - name: OUTRDYIFG + description: Masks OUTRDYIFG. + bit_offset: 3 + bit_size: 1 +fieldset/PWREN: + description: Power enable. + fields: + - name: ENABLE + description: Enable the power. + bit_offset: 0 + bit_size: 1 + - name: KEY + description: KEY to allow Power State Change 26h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: PWREN_KEY +fieldset/RSTCTL: + description: Reset Control. + fields: + - name: RESETASSERT + description: Assert reset to the peripheral. + bit_offset: 0 + bit_size: 1 + - name: RESETSTKYCLR + description: Clear the RESETSTKY bit in the STAT register. + bit_offset: 1 + bit_size: 1 + - name: KEY + description: Unlock key B1h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: RESET_KEY +fieldset/STAT: + description: Status. + fields: + - name: OUT + description: This bit reflects the value of the comparator output. Writing to this bit has no effect on the comparator output. + bit_offset: 0 + bit_size: 1 +enum/BLANKSRC: + bit_size: 3 + variants: + - name: DISABLE + description: Blanking source disabled. + value: 0 + - name: BLANKSRC1 + description: Select Blanking Source 1. + value: 1 + - name: BLANKSRC2 + description: Select Blanking Source 2. + value: 2 + - name: BLANKSRC3 + description: Select Blanking Source 3. + value: 3 + - name: BLANKSRC4 + description: Select Blanking Source 4. + value: 4 + - name: BLANKSRC5 + description: Select Blanking Source 5. + value: 5 + - name: BLANKSRC6 + description: Select Blanking Source 6. + value: 6 +enum/CLKCFG_KEY: + bit_size: 8 + variants: + - name: KEY + value: 169 +enum/DACCTL: + bit_size: 1 + variants: + - name: COMPOUT_SEL + description: Comparator output controls selection between DACCODE0 and DACCODE1. + value: 0 + - name: DACSW_SEL + description: DACSW bit controls selection between DACCODE0 and DACCODE1. + value: 1 +enum/DACSW: + bit_size: 1 + variants: + - name: DACCODE0_SEL + description: DACCODE0 selected for 8-bit DAC. + value: 0 + - name: DACCODE1_SEL + description: DACCODE1 selected for 8-bit DAC. + value: 1 +enum/FLTDLY: + bit_size: 2 + variants: + - name: DLY_0 + description: Typical filter delay of 70 ns. + value: 0 + - name: DLY_1 + description: Typical filter delay of 500 ns. + value: 1 + - name: DLY_2 + description: Typical filter delay of 1200 ns. + value: 2 + - name: DLY_3 + description: Typical filter delay of 2700 ns. + value: 3 +enum/HYST: + bit_size: 2 + variants: + - name: NO_HYS + description: No hysteresis. + value: 0 + - name: LOW_HYS + description: Low hysteresis, typical 10mV. + value: 1 + - name: MED_HYS + description: Medium hysteresis, typical 20mV. + value: 2 + - name: HIGH_HYS + description: High hysteresis, typical 30mV. + value: 3 +enum/IES: + bit_size: 1 + variants: + - name: RISING + description: Rising edge sets COMPIFG and falling edge sets COMPINVIFG. + value: 0 + - name: FALLING + description: Falling edge sets COMPIFG and rising edge sets COMPINVIFG. + value: 1 +enum/IIDX_STAT: + bit_size: 3 + variants: + - name: NO_INTR + description: No pending interrupt. + value: 0 + - name: COMPIFG + description: Comparator output interrupt. + value: 2 + - name: COMPINVIFG + description: Comparator output inverted interrupt. + value: 3 + - name: OUTRDYIFG + description: Comparator output ready interrupt. + value: 4 +enum/INT_CFG: + bit_size: 2 + variants: + - name: DISABLE + description: The interrupt or event line is disabled. + value: 0 + - name: SOFTWARE + description: The interrupt or event line is in software mode. Software must clear the RIS. + value: 1 + - name: HARDWARE + description: The interrupt or event line is in hardware mode. The hardware (another module) clears automatically the associated RIS flag. + value: 2 +enum/MODE: + bit_size: 1 + variants: + - name: FAST + description: Comparator is in fast mode. + value: 0 + - name: ULP + description: Comparator is in ultra-low power mode. + value: 1 +enum/OUTPOL: + bit_size: 1 + variants: + - name: NON_INV + description: Comparator output is non-inverted. + value: 0 + - name: INV + description: Comparator output is inverted. + value: 1 +enum/PWREN_KEY: + bit_size: 8 + variants: + - name: KEY + value: 38 +enum/REFMODE: + bit_size: 1 + variants: + - name: STATIC + description: ULP_REF bandgap, local reference buffer and 8-bit DAC inside comparator operate in static mode. + value: 0 + - name: SAMPLED + description: ULP_REF bandgap, local reference buffer and 8-bit DAC inside comparator operate in sampled mode. + value: 1 +enum/REFSEL: + bit_size: 1 + variants: + - name: POSITIVE + description: If EXCH bit is 0, the selected reference is applied to positive terminal. If EXCH bit is 1, the selected reference is applied to negative terminal. + value: 0 + - name: NEGATIVE + description: If EXCH bit is 0, the selected reference is applied to negative terminal. If EXCH bit is 1, the selected reference is applied to positive terminal. + value: 1 +enum/REFSRC: + bit_size: 3 + variants: + - name: OFF + description: Reference voltage generator is disabled (local reference buffer as well as DAC). + value: 0 + - name: VDDA_DAC + description: VDDA selected as the reference source to DAC and DAC output applied as reference to comparator. + value: 1 + - name: VREF_DAC + description: VREF selected as reference to DAC and DAC output applied as reference to comparator. + value: 2 + - name: VREF + description: In devices where internal VREF is buffered and hookedup to extrernal VREF pin, VREF applied as reference to comparator. DAC is switched off. + value: 3 + - name: VDDA + description: VDDA is used as comparator reference. + value: 5 + - name: INTVREF_DAC + description: Internal reference selected as the reference source to DAC and DAC output applied as reference to comparator. + value: 6 + - name: INTVREF + description: Internal VREF is used as the source of comparator. Not all devices will have this option. + value: 7 +enum/RESET_KEY: + bit_size: 8 + variants: + - name: KEY + value: 177 diff --git a/data/registers/comp_v1.yaml b/data/registers/comp_v1.yaml new file mode 100644 index 0000000..ef722ec --- /dev/null +++ b/data/registers/comp_v1.yaml @@ -0,0 +1,561 @@ +block/COMP: + description: PERIPHERALREGION. + items: + - name: FSUB + description: Subscriber Configuration Register. + array: + len: 2 + stride: 4 + byte_offset: 1024 + fieldset: FPORT + - name: FPUB + description: Publisher Configuration Register. + byte_offset: 1092 + fieldset: FPORT + - name: GPRCM + array: + len: 1 + stride: 24 + byte_offset: 2048 + block: GPRCM + - name: CPU_INT + array: + len: 1 + stride: 44 + byte_offset: 4128 + block: CPU_INT + - name: GEN_EVENT + array: + len: 1 + stride: 44 + byte_offset: 4176 + block: GEN_EVENT + - name: EVT_MODE + description: Event Mode. + byte_offset: 4320 + access: Read + fieldset: EVT_MODE + - name: DESC + description: Module Description. + byte_offset: 4348 + access: Read + fieldset: DESC + - name: CTL0 + description: Control 0. + byte_offset: 4352 + fieldset: CTL0 + - name: CTL1 + description: Control 1. + byte_offset: 4356 + fieldset: CTL1 + - name: CTL2 + description: Control 2. + byte_offset: 4360 + fieldset: CTL2 + - name: CTL3 + description: Control 3. + byte_offset: 4364 + fieldset: CTL3 + - name: STAT + description: Status. + byte_offset: 4384 + access: Read + fieldset: STAT +block/CPU_INT: + items: + - name: IIDX + description: Interrupt index. + byte_offset: 0 + access: Read + fieldset: IIDX + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: INT + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: INT + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: INT + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: INT + - name: ICLR + description: Interrupt clear. + byte_offset: 40 + access: Write + fieldset: INT +block/GEN_EVENT: + items: + - name: IIDX + description: Interrupt index. + byte_offset: 0 + access: Read + fieldset: IIDX + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: INT + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: INT + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: INT + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: INT + - name: ICLR + description: Interrupt clear. + byte_offset: 40 + access: Write + fieldset: INT +block/GPRCM: + items: + - name: PWREN + description: Power enable. + byte_offset: 0 + fieldset: PWREN + - name: RSTCTL + description: Reset Control. + byte_offset: 4 + access: Write + fieldset: RSTCTL + - name: CLKCFG + description: Peripheral Clock Configuration Register. + byte_offset: 8 + fieldset: CLKCFG + - name: GPRCM_STAT + description: Status Register. + byte_offset: 20 + access: Read + fieldset: GPRCM_STAT +fieldset/CLKCFG: + description: Peripheral Clock Configuration Register. + fields: + - name: BLOCKASYNC + description: Async Clock Request is blocked from starting SYSOSC or forcing bus clock to 32MHz. + bit_offset: 8 + bit_size: 1 + - name: KEY + description: KEY to Allow State Change A9h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: CLKCFG_KEY +fieldset/CTL0: + description: Control 0. + fields: + - name: IPSEL + description: Channel input selected for the positive terminal of the comparator if IPEN is set to 1. + bit_offset: 0 + bit_size: 3 + - name: IPEN + description: Channel input enable for the positive terminal of the comparator. + bit_offset: 15 + bit_size: 1 + - name: IMSEL + description: Channel input selected for the negative terminal of the comparator if IMEN is set to 1. + bit_offset: 16 + bit_size: 3 + - name: IMEN + description: Channel input enable for the negative terminal of the comparator. + bit_offset: 31 + bit_size: 1 +fieldset/CTL1: + description: Control 1. + fields: + - name: ENABLE + description: This bit turns on the comparator. When the comparator is turned off it consumes no power. + bit_offset: 0 + bit_size: 1 + - name: MODE + description: This bit selects the comparator operating mode. + bit_offset: 1 + bit_size: 1 + enum: MODE + - name: EXCH + description: This bit exchanges the comparator inputs and inverts the comparator output. + bit_offset: 2 + bit_size: 1 + - name: SHORT + description: This bit shorts the positive and negative input terminals of the comparator. + bit_offset: 3 + bit_size: 1 + - name: IES + description: This bit selected the interrupt edge for COMPIFG and COMPINVIFG. + bit_offset: 4 + bit_size: 1 + enum: IES + - name: HYST + description: These bits select the hysteresis setting of the comparator. + bit_offset: 5 + bit_size: 2 + enum: HYST + - name: OUTPOL + description: This bit selects the comparator output polarity. + bit_offset: 7 + bit_size: 1 + enum: OUTPOL + - name: FLTEN + description: This bit enables the analog filter at comparator output. + bit_offset: 8 + bit_size: 1 + - name: FLTDLY + description: These bits select the comparator output filter delay. See the device-specific data sheet for specific values on comparator propagation delay for different filter delay settings. + bit_offset: 9 + bit_size: 2 + enum: FLTDLY + - name: WINCOMPEN + description: This bit enables window comparator operation of comparator. + bit_offset: 12 + bit_size: 1 +fieldset/CTL2: + description: Control 2. + fields: + - name: REFMODE + description: This bit requests ULP_REF bandgap operation in fast mode(static) or low power mode (sampled). The local reference buffer and 8-bit DAC inside comparator module are also configured accordingly. Fast mode operation offers higher accuracy but consumes higher current. Low power operation consumes lower current but with relaxed reference voltage accuracy. Comparator requests for reference voltage from ULP_REF only when REFLVL > 0. + bit_offset: 0 + bit_size: 1 + enum: REFMODE + - name: REFSRC + description: These bits select the reference source for the comparator. + bit_offset: 3 + bit_size: 3 + enum: REFSRC + - name: REFSEL + description: This bit selects if the selected reference voltage is applied to positive or negative terminal of the comparator. + bit_offset: 7 + bit_size: 1 + enum: REFSEL + - name: BLANKSRC + description: These bits select the blanking source for the comparator. + bit_offset: 8 + bit_size: 3 + enum: BLANKSRC + - name: DACCTL + description: This bit determines if the comparator output or DACSW bit controls the selection between DACCODE0 and DACCODE1. + bit_offset: 16 + bit_size: 1 + enum: DACCTL + - name: DACSW + description: This bit selects between DACCODE0 and DACCODE1 to 8-bit DAC when DACCTL bit is 1. + bit_offset: 17 + bit_size: 1 + enum: DACSW + - name: SAMPMODE + description: Enable sampled mode of comparator. + bit_offset: 24 + bit_size: 1 +fieldset/CTL3: + description: Control 3. + fields: + - name: DACCODE + description: The two 8-bit DAC codes; DACCTL and DACSW select which one drives the DAC. When the code is 0x0 the DAC output is the selected reference voltage x 1/256 V, and when it is 0xFF, x 255/256 V. + bit_offset: 0 + bit_size: 8 + array: + len: 2 + stride: 16 +fieldset/DESC: + description: Module Description. + fields: + - name: MINREV + description: Minor rev of the IP. + bit_offset: 0 + bit_size: 4 + - name: MAJREV + description: Major rev of the IP. + bit_offset: 4 + bit_size: 4 + - name: FEATUREVER + description: Feature Set for the module *instance*. + bit_offset: 12 + bit_size: 4 + - name: MODULEID + description: Module identification contains a unique peripheral identification number. The assignments are maintained in a central database for all of the platform modules to ensure uniqueness. + bit_offset: 16 + bit_size: 16 +fieldset/EVT_MODE: + description: Event Mode. + fields: + - name: CPU + description: Event line mode select for event corresponding to CPU_INT. + bit_offset: 0 + bit_size: 2 + enum: INT_CFG + - name: GEN_EVENT + description: Event line mode select for event corresponding to GEN_EVENT. + bit_offset: 2 + bit_size: 2 + enum: INT_CFG +fieldset/FPORT: + description: Event subscriber/publisher channel selection. + fields: + - name: CHANID + description: 0 = disconnected. 1-15 = connected to channelID = CHANID. + bit_offset: 0 + bit_size: 4 +fieldset/GPRCM_STAT: + description: Status Register. + fields: + - name: RESETSTKY + description: This bit indicates, if the peripheral was reset, since this bit was cleared by RESETSTKYCLR in the RSTCTL register. + bit_offset: 16 + bit_size: 1 +fieldset/IIDX: + description: Interrupt index. + fields: + - name: STAT + # Three bits, not the two every TI source states: the SVDs, hw_comp.h and the TRMs all mask + # STAT 0x3 while their own value lists reach 4h (OUTRDYIFG). Sibling IPs size it properly + # (WWDT 0x1F, ADC 0x3FF), so the 0x3 is a defect in the COMP IP description alone. + description: Interrupt index status. + bit_offset: 0 + bit_size: 3 + enum: IIDX_STAT +fieldset/INT: + description: Interrupt. + fields: + - name: COMPIFG + description: Masks COMPIFG. + bit_offset: 1 + bit_size: 1 + - name: COMPINVIFG + description: Masks COMPINVIFG. + bit_offset: 2 + bit_size: 1 + - name: OUTRDYIFG + description: Masks OUTRDYIFG. + bit_offset: 3 + bit_size: 1 +fieldset/PWREN: + description: Power enable. + fields: + - name: ENABLE + description: Enable the power. + bit_offset: 0 + bit_size: 1 + - name: KEY + description: KEY to allow Power State Change 26h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: PWREN_KEY +fieldset/RSTCTL: + description: Reset Control. + fields: + - name: RESETASSERT + description: Assert reset to the peripheral. + bit_offset: 0 + bit_size: 1 + - name: RESETSTKYCLR + description: Clear the RESETSTKY bit in the STAT register. + bit_offset: 1 + bit_size: 1 + - name: KEY + description: Unlock key B1h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: RESET_KEY +fieldset/STAT: + description: Status. + fields: + - name: OUT + description: This bit reflects the value of the comparator output. Writing to this bit has no effect on the comparator output. + bit_offset: 0 + bit_size: 1 +enum/BLANKSRC: + bit_size: 3 + variants: + - name: DISABLE + description: Blanking source disabled. + value: 0 + - name: BLANKSRC1 + description: Select Blanking Source 1. + value: 1 + - name: BLANKSRC2 + description: Select Blanking Source 2. + value: 2 + - name: BLANKSRC3 + description: Select Blanking Source 3. + value: 3 + - name: BLANKSRC4 + description: Select Blanking Source 4. + value: 4 + - name: BLANKSRC5 + description: Select Blanking Source 5. + value: 5 + - name: BLANKSRC6 + description: Select Blanking Source 6. + value: 6 +enum/CLKCFG_KEY: + bit_size: 8 + variants: + - name: KEY + value: 169 +enum/DACCTL: + bit_size: 1 + variants: + - name: COMPOUT_SEL + description: Comparator output controls selection between DACCODE0 and DACCODE1. + value: 0 + - name: DACSW_SEL + description: DACSW bit controls selection between DACCODE0 and DACCODE1. + value: 1 +enum/DACSW: + bit_size: 1 + variants: + - name: DACCODE0_SEL + description: DACCODE0 selected for 8-bit DAC. + value: 0 + - name: DACCODE1_SEL + description: DACCODE1 selected for 8-bit DAC. + value: 1 +enum/FLTDLY: + bit_size: 2 + variants: + - name: DLY_0 + description: Typical filter delay of 70 ns. + value: 0 + - name: DLY_1 + description: Typical filter delay of 500 ns. + value: 1 + - name: DLY_2 + description: Typical filter delay of 1200 ns. + value: 2 + - name: DLY_3 + description: Typical filter delay of 2700 ns. + value: 3 +enum/HYST: + bit_size: 2 + variants: + - name: NO_HYS + description: No hysteresis. + value: 0 + - name: LOW_HYS + description: Low hysteresis, typical 10mV. + value: 1 + - name: MED_HYS + description: Medium hysteresis, typical 20mV. + value: 2 + - name: HIGH_HYS + description: High hysteresis, typical 30mV. + value: 3 +enum/IES: + bit_size: 1 + variants: + - name: RISING + description: Rising edge sets COMPIFG and falling edge sets COMPINVIFG. + value: 0 + - name: FALLING + description: Falling edge sets COMPIFG and rising edge sets COMPINVIFG. + value: 1 +enum/IIDX_STAT: + bit_size: 3 + variants: + - name: NO_INTR + description: No pending interrupt. + value: 0 + - name: COMPIFG + description: Comparator output interrupt. + value: 2 + - name: COMPINVIFG + description: Comparator output inverted interrupt. + value: 3 + - name: OUTRDYIFG + description: Comparator output ready interrupt. + value: 4 +enum/INT_CFG: + bit_size: 2 + variants: + - name: DISABLE + description: The interrupt or event line is disabled. + value: 0 + - name: SOFTWARE + description: The interrupt or event line is in software mode. Software must clear the RIS. + value: 1 + - name: HARDWARE + description: The interrupt or event line is in hardware mode. The hardware (another module) clears automatically the associated RIS flag. + value: 2 +enum/MODE: + bit_size: 1 + variants: + - name: FAST + description: Comparator is in fast mode. + value: 0 + - name: ULP + description: Comparator is in ultra-low power mode. + value: 1 +enum/OUTPOL: + bit_size: 1 + variants: + - name: NON_INV + description: Comparator output is non-inverted. + value: 0 + - name: INV + description: Comparator output is inverted. + value: 1 +enum/PWREN_KEY: + bit_size: 8 + variants: + - name: KEY + value: 38 +enum/REFMODE: + bit_size: 1 + variants: + - name: STATIC + description: ULP_REF bandgap, local reference buffer and 8-bit DAC inside comparator operate in static mode. + value: 0 + - name: SAMPLED + description: ULP_REF bandgap, local reference buffer and 8-bit DAC inside comparator operate in sampled mode. + value: 1 +enum/REFSEL: + bit_size: 1 + variants: + - name: POSITIVE + description: If EXCH bit is 0, the selected reference is applied to positive terminal. If EXCH bit is 1, the selected reference is applied to negative terminal. + value: 0 + - name: NEGATIVE + description: If EXCH bit is 0, the selected reference is applied to negative terminal. If EXCH bit is 1, the selected reference is applied to positive terminal. + value: 1 +enum/REFSRC: + bit_size: 3 + variants: + - name: OFF + description: Reference voltage generator is disabled (local reference buffer as well as DAC). + value: 0 + - name: VDDA_DAC + description: VDDA selected as the reference source to DAC and DAC output applied as reference to comparator. + value: 1 + - name: VREF_DAC + description: VREF selected as reference to DAC and DAC output applied as reference to comparator. + value: 2 + - name: VREF + description: In devices where internal VREF is buffered and hookedup to extrernal VREF pin, VREF applied as reference to comparator. DAC is switched off. + value: 3 + - name: VDDA + description: VDDA is used as comparator reference. + value: 5 + - name: INTVREF_DAC + description: Internal reference selected as the reference source to DAC and DAC output applied as reference to comparator. + value: 6 + - name: INTVREF + description: Internal VREF is used as the source of comparator. Not all devices will have this option. + value: 7 +enum/RESET_KEY: + bit_size: 8 + variants: + - name: KEY + value: 177 diff --git a/data/registers/crc_16.yaml b/data/registers/crc_16.yaml new file mode 100644 index 0000000..c716816 --- /dev/null +++ b/data/registers/crc_16.yaml @@ -0,0 +1,150 @@ +block/CRC: + description: PERIPHERALREGION. + items: + - name: GPRCM + array: + len: 1 + stride: 24 + byte_offset: 2048 + block: GPRCM + - name: DESC + description: Module Description. + byte_offset: 4348 + access: Read + fieldset: DESC + - name: CTRL + description: CRC control. + byte_offset: 4352 + fieldset: CTRL + - name: SEED + description: CRC seed. + byte_offset: 4356 + access: Write + - name: IN + description: CRC input data. + byte_offset: 4360 + access: Write + - name: OUT + description: CRC result. + byte_offset: 4364 + access: Read + - name: IN_IDX + description: CRC input data, memory-mapped region. A write anywhere in it feeds the engine; the index only serves address-incrementing DMA. + array: + len: 512 + stride: 4 + byte_offset: 6144 + access: Write +block/GPRCM: + items: + - name: PWREN + description: Power enable. + byte_offset: 0 + fieldset: PWREN + - name: RSTCTL + description: Reset Control. + byte_offset: 4 + access: Write + fieldset: RSTCTL + - name: GPRCM_STAT + description: Status Register. + byte_offset: 20 + access: Read + fieldset: GPRCM_STAT +fieldset/CTRL: + description: CRC control. + # No POLYSIZE: these devices implement CRC-16 only. Their headers state it per device + # (CRC_SYS_CRC32_ENABLE = 0) and their SVDs omit the field; SLAU893 SS17.1 describes the C-series + # generator as CRC16-CCITT alone. The TRM's own register table still shows POLYSIZE - it is the + # portfolio-shared chapter, not the device. + fields: + - name: BITREVERSE + description: CRC Bit Input and output Reverse. This bit indictes that the bit order of each input byte used for the CRC calculation is reversed before it is passed to the generator, and that the bit order of the calculated CRC is be reversed when read from CRC_RESULT. + bit_offset: 1 + bit_size: 1 + - name: INPUT_ENDIANNESS + description: CRC Endian. This bit indicates the byte order within a word or half word of input data. + bit_offset: 2 + bit_size: 1 + enum: INPUT_ENDIANNESS + - name: OUTPUT_BYTESWAP + description: 'CRC Output Byteswap Enable. This bit controls whether the output is byte-swapped upon a read of the CRCOUT register. If CRCOUT is accessed as a half-word, and the OUTPUT_BYTESWAP is set to to 1, then the two bytes in the 16-bit access are swapped and returned. B1 is returned as B0 B0 is returned as B1 If CRCOUT is accessed as a word, and the OUTPUT_BYTESWAP is set to 1, then the four bytes in the 32-bit read are swapped. B3 is returned as B0 B2 is returned as B1 B1 is returned as B2 B0 is returned as B3 Note that if the CRC POLYSIZE is 16-bit and a 32-bit read of CRCOUT is performed with OUTPUT_BYTESWAP enabled, then the output is: MSB LSB 0x0 0x0 B0 B1 If the CRC POLYSIZE is 16-bit and a 32-bit read of CRCOUT is performed with OUTPUT_BYTESWAP disabled, then the output is: MSB LSB 0x0 0x0 B1 B0.' + bit_offset: 4 + bit_size: 1 +fieldset/DESC: + description: Module Description. + fields: + - name: MINREV + description: Minor rev of the IP. + bit_offset: 0 + bit_size: 4 + - name: MAJREV + description: Major rev of the IP. + bit_offset: 4 + bit_size: 4 + - name: INSTNUM + description: Instance Number within the device. This will be a parameter to the RTL for modules that can have multiple instances. + bit_offset: 8 + bit_size: 4 + - name: FEATUREVER + description: Feature Set for the module *instance*. + bit_offset: 12 + bit_size: 4 + - name: MODULEID + description: Module identification contains a unique peripheral identification number. The assignments are maintained in a central database for all of the platform modules to ensure uniqueness. + bit_offset: 16 + bit_size: 16 +fieldset/GPRCM_STAT: + description: Status Register. + fields: + - name: RESETSTKY + description: This bit indicates, if the peripheral was reset, since this bit was cleared by RESETSTKYCLR in the RSTCTL register. + bit_offset: 16 + bit_size: 1 +fieldset/PWREN: + description: Power enable. + fields: + - name: ENABLE + description: Enable the power. + bit_offset: 0 + bit_size: 1 + - name: KEY + description: KEY to allow Power State Change 26h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: PWREN_KEY +fieldset/RSTCTL: + description: Reset Control. + fields: + - name: RESETASSERT + description: Assert reset to the peripheral. + bit_offset: 0 + bit_size: 1 + - name: RESETSTKYCLR + description: Clear the RESETSTKY bit in the STAT register. + bit_offset: 1 + bit_size: 1 + - name: KEY + description: Unlock key B1h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: RESET_KEY +enum/INPUT_ENDIANNESS: + bit_size: 1 + variants: + - name: LITTLE_ENDIAN + description: LSB is lowest memory address and first to be processed. + value: 0 + - name: BIG_ENDIAN + description: LSB is highest memory address and last to be processed. + value: 1 +enum/PWREN_KEY: + bit_size: 8 + variants: + - name: KEY + value: 38 +enum/RESET_KEY: + bit_size: 8 + variants: + - name: KEY + value: 177 diff --git a/data/registers/crc_p.yaml b/data/registers/crc_p.yaml new file mode 100644 index 0000000..a8bd788 --- /dev/null +++ b/data/registers/crc_p.yaml @@ -0,0 +1,163 @@ +block/CRC: + description: PERIPHERALREGION. + items: + - name: GPRCM + array: + len: 1 + stride: 24 + byte_offset: 2048 + block: GPRCM + - name: DESC + description: Module Description. + byte_offset: 4348 + access: Read + fieldset: DESC + - name: CTRL + description: CRC control. + byte_offset: 4352 + fieldset: CTRL + - name: SEED + description: CRC seed. + byte_offset: 4356 + access: Write + - name: IN + description: CRC input data. + byte_offset: 4360 + access: Write + - name: OUT + description: CRC result. + byte_offset: 4364 + access: Read + - name: POLY + description: CRC polynomial. Bit positions above the configured POLYSIZE are ignored. + byte_offset: 4368 + - name: IN_IDX + description: CRC input data, memory-mapped region. A write anywhere in it feeds the engine; the index only serves address-incrementing DMA. + array: + len: 512 + stride: 4 + byte_offset: 6144 + access: Write +block/GPRCM: + items: + - name: PWREN + description: Power enable. + byte_offset: 0 + fieldset: PWREN + - name: RSTCTL + description: Reset Control. + byte_offset: 4 + access: Write + fieldset: RSTCTL + - name: GPRCM_STAT + description: Status Register. + byte_offset: 20 + access: Read + fieldset: GPRCM_STAT +fieldset/CTRL: + description: CRC control. + fields: + - name: POLYSIZE + description: This bit indicates which CRC calculation is performed by the generator. + bit_offset: 0 + bit_size: 1 + enum: POLYSIZE + - name: BITREVERSE + description: CRC Bit Input and output Reverse. This bit indictes that the bit order of each input byte used for the CRC calculation is reversed before it is passed to the generator, and that the bit order of the calculated CRC is be reversed when read from CRC_RESULT. + bit_offset: 1 + bit_size: 1 + - name: INPUT_ENDIANNESS + description: CRC Endian. This bit indicates the byte order within a word or half word of input data. + bit_offset: 2 + bit_size: 1 + enum: INPUT_ENDIANNESS + - name: OUTPUT_BYTESWAP + description: 'CRC Output Byteswap Enable. This bit controls whether the output is byte-swapped upon a read of the CRCOUT register. If CRCOUT is accessed as a half-word, and the OUTPUT_BYTESWAP is set to to 1, then the two bytes in the 16-bit access are swapped and returned. B1 is returned as B0 B0 is returned as B1 If CRCOUT is accessed as a word, and the OUTPUT_BYTESWAP is set to 1, then the four bytes in the 32-bit read are swapped. B3 is returned as B0 B2 is returned as B1 B1 is returned as B2 B0 is returned as B3 Note that if the CRC POLYSIZE is 16-bit and a 32-bit read of CRCOUT is performed with OUTPUT_BYTESWAP enabled, then the output is: MSB LSB 0x0 0x0 B0 B1 If the CRC POLYSIZE is 16-bit and a 32-bit read of CRCOUT is performed with OUTPUT_BYTESWAP disabled, then the output is: MSB LSB 0x0 0x0 B1 B0.' + bit_offset: 4 + bit_size: 1 +fieldset/DESC: + description: Module Description. + fields: + - name: MINREV + description: Minor rev of the IP. + bit_offset: 0 + bit_size: 4 + - name: MAJREV + description: Major rev of the IP. + bit_offset: 4 + bit_size: 4 + - name: INSTNUM + description: Instance Number within the device. This will be a parameter to the RTL for modules that can have multiple instances. + bit_offset: 8 + bit_size: 4 + - name: FEATUREVER + description: Feature Set for the module *instance*. + bit_offset: 12 + bit_size: 4 + - name: MODULEID + description: Module identification contains a unique peripheral identification number. The assignments are maintained in a central database for all of the platform modules to ensure uniqueness. + bit_offset: 16 + bit_size: 16 +fieldset/GPRCM_STAT: + description: Status Register. + fields: + - name: RESETSTKY + description: This bit indicates, if the peripheral was reset, since this bit was cleared by RESETSTKYCLR in the RSTCTL register. + bit_offset: 16 + bit_size: 1 +fieldset/PWREN: + description: Power enable. + fields: + - name: ENABLE + description: Enable the power. + bit_offset: 0 + bit_size: 1 + - name: KEY + description: KEY to allow Power State Change 26h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: PWREN_KEY +fieldset/RSTCTL: + description: Reset Control. + fields: + - name: RESETASSERT + description: Assert reset to the peripheral. + bit_offset: 0 + bit_size: 1 + - name: RESETSTKYCLR + description: Clear the RESETSTKY bit in the STAT register. + bit_offset: 1 + bit_size: 1 + - name: KEY + description: Unlock key B1h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: RESET_KEY +enum/INPUT_ENDIANNESS: + bit_size: 1 + variants: + - name: LITTLE_ENDIAN + description: LSB is lowest memory address and first to be processed. + value: 0 + - name: BIG_ENDIAN + description: LSB is highest memory address and last to be processed. + value: 1 +enum/POLYSIZE: + bit_size: 1 + variants: + - name: CRC32 + description: CRC-32 ISO-3309 calulation is performed. + value: 0 + - name: CRC16 + description: CRC-16 CCITT is performed. + value: 1 +enum/PWREN_KEY: + bit_size: 8 + variants: + - name: KEY + value: 38 +enum/RESET_KEY: + bit_size: 8 + variants: + - name: KEY + value: 177 diff --git a/data/registers/crc_v1.yaml b/data/registers/crc_v1.yaml new file mode 100644 index 0000000..2038248 --- /dev/null +++ b/data/registers/crc_v1.yaml @@ -0,0 +1,160 @@ +block/CRC: + description: PERIPHERALREGION. + items: + - name: GPRCM + array: + len: 1 + stride: 24 + byte_offset: 2048 + block: GPRCM + - name: DESC + description: Module Description. + byte_offset: 4348 + access: Read + fieldset: DESC + - name: CTRL + description: CRC control. + byte_offset: 4352 + fieldset: CTRL + - name: SEED + description: CRC seed. + byte_offset: 4356 + access: Write + - name: IN + description: CRC input data. + byte_offset: 4360 + access: Write + - name: OUT + description: CRC result. + byte_offset: 4364 + access: Read + - name: IN_IDX + description: CRC input data, memory-mapped region. A write anywhere in it feeds the engine; the index only serves address-incrementing DMA. + array: + len: 512 + stride: 4 + byte_offset: 6144 + access: Write +block/GPRCM: + items: + - name: PWREN + description: Power enable. + byte_offset: 0 + fieldset: PWREN + - name: RSTCTL + description: Reset Control. + byte_offset: 4 + access: Write + fieldset: RSTCTL + - name: GPRCM_STAT + description: Status Register. + byte_offset: 20 + access: Read + fieldset: GPRCM_STAT +fieldset/CTRL: + description: CRC control. + fields: + - name: POLYSIZE + description: This bit indicates which CRC calculation is performed by the generator. + bit_offset: 0 + bit_size: 1 + enum: POLYSIZE + - name: BITREVERSE + description: CRC Bit Input and output Reverse. This bit indictes that the bit order of each input byte used for the CRC calculation is reversed before it is passed to the generator, and that the bit order of the calculated CRC is be reversed when read from CRC_RESULT. + bit_offset: 1 + bit_size: 1 + - name: INPUT_ENDIANNESS + description: CRC Endian. This bit indicates the byte order within a word or half word of input data. + bit_offset: 2 + bit_size: 1 + enum: INPUT_ENDIANNESS + - name: OUTPUT_BYTESWAP + description: 'CRC Output Byteswap Enable. This bit controls whether the output is byte-swapped upon a read of the CRCOUT register. If CRCOUT is accessed as a half-word, and the OUTPUT_BYTESWAP is set to to 1, then the two bytes in the 16-bit access are swapped and returned. B1 is returned as B0 B0 is returned as B1 If CRCOUT is accessed as a word, and the OUTPUT_BYTESWAP is set to 1, then the four bytes in the 32-bit read are swapped. B3 is returned as B0 B2 is returned as B1 B1 is returned as B2 B0 is returned as B3 Note that if the CRC POLYSIZE is 16-bit and a 32-bit read of CRCOUT is performed with OUTPUT_BYTESWAP enabled, then the output is: MSB LSB 0x0 0x0 B0 B1 If the CRC POLYSIZE is 16-bit and a 32-bit read of CRCOUT is performed with OUTPUT_BYTESWAP disabled, then the output is: MSB LSB 0x0 0x0 B1 B0.' + bit_offset: 4 + bit_size: 1 +fieldset/DESC: + description: Module Description. + fields: + - name: MINREV + description: Minor rev of the IP. + bit_offset: 0 + bit_size: 4 + - name: MAJREV + description: Major rev of the IP. + bit_offset: 4 + bit_size: 4 + - name: INSTNUM + description: Instance Number within the device. This will be a parameter to the RTL for modules that can have multiple instances. + bit_offset: 8 + bit_size: 4 + - name: FEATUREVER + description: Feature Set for the module *instance*. + bit_offset: 12 + bit_size: 4 + - name: MODULEID + description: Module identification contains a unique peripheral identification number. The assignments are maintained in a central database for all of the platform modules to ensure uniqueness. + bit_offset: 16 + bit_size: 16 +fieldset/GPRCM_STAT: + description: Status Register. + fields: + - name: RESETSTKY + description: This bit indicates, if the peripheral was reset, since this bit was cleared by RESETSTKYCLR in the RSTCTL register. + bit_offset: 16 + bit_size: 1 +fieldset/PWREN: + description: Power enable. + fields: + - name: ENABLE + description: Enable the power. + bit_offset: 0 + bit_size: 1 + - name: KEY + description: KEY to allow Power State Change 26h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: PWREN_KEY +fieldset/RSTCTL: + description: Reset Control. + fields: + - name: RESETASSERT + description: Assert reset to the peripheral. + bit_offset: 0 + bit_size: 1 + - name: RESETSTKYCLR + description: Clear the RESETSTKY bit in the STAT register. + bit_offset: 1 + bit_size: 1 + - name: KEY + description: Unlock key B1h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: RESET_KEY +enum/INPUT_ENDIANNESS: + bit_size: 1 + variants: + - name: LITTLE_ENDIAN + description: LSB is lowest memory address and first to be processed. + value: 0 + - name: BIG_ENDIAN + description: LSB is highest memory address and last to be processed. + value: 1 +enum/POLYSIZE: + bit_size: 1 + variants: + - name: CRC32 + description: CRC-32 ISO-3309 calulation is performed. + value: 0 + - name: CRC16 + description: CRC-16 CCITT is performed. + value: 1 +enum/PWREN_KEY: + bit_size: 8 + variants: + - name: KEY + value: 38 +enum/RESET_KEY: + bit_size: 8 + variants: + - name: KEY + value: 177 diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index f9d693c..491071e 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -20,6 +20,26 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ (".*:flashctl", "v1"), (".*:trng", "v1"), (".*:canfd", "v1"), + // One layout, but DACOUTEN (CTL1 bit 11) only exists where the header states + // `COMP_SYS_DACOUT_EN`: the 8-bit reference DAC reaches a pin on these four families only. + ("mspm0c1105_c1106:comp", "dacout"), + ("mspm0g518x:comp", "dacout"), + ("mspm0l112x:comp", "dacout"), + ("mspm0l211x:comp", "dacout"), + (".*:comp", "v1"), + // Three CRC generators. `16` lacks CRCCTRL.POLYSIZE: those devices are CRC-16 only, stated + // per device by the headers' `CRC_SYS_CRC32_ENABLE = 0`. `p` adds the programmable-polynomial + // CRCPOLY register; TI types those instances `CRCP_Regs` and the TRMs call them CRCP0. + ("mspm0c110x:crc", "16"), + ("msps003fx:crc", "16"), + ("mspm0c1105_c1106:crc", "16"), + ("mspm0h321x:crc", "16"), + ("mspm0l112x:crc", "16"), + ("mspm0l211x:crc", "16"), + ("mspm0g..1x:crc", "p"), + ("mspm0g518x:crc", "p"), + ("mspm0l.22x:crc", "p"), + (".*:crc", "v1"), // One version: the SDK ships a single hw_unicomm.h for the portfolio, and the wrapper is what // every instance has regardless of which mode maps it implements. (".*:unicomm", "v1"), diff --git a/mspm0-metapac-gen/src/peripheral.rs b/mspm0-metapac-gen/src/peripheral.rs index 60dffcc..e60f457 100644 --- a/mspm0-metapac-gen/src/peripheral.rs +++ b/mspm0-metapac-gen/src/peripheral.rs @@ -14,6 +14,8 @@ const GENERATE_PERIPHERALS: &[PeripheralType] = &[ PeripheralType::Cpuss, PeripheralType::Dma, PeripheralType::Canfd, + PeripheralType::Comp, + PeripheralType::Crc, PeripheralType::FactoryRegion, PeripheralType::FlashCtl, PeripheralType::Gpio, From 96b2a0ce384175965338f1c20506a1913d8df54c Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 18:16:48 +0100 Subject: [PATCH 091/134] vref: record whether the reference drives the VREF+ pin The families genuinely differ: an OPA reading PSEL = 5 (the VREF+ pin node) gets the internal reference on the G parts and a floating pin on the others, and both datasheets label the position identically. The extraction signal is the output drive-strength row, which every G-family datasheet states and no C/H/L one does. --- data/vref/mspm0c1105_c1106.yaml | 4 ++++ data/vref/mspm0c110x.yaml | 4 ++++ data/vref/mspm0g110x.yaml | 4 ++++ data/vref/mspm0g150x.yaml | 4 ++++ data/vref/mspm0g151x.yaml | 4 ++++ data/vref/mspm0g310x.yaml | 4 ++++ data/vref/mspm0g350x.yaml | 4 ++++ data/vref/mspm0g351x.yaml | 4 ++++ data/vref/mspm0g518x.yaml | 4 ++++ data/vref/mspm0h321x.yaml | 4 ++++ data/vref/mspm0l110x.yaml | 4 ++++ data/vref/mspm0l112x.yaml | 4 ++++ data/vref/mspm0l122x.yaml | 4 ++++ data/vref/mspm0l130x.yaml | 4 ++++ data/vref/mspm0l134x.yaml | 4 ++++ data/vref/mspm0l211x.yaml | 4 ++++ data/vref/mspm0l222x.yaml | 4 ++++ data/vref/msps003fx.yaml | 4 ++++ tools/vref.py | 33 ++++++++++++++++++++++++++++++++- 19 files changed, 104 insertions(+), 1 deletion(-) diff --git a/data/vref/mspm0c1105_c1106.yaml b/data/vref/mspm0c1105_c1106.yaml index 7fc5d13..79335cf 100644 --- a/data/vref/mspm0c1105_c1106.yaml +++ b/data/vref/mspm0c1105_c1106.yaml @@ -10,3 +10,7 @@ # BUFCONFIG = {0, 1} , VDD = 2.8 V startup_ns: 30000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: false diff --git a/data/vref/mspm0c110x.yaml b/data/vref/mspm0c110x.yaml index 36504e2..8154ced 100644 --- a/data/vref/mspm0c110x.yaml +++ b/data/vref/mspm0c110x.yaml @@ -10,3 +10,7 @@ # BUFCONFIG = {0, 1} , VDD = 2.7 V startup_ns: 10000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: false diff --git a/data/vref/mspm0g110x.yaml b/data/vref/mspm0g110x.yaml index e6a7971..da15135 100644 --- a/data/vref/mspm0g110x.yaml +++ b/data/vref/mspm0g110x.yaml @@ -10,3 +10,7 @@ # BUFCONFIG = {0, 1} , VDD = 2.8 V, C = 1ยตF VREF startup_ns: 200000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: true diff --git a/data/vref/mspm0g150x.yaml b/data/vref/mspm0g150x.yaml index 6b552af..b9ae13b 100644 --- a/data/vref/mspm0g150x.yaml +++ b/data/vref/mspm0g150x.yaml @@ -10,3 +10,7 @@ # BUFCONFIG = {0, 1} , VDD = 2.8 V, C = 1ยตF VREF startup_ns: 200000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: true diff --git a/data/vref/mspm0g151x.yaml b/data/vref/mspm0g151x.yaml index c97598e..d8761f4 100644 --- a/data/vref/mspm0g151x.yaml +++ b/data/vref/mspm0g151x.yaml @@ -10,3 +10,7 @@ # BUFCONFIG = {0, 1} , VDD โ‰ฅ 2.7 V, C = 1ยตF VREF startup_ns: 200000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: true diff --git a/data/vref/mspm0g310x.yaml b/data/vref/mspm0g310x.yaml index f625258..9c45780 100644 --- a/data/vref/mspm0g310x.yaml +++ b/data/vref/mspm0g310x.yaml @@ -10,3 +10,7 @@ # BUFCONFIG = {0, 1} , VDD = 2.8 V, C = 1ยตF VREF startup_ns: 200000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: true diff --git a/data/vref/mspm0g350x.yaml b/data/vref/mspm0g350x.yaml index 51af589..d3ae620 100644 --- a/data/vref/mspm0g350x.yaml +++ b/data/vref/mspm0g350x.yaml @@ -10,3 +10,7 @@ # BUFCONFIG = {0, 1} , VDD = 2.8 V, C = 1ยตF VREF startup_ns: 200000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: true diff --git a/data/vref/mspm0g351x.yaml b/data/vref/mspm0g351x.yaml index 7f8bcd1..80e5f99 100644 --- a/data/vref/mspm0g351x.yaml +++ b/data/vref/mspm0g351x.yaml @@ -10,3 +10,7 @@ # BUFCONFIG = {0, 1} , VDD โ‰ฅ 2.7 V, C = 1ยตF VREF startup_ns: 200000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: true diff --git a/data/vref/mspm0g518x.yaml b/data/vref/mspm0g518x.yaml index 57cca84..6be3d9c 100644 --- a/data/vref/mspm0g518x.yaml +++ b/data/vref/mspm0g518x.yaml @@ -12,3 +12,7 @@ # slowest of: 20us at BUFCONFIG = {0, 1} , VDD โ‰ฅ 2.7 V; 200us at BUFCONFIG = {0, 1} , VDD โ‰ฅ 2.7 V, C = 1ยตF VREF startup_ns: 200000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: true diff --git a/data/vref/mspm0h321x.yaml b/data/vref/mspm0h321x.yaml index 9833e70..fb440a2 100644 --- a/data/vref/mspm0h321x.yaml +++ b/data/vref/mspm0h321x.yaml @@ -10,3 +10,7 @@ # VDD = 5 V startup_ns: 30000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: false diff --git a/data/vref/mspm0l110x.yaml b/data/vref/mspm0l110x.yaml index 5e1be63..f5f5bb5 100644 --- a/data/vref/mspm0l110x.yaml +++ b/data/vref/mspm0l110x.yaml @@ -10,3 +10,7 @@ # BUFCONFIG = {0, 1} , VDD = 2.8 V startup_ns: 15000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: false diff --git a/data/vref/mspm0l112x.yaml b/data/vref/mspm0l112x.yaml index 0a2ba29..16a8b8e 100644 --- a/data/vref/mspm0l112x.yaml +++ b/data/vref/mspm0l112x.yaml @@ -10,3 +10,7 @@ # BUFCONFIG = {0, 1} , VDD = 2.8 V startup_ns: 30000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: false diff --git a/data/vref/mspm0l122x.yaml b/data/vref/mspm0l122x.yaml index 36d3a43..d73c9d4 100644 --- a/data/vref/mspm0l122x.yaml +++ b/data/vref/mspm0l122x.yaml @@ -10,3 +10,7 @@ # BUFCONFIG = {0, 1} , VDD = 2.8 V startup_ns: 15000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: false diff --git a/data/vref/mspm0l130x.yaml b/data/vref/mspm0l130x.yaml index 03cc6aa..1d04867 100644 --- a/data/vref/mspm0l130x.yaml +++ b/data/vref/mspm0l130x.yaml @@ -10,3 +10,7 @@ # BUFCONFIG = {0, 1} , VDD = 2.8 V startup_ns: 15000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: false diff --git a/data/vref/mspm0l134x.yaml b/data/vref/mspm0l134x.yaml index bedd12e..5456e21 100644 --- a/data/vref/mspm0l134x.yaml +++ b/data/vref/mspm0l134x.yaml @@ -10,3 +10,7 @@ # BUFCONFIG = {0, 1} , VDD = 2.8 V startup_ns: 15000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: false diff --git a/data/vref/mspm0l211x.yaml b/data/vref/mspm0l211x.yaml index 0a2ba29..16a8b8e 100644 --- a/data/vref/mspm0l211x.yaml +++ b/data/vref/mspm0l211x.yaml @@ -10,3 +10,7 @@ # BUFCONFIG = {0, 1} , VDD = 2.8 V startup_ns: 30000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: false diff --git a/data/vref/mspm0l222x.yaml b/data/vref/mspm0l222x.yaml index 28eaf7f..95336d9 100644 --- a/data/vref/mspm0l222x.yaml +++ b/data/vref/mspm0l222x.yaml @@ -10,3 +10,7 @@ # BUFCONFIG = {0, 1} , VDD = 2.8 V startup_ns: 15000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: false diff --git a/data/vref/msps003fx.yaml b/data/vref/msps003fx.yaml index 36504e2..8154ced 100644 --- a/data/vref/msps003fx.yaml +++ b/data/vref/msps003fx.yaml @@ -10,3 +10,7 @@ # BUFCONFIG = {0, 1} , VDD = 2.7 V startup_ns: 10000 +# Whether the internal reference is buffered out to the VREF+ pin, from the +# datasheet's "VREF output drive strength" row. false also covers devices +# with no VREF pins. +output_to_pin: false diff --git a/tools/vref.py b/tools/vref.py index 933e0b1..9279826 100644 --- a/tools/vref.py +++ b/tools/vref.py @@ -21,6 +21,16 @@ - **The stated conditions differ between datasheets** -- `VDD = 2.8 V` on most, `2.7 V` on the C1104 -- and one figure per device is therefore a simplification. The conditions are written into the generated file's header so the simplification is visible rather than implied. + +This also extracts `output_to_pin`: whether the internal reference is buffered out to the VREF+ +package pin. The families genuinely differ -- an OPA reading its `PSEL = 5` (the VREF+ pin node) gets +the internal reference on a G3507 and a floating pin on an L1306, measured on hardware, and TI's own +SDK example behaves the same way. The signal is the "VREF output drive strength" row of the same +electrical table: every G-family datasheet has it (with a short-circuit current and a *required* +decoupling capacitor for internal-reference use), no C/H/L datasheet does, and the older G pin tables +say it outright ("external reference input / internal reference output"). The C/H/L datasheets +describe VREF+ only as "bringing in an external reference", and the C1104 has no VREF pins at all -- +`false` covers both. """ # /// script @@ -42,6 +52,10 @@ CAPTION = "VREF startup time" +#: The row that says the internal reference drives the VREF+ pin. Matched over the page text +#: rather than parsed: presence is the fact. +OUTPUT_DRIVE = "VREF output drive strength" + LATTICE = {"vertical_strategy": "lines", "horizontal_strategy": "lines"} #: Time units the figure may be given in, as a multiplier to nanoseconds. The datasheets are @@ -60,6 +74,17 @@ def caption_pages(path: Path) -> list[int]: doc.close() +def drives_pin(path: Path) -> bool: + """Whether the datasheet states the output-drive row for the VREF+ pin.""" + doc = pypdfium2.PdfDocument(str(path)) + try: + return any( + OUTPUT_DRIVE in doc[i].get_textpage().get_text_range() for i in range(len(doc)) + ) + finally: + doc.close() + + def read_tables(path: Path) -> Iterator[tuple[int, str]]: """Yield `(startup time in ns, conditions)` for each VREF startup row found.""" for page_index in caption_pages(path): @@ -164,11 +189,16 @@ def write(datasheets: str) -> int: others = "; ".join(f"{v / 1000:g}us at {c}" for v, c in sorted(found)) conditions = f"{conditions}\n#\n# slowest of: {others}" + output = drives_pin(pdf) path = Path(f"data/vref/{family}.yaml") with open(path, "w", encoding="utf-8", newline="\n") as out: out.write(HEADER.format(ds=gpn.upper(), conditions=conditions or "none stated")) out.write(f"\nstartup_ns: {ns}\n") - print(f"{family}: {ns / 1000:g} us, from {pdf.name}") + out.write("# Whether the internal reference is buffered out to the VREF+ pin, from the\n") + out.write("# datasheet's \"VREF output drive strength\" row. false also covers devices\n") + out.write("# with no VREF pins.\n") + out.write(f"output_to_pin: {str(output).lower()}\n") + print(f"{family}: {ns / 1000:g} us, output_to_pin={output}, from {pdf.name}") return problems @@ -184,6 +214,7 @@ def main(argv: list[str]) -> None: for path in paths: print(f"########## {path.name}") + print(f" output_to_pin: {drives_pin(path)}") for ns, conditions in read_tables(path): print(f" {ns / 1000:>8g} us {conditions}") From 335808ab97b80c5b5e9817bc323213b79c696b93 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 18:16:48 +0100 Subject: [PATCH 092/134] opa: curate the input-mux maps from the datasheets Hand-curated rather than extracted: the G families state them as the OPAx input channel mapping tables, but the L1306/L1346 datasheet promises those tables without containing them, and its Figure 8-1 is the only per-device statement - a figure, with the mux positions printed with gaps that are the answer. Positions absent from a map do not exist on the device and select nothing; the cascade positions name the peer instance, and DAC8 positions name the COMP whose reference DAC feeds them. --- data/opa/mspm0g150x.yaml | 47 +++++++++++++++++++++++++++++++++++++++ data/opa/mspm0g350x.yaml | 47 +++++++++++++++++++++++++++++++++++++++ data/opa/mspm0l130x.yaml | 42 ++++++++++++++++++++++++++++++++++ data/opa/mspm0l134x.yaml | 42 ++++++++++++++++++++++++++++++++++ mspm0-data-gen/src/opa.rs | 21 +++++++++++++++++ 5 files changed, 199 insertions(+) create mode 100644 data/opa/mspm0g150x.yaml create mode 100644 data/opa/mspm0g350x.yaml create mode 100644 data/opa/mspm0l130x.yaml create mode 100644 data/opa/mspm0l134x.yaml create mode 100644 mspm0-data-gen/src/opa.rs diff --git a/data/opa/mspm0g150x.yaml b/data/opa/mspm0g150x.yaml new file mode 100644 index 0000000..8f5f935 --- /dev/null +++ b/data/opa/mspm0g150x.yaml @@ -0,0 +1,47 @@ +# What each OPA instance's input muxes select, per position. +# +# HAND-CURATED from the "OPAx Input Channel Mapping" tables of the MSPM0G1507 datasheet +# (section 8.x OPA). Position 0 is Open everywhere and not listed; a position absent +# here does not exist on this family and selects nothing. +OPA0: + pmux: + 1: !In 0 + 2: !In 1 + 3: Dac12 + 4: !Dac8 0 + 5: VrefPlus + 6: !Rtop 1 + 7: Gpamp + 8: Ground + nmux: + 1: !In 0 + 2: !In 1 + 3: !Rbot 1 + 4: OwnRtap + 5: OwnRtop + mmux: + 1: !In 1 + 2: Ground + 3: Dac12 + 4: !Rtop 1 +OPA1: + pmux: + 1: !In 0 + 2: !In 1 + 3: Dac12 + 4: !Dac8 1 + 5: VrefPlus + 6: !Rtop 0 + 7: Gpamp + 8: Ground + nmux: + 1: !In 0 + 2: !In 1 + 3: !Rbot 0 + 4: OwnRtap + 5: OwnRtop + mmux: + 1: !In 1 + 2: Ground + 3: Dac12 + 4: !Rtop 0 diff --git a/data/opa/mspm0g350x.yaml b/data/opa/mspm0g350x.yaml new file mode 100644 index 0000000..bbb0594 --- /dev/null +++ b/data/opa/mspm0g350x.yaml @@ -0,0 +1,47 @@ +# What each OPA instance's input muxes select, per position. +# +# HAND-CURATED from the "OPAx Input Channel Mapping" tables of the MSPM0G3507 datasheet +# (section 8.x OPA). Position 0 is Open everywhere and not listed; a position absent +# here does not exist on this family and selects nothing. +OPA0: + pmux: + 1: !In 0 + 2: !In 1 + 3: Dac12 + 4: !Dac8 0 + 5: VrefPlus + 6: !Rtop 1 + 7: Gpamp + 8: Ground + nmux: + 1: !In 0 + 2: !In 1 + 3: !Rbot 1 + 4: OwnRtap + 5: OwnRtop + mmux: + 1: !In 1 + 2: Ground + 3: Dac12 + 4: !Rtop 1 +OPA1: + pmux: + 1: !In 0 + 2: !In 1 + 3: Dac12 + 4: !Dac8 1 + 5: VrefPlus + 6: !Rtop 0 + 7: Gpamp + 8: Ground + nmux: + 1: !In 0 + 2: !In 1 + 3: !Rbot 0 + 4: OwnRtap + 5: OwnRtop + mmux: + 1: !In 1 + 2: Ground + 3: Dac12 + 4: !Rtop 0 diff --git a/data/opa/mspm0l130x.yaml b/data/opa/mspm0l130x.yaml new file mode 100644 index 0000000..31b1781 --- /dev/null +++ b/data/opa/mspm0l130x.yaml @@ -0,0 +1,42 @@ +# What each OPA instance's input muxes select, per position. +# +# HAND-CURATED from Figure 8-1 "Analog Connections" of the MSPM0L1306 datasheet (section +# 8.24): its section 8.18 promises input channel mapping tables which are not in the +# document, and the figure is the only per-device statement. The mux position numbers +# in the figure are printed with gaps; the gaps are real. Position 0 is Open everywhere +# and not listed; a position absent here does not exist on this family and selects +# nothing - notably PSEL 2 (IN1+), 3 (DAC12), 8 (ground) and MSEL 3 (DAC12). +OPA0: + pmux: + 1: !In 0 + 4: !Dac8 0 + 5: VrefPlus + 6: !Rtop 1 + 7: Gpamp + nmux: + 1: !In 0 + 2: !In 1 + 3: !Rbot 1 + 4: OwnRtap + 5: OwnRtop + mmux: + 1: !In 1 + 2: Ground + 4: !Rtop 1 +OPA1: + pmux: + 1: !In 0 + 4: !Dac8 0 + 5: VrefPlus + 6: !Rtop 0 + 7: Gpamp + nmux: + 1: !In 0 + 2: !In 1 + 3: !Rbot 0 + 4: OwnRtap + 5: OwnRtop + mmux: + 1: !In 1 + 2: Ground + 4: !Rtop 0 diff --git a/data/opa/mspm0l134x.yaml b/data/opa/mspm0l134x.yaml new file mode 100644 index 0000000..e65d99d --- /dev/null +++ b/data/opa/mspm0l134x.yaml @@ -0,0 +1,42 @@ +# What each OPA instance's input muxes select, per position. +# +# HAND-CURATED from Figure 8-1 "Analog Connections" of the MSPM0L1346 datasheet (section +# 8.24): its section 8.18 promises input channel mapping tables which are not in the +# document, and the figure is the only per-device statement. The mux position numbers +# in the figure are printed with gaps; the gaps are real. Position 0 is Open everywhere +# and not listed; a position absent here does not exist on this family and selects +# nothing - notably PSEL 2 (IN1+), 3 (DAC12), 8 (ground) and MSEL 3 (DAC12). +OPA0: + pmux: + 1: !In 0 + 4: !Dac8 0 + 5: VrefPlus + 6: !Rtop 1 + 7: Gpamp + nmux: + 1: !In 0 + 2: !In 1 + 3: !Rbot 1 + 4: OwnRtap + 5: OwnRtop + mmux: + 1: !In 1 + 2: Ground + 4: !Rtop 1 +OPA1: + pmux: + 1: !In 0 + 4: !Dac8 0 + 5: VrefPlus + 6: !Rtop 0 + 7: Gpamp + nmux: + 1: !In 0 + 2: !In 1 + 3: !Rbot 0 + 4: OwnRtap + 5: OwnRtop + mmux: + 1: !In 1 + 2: Ground + 4: !Rtop 0 diff --git a/mspm0-data-gen/src/opa.rs b/mspm0-data-gen/src/opa.rs new file mode 100644 index 0000000..717e632 --- /dev/null +++ b/mspm0-data-gen/src/opa.rs @@ -0,0 +1,21 @@ +//! Reads `data/opa/<family>.yaml`, hand-curated from the datasheets. +//! +//! The G-family files come from the "OPAx Input Channel Mapping" tables. The L-family files come +//! from Figure 8-1 "Device Analog Connections": the L1306/L1346 datasheet promises those tables +//! and does not contain them, and the figure โ€” with its gapped mux position numbering โ€” is the +//! only per-device statement. Hand-curated rather than extracted because a figure cannot be +//! parsed and the fact set is small and stable; each file cites its source. + +use std::collections::BTreeMap; + +use mspm0_data_types::Opa; + +use crate::util; + +/// One family's mapping: OPA instance name to its input-mux maps. +pub type Opas = BTreeMap<String, Opa>; + +/// Read every `data/opa/<family>.yaml`, keyed by family name. +pub fn parse() -> anyhow::Result<BTreeMap<String, Opas>> { + util::per_family("opa") +} From db143d90ecc530fc446bce19cd2c3b7e474b43f4 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 18:16:57 +0100 Subject: [PATCH 093/134] opa: carry the mux maps and VREF pin output through to the PAC metadata Peripheral::opa holds the three maps per instance, connected positions only - position 0 is Open everywhere and any other absent position selects nothing, which is the fact a consumer needs before offering an input. Vref::output_to_pin sits beside the startup time. verify holds the maps to the chip: a named peer OPA, COMP, DAC, GPAMP or VREF must exist on the part. --- mspm0-data-gen/src/generate.rs | 21 ++++++++ mspm0-data-gen/src/main.rs | 3 ++ mspm0-data-gen/src/sources.rs | 4 ++ mspm0-data-gen/src/verify.rs | 73 +++++++++++++++++++++++++ mspm0-data-types/src/lib.rs | 82 ++++++++++++++++++++++++++++ mspm0-metapac-gen/res/metadata.rs | 89 +++++++++++++++++++++++++++++++ mspm0-metapac-gen/src/metadata.rs | 61 +++++++++++++++++++-- 7 files changed, 329 insertions(+), 4 deletions(-) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index ad31e2c..ff0e3a1 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -18,6 +18,7 @@ use crate::{ adc_channels::AdcChannels, header::Header, int_group::Groups, + opa::Opas, operating_modes::OperatingModes, parts::{PartFamily, PartMemory}, perimap::PERIMAP, @@ -52,6 +53,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu operating_modes, timers, uart, + opa, errata, wake, vref, @@ -80,6 +82,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu apply_adc(family, sysconfig, adc_channels, &mut peripherals)?; apply_unicomm(family, header, &mut peripherals)?; apply_uart(family, sysconfig, uart, &mut peripherals)?; + apply_opa(opa, &mut peripherals); apply_vref(vref, &mut peripherals); for part_number in family.part_numbers.iter() { @@ -312,6 +315,7 @@ fn generate_peripherals2( adc: None, unicomm: None, uart: None, + opa: None, vref: None, }; @@ -548,6 +552,7 @@ fn generate_missing( adc: None, unicomm: None, uart: None, + opa: None, vref: None, }, ); @@ -582,6 +587,7 @@ fn generate_missing( adc: None, unicomm: None, uart: None, + opa: None, vref: None, }, ); @@ -624,6 +630,7 @@ fn generate_missing( adc: None, unicomm: None, uart: None, + opa: None, vref: None, }); @@ -689,6 +696,7 @@ fn generate_missing( adc: None, unicomm: None, uart: None, + opa: None, vref: None, }, ); @@ -1042,6 +1050,7 @@ fn apply_unicomm( adc: None, unicomm: None, uart: None, + opa: None, vref: None, }); } @@ -1054,6 +1063,18 @@ fn apply_unicomm( Ok(()) } +/// Attach each OPA instance's input-mux maps from `data/opa`. +fn apply_opa(opas: Option<&Opas>, peripherals: &mut BTreeMap<String, Peripheral>) { + for (name, peripheral) in peripherals.iter_mut() { + if peripheral.ty != PeripheralType::Opa { + continue; + } + + // Absent data is a gap verify.rs reports, like the other curated per-instance facts. + peripheral.opa = opas.and_then(|family| family.get(name)).cloned(); + } +} + /// Attach the family's VREF startup time to its VREF instance. /// /// Left absent rather than defaulted when the family has no figure: a consumer which has to wait out diff --git a/mspm0-data-gen/src/main.rs b/mspm0-data-gen/src/main.rs index d8cd77c..9f96fca 100644 --- a/mspm0-data-gen/src/main.rs +++ b/mspm0-data-gen/src/main.rs @@ -4,6 +4,7 @@ mod errata; mod generate; mod header; mod int_group; +mod opa; mod operating_modes; mod parts; mod perimap; @@ -80,6 +81,7 @@ fn main() -> anyhow::Result<()> { let operating_modes = operating_modes::parse()?; let timers = timers::parse()?; let uart = uart::parse()?; + let opa = opa::parse()?; let errata = errata::parse()?; let wake = wakeup::parse()?; let vref = vref::parse()?; @@ -98,6 +100,7 @@ fn main() -> anyhow::Result<()> { int_groups, timers, uart, + opa, errata, wake, vref, diff --git a/mspm0-data-gen/src/sources.rs b/mspm0-data-gen/src/sources.rs index f311dc8..881629c 100644 --- a/mspm0-data-gen/src/sources.rs +++ b/mspm0-data-gen/src/sources.rs @@ -14,6 +14,7 @@ use crate::{ errata::Errata, header::{Header, Headers}, int_group::Groups, + opa::Opas, operating_modes::OperatingModes, parts::PartsFile, svd::{Svd, Svds}, @@ -34,6 +35,7 @@ pub struct Sources { pub int_groups: BTreeMap<String, Groups>, pub timers: BTreeMap<String, Timers>, pub uart: BTreeMap<String, Uarts>, + pub opa: BTreeMap<String, Opas>, pub errata: BTreeMap<String, Errata>, pub wake: BTreeMap<String, WakeTimes>, pub vref: BTreeMap<String, Vref>, @@ -52,6 +54,7 @@ pub struct FamilySources<'a> { pub operating_modes: Option<&'a OperatingModes>, pub timers: Option<&'a Timers>, pub uart: Option<&'a Uarts>, + pub opa: Option<&'a Opas>, pub errata: Option<&'a Errata>, pub wake: Option<WakeTimes>, pub vref: Option<Vref>, @@ -93,6 +96,7 @@ impl Sources { operating_modes: self.operating_modes.get(family), timers: self.timers.get(family), uart: self.uart.get(family), + opa: self.opa.get(family), errata: self.errata.get(family), wake: self.wake.get(family).copied(), vref: self.vref.get(family).copied(), diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index ae94c56..479dcb2 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -16,6 +16,8 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { adc_channels_known, adc_internal_sources_exist, uart_features_known, + opa_inputs_known, + opa_input_sources_exist, // Peripherals which don't actually exist no_gpamp_c110x_l151x, // Low power data which is only as complete as the data sources @@ -184,6 +186,77 @@ fn adc_internal_sources_exist(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// Every OPA instance should say what its input muxes select. +/// +/// All four OPA-bearing families have the data โ€” from the G datasheets' mapping tables or the L +/// datasheets' analog-connections figure โ€” so an instance without it means `data/opa/<family>.yaml` +/// is missing or lost the instance. +fn opa_inputs_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + for peripheral in chip + .peripherals + .values() + .filter(|peripheral| peripheral.ty == PeripheralType::Opa) + { + if peripheral.opa.is_none() { + bail!( + "{name}: {} has no input-mux data; data/opa/{}.yaml is missing or lost the \ + instance", + peripheral.name, + chip.family + ); + } + } + + Ok(()) +} + +/// An input-mux position naming another instance requires that instance to exist. +/// +/// The same standing as `adc_internal_sources_exist`: a curated map naming a peer OPA, a COMP's +/// DAC, the 12-bit DAC, the GPAMP or the VREF is only right if the chip has it. +fn opa_input_sources_exist(chip: &Chip, name: &str) -> anyhow::Result<()> { + use mspm0_data_types::OpaInput; + + for peripheral in chip + .peripherals + .values() + .filter(|peripheral| peripheral.ty == PeripheralType::Opa) + { + let Some(opa) = &peripheral.opa else { + continue; + }; + + for (mux, map) in [ + ("PSEL", &opa.pmux), + ("NSEL", &opa.nmux), + ("MSEL", &opa.mmux), + ] { + for (position, input) in map { + let instance = match input { + OpaInput::Dac8(n) => format!("COMP{n}"), + OpaInput::Rtop(n) | OpaInput::Rbot(n) => format!("OPA{n}"), + OpaInput::Dac12 => "DAC0".to_string(), + OpaInput::Gpamp => "GPAMP".to_string(), + OpaInput::VrefPlus => "VREF".to_string(), + OpaInput::In(_) | OpaInput::OwnRtap | OpaInput::OwnRtop | OpaInput::Ground => { + continue + } + }; + + if !chip.peripherals.contains_key(&instance) { + bail!( + "{name}: {} {mux} position {position} selects {instance}, which the chip \ + does not have", + peripheral.name + ); + } + } + } + } + + Ok(()) +} + /// Every UART instance should say which extended features it has. /// /// Every datasheet so far has the "UART Features" table, so an instance without the data means diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index ea6d8c3..bcb6564 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -543,6 +543,12 @@ pub struct Peripheral { #[serde(skip_serializing_if = "Option::is_none")] pub uart: Option<Uart>, + /// What this OPA instance's input muxes select. + /// + /// `None` for peripherals which are not OPAs. + #[serde(skip_serializing_if = "Option::is_none")] + pub opa: Option<Opa>, + /// The parts of the VREF instance which the `vref_v1` register block does not describe. /// /// `None` for peripherals which are not the VREF. @@ -568,6 +574,17 @@ pub struct Vref { /// `None` when the family's datasheet has no `Tstartup` row. #[serde(skip_serializing_if = "Option::is_none")] pub startup_ns: Option<u32>, + + /// Whether the internal reference is buffered out to the `VREF+` package pin. + /// + /// True on the G families, whose datasheets state an output drive strength for the pin and + /// require the decoupling capacitor for internal-reference use. False on the C/H/L families, + /// where `VREF+` only brings an external reference in โ€” and on devices with no VREF pins at + /// all. Anything reading the `VREF+` pin node, such as an OPA input mux position routed to it, + /// sees the internal reference only where this is true; with an external reference driving the + /// pin it works regardless. + #[serde(default)] + pub output_to_pin: bool, } /// The parts of one ADC instance which the single `adc_v1` register block does not describe. @@ -735,6 +752,71 @@ pub struct Unicomm { pub spi: bool, } +/// What an OPA instance's three input muxes select at each position. +/// +/// The register block is shared, so which positions exist โ€” and which peer instance the cascade +/// positions reach โ€” comes from the datasheet: the "OPAx Input Channel Mapping" tables on the +/// G families, and the "Device Analog Connections" figure on the L families, whose datasheet +/// promises those tables and does not contain them. +/// +/// Position 0 is `Open` (deliberately no connection) on every mux of every instance and is not +/// listed. **Any other position absent from its map selects nothing**: the input floats, which on +/// the M-mux means the gain ladder pivots about a floating node and produces plausible-looking +/// wrong results. The known holes are on the L families, which lack `PSEL` 2 (`IN1+`), 3 (DAC12) +/// and 8 (ground), and `MSEL` 3 (DAC12). +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Opa { + /// The non-inverting input mux, `CFG.PSEL`. + pub pmux: BTreeMap<u8, OpaInput>, + + /// The inverting input mux, `CFG.NSEL`. + pub nmux: BTreeMap<u8, OpaInput>, + + /// The gain-ladder bottom mux, `CFG.MSEL`. + pub mmux: BTreeMap<u8, OpaInput>, +} + +/// A source an OPA input-mux position selects. +/// +/// Pin polarity follows the mux: on the P-mux [`OpaInput::In0`] is the `OPAx_IN0+` package pin, on +/// the N-mux and M-mux it is the corresponding `-` pin. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +pub enum OpaInput { + /// The instance's own `INn+`/`INn-` package pin; the payload is `n`. + In(u8), + + /// The 12-bit DAC output. It reaches the mux through the `DAC_OUT` package pin, which is also + /// the `OPAx_IN2ยฑ` input: the datasheets warn against external circuitry on that pin while the + /// DAC drives the OPA. + Dac12, + + /// The 8-bit reference DAC of the COMP instance named by the payload. On the G families + /// `OPAn` is fed by `COMPn`'s DAC; on the L families both OPAs are fed by `COMP0`'s. + Dac8(u8), + + /// The `VREF+` pin node. The internal reference reaches it only where [`Vref::output_to_pin`] + /// is true; an external reference on the pin works regardless. + VrefPlus, + + /// The gain-ladder top of the OPA instance named by the payload โ€” the cascade connection. + Rtop(u8), + + /// The gain-ladder bottom of the OPA instance named by the payload โ€” the cascade connection. + Rbot(u8), + + /// The instance's own gain-ladder tap. + OwnRtap, + + /// The instance's own gain-ladder top. + OwnRtop, + + /// The GPAMP output. + Gpamp, + + /// Ground. + Ground, +} + /// Which extended-UART features a UART instance implements. /// /// Every legacy UART shares the `uart_v1` register block and every UNICOMM UART function shares diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 9830f4b..51bd2f8 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -246,6 +246,11 @@ pub struct Peripheral { /// `None` for peripherals which are neither a UART nor a UNICOMM UART function. pub uart: Option<Uart>, + /// What this OPA instance's input muxes select. + /// + /// `None` for peripherals which are not OPAs. + pub opa: Option<Opa>, + /// The parts of the VREF instance which the register block does not describe. /// /// `None` for peripherals which are not the VREF. @@ -469,6 +474,80 @@ pub enum PowerMode { Shutdown, } +/// What an OPA instance's three input muxes select at each position, sorted by position. +/// +/// The register block is shared, so which positions exist โ€” and which peer instance the cascade +/// positions reach โ€” comes from the datasheet: the "OPAx Input Channel Mapping" tables on the +/// G families, and the "Device Analog Connections" figure on the L families. +/// +/// Position 0 is `Open` (deliberately no connection) on every mux of every instance and is not +/// listed. **Any other position absent from its map selects nothing**: the input floats, which on +/// the M-mux means the gain ladder pivots about a floating node and produces plausible-looking +/// wrong results. The known holes are on the L families, which lack `PSEL` 2 (`IN1+`), 3 (DAC12) +/// and 8 (ground), and `MSEL` 3 (DAC12). +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct Opa { + /// The non-inverting input mux, `CFG.PSEL`. + pub pmux: &'static [OpaMuxEntry], + + /// The inverting input mux, `CFG.NSEL`. + pub nmux: &'static [OpaMuxEntry], + + /// The gain-ladder bottom mux, `CFG.MSEL`. + pub mmux: &'static [OpaMuxEntry], +} + +/// One connected position of an OPA input mux. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct OpaMuxEntry { + /// The mux selector value. + pub position: u8, + + /// What the position selects. + pub input: OpaInput, +} + +/// A source an OPA input-mux position selects. +/// +/// Pin polarity follows the mux: on the P-mux `In(0)` is the `OPAx_IN0+` package pin, on the +/// N-mux and M-mux it is the corresponding `-` pin. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum OpaInput { + /// The instance's own `INn+`/`INn-` package pin; the payload is `n`. + In(u8), + + /// The 12-bit DAC output. It reaches the mux through the `DAC_OUT` package pin, which is also + /// the `OPAx_IN2ยฑ` input: the datasheets warn against external circuitry on that pin while + /// the DAC drives the OPA. + Dac12, + + /// The 8-bit reference DAC of the COMP instance named by the payload. On the G families + /// `OPAn` is fed by `COMPn`'s DAC; on the L families both OPAs are fed by `COMP0`'s. + Dac8(u8), + + /// The `VREF+` pin node. The internal reference reaches it only where `Vref::output_to_pin` + /// is true; an external reference on the pin works regardless. + VrefPlus, + + /// The gain-ladder top of the OPA instance named by the payload โ€” the cascade connection. + Rtop(u8), + + /// The gain-ladder bottom of the OPA instance named by the payload โ€” the cascade connection. + Rbot(u8), + + /// The instance's own gain-ladder tap. + OwnRtap, + + /// The instance's own gain-ladder top. + OwnRtop, + + /// The GPAMP output. + Gpamp, + + /// Ground. + Ground, +} + /// The parts of the VREF instance which the register block does not describe. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub struct Vref { @@ -486,6 +565,16 @@ pub struct Vref { /// /// `None` when the family's datasheet has no `Tstartup` row. pub startup_ns: Option<u32>, + + /// Whether the internal reference is buffered out to the `VREF+` package pin. + /// + /// True on the G families, whose datasheets state an output drive strength for the pin and + /// require the decoupling capacitor for internal-reference use. False on the C/H/L families, + /// where `VREF+` only brings an external reference in โ€” and on devices with no VREF pins at + /// all. Anything reading the `VREF+` pin node, such as an OPA input mux position routed to it, + /// sees the internal reference only where this is true; with an external reference driving + /// the pin it works regardless. + pub output_to_pin: bool, } /// The interrupt raised by a peripheral. diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 524b73e..630a2ee 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -204,6 +204,35 @@ fn adc_internal_source(source: AdcInternalSource) -> TokenStream { } } +fn opa_input(input: mspm0_data_types::OpaInput) -> TokenStream { + use mspm0_data_types::OpaInput; + + match input { + OpaInput::In(n) => { + let n = Literal::u8_unsuffixed(n); + quote! { OpaInput::In(#n) } + } + OpaInput::Dac12 => quote! { OpaInput::Dac12 }, + OpaInput::Dac8(n) => { + let n = Literal::u8_unsuffixed(n); + quote! { OpaInput::Dac8(#n) } + } + OpaInput::VrefPlus => quote! { OpaInput::VrefPlus }, + OpaInput::Rtop(n) => { + let n = Literal::u8_unsuffixed(n); + quote! { OpaInput::Rtop(#n) } + } + OpaInput::Rbot(n) => { + let n = Literal::u8_unsuffixed(n); + quote! { OpaInput::Rbot(#n) } + } + OpaInput::OwnRtap => quote! { OpaInput::OwnRtap }, + OpaInput::OwnRtop => quote! { OpaInput::OwnRtop }, + OpaInput::Gpamp => quote! { OpaInput::Gpamp }, + OpaInput::Ground => quote! { OpaInput::Ground }, + } +} + fn skip_peripheral(ty: PeripheralType) -> bool { matches!(ty, PeripheralType::Unknown) } @@ -413,10 +442,33 @@ fn generate_peripheral( None => quote! { None }, }; - let vref = match peripheral.vref.and_then(|vref| vref.startup_ns) { - Some(ns) => { - let ns = Literal::u32_unsuffixed(ns); - quote! { Some(Vref { startup_ns: Some(#ns) }) } + let opa = match &peripheral.opa { + Some(opa) => { + let mux = |map: &std::collections::BTreeMap<u8, mspm0_data_types::OpaInput>| { + let entries = map.iter().map(|(position, input)| { + let position = Literal::u8_unsuffixed(*position); + let input = opa_input(*input); + quote! { OpaMuxEntry { position: #position, input: #input } } + }); + quote! { &[#(#entries),*] } + }; + let (pmux, nmux, mmux) = (mux(&opa.pmux), mux(&opa.nmux), mux(&opa.mmux)); + quote! { Some(Opa { pmux: #pmux, nmux: #nmux, mmux: #mmux }) } + } + None => quote! { None }, + }; + + let vref = match peripheral.vref { + Some(vref) => { + let startup_ns = match vref.startup_ns { + Some(ns) => { + let ns = Literal::u32_unsuffixed(ns); + quote! { Some(#ns) } + } + None => quote! { None }, + }; + let output_to_pin = vref.output_to_pin; + quote! { Some(Vref { startup_ns: #startup_ns, output_to_pin: #output_to_pin }) } } None => quote! { None }, }; @@ -439,6 +491,7 @@ fn generate_peripheral( adc: #adc, unicomm: #unicomm, uart: #uart, + opa: #opa, vref: #vref, } }) From 526c22ba77b9fdbc4eb7a3725b4fa7f3d800da67 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 18:16:58 +0100 Subject: [PATCH 094/134] docs: list the OPA mux curation alongside the other data sources --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6f5dc74..b493410 100644 --- a/README.md +++ b/README.md @@ -61,10 +61,11 @@ These are the data sources currently used. ([`data/timers/`](./data/timers)) * How long the device takes to reach RUN from each sleep mode, from the wake-up timing table ([`data/wakeup/`](./data/wakeup)) - * How long VREF takes to settle after being enabled, from the `Tstartup` row - ([`data/vref/`](./data/vref)). `CTL1.READY` would answer it, but `VREF_ERR_01` leaves that bit - set after the first enable since reset. On an affected device the datasheet figure is the only - signal + * How long VREF takes to settle after being enabled, from the `Tstartup` row, and whether the + internal reference is buffered out to the `VREF+` pin, from the output drive-strength row + ([`data/vref/`](./data/vref)). `CTL1.READY` would answer the first, but `VREF_ERR_01` leaves + that bit set after the first enable since reset. On an affected device the datasheet figure is + the only signal * Which ADC channels sample an internal signal instead of a pin (the temperature sensor, the OPA, GPAMP and DAC outputs, the internal reference, the supply monitors), from the ADC channel-mapping table ([`data/adc_channels/`](./data/adc_channels)). The SDK states the same @@ -80,6 +81,9 @@ These are the data sources currently used. * Which functional advisories apply ([`data/errata/`](./data/errata)) * Manually entered * IIDX values for interrupts within a `INT_GROUP` + * What each OPA input-mux position selects ([`data/opa/`](./data/opa)), including which peer + instance the cascade positions reach. Hand-curated because the L-series datasheet publishes it + only as a figure โ€” one it promises as tables it does not contain * Whether the device has `MCLKCFG.UDIV` and the STOP1 sub-mode (`clock_tree` in [`parts.yaml`](./data/parts.yaml)). Neither can be keyed off the SYSCTL version: MSPM0L112x and MSPM0L211x share `sysctl_l122x_l222x` with MSPM0L122x and MSPM0L222x, but have no STOP1 From 60d3cbf82f1684546fc85fb8fd5775dabe2f41cf Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 20:59:05 +0100 Subject: [PATCH 095/134] comp: record which instances implement the REFSRC internal-reference positions Both COMP register blocks enumerate every CTL2.REFSRC value, so the PAC offers reference sources that select nothing on the older instances. Sysconfig's per-instance SYS_COMP_INT_VREF states the split and agrees with every SVD that enumerates the field and with the two datasheets that name the feature; driverlib's single-family gate does not. --- mspm0-data-gen/src/generate.rs | 35 ++++++++++++++++++++++++++++++- mspm0-data-gen/src/verify.rs | 24 +++++++++++++++++++++ mspm0-data-types/src/lib.rs | 25 ++++++++++++++++++++++ mspm0-metapac-gen/res/metadata.rs | 24 +++++++++++++++++++++ mspm0-metapac-gen/src/metadata.rs | 9 ++++++++ 5 files changed, 116 insertions(+), 1 deletion(-) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index ff0e3a1..9e4c492 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -8,7 +8,7 @@ use std::{ use anyhow::{anyhow, bail, ensure, Context}; use mspm0_data_types::{ - Adc, Chip, DmaChannel, Interrupt, Memory, MemoryKind, Package, PackagePin, Peripheral, + Adc, Chip, Comp, DmaChannel, Interrupt, Memory, MemoryKind, Package, PackagePin, Peripheral, PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, Timer, Uart, Unicomm, Vref, }; @@ -84,6 +84,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu apply_uart(family, sysconfig, uart, &mut peripherals)?; apply_opa(opa, &mut peripherals); apply_vref(vref, &mut peripherals); + apply_comp(sysconfig, &mut peripherals); for part_number in family.part_numbers.iter() { // Filter for package types available on the part number. @@ -317,6 +318,7 @@ fn generate_peripherals2( uart: None, opa: None, vref: None, + comp: None, }; // Lookup the pins @@ -554,6 +556,7 @@ fn generate_missing( uart: None, opa: None, vref: None, + comp: None, }, ); @@ -589,6 +592,7 @@ fn generate_missing( uart: None, opa: None, vref: None, + comp: None, }, ); @@ -632,6 +636,7 @@ fn generate_missing( uart: None, opa: None, vref: None, + comp: None, }); let pin = device_pin @@ -698,6 +703,7 @@ fn generate_missing( uart: None, opa: None, vref: None, + comp: None, }, ); } @@ -1052,6 +1058,7 @@ fn apply_unicomm( uart: None, opa: None, vref: None, + comp: None, }); } } @@ -1093,6 +1100,32 @@ fn apply_vref(vref: Option<Vref>, peripherals: &mut BTreeMap<String, Peripheral> } } +/// Attach whether each COMP instance implements the `CTL2.REFSRC` internal-reference positions. +/// +/// `SYS_COMP_INT_VREF` is present (as `"1"`) exactly on the instances which have them and absent +/// otherwise. It agrees with every SVD which enumerates `REFSRC` โ€” the five newer-generation SVDs +/// list values 5 through 7 and the four older ones stop at 3 โ€” and with the two datasheets which +/// state the feature in prose (the L1228's "dedicated internal reference", the G5187's "internal +/// VREF1"), so unlike `SYS_LIN_EN` it is safe to read directly. driverlib is *not* a cross-check: +/// its `DL_COMP_REF_SOURCE_INT_VREF` gate names only L122X_L222X, against both of the above. +fn apply_comp(sysconfig: &SysconfigFile, peripherals: &mut BTreeMap<String, Peripheral>) { + for peripheral in sysconfig.peripherals.values() { + let name = maybe_rename(&peripheral.name); + let Some(comp) = peripherals.get_mut(&name) else { + continue; + }; + if comp.ty != PeripheralType::Comp { + continue; + } + + let int_vref = peripheral + .attributes + .get("SYS_COMP_INT_VREF") + .is_some_and(|value| value.as_str() == Some("1")); + comp.comp = Some(Comp { int_vref }); + } +} + /// Attach each UART instance's extended-feature flags from `data/uart`. fn apply_uart( family: &PartFamily, diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 479dcb2..edd2c0f 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -18,6 +18,7 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { uart_features_known, opa_inputs_known, opa_input_sources_exist, + comp_features_known, // Peripherals which don't actually exist no_gpamp_c110x_l151x, // Low power data which is only as complete as the data sources @@ -281,6 +282,29 @@ fn uart_features_known(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// Every comparator states whether it has the `CTL2.REFSRC` internal-reference positions. +/// +/// The data comes from sysconfig's `SYS_COMP_INT_VREF`, which every family's metadata carries per +/// instance, so an instance without it means the attribute moved rather than that the answer is +/// unknown. +fn comp_features_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + for peripheral in chip + .peripherals + .values() + .filter(|peripheral| peripheral.ty == PeripheralType::Comp) + { + if peripheral.comp.is_none() { + bail!( + "{name}: {} has no comparator data; SYS_COMP_INT_VREF was not found on the \ + sysconfig instance", + peripheral.name, + ); + } + } + + Ok(()) +} + /// Verify all core peripherals are present. fn core_peripherals(chip: &Chip, name: &str) -> anyhow::Result<()> { if !chip.peripherals.contains_key("CPUSS") { diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index bcb6564..8f5b68e 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -554,6 +554,31 @@ pub struct Peripheral { /// `None` for peripherals which are not the VREF. #[serde(skip_serializing_if = "Option::is_none")] pub vref: Option<Vref>, + + /// The parts of this COMP instance which its register block does not describe. + /// + /// `None` for peripherals which are not comparators. + #[serde(skip_serializing_if = "Option::is_none")] + pub comp: Option<Comp>, +} + +/// The parts of one COMP instance which its register block does not describe. +/// +/// Both COMP register blocks enumerate every `CTL2.REFSRC` value the IP has ever had, so on its own +/// the PAC offers reference sources some devices do not implement โ€” the same shape as the OPA mux +/// positions, where a selection that does not exist selects nothing and reads as a healthy +/// configuration. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Comp { + /// Whether `CTL2.REFSRC` positions 5 (`VDDA`), 6 (`INTVREF_DAC`) and 7 (`INTVREF`) select a + /// source on this instance. The three come and go together; positions 0 through 3 exist on + /// every comparator, and 4 is reserved everywhere. + /// + /// When false, writing 5 through 7 selects no reference. The internal-reference positions are + /// the only way to a reference in modes where the VREF module is unavailable; when this is + /// false, `REFSRC` 2 and 3 reach the VREF module's output instead, which follows the VREF + /// configuration (internal where the device generates one, the `VREF+`/`VREF-` pins otherwise). + pub int_vref: bool, } /// The parts of the VREF instance which the single `vref_v1` register block does not describe. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 51bd2f8..5373ce6 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -255,6 +255,30 @@ pub struct Peripheral { /// /// `None` for peripherals which are not the VREF. pub vref: Option<Vref>, + + /// The parts of this COMP instance which its register block does not describe. + /// + /// `None` for peripherals which are not comparators. + pub comp: Option<Comp>, +} + +/// The parts of one COMP instance which its register block does not describe. +/// +/// Both COMP register blocks enumerate every `CTL2.REFSRC` value the IP has ever had, so on its own +/// the PAC offers reference sources some devices do not implement โ€” the same shape as the OPA mux +/// positions, where a selection that does not exist selects nothing and reads as a healthy +/// configuration. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct Comp { + /// Whether `CTL2.REFSRC` positions 5 (`VDDA`), 6 (`INTVREF_DAC`) and 7 (`INTVREF`) select a + /// source on this instance. The three come and go together; positions 0 through 3 exist on + /// every comparator, and 4 is reserved everywhere. + /// + /// When false, writing 5 through 7 selects no reference. The internal-reference positions are + /// the only way to a reference in modes where the VREF module is unavailable; when this is + /// false, `REFSRC` 2 and 3 reach the VREF module's output instead, which follows the VREF + /// configuration (internal where the device generates one, the `VREF+`/`VREF-` pins otherwise). + pub int_vref: bool, } /// Which extended-UART features a UART instance implements. diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 630a2ee..a01d1cf 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -473,6 +473,14 @@ fn generate_peripheral( None => quote! { None }, }; + let comp = match peripheral.comp { + Some(comp) => { + let int_vref = comp.int_vref; + quote! { Some(Comp { int_vref: #int_vref }) } + } + None => quote! { None }, + }; + Some(quote! { Peripheral { name: #name, @@ -493,6 +501,7 @@ fn generate_peripheral( uart: #uart, opa: #opa, vref: #vref, + comp: #comp, } }) } From 46c46976610fa7fa53ca807283c959c1f20907d1 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 21:44:41 +0100 Subject: [PATCH 096/134] comp: extract the timing figures from the datasheets Neither the enable time nor the reference DAC's settling time has a status bit behind it, so the datasheet rows are the only way to know the output is meaningful. The enable time is split per CTL1.MODE: the text layout hides that high-speed mode is 5us on the newer comparators, and the pin-loaded settling row exists exactly on the families whose COMP drives a pin. --- data/comp/mspm0c1105_c1106.yaml | 15 ++ data/comp/mspm0g150x.yaml | 13 ++ data/comp/mspm0g151x.yaml | 13 ++ data/comp/mspm0g350x.yaml | 13 ++ data/comp/mspm0g351x.yaml | 13 ++ data/comp/mspm0g518x.yaml | 15 ++ data/comp/mspm0l112x.yaml | 15 ++ data/comp/mspm0l122x.yaml | 13 ++ data/comp/mspm0l130x.yaml | 13 ++ data/comp/mspm0l134x.yaml | 13 ++ data/comp/mspm0l211x.yaml | 15 ++ data/comp/mspm0l222x.yaml | 13 ++ tools/comp.py | 238 ++++++++++++++++++++++++++++++++ 13 files changed, 402 insertions(+) create mode 100644 data/comp/mspm0c1105_c1106.yaml create mode 100644 data/comp/mspm0g150x.yaml create mode 100644 data/comp/mspm0g151x.yaml create mode 100644 data/comp/mspm0g350x.yaml create mode 100644 data/comp/mspm0g351x.yaml create mode 100644 data/comp/mspm0g518x.yaml create mode 100644 data/comp/mspm0l112x.yaml create mode 100644 data/comp/mspm0l122x.yaml create mode 100644 data/comp/mspm0l130x.yaml create mode 100644 data/comp/mspm0l134x.yaml create mode 100644 data/comp/mspm0l211x.yaml create mode 100644 data/comp/mspm0l222x.yaml create mode 100644 tools/comp.py diff --git a/data/comp/mspm0c1105_c1106.yaml b/data/comp/mspm0c1105_c1106.yaml new file mode 100644 index 0000000..6cd1a60 --- /dev/null +++ b/data/comp/mspm0c1105_c1106.yaml @@ -0,0 +1,15 @@ +# The comparator timing figures, in nanoseconds. +# +# GENERATED by `tools/comp.py --write` from the `ten` and `tdac_settle` rows of the MSPM0C1106 +# datasheet. Re-run the tool rather than editing by hand. +# +# Neither figure has a status bit behind it, so waiting them out is the only way to know the +# comparator output or the reference DAC is meaningful. Each figure's cell spans the datasheet's +# MIN, TYP and MAX columns, so it is a stated figure rather than a guaranteed ceiling. +# Startup to the propagation-delay specification, per CTL1.MODE. +enable_fast_ns: 5000 +enable_ulp_ns: 10000 +# Full-scale DACCODE step to 1 LSB, unloaded. The internal path. +dac_settle_ns: 1500 +# The same step driven out on a pin (CTL1.DACOUTEN), Cload = 15pF. +dac_settle_pin_ns: 6000 diff --git a/data/comp/mspm0g150x.yaml b/data/comp/mspm0g150x.yaml new file mode 100644 index 0000000..208a6af --- /dev/null +++ b/data/comp/mspm0g150x.yaml @@ -0,0 +1,13 @@ +# The comparator timing figures, in nanoseconds. +# +# GENERATED by `tools/comp.py --write` from the `ten` and `tdac_settle` rows of the MSPM0G1507 +# datasheet. Re-run the tool rather than editing by hand. +# +# Neither figure has a status bit behind it, so waiting them out is the only way to know the +# comparator output or the reference DAC is meaningful. Each figure's cell spans the datasheet's +# MIN, TYP and MAX columns, so it is a stated figure rather than a guaranteed ceiling. +# Startup to the propagation-delay specification, per CTL1.MODE. +enable_fast_ns: 10000 +enable_ulp_ns: 10000 +# Full-scale DACCODE step to 1 LSB, unloaded. The internal path. +dac_settle_ns: 1500 diff --git a/data/comp/mspm0g151x.yaml b/data/comp/mspm0g151x.yaml new file mode 100644 index 0000000..f09c772 --- /dev/null +++ b/data/comp/mspm0g151x.yaml @@ -0,0 +1,13 @@ +# The comparator timing figures, in nanoseconds. +# +# GENERATED by `tools/comp.py --write` from the `ten` and `tdac_settle` rows of the MSPM0G1519 +# datasheet. Re-run the tool rather than editing by hand. +# +# Neither figure has a status bit behind it, so waiting them out is the only way to know the +# comparator output or the reference DAC is meaningful. Each figure's cell spans the datasheet's +# MIN, TYP and MAX columns, so it is a stated figure rather than a guaranteed ceiling. +# Startup to the propagation-delay specification, per CTL1.MODE. +enable_fast_ns: 5000 +enable_ulp_ns: 10000 +# Full-scale DACCODE step to 1 LSB, unloaded. The internal path. +dac_settle_ns: 1500 diff --git a/data/comp/mspm0g350x.yaml b/data/comp/mspm0g350x.yaml new file mode 100644 index 0000000..89bc541 --- /dev/null +++ b/data/comp/mspm0g350x.yaml @@ -0,0 +1,13 @@ +# The comparator timing figures, in nanoseconds. +# +# GENERATED by `tools/comp.py --write` from the `ten` and `tdac_settle` rows of the MSPM0G3507 +# datasheet. Re-run the tool rather than editing by hand. +# +# Neither figure has a status bit behind it, so waiting them out is the only way to know the +# comparator output or the reference DAC is meaningful. Each figure's cell spans the datasheet's +# MIN, TYP and MAX columns, so it is a stated figure rather than a guaranteed ceiling. +# Startup to the propagation-delay specification, per CTL1.MODE. +enable_fast_ns: 10000 +enable_ulp_ns: 10000 +# Full-scale DACCODE step to 1 LSB, unloaded. The internal path. +dac_settle_ns: 1500 diff --git a/data/comp/mspm0g351x.yaml b/data/comp/mspm0g351x.yaml new file mode 100644 index 0000000..93f5001 --- /dev/null +++ b/data/comp/mspm0g351x.yaml @@ -0,0 +1,13 @@ +# The comparator timing figures, in nanoseconds. +# +# GENERATED by `tools/comp.py --write` from the `ten` and `tdac_settle` rows of the MSPM0G3519 +# datasheet. Re-run the tool rather than editing by hand. +# +# Neither figure has a status bit behind it, so waiting them out is the only way to know the +# comparator output or the reference DAC is meaningful. Each figure's cell spans the datasheet's +# MIN, TYP and MAX columns, so it is a stated figure rather than a guaranteed ceiling. +# Startup to the propagation-delay specification, per CTL1.MODE. +enable_fast_ns: 5000 +enable_ulp_ns: 10000 +# Full-scale DACCODE step to 1 LSB, unloaded. The internal path. +dac_settle_ns: 1500 diff --git a/data/comp/mspm0g518x.yaml b/data/comp/mspm0g518x.yaml new file mode 100644 index 0000000..837b8d8 --- /dev/null +++ b/data/comp/mspm0g518x.yaml @@ -0,0 +1,15 @@ +# The comparator timing figures, in nanoseconds. +# +# GENERATED by `tools/comp.py --write` from the `ten` and `tdac_settle` rows of the MSPM0G5187 +# datasheet. Re-run the tool rather than editing by hand. +# +# Neither figure has a status bit behind it, so waiting them out is the only way to know the +# comparator output or the reference DAC is meaningful. Each figure's cell spans the datasheet's +# MIN, TYP and MAX columns, so it is a stated figure rather than a guaranteed ceiling. +# Startup to the propagation-delay specification, per CTL1.MODE. +enable_fast_ns: 5000 +enable_ulp_ns: 10000 +# Full-scale DACCODE step to 1 LSB, unloaded. The internal path. +dac_settle_ns: 1500 +# The same step driven out on a pin (CTL1.DACOUTEN), Cload = 15pF. +dac_settle_pin_ns: 6000 diff --git a/data/comp/mspm0l112x.yaml b/data/comp/mspm0l112x.yaml new file mode 100644 index 0000000..1693d5d --- /dev/null +++ b/data/comp/mspm0l112x.yaml @@ -0,0 +1,15 @@ +# The comparator timing figures, in nanoseconds. +# +# GENERATED by `tools/comp.py --write` from the `ten` and `tdac_settle` rows of the MSPM0L2117 +# datasheet. Re-run the tool rather than editing by hand. +# +# Neither figure has a status bit behind it, so waiting them out is the only way to know the +# comparator output or the reference DAC is meaningful. Each figure's cell spans the datasheet's +# MIN, TYP and MAX columns, so it is a stated figure rather than a guaranteed ceiling. +# Startup to the propagation-delay specification, per CTL1.MODE. +enable_fast_ns: 5000 +enable_ulp_ns: 10000 +# Full-scale DACCODE step to 1 LSB, unloaded. The internal path. +dac_settle_ns: 1500 +# The same step driven out on a pin (CTL1.DACOUTEN), Cload = 15pF. +dac_settle_pin_ns: 6000 diff --git a/data/comp/mspm0l122x.yaml b/data/comp/mspm0l122x.yaml new file mode 100644 index 0000000..ba601f9 --- /dev/null +++ b/data/comp/mspm0l122x.yaml @@ -0,0 +1,13 @@ +# The comparator timing figures, in nanoseconds. +# +# GENERATED by `tools/comp.py --write` from the `ten` and `tdac_settle` rows of the MSPM0L1228 +# datasheet. Re-run the tool rather than editing by hand. +# +# Neither figure has a status bit behind it, so waiting them out is the only way to know the +# comparator output or the reference DAC is meaningful. Each figure's cell spans the datasheet's +# MIN, TYP and MAX columns, so it is a stated figure rather than a guaranteed ceiling. +# Startup to the propagation-delay specification, per CTL1.MODE. +enable_fast_ns: 5000 +enable_ulp_ns: 10000 +# Full-scale DACCODE step to 1 LSB, unloaded. The internal path. +dac_settle_ns: 1500 diff --git a/data/comp/mspm0l130x.yaml b/data/comp/mspm0l130x.yaml new file mode 100644 index 0000000..a436545 --- /dev/null +++ b/data/comp/mspm0l130x.yaml @@ -0,0 +1,13 @@ +# The comparator timing figures, in nanoseconds. +# +# GENERATED by `tools/comp.py --write` from the `ten` and `tdac_settle` rows of the MSPM0L1306 +# datasheet. Re-run the tool rather than editing by hand. +# +# Neither figure has a status bit behind it, so waiting them out is the only way to know the +# comparator output or the reference DAC is meaningful. Each figure's cell spans the datasheet's +# MIN, TYP and MAX columns, so it is a stated figure rather than a guaranteed ceiling. +# Startup to the propagation-delay specification, per CTL1.MODE. +enable_fast_ns: 10000 +enable_ulp_ns: 10000 +# Full-scale DACCODE step to 1 LSB, unloaded. The internal path. +dac_settle_ns: 1500 diff --git a/data/comp/mspm0l134x.yaml b/data/comp/mspm0l134x.yaml new file mode 100644 index 0000000..617df36 --- /dev/null +++ b/data/comp/mspm0l134x.yaml @@ -0,0 +1,13 @@ +# The comparator timing figures, in nanoseconds. +# +# GENERATED by `tools/comp.py --write` from the `ten` and `tdac_settle` rows of the MSPM0L1346 +# datasheet. Re-run the tool rather than editing by hand. +# +# Neither figure has a status bit behind it, so waiting them out is the only way to know the +# comparator output or the reference DAC is meaningful. Each figure's cell spans the datasheet's +# MIN, TYP and MAX columns, so it is a stated figure rather than a guaranteed ceiling. +# Startup to the propagation-delay specification, per CTL1.MODE. +enable_fast_ns: 10000 +enable_ulp_ns: 10000 +# Full-scale DACCODE step to 1 LSB, unloaded. The internal path. +dac_settle_ns: 1500 diff --git a/data/comp/mspm0l211x.yaml b/data/comp/mspm0l211x.yaml new file mode 100644 index 0000000..1693d5d --- /dev/null +++ b/data/comp/mspm0l211x.yaml @@ -0,0 +1,15 @@ +# The comparator timing figures, in nanoseconds. +# +# GENERATED by `tools/comp.py --write` from the `ten` and `tdac_settle` rows of the MSPM0L2117 +# datasheet. Re-run the tool rather than editing by hand. +# +# Neither figure has a status bit behind it, so waiting them out is the only way to know the +# comparator output or the reference DAC is meaningful. Each figure's cell spans the datasheet's +# MIN, TYP and MAX columns, so it is a stated figure rather than a guaranteed ceiling. +# Startup to the propagation-delay specification, per CTL1.MODE. +enable_fast_ns: 5000 +enable_ulp_ns: 10000 +# Full-scale DACCODE step to 1 LSB, unloaded. The internal path. +dac_settle_ns: 1500 +# The same step driven out on a pin (CTL1.DACOUTEN), Cload = 15pF. +dac_settle_pin_ns: 6000 diff --git a/data/comp/mspm0l222x.yaml b/data/comp/mspm0l222x.yaml new file mode 100644 index 0000000..7472747 --- /dev/null +++ b/data/comp/mspm0l222x.yaml @@ -0,0 +1,13 @@ +# The comparator timing figures, in nanoseconds. +# +# GENERATED by `tools/comp.py --write` from the `ten` and `tdac_settle` rows of the MSPM0L2228 +# datasheet. Re-run the tool rather than editing by hand. +# +# Neither figure has a status bit behind it, so waiting them out is the only way to know the +# comparator output or the reference DAC is meaningful. Each figure's cell spans the datasheet's +# MIN, TYP and MAX columns, so it is a stated figure rather than a guaranteed ceiling. +# Startup to the propagation-delay specification, per CTL1.MODE. +enable_fast_ns: 5000 +enable_ulp_ns: 10000 +# Full-scale DACCODE step to 1 LSB, unloaded. The internal path. +dac_settle_ns: 1500 diff --git a/tools/comp.py b/tools/comp.py new file mode 100644 index 0000000..fcd61c4 --- /dev/null +++ b/tools/comp.py @@ -0,0 +1,238 @@ +"""Extract the comparator timing figures from MSPM0 datasheets. + +The COMP has no status bit behind either figure: nothing reports that the comparator has reached +its propagation-delay specification after `CTL1.ENABLE`, or that the 8-bit reference DAC has +settled after a code change. The datasheet's `ten` and `tdac_settle` rows are the only way to know, +so a consumer waits them out the same way `Vref::startup_ns` is waited out on devices where +`VREF_ERR_01` breaks `CTL1.READY`. This writes `data/comp/<family>.yaml`. + +Like `tools/vref.py`, this is an offline aid rather than part of the build. Re-run it after a data +source bump rather than editing the YAML by hand. + +Usage: + uv run tools/comp.py <datasheet.pdf> [...] # print what it finds + uv run tools/comp.py --write <dir-of-datasheets> # regenerate data/comp + +`uv run` installs the dependencies below on its own; a bare `python` needs them on the path. + +What the rows look like, and what this reads out of them: + +- **`ten` is two rows sharing one symbol cell**, one condition per comparator mode. The figures + genuinely differ per family: high-speed mode reaches its specification in 5us on the + newer-generation comparators and 10us on the older, while low-power mode is 10us everywhere. + The mode is read from the condition text, not the row order. +- **`tdac_settle` is stated once, or twice on the devices whose DAC reaches a pin.** The bare row + (to 1 LSB, unloaded) is the internal path -- the one an OPA or the comparator itself sees. The + second row, `DAC output on pin PA11, Cload = 15pF`, exists exactly on the datasheets whose COMP + has `CTL1.DACOUTEN`, and only applies when that bit drives the pin. +- **Each figure's cell spans the MIN, TYP and MAX columns**, as the wake-up and VREF rows do, so + they are stated figures rather than guaranteed ceilings. + +A family whose datasheet has no `Comparator enable time` row is skipped without complaint -- that +is what a family without a comparator looks like. A COMP instance with no timing data is reported +by `verify.rs` instead, which knows which chips have one. +""" + +# /// script +# requires-python = ">=3.10" +# dependencies = ["pdfplumber", "pypdfium2", "pyyaml"] +# /// + +import re +import sys +from pathlib import Path + +try: + import pdfplumber + import pypdfium2 + import yaml +except ImportError as e: # pragma: no cover + raise SystemExit(f"{e.name} is missing; run this with `uv run` instead of `python`") + +ENABLE = "Comparator enable time" +DAC_SETTLE = "8-bit DAC settling time" + +LATTICE = {"vertical_strategy": "lines", "horizontal_strategy": "lines"} + +#: Time units the figures may be given in, as a multiplier to nanoseconds. +UNITS = {"ns": 1, "us": 1_000, "ยตs": 1_000, "ms": 1_000_000} + +NUMBER = re.compile(r"\d+(?:\.\d+)?") + +#: Condition-text fragment to the `CTL1.MODE` value it describes. +MODES = {"high speed": "fast", "low power": "ulp"} + + +def caption_pages(path: Path) -> list[int]: + """Indices of the pages carrying either timing row.""" + doc = pypdfium2.PdfDocument(str(path)) + try: + return [ + i + for i in range(len(doc)) + if any(c in doc[i].get_textpage().get_text_range() for c in (ENABLE, DAC_SETTLE)) + ] + finally: + doc.close() + + +def read_row(cells: list[str]) -> tuple[int, str] | None: + """Read one row into `(nanoseconds, condition text)`, or `None` if it does not parse.""" + unit = next((c for c in reversed(cells) if c and c.lower() in UNITS), "") + if not unit: + return None + + condition = "" + value = None + for cell in cells: + stripped = cell.replace(ENABLE, "").replace(DAC_SETTLE + " in static mode", "").strip() + if NUMBER.fullmatch(stripped): + value = float(stripped) + break + if stripped and not stripped.startswith("t"): + condition = f"{condition} {stripped}".strip() + + if value is None: + return None + + return round(value * UNITS[unit.lower()]), condition + + +def read_tables(path: Path) -> tuple[dict[str, int], list[str]]: + """Extract the timing figures from one datasheet. + + Returns the figures keyed by YAML field name, and a list of problems. The `ten` and + `tdac_settle` blocks each span rows -- the symbol cell is stated once and the continuation rows + carry only a condition and a figure -- so rows are attributed to the block opened above them + until a row names a different symbol. + """ + found: dict[str, int] = {} + problems: list[str] = [] + + def record(key: str, ns: int, condition: str) -> None: + if found.get(key, ns) != ns: + problems.append(f"{key} stated twice with different figures: {found[key]} and {ns} ns") + found[key] = ns + + with pdfplumber.open(str(path)) as pdf: + for page_index in caption_pages(path): + for table in pdf.pages[page_index].extract_tables(LATTICE): + block = None + for row in table: + cells = [(c or "").replace("\n", " ").strip() for c in row] + text = " ".join(cells) + + if ENABLE in text: + block = "enable" + elif DAC_SETTLE in text: + block = "dac" + elif any(cells[:2]): + # A row with its own symbol or description belongs to another parameter. + block = None + continue + + if block is None: + continue + + parsed = read_row(cells) + if parsed is None: + continue + ns, condition = parsed + + if block == "enable": + mode = next( + (m for frag, m in MODES.items() if frag in condition.lower()), None + ) + if mode is None: + problems.append(f"enable-time condition names no mode: {condition!r}") + else: + record(f"enable_{mode}_ns", ns, condition) + else: + key = "dac_settle_pin_ns" if "pin" in condition.lower() else "dac_settle_ns" + record(key, ns, condition) + + if found: + for key in ("enable_fast_ns", "enable_ulp_ns", "dac_settle_ns"): + if key not in found: + problems.append(f"{key} not found") + + return found, problems + + +HEADER = """\ +# The comparator timing figures, in nanoseconds. +# +# GENERATED by `tools/comp.py --write` from the `ten` and `tdac_settle` rows of the {ds} +# datasheet. Re-run the tool rather than editing by hand. +# +# Neither figure has a status bit behind it, so waiting them out is the only way to know the +# comparator output or the reference DAC is meaningful. Each figure's cell spans the datasheet's +# MIN, TYP and MAX columns, so it is a stated figure rather than a guaranteed ceiling. +""" + +FIELD_COMMENTS = { + "enable_fast_ns": "# Startup to the propagation-delay specification, per CTL1.MODE.", + "dac_settle_ns": "# Full-scale DACCODE step to 1 LSB, unloaded. The internal path.", + "dac_settle_pin_ns": "# The same step driven out on a pin (CTL1.DACOUTEN), Cload = 15pF.", +} + + +def write(datasheets: str) -> int: + """Regenerate data/comp/*.yaml. Returns the number of problems reported.""" + parts = yaml.safe_load(Path("data/parts.yaml").read_text(encoding="utf-8")) + families = [(f["family"], f["datasheet_url"].rsplit("/", 1)[-1]) for f in parts["families"]] + + Path("data/comp").mkdir(parents=True, exist_ok=True) + problem_count = 0 + + for family, gpn in families: + pdf = Path(datasheets) / f"{gpn}_datasheet.pdf" + if not pdf.exists(): + print(f"{family}: no datasheet for {gpn} in {datasheets}") + problem_count += 1 + continue + + found, problems = read_tables(pdf) + for problem in problems: + print(f"{family}: {problem}") + problem_count += len(problems) + + if not found: + print(f"{family}: no comparator timing rows in {pdf.name}") + continue + + path = Path(f"data/comp/{family}.yaml") + with open(path, "w", encoding="utf-8", newline="\n") as out: + out.write(HEADER.format(ds=gpn.upper())) + for key in ("enable_fast_ns", "enable_ulp_ns", "dac_settle_ns", "dac_settle_pin_ns"): + if key not in found: + continue + comment = FIELD_COMMENTS.get(key) + if comment: + out.write(f"{comment}\n") + out.write(f"{key}: {found[key]}\n") + print(f"{family}: {found} from {pdf.name}") + + return problem_count + + +def main(argv: list[str]) -> None: + if "--write" in argv: + i = argv.index("--write") + raise SystemExit(1 if write(argv[i + 1] if i + 1 < len(argv) else ".") else 0) + + paths = [Path(a) for a in argv if not a.startswith("--")] + if not paths: + raise SystemExit(__doc__) + + for path in paths: + print(f"########## {path.name}") + found, problems = read_tables(path) + for key, ns in found.items(): + print(f" {key}: {ns / 1000:g} us") + for problem in problems: + print(f" PROBLEM: {problem}") + + +if __name__ == "__main__": + main(sys.argv[1:]) From 802dd9eb4b74142dea31619d11e05cfb70686fd8 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 21:44:41 +0100 Subject: [PATCH 097/134] comp: carry the timing figures through to the PAC metadata --- mspm0-data-gen/src/comp.rs | 27 ++++++++++++++++++++++++ mspm0-data-gen/src/generate.rs | 23 ++++++++++++++++---- mspm0-data-gen/src/main.rs | 3 +++ mspm0-data-gen/src/sources.rs | 4 ++++ mspm0-data-gen/src/verify.rs | 22 +++++++++++++++---- mspm0-data-types/src/lib.rs | 35 +++++++++++++++++++++++++++++++ mspm0-metapac-gen/res/metadata.rs | 31 +++++++++++++++++++++++++++ mspm0-metapac-gen/src/metadata.rs | 19 ++++++++++++++++- 8 files changed, 155 insertions(+), 9 deletions(-) create mode 100644 mspm0-data-gen/src/comp.rs diff --git a/mspm0-data-gen/src/comp.rs b/mspm0-data-gen/src/comp.rs new file mode 100644 index 0000000..5fa5ca2 --- /dev/null +++ b/mspm0-data-gen/src/comp.rs @@ -0,0 +1,27 @@ +//! Reads `data/comp/<family>.yaml`, which `tools/comp.py` extracts from the datasheets. +//! +//! Neither comparator timing figure has a status bit behind it: nothing reports that the +//! comparator has reached its propagation-delay specification after `CTL1.ENABLE`, or that the +//! reference DAC has settled after a code change. The datasheet rows are the only source, which is +//! why figures with no machine-readable origin are worth transcribing. `Comp::int_vref` is not +//! here โ€” it comes from sysconfig, per instance, in `apply_comp`. + +use std::collections::BTreeMap; + +use serde::Deserialize; + +use crate::util; + +/// The per-family timing figures, merged with sysconfig's per-instance facts in `apply_comp`. +#[derive(Debug, Clone, Copy, Deserialize)] +pub struct CompTiming { + pub enable_fast_ns: Option<u32>, + pub enable_ulp_ns: Option<u32>, + pub dac_settle_ns: Option<u32>, + pub dac_settle_pin_ns: Option<u32>, +} + +/// Read every `data/comp/<family>.yaml`, keyed by family name. +pub fn parse() -> anyhow::Result<BTreeMap<String, CompTiming>> { + util::per_family("comp") +} diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 9e4c492..432b0a6 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -14,6 +14,7 @@ use mspm0_data_types::{ }; use regex::Regex; +use crate::comp::CompTiming; use crate::{ adc_channels::AdcChannels, header::Header, @@ -57,6 +58,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu errata, wake, vref, + comp, int_groups, } = *sources; @@ -84,7 +86,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu apply_uart(family, sysconfig, uart, &mut peripherals)?; apply_opa(opa, &mut peripherals); apply_vref(vref, &mut peripherals); - apply_comp(sysconfig, &mut peripherals); + apply_comp(sysconfig, comp, &mut peripherals); for part_number in family.part_numbers.iter() { // Filter for package types available on the part number. @@ -1100,7 +1102,8 @@ fn apply_vref(vref: Option<Vref>, peripherals: &mut BTreeMap<String, Peripheral> } } -/// Attach whether each COMP instance implements the `CTL2.REFSRC` internal-reference positions. +/// Attach each COMP instance's facts: whether it implements the `CTL2.REFSRC` internal-reference +/// positions, and the family's timing figures from `data/comp`. /// /// `SYS_COMP_INT_VREF` is present (as `"1"`) exactly on the instances which have them and absent /// otherwise. It agrees with every SVD which enumerates `REFSRC` โ€” the five newer-generation SVDs @@ -1108,7 +1111,11 @@ fn apply_vref(vref: Option<Vref>, peripherals: &mut BTreeMap<String, Peripheral> /// state the feature in prose (the L1228's "dedicated internal reference", the G5187's "internal /// VREF1"), so unlike `SYS_LIN_EN` it is safe to read directly. driverlib is *not* a cross-check: /// its `DL_COMP_REF_SOURCE_INT_VREF` gate names only L122X_L222X, against both of the above. -fn apply_comp(sysconfig: &SysconfigFile, peripherals: &mut BTreeMap<String, Peripheral>) { +fn apply_comp( + sysconfig: &SysconfigFile, + timing: Option<CompTiming>, + peripherals: &mut BTreeMap<String, Peripheral>, +) { for peripheral in sysconfig.peripherals.values() { let name = maybe_rename(&peripheral.name); let Some(comp) = peripherals.get_mut(&name) else { @@ -1122,7 +1129,15 @@ fn apply_comp(sysconfig: &SysconfigFile, peripherals: &mut BTreeMap<String, Peri .attributes .get("SYS_COMP_INT_VREF") .is_some_and(|value| value.as_str() == Some("1")); - comp.comp = Some(Comp { int_vref }); + + // Absent timing is a gap verify.rs reports, like the VREF startup figure. + comp.comp = Some(Comp { + int_vref, + enable_fast_ns: timing.and_then(|t| t.enable_fast_ns), + enable_ulp_ns: timing.and_then(|t| t.enable_ulp_ns), + dac_settle_ns: timing.and_then(|t| t.dac_settle_ns), + dac_settle_pin_ns: timing.and_then(|t| t.dac_settle_pin_ns), + }); } } diff --git a/mspm0-data-gen/src/main.rs b/mspm0-data-gen/src/main.rs index 9f96fca..f166b15 100644 --- a/mspm0-data-gen/src/main.rs +++ b/mspm0-data-gen/src/main.rs @@ -1,5 +1,6 @@ mod adc_channels; mod clock_tree; +mod comp; mod errata; mod generate; mod header; @@ -85,6 +86,7 @@ fn main() -> anyhow::Result<()> { let errata = errata::parse()?; let wake = wakeup::parse()?; let vref = vref::parse()?; + let comp = comp::parse()?; let parts = parts::PartsFile::read()?; // TODO: Expanded family names (ex. C110X -> C1103 & C1104) @@ -104,6 +106,7 @@ fn main() -> anyhow::Result<()> { errata, wake, vref, + comp, }; stopwatch.section("Generate data"); diff --git a/mspm0-data-gen/src/sources.rs b/mspm0-data-gen/src/sources.rs index 881629c..e28af86 100644 --- a/mspm0-data-gen/src/sources.rs +++ b/mspm0-data-gen/src/sources.rs @@ -11,6 +11,7 @@ use mspm0_data_types::{Vref, WakeTimes}; use crate::{ adc_channels::AdcChannels, clock_tree::{ClockTreeFile, ClockTrees}, + comp::CompTiming, errata::Errata, header::{Header, Headers}, int_group::Groups, @@ -39,6 +40,7 @@ pub struct Sources { pub errata: BTreeMap<String, Errata>, pub wake: BTreeMap<String, WakeTimes>, pub vref: BTreeMap<String, Vref>, + pub comp: BTreeMap<String, CompTiming>, } /// What one family is described by. @@ -58,6 +60,7 @@ pub struct FamilySources<'a> { pub errata: Option<&'a Errata>, pub wake: Option<WakeTimes>, pub vref: Option<Vref>, + pub comp: Option<CompTiming>, /// Not narrowed to the family: `generate_irqs` looks groups up per chip, not per family. pub int_groups: &'a BTreeMap<String, Groups>, @@ -100,6 +103,7 @@ impl Sources { errata: self.errata.get(family), wake: self.wake.get(family).copied(), vref: self.vref.get(family).copied(), + comp: self.comp.get(family).copied(), int_groups: &self.int_groups, }) } diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index edd2c0f..4bbd38a 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -282,23 +282,37 @@ fn uart_features_known(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } -/// Every comparator states whether it has the `CTL2.REFSRC` internal-reference positions. +/// Every comparator states whether it has the `CTL2.REFSRC` internal-reference positions, and +/// carries the family's timing figures. /// -/// The data comes from sysconfig's `SYS_COMP_INT_VREF`, which every family's metadata carries per +/// The first comes from sysconfig's `SYS_COMP_INT_VREF`, which every family's metadata carries per /// instance, so an instance without it means the attribute moved rather than that the answer is -/// unknown. +/// unknown. The timing comes from `data/comp/<family>.yaml`, and every COMP-bearing datasheet so +/// far has the `ten` and `tdac_settle` rows, so a missing figure means the extraction broke. +/// `dac_settle_pin_ns` is not checked: only the datasheets whose COMP drives a pin state it. fn comp_features_known(chip: &Chip, name: &str) -> anyhow::Result<()> { for peripheral in chip .peripherals .values() .filter(|peripheral| peripheral.ty == PeripheralType::Comp) { - if peripheral.comp.is_none() { + let Some(comp) = &peripheral.comp else { bail!( "{name}: {} has no comparator data; SYS_COMP_INT_VREF was not found on the \ sysconfig instance", peripheral.name, ); + }; + + if comp.enable_fast_ns.is_none() + || comp.enable_ulp_ns.is_none() + || comp.dac_settle_ns.is_none() + { + bail!( + "{name}: {} has no timing figures; data/comp/{}.yaml is missing or incomplete", + peripheral.name, + chip.family, + ); } } diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 8f5b68e..195c863 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -579,6 +579,41 @@ pub struct Comp { /// false, `REFSRC` 2 and 3 reach the VREF module's output instead, which follows the VREF /// configuration (internal where the device generates one, the `VREF+`/`VREF-` pins otherwise). pub int_vref: bool, + + /// Time from `CTL1.ENABLE` in high-speed mode (`CTL1.MODE = 0`) until the comparator meets its + /// propagation-delay specification, in nanoseconds. Nothing reports this โ€” there is no ready + /// bit โ€” so waiting it out is the only option. The figure's cell spans the datasheet's MIN, + /// TYP and MAX columns, so it is a stated figure rather than a guaranteed ceiling; the same + /// holds for the other three figures here. + /// + /// `None` when the family's datasheet has no `ten` row. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enable_fast_ns: Option<u32>, + + /// Time from `CTL1.ENABLE` in low-power mode (`CTL1.MODE = 1`) until the comparator meets its + /// propagation-delay specification, in nanoseconds. 10us on every device so far, where + /// high-speed mode reaches its own specification in 5us on the newer-generation comparators. + /// + /// `None` when the family's datasheet has no `ten` row. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enable_ulp_ns: Option<u32>, + + /// Time for the 8-bit reference DAC to settle to 1 LSB after a full-scale code change, in + /// nanoseconds, unloaded. This is the internal path โ€” what the comparator itself, or an OPA + /// sampling the DAC through its input mux, sees. + /// + /// `None` when the family's datasheet has no `tdac_settle` row. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dac_settle_ns: Option<u32>, + + /// The same settling figure with the DAC driven out to a package pin (`CTL1.DACOUTEN`) under + /// the datasheet's stated load. Stated exactly on the families whose COMP has that bit, and + /// slower than the internal path โ€” 6us against 1.5us so far. + /// + /// `None` when the datasheet does not state the pin-loaded row, including every device whose + /// DAC cannot reach a pin. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dac_settle_pin_ns: Option<u32>, } /// The parts of the VREF instance which the single `vref_v1` register block does not describe. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 5373ce6..bee9db0 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -279,6 +279,37 @@ pub struct Comp { /// false, `REFSRC` 2 and 3 reach the VREF module's output instead, which follows the VREF /// configuration (internal where the device generates one, the `VREF+`/`VREF-` pins otherwise). pub int_vref: bool, + + /// Time from `CTL1.ENABLE` in high-speed mode (`CTL1.MODE = 0`) until the comparator meets its + /// propagation-delay specification, in nanoseconds. Nothing reports this โ€” there is no ready + /// bit โ€” so waiting it out is the only option. The figure's cell spans the datasheet's MIN, + /// TYP and MAX columns, so it is a stated figure rather than a guaranteed ceiling; the same + /// holds for the other three figures here. + /// + /// `None` when the family's datasheet has no `ten` row. + pub enable_fast_ns: Option<u32>, + + /// Time from `CTL1.ENABLE` in low-power mode (`CTL1.MODE = 1`) until the comparator meets its + /// propagation-delay specification, in nanoseconds. 10us on every device so far, where + /// high-speed mode reaches its own specification in 5us on the newer-generation comparators. + /// + /// `None` when the family's datasheet has no `ten` row. + pub enable_ulp_ns: Option<u32>, + + /// Time for the 8-bit reference DAC to settle to 1 LSB after a full-scale code change, in + /// nanoseconds, unloaded. This is the internal path โ€” what the comparator itself, or an OPA + /// sampling the DAC through its input mux, sees. + /// + /// `None` when the family's datasheet has no `tdac_settle` row. + pub dac_settle_ns: Option<u32>, + + /// The same settling figure with the DAC driven out to a package pin (`CTL1.DACOUTEN`) under + /// the datasheet's stated load. Stated exactly on the families whose COMP has that bit, and + /// slower than the internal path โ€” 6us against 1.5us so far. + /// + /// `None` when the datasheet does not state the pin-loaded row, including every device whose + /// DAC cannot reach a pin. + pub dac_settle_pin_ns: Option<u32>, } /// Which extended-UART features a UART instance implements. diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index a01d1cf..69de6a3 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -476,7 +476,24 @@ fn generate_peripheral( let comp = match peripheral.comp { Some(comp) => { let int_vref = comp.int_vref; - quote! { Some(Comp { int_vref: #int_vref }) } + let ns = |value: Option<u32>| match value { + Some(ns) => { + let ns = Literal::u32_unsuffixed(ns); + quote! { Some(#ns) } + } + None => quote! { None }, + }; + let enable_fast_ns = ns(comp.enable_fast_ns); + let enable_ulp_ns = ns(comp.enable_ulp_ns); + let dac_settle_ns = ns(comp.dac_settle_ns); + let dac_settle_pin_ns = ns(comp.dac_settle_pin_ns); + quote! { Some(Comp { + int_vref: #int_vref, + enable_fast_ns: #enable_fast_ns, + enable_ulp_ns: #enable_ulp_ns, + dac_settle_ns: #dac_settle_ns, + dac_settle_pin_ns: #dac_settle_pin_ns, + }) } } None => quote! { None }, }; From ce681e6cb3813492ff34b6d3d39fe10473c3a07d Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 21:44:41 +0100 Subject: [PATCH 098/134] docs: list the comparator timing extraction alongside the other data sources --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b493410..8cdb7b8 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,10 @@ These are the data sources currently used. * Which extended-UART features (LIN, DALI, IrDA, ISO7816, Manchester) each UART instance implements, from the "UART Features" table ([`data/uart/`](./data/uart)). One register block serves every instance, so this is the only place the difference is stated per part + * The comparator timing figures โ€” enable time per mode, and the reference DAC's settling time, + internal and pin-loaded โ€” from the `ten` and `tdac_settle` rows ([`data/comp/`](./data/comp)). + Neither has a status bit behind it, so waiting them out is the only way to know the output is + meaningful * Which timers stay clocked in STANDBY1 (`standby1_timers` in [`parts.yaml`](./data/parts.yaml)) * MCLK and ULPCLK ceilings, the SYSOSC base frequency, the flash wait-state bands, `fADCCLK` and `TRNGCLKF` (all in [`parts.yaml`](./data/parts.yaml)) @@ -101,7 +105,7 @@ the `tools/` scripts read them from there. 4. Check the peripheral mapping in [`perimap.rs`](./mspm0-data-gen/src/perimap.rs) to use the correct peripherals. 5. Fetch the documents with `./d download-docs`, then regenerate the extracted data: `tools/operating_modes.py`, `tools/timers.py`, `tools/wakeup.py`, `tools/vref.py`, - `tools/adc_channels.py`, `tools/uart.py` and `tools/errata.py`, each with + `tools/adc_channels.py`, `tools/uart.py`, `tools/comp.py` and `tools/errata.py`, each with `--write files`. 6. Run `./d gen` and read its output. `verify.rs` reports every per-chip gap it can detect, including a family with no timer, errata or operating-mode data. From 564e375e08fdb475fe0e0955866784a02ae34dff Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 22:22:02 +0100 Subject: [PATCH 099/134] flash: record the geometry and protection layout in the PAC metadata One flashctl register block serves every device, so the sector size, programming word width, CMDWEPROT* register widths and ECC presence had no per-device statement. The widths and the ECC flag come from the per-device headers; the sector size is each datasheet's, curated in parts.yaml. The SVDs are not consulted: the H321X SVD describes CMDWEPROTA although the part's header gives it zero width. --- data/parts.yaml | 54 +++++++++++++++++++++++++ mspm0-data-gen/src/generate.rs | 39 +++++++++++++++++-- mspm0-data-gen/src/header.rs | 65 ++++++++++++++++++++++++++++++- mspm0-data-gen/src/parts.rs | 4 ++ mspm0-data-gen/src/verify.rs | 29 ++++++++++++++ mspm0-data-types/src/lib.rs | 49 +++++++++++++++++++++++ mspm0-metapac-gen/res/metadata.rs | 48 +++++++++++++++++++++++ mspm0-metapac-gen/src/metadata.rs | 21 ++++++++++ 8 files changed, 305 insertions(+), 4 deletions(-) diff --git a/data/parts.yaml b/data/parts.yaml index e65ecb1..16e83b3 100644 --- a/data/parts.yaml +++ b/data/parts.yaml @@ -23,6 +23,9 @@ families: # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. flash_wait_hz: [24000000] adc_clock_hz: { min: 12000000, max: 24000000 } # fADCCLK + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: ulpclk_div: false stop1: false @@ -66,6 +69,9 @@ families: # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. flash_wait_hz: [24000000] adc_clock_hz: { min: 12000000, max: 24000000 } # fADCCLK + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: ulpclk_div: false stop1: false @@ -115,6 +121,9 @@ families: # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. flash_wait_hz: [24000000, 32000000] adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN ulpclk_div: false @@ -176,6 +185,9 @@ families: # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. flash_wait_hz: [24000000, 48000000, 80000000] adc_clock_hz: { min: 4000000, max: 48000000 } # fADCCLK + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN ulpclk_div: true @@ -246,6 +258,9 @@ families: flash_wait_hz: [24000000, 48000000, 80000000] adc_clock_hz: { min: 4000000, max: 48000000 } # fADCCLK trng_clock_hz: { min: 9500000, max: 25000000 } # TRNGCLKF + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN ulpclk_div: true @@ -316,6 +331,9 @@ families: flash_wait_hz: [24000000, 48000000, 80000000] adc_clock_hz: { min: 4000000, max: 48000000 } # fADCCLK trng_clock_hz: { min: 9500000, max: 25000000 } # TRNGCLKF + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN ulpclk_div: true @@ -399,6 +417,9 @@ families: flash_wait_hz: [24000000, 48000000, 80000000] adc_clock_hz: { min: 4000000, max: 48000000 } # fADCCLK trng_clock_hz: { min: 9500000, max: 25000000 } # TRNGCLKF + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN ulpclk_div: true @@ -459,6 +480,9 @@ families: flash_wait_hz: [24000000, 48000000, 80000000] adc_clock_hz: { min: 4000000, max: 48000000 } # fADCCLK trng_clock_hz: { min: 9500000, max: 25000000 } # TRNGCLKF + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN ulpclk_div: true @@ -525,6 +549,9 @@ families: flash_wait_hz: [24000000, 48000000, 80000000] adc_clock_hz: { min: 4000000, max: 48000000 } # fADCCLK trng_clock_hz: { min: 9500000, max: 25000000 } # TRNGCLKF + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN ulpclk_div: true @@ -607,6 +634,9 @@ families: # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. flash_wait_hz: [24000000, 48000000, 80000000] adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN ulpclk_div: true @@ -646,6 +676,9 @@ families: # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. flash_wait_hz: [24000000, 32000000] adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN ulpclk_div: false @@ -688,6 +721,9 @@ families: # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. flash_wait_hz: [24000000, 32000000] adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: ulpclk_div: false stop1: true @@ -739,6 +775,9 @@ families: # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. flash_wait_hz: [24000000, 32000000] adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN ulpclk_div: false @@ -803,6 +842,9 @@ families: flash_wait_hz: [24000000, 32000000] adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK trng_clock_hz: { min: 9500000, max: 25000000 } # TRNGCLKF + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN ulpclk_div: false @@ -859,6 +901,9 @@ families: # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. flash_wait_hz: [24000000, 32000000] adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: ulpclk_div: false stop1: true @@ -936,6 +981,9 @@ families: # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. flash_wait_hz: [24000000, 32000000] adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: ulpclk_div: false stop1: true @@ -999,6 +1047,9 @@ families: # MCLK ceiling for each MCLKCFG.FLASHWAIT setting, from zero wait states up. flash_wait_hz: [24000000, 32000000] adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN ulpclk_div: false @@ -1063,6 +1114,9 @@ families: flash_wait_hz: [24000000, 32000000] adc_clock_hz: { min: 4000000, max: 32000000 } # fADCCLK trng_clock_hz: { min: 9500000, max: 25000000 } # TRNGCLKF + # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and + # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. + flash_sector_bytes: 1024 clock_tree: hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN ulpclk_div: false diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 432b0a6..e743a59 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -8,9 +8,9 @@ use std::{ use anyhow::{anyhow, bail, ensure, Context}; use mspm0_data_types::{ - Adc, Chip, Comp, DmaChannel, Interrupt, Memory, MemoryKind, Package, PackagePin, Peripheral, - PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, Timer, Uart, - Unicomm, Vref, + Adc, Chip, Comp, DmaChannel, Flashctl, Interrupt, Memory, MemoryKind, Package, PackagePin, + Peripheral, PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, Timer, + Uart, Unicomm, Vref, }; use regex::Regex; @@ -87,6 +87,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu apply_opa(opa, &mut peripherals); apply_vref(vref, &mut peripherals); apply_comp(sysconfig, comp, &mut peripherals); + apply_flashctl(family, header, &mut peripherals); for part_number in family.part_numbers.iter() { // Filter for package types available on the part number. @@ -321,6 +322,7 @@ fn generate_peripherals2( opa: None, vref: None, comp: None, + flashctl: None, }; // Lookup the pins @@ -559,6 +561,7 @@ fn generate_missing( opa: None, vref: None, comp: None, + flashctl: None, }, ); @@ -595,6 +598,7 @@ fn generate_missing( opa: None, vref: None, comp: None, + flashctl: None, }, ); @@ -639,6 +643,7 @@ fn generate_missing( opa: None, vref: None, comp: None, + flashctl: None, }); let pin = device_pin @@ -706,6 +711,7 @@ fn generate_missing( opa: None, vref: None, comp: None, + flashctl: None, }, ); } @@ -1061,6 +1067,7 @@ fn apply_unicomm( opa: None, vref: None, comp: None, + flashctl: None, }); } } @@ -1141,6 +1148,32 @@ fn apply_comp( } } +/// Attach the flash geometry and protection layout to the FLASHCTL instance. +/// +/// The widths and the ECC flag come from the per-device header's `FLASHCTL_SYS_*` constants and +/// `__MSPM0_HAS_ECC__`; the sector size is the datasheet's, curated in `parts.yaml`. Not from the +/// SVDs, which describe `CMDWEPROTA` on parts whose header gives it zero width, nor from +/// driverlib, whose `DL_FLASHCTL_SECTOR_SIZE` is one portfolio-wide constant. +fn apply_flashctl( + family: &PartFamily, + header: &Header, + peripherals: &mut BTreeMap<String, Peripheral>, +) { + for peripheral in peripherals + .values_mut() + .filter(|peripheral| peripheral.ty == PeripheralType::FlashCtl) + { + peripheral.flashctl = Some(Flashctl { + sector_bytes: family.flash_sector_bytes, + word_bytes: header.flash.datawidth_bits / 8, + weprota_bits: header.flash.weprota_bits, + weprotb_bits: header.flash.weprotb_bits, + weprotc_bits: header.flash.weprotc_bits, + has_ecc: header.flash.has_ecc, + }); + } +} + /// Attach each UART instance's extended-feature flags from `data/uart`. fn apply_uart( family: &PartFamily, diff --git a/mspm0-data-gen/src/header.rs b/mspm0-data-gen/src/header.rs index 5375b3e..f769e13 100644 --- a/mspm0-data-gen/src/header.rs +++ b/mspm0-data-gen/src/header.rs @@ -47,12 +47,39 @@ pub struct Header { /// Number of bits the NVIC uses for interrupt priority levels. pub nvic_priority_bits: u8, - // TODO: flash info + + /// The flash-controller facts the header states as `FLASHCTL_SYS_*` constants. + pub flash: HeaderFlash, // TODO: Available IOMUX indices // TODO: PF values (for non-analog) // TODO: DMA triggers (used for dma transfers) } +/// The `FLASHCTL_SYS_*` constants and `__MSPM0_HAS_ECC__`, from the per-device header. +/// +/// Deliberately not taken from the SVDs: the H321X SVD describes `CMDWEPROTA` although the part's +/// header gives it zero width, the same superset-IP pattern as elsewhere. +#[derive(Debug, Clone, Copy)] +pub struct HeaderFlash { + /// Bits in one flash word, the minimum programming unit. 64 everywhere except the G518x's 128. + pub datawidth_bits: u8, + + /// Implemented bits in `CMDWEPROTA`, one sector each. 0 means the register does not exist and + /// `CMDWEPROTB` alone protects MAIN memory; 16 on the C110x, 32 on the other older families. + pub weprota_bits: u8, + + /// Implemented bits in `CMDWEPROTB`. Eight sectors each on parts with `CMDWEPROTA` (covering + /// the space above its 32 sectors), and the whole of MAIN at eight sectors per bit without it. + pub weprotb_bits: u8, + + /// Implemented bits in `CMDWEPROTC`. 0 on every current part. + pub weprotc_bits: u8, + + /// Whether the flash carries ECC (a 72-bit total word). Absent from the C, H321x and + /// L110x/L130x/L134x headers, matching their datasheets' hedged "on devices with ECC". + pub has_ecc: bool, +} + impl Header { fn read(chip_name: &str, path: &Path) -> anyhow::Result<Self> { let content = fs::read_to_string(path)?; @@ -64,12 +91,48 @@ impl Header { let irq_numbers = Self::get_irq_numbers(chip_name, &content)?; let nvic_priority_bits = Self::get_nvic_priority_bits(chip_name, &content)?; let unicomm_modes = Self::get_unicomm_modes(&content); + let flash = Self::get_flash(chip_name, &content)?; Ok(Self { peripheral_addresses, unicomm_modes, irq_numbers, nvic_priority_bits, + flash, + }) + } + + /// Read the `FLASHCTL_SYS_*` constants and the `__MSPM0_HAS_ECC__` flag. + fn get_flash(chip_name: &str, content: &str) -> anyhow::Result<HeaderFlash> { + /// Example: + /// ```c,no_run + /// #define FLASHCTL_SYS_DATAWIDTH (64) /* !< Data bit width ... */ + /// ``` + static CONSTANT: LazyLock<Regex> = LazyLock::new(|| { + Regex::new(r"(?m)#define\s+FLASHCTL_SYS_(?<name>\w+)\s+\((?<value>\d+)\)").unwrap() + }); + + let mut constants = BTreeMap::new(); + for capture in CONSTANT.captures_iter(content) { + let value: u8 = capture["value"] + .parse() + .context(format!("{chip_name}: FLASHCTL_SYS_{} overflows", &capture["name"]))?; + constants.insert(capture["name"].to_string(), value); + } + + let get = |name: &str| { + constants + .get(name) + .copied() + .context(format!("{chip_name}: no FLASHCTL_SYS_{name} in header")) + }; + + Ok(HeaderFlash { + datawidth_bits: get("DATAWIDTH")?, + weprota_bits: get("WEPROTAWIDTH")?, + weprotb_bits: get("WEPROTBWIDTH")?, + weprotc_bits: get("WEPROTCWIDTH")?, + has_ecc: content.contains("__MSPM0_HAS_ECC__"), }) } diff --git a/mspm0-data-gen/src/parts.rs b/mspm0-data-gen/src/parts.rs index a971ef7..d89954f 100644 --- a/mspm0-data-gen/src/parts.rs +++ b/mspm0-data-gen/src/parts.rs @@ -55,6 +55,10 @@ pub struct PartFamily { #[serde(default)] pub trng_clock_hz: Option<ClockRange>, + /// The flash erase granularity in bytes, from the datasheet's "minimum erase resolution" + /// bullet. 1024 on every family so far; stated per device so the question stays closed. + pub flash_sector_bytes: u32, + /// The parts of the clock tree with no machine-readable source. pub clock_tree: ClockTreeSpec, diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 4bbd38a..421a918 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -19,6 +19,7 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { opa_inputs_known, opa_input_sources_exist, comp_features_known, + flashctl_known, // Peripherals which don't actually exist no_gpamp_c110x_l151x, // Low power data which is only as complete as the data sources @@ -319,6 +320,34 @@ fn comp_features_known(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// The flash controller states its geometry, and the figures are ones the sources can produce. +/// +/// No check ties the `CMDWEPROT` widths to the flash size: the bit-to-sector mapping depends on +/// the bank count, which is a runtime fact (`FACTORYREGION`), and the obvious single-bank formula +/// is genuinely wrong for the dual-bank L122x/L222x. +fn flashctl_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + for peripheral in chip + .peripherals + .values() + .filter(|peripheral| peripheral.ty == PeripheralType::FlashCtl) + { + let Some(flashctl) = &peripheral.flashctl else { + bail!("{name}: {} has no flash geometry data", peripheral.name); + }; + + if !matches!(flashctl.word_bytes, 8 | 16) { + bail!( + "{name}: {} states a {}-byte flash word; the portfolio has only 64- and 128-bit \ + words, so the header read likely broke", + peripheral.name, + flashctl.word_bytes, + ); + } + } + + Ok(()) +} + /// Verify all core peripherals are present. fn core_peripherals(chip: &Chip, name: &str) -> anyhow::Result<()> { if !chip.peripherals.contains_key("CPUSS") { diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 195c863..b64cfc4 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -560,6 +560,55 @@ pub struct Peripheral { /// `None` for peripherals which are not comparators. #[serde(skip_serializing_if = "Option::is_none")] pub comp: Option<Comp>, + + /// How this flash controller writes and protects its flash. + /// + /// `None` for peripherals which are not the FLASHCTL. + #[serde(skip_serializing_if = "Option::is_none")] + pub flashctl: Option<Flashctl>, +} + +/// How the flash controller writes and protects its flash. +/// +/// One `flashctl` register block serves every device, so these are the per-device facts a flash +/// driver needs and the block cannot state. They describe the controller: no current device varies +/// its flash geometry per bank, and the sources state them once per device, so a per-`MemoryRegion` +/// statement would only repeat them. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Flashctl { + /// Erase granularity in bytes. 1024 on every device so far โ€” stated per device by the + /// datasheet ("minimum erase resolution of 1KB"), recorded per device so a driver need not + /// assume TI's portfolio-wide driverlib constant holds for a part it was never checked on. + pub sector_bytes: u32, + + /// The flash word โ€” the minimum programming unit โ€” in bytes. 8 on every device except the + /// G518x's 16. Data written per program command must be a multiple of this, and a word can + /// only be programmed once per erase. + pub word_bytes: u8, + + /// Implemented bits in `CMDWEPROTA`, each write-protecting one sector of physical bank 0. + /// 0 means the register does not exist and `CMDWEPROTB` alone protects MAIN memory โ€” TI's + /// newer scheme; 16 bits on the C110x, 32 on the other older families. + /// + /// These registers reset to fully protected on every program or erase completion, so a driver + /// clears the relevant bit before every operation, not once. + pub weprota_bits: u8, + + /// Implemented bits in `CMDWEPROTB`, each write-protecting eight sectors. Which eight depends + /// on more than these widths: on a single-bank part with `CMDWEPROTA` its bit 0 starts above + /// `CMDWEPROTA`'s 32 sectors, while on a multi-bank part it starts at each bank's base โ€” the + /// three mask formulas in TI's `dl_flashctl.c` `DL_FlashCTL_unprotectSector` are the + /// reference, and the bank count is a runtime fact (`FACTORYREGION`), not a metadata one. + pub weprotb_bits: u8, + + /// Implemented bits in `CMDWEPROTC`. 0 on every current device; carried so the day TI ships + /// one it is data rather than a schema change. + pub weprotc_bits: u8, + + /// Whether the flash carries ECC โ€” a 72-bit stored word of 64 data bits plus 8 ECC bits. + /// False on the C, H321x and L110x/L130x/L134x families, whose datasheets hedge their flash + /// word footnote with "on devices with ECC" and never state the ECC variant. + pub has_ecc: bool, } /// The parts of one COMP instance which its register block does not describe. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index bee9db0..523d40d 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -260,6 +260,54 @@ pub struct Peripheral { /// /// `None` for peripherals which are not comparators. pub comp: Option<Comp>, + + /// How this flash controller writes and protects its flash. + /// + /// `None` for peripherals which are not the FLASHCTL. + pub flashctl: Option<Flashctl>, +} + +/// How the flash controller writes and protects its flash. +/// +/// One `flashctl` register block serves every device, so these are the per-device facts a flash +/// driver needs and the block cannot state. They describe the controller: no current device varies +/// its flash geometry per bank, and the sources state them once per device, so a per-region +/// statement would only repeat them. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct Flashctl { + /// Erase granularity in bytes. 1024 on every device so far โ€” stated per device by the + /// datasheet ("minimum erase resolution of 1KB"), recorded per device so a driver need not + /// assume TI's portfolio-wide driverlib constant holds for a part it was never checked on. + pub sector_bytes: u32, + + /// The flash word โ€” the minimum programming unit โ€” in bytes. 8 on every device except the + /// G518x's 16. Data written per program command must be a multiple of this, and a word can + /// only be programmed once per erase. + pub word_bytes: u8, + + /// Implemented bits in `CMDWEPROTA`, each write-protecting one sector of physical bank 0. + /// 0 means the register does not exist and `CMDWEPROTB` alone protects MAIN memory โ€” TI's + /// newer scheme; 16 bits on the C110x, 32 on the other older families. + /// + /// These registers reset to fully protected on every program or erase completion, so a driver + /// clears the relevant bit before every operation, not once. + pub weprota_bits: u8, + + /// Implemented bits in `CMDWEPROTB`, each write-protecting eight sectors. Which eight depends + /// on more than these widths: on a single-bank part with `CMDWEPROTA` its bit 0 starts above + /// `CMDWEPROTA`'s 32 sectors, while on a multi-bank part it starts at each bank's base โ€” the + /// three mask formulas in TI's `dl_flashctl.c` `DL_FlashCTL_unprotectSector` are the + /// reference, and the bank count is a runtime fact (`FACTORYREGION`), not a metadata one. + pub weprotb_bits: u8, + + /// Implemented bits in `CMDWEPROTC`. 0 on every current device; carried so the day TI ships + /// one it is data rather than a schema change. + pub weprotc_bits: u8, + + /// Whether the flash carries ECC โ€” a 72-bit stored word of 64 data bits plus 8 ECC bits. + /// False on the C, H321x and L110x/L130x/L134x families, whose datasheets hedge their flash + /// word footnote with "on devices with ECC" and never state the ECC variant. + pub has_ecc: bool, } /// The parts of one COMP instance which its register block does not describe. diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 69de6a3..e5dda9b 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -498,6 +498,26 @@ fn generate_peripheral( None => quote! { None }, }; + let flashctl = match peripheral.flashctl { + Some(flashctl) => { + let sector_bytes = Literal::u32_unsuffixed(flashctl.sector_bytes); + let word_bytes = Literal::u8_unsuffixed(flashctl.word_bytes); + let weprota_bits = Literal::u8_unsuffixed(flashctl.weprota_bits); + let weprotb_bits = Literal::u8_unsuffixed(flashctl.weprotb_bits); + let weprotc_bits = Literal::u8_unsuffixed(flashctl.weprotc_bits); + let has_ecc = flashctl.has_ecc; + quote! { Some(Flashctl { + sector_bytes: #sector_bytes, + word_bytes: #word_bytes, + weprota_bits: #weprota_bits, + weprotb_bits: #weprotb_bits, + weprotc_bits: #weprotc_bits, + has_ecc: #has_ecc, + }) } + } + None => quote! { None }, + }; + Some(quote! { Peripheral { name: #name, @@ -519,6 +539,7 @@ fn generate_peripheral( opa: #opa, vref: #vref, comp: #comp, + flashctl: #flashctl, } }) } From 41f067a9cf78bc4e3a543b0d36ab21b195a53a7d Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 22:22:02 +0100 Subject: [PATCH 100/134] docs: list the flash geometry sources --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 8cdb7b8..d2e21af 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,9 @@ These are the data sources currently used. * Interrupt number, name * Peripheral addresses * NVIC interrupt priority bits + * Flash geometry: the programming word width, the `CMDWEPROT*` register widths and whether the + flash has ECC (`FLASHCTL_SYS_*`, `__MSPM0_HAS_ECC__`). Not from the SVDs, which describe + `CMDWEPROTA` on parts whose header gives it zero width * mspm0 SVDs * Register blocks * Which peripheral instances have a `CLKCFG.BLOCKASYNC` bit. TI does not publish an SVD for every @@ -85,6 +88,9 @@ These are the data sources currently used. * Which functional advisories apply ([`data/errata/`](./data/errata)) * Manually entered * IIDX values for interrupts within a `INT_GROUP` + * The flash erase-sector size (`flash_sector_bytes` in [`parts.yaml`](./data/parts.yaml)), from + each datasheet's "minimum erase resolution" bullet. 1KB everywhere so far, recorded per family + so no driver has to trust driverlib's single portfolio-wide constant * What each OPA input-mux position selects ([`data/opa/`](./data/opa)), including which peer instance the cascade positions reach. Hand-curated because the L-series datasheet publishes it only as a figure โ€” one it promises as tables it does not contain From dc63015b9998ef8bef7a152a7e1a2f708f3ec4c8 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 23:30:23 +0100 Subject: [PATCH 101/134] flash: word_bytes is the widest program command, not the flash word The doc called it the minimum programming unit, which would make a driver demand 16-byte alignment on the G518x. That device's flash word is 64 bits like every other; DATAWIDTH=128 means its controller programs two words in one command. --- mspm0-data-types/src/lib.rs | 9 ++++++--- mspm0-metapac-gen/res/metadata.rs | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index b64cfc4..58ef752 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -581,9 +581,12 @@ pub struct Flashctl { /// assume TI's portfolio-wide driverlib constant holds for a part it was never checked on. pub sector_bytes: u32, - /// The flash word โ€” the minimum programming unit โ€” in bytes. 8 on every device except the - /// G518x's 16. Data written per program command must be a multiple of this, and a word can - /// only be programmed once per erase. + /// The widest single program command in bytes. 8 on every device except the G518x's 16, + /// which programs two flash words in one command (TI's `programMemory128` sets + /// `CMDTYPE.SIZE` to `TWO_WORDS`). Not the minimum programming unit: the flash word is + /// 8 bytes on every device, the G518x included ("Flash word size is 64 data bits (8 bytes)" + /// in its own datasheet), and a word can only be programmed once per erase. The value is the + /// header's `FLASHCTL_SYS_DATAWIDTH`, whose comment miscalls it the flash-word width. pub word_bytes: u8, /// Implemented bits in `CMDWEPROTA`, each write-protecting one sector of physical bank 0. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 523d40d..253c569 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -280,9 +280,12 @@ pub struct Flashctl { /// assume TI's portfolio-wide driverlib constant holds for a part it was never checked on. pub sector_bytes: u32, - /// The flash word โ€” the minimum programming unit โ€” in bytes. 8 on every device except the - /// G518x's 16. Data written per program command must be a multiple of this, and a word can - /// only be programmed once per erase. + /// The widest single program command in bytes. 8 on every device except the G518x's 16, + /// which programs two flash words in one command (TI's `programMemory128` sets + /// `CMDTYPE.SIZE` to `TWO_WORDS`). Not the minimum programming unit: the flash word is + /// 8 bytes on every device, the G518x included ("Flash word size is 64 data bits (8 bytes)" + /// in its own datasheet), and a word can only be programmed once per erase. The value is the + /// header's `FLASHCTL_SYS_DATAWIDTH`, whose comment miscalls it the flash-word width. pub word_bytes: u8, /// Implemented bits in `CMDWEPROTA`, each write-protecting one sector of physical bank 0. From 26d7c0be4cfcd6cbfdb65faf43630f0c4a19f9a7 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 10 Aug 2026 23:30:23 +0100 Subject: [PATCH 102/134] registers: record the CMDCTL verify-bit documentation split PREVEREN/POSTVEREN/PROGMASKDIS/ERASEMASKDIS exist only in the L/C/H TRM field tables; the G TRM reserves the bits and the headers and SVDs omit them. The two VEREN resets are 1h per those tables but 0 per the same sections' figures, and clearing them silently disables program/erase verify. --- data/registers/flashctl_v1.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/data/registers/flashctl_v1.yaml b/data/registers/flashctl_v1.yaml index 1d0235b..53ff51b 100644 --- a/data/registers/flashctl_v1.yaml +++ b/data/registers/flashctl_v1.yaml @@ -251,6 +251,14 @@ fieldset/CMDCTL: bit_offset: 9 bit_size: 4 enum: REGIONSEL + # PREVEREN, POSTVEREN, PROGMASKDIS and ERASEMASKDIS are documented only by the SLAU847/893/923 + # CMDCTL field tables; SLAU846 marks bits 20-18 and 15-14 reserved, and hw_flashctl.h and the + # SVDs omit all four (they do carry SSERASEDIS and DATAVEREN, so SLAU846's reserved span is + # wrong about bit 20 either way). Kept: the MASKDIS descriptions name the prever/postver + # machinery these bits control. The field tables give PREVEREN/POSTVEREN a 1h reset where the + # same sections' figures and [Reset =] headers say 0 โ€” clearing them disables the + # verify-and-repulse loop, leaving marginal cells with no error, so never write CMDCTL whole: + # driverlib only ever read-modify-writes it (BANKSEL, REGIONSEL, ECCGENOVR). - name: PREVEREN description: Enable verify before program or erase. For program, bits already programmed to the requests value will be masked. For erase, sectors already erased will be masked. bit_offset: 14 From ce0d0604a55a133aa29485b1328ad6c6cf6e61c7 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Tue, 11 Aug 2026 09:15:16 +0100 Subject: [PATCH 103/134] sysctl: record whether the BOR warning thresholds exist The register interface cannot say: the SVDs enumerate four levels on every device and the headers define all four encodings, while driverlib's per-family enums contradict the datasheets in both directions. Curated per family from the VBOR1-VBOR3 rows; false only on mspm0h321x. --- README.md | 4 +++ data/parts.yaml | 54 +++++++++++++++++++++++++++++++ mspm0-data-gen/src/generate.rs | 20 +++++++++++- mspm0-data-gen/src/parts.rs | 5 +++ mspm0-data-types/src/lib.rs | 29 +++++++++++++++++ mspm0-metapac-gen/res/metadata.rs | 28 ++++++++++++++++ mspm0-metapac-gen/src/metadata.rs | 11 +++++++ 7 files changed, 150 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d2e21af..d9bf7c8 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,10 @@ These are the data sources currently used. * Which functional advisories apply ([`data/errata/`](./data/errata)) * Manually entered * IIDX values for interrupts within a `INT_GROUP` + * Whether the BOR warning thresholds BOR1โ€“BOR3 exist (`bor_warning_levels` in + [`parts.yaml`](./data/parts.yaml)), from each datasheet's `VBOR1`โ€“`VBOR3` rows. The SVDs + enumerate four levels on every device, and driverlib's per-family enums disagree with the + datasheets in both directions, so only the datasheet answers * The flash erase-sector size (`flash_sector_bytes` in [`parts.yaml`](./data/parts.yaml)), from each datasheet's "minimum erase resolution" bullet. 1KB everywhere so far, recorded per family so no driver has to trust driverlib's single portfolio-wide constant diff --git a/data/parts.yaml b/data/parts.yaml index 16e83b3..6901caa 100644 --- a/data/parts.yaml +++ b/data/parts.yaml @@ -26,6 +26,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: ulpclk_div: false stop1: false @@ -72,6 +75,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: ulpclk_div: false stop1: false @@ -124,6 +130,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN ulpclk_div: false @@ -188,6 +197,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN ulpclk_div: true @@ -261,6 +273,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN ulpclk_div: true @@ -334,6 +349,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN ulpclk_div: true @@ -420,6 +438,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN ulpclk_div: true @@ -483,6 +504,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN ulpclk_div: true @@ -552,6 +576,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN ulpclk_div: true @@ -637,6 +664,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: hfclk_hz: { min: 4000000, max: 48000000 } # fHFXT and fHFIN ulpclk_div: true @@ -679,6 +709,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # The H3216 datasheet publishes no VBOR1-VBOR3 rows and qualifies its BOR hysteresis + # "Level 0": BOR0 is the only threshold. + bor_warning_levels: false clock_tree: hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN ulpclk_div: false @@ -724,6 +757,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: ulpclk_div: false stop1: true @@ -778,6 +814,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN ulpclk_div: false @@ -845,6 +884,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN ulpclk_div: false @@ -904,6 +946,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: ulpclk_div: false stop1: true @@ -984,6 +1029,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: ulpclk_div: false stop1: true @@ -1050,6 +1098,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN ulpclk_div: false @@ -1117,6 +1168,9 @@ families: # Erase granularity, from the datasheet's "minimum erase resolution of 1KB" bullet and # its "program time for 1kB sector" row. Stated per device; 1KB on every family so far. flash_sector_bytes: 1024 + # BOR warning thresholds BOR1-BOR3, from the datasheet's VBOR1-VBOR3 rows (rising, + # falling and STANDBY figures all stated). + bor_warning_levels: true clock_tree: hfclk_hz: { min: 4000000, max: 32000000 } # fHFXT and fHFIN ulpclk_div: false diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index e743a59..5f612e4 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -10,7 +10,7 @@ use anyhow::{anyhow, bail, ensure, Context}; use mspm0_data_types::{ Adc, Chip, Comp, DmaChannel, Flashctl, Interrupt, Memory, MemoryKind, Package, PackagePin, Peripheral, PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, Timer, - Uart, Unicomm, Vref, + Sysctl, Uart, Unicomm, Vref, }; use regex::Regex; @@ -88,6 +88,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu apply_vref(vref, &mut peripherals); apply_comp(sysconfig, comp, &mut peripherals); apply_flashctl(family, header, &mut peripherals); + apply_sysctl(family, &mut peripherals); for part_number in family.part_numbers.iter() { // Filter for package types available on the part number. @@ -323,6 +324,7 @@ fn generate_peripherals2( vref: None, comp: None, flashctl: None, + sysctl: None, }; // Lookup the pins @@ -562,6 +564,7 @@ fn generate_missing( vref: None, comp: None, flashctl: None, + sysctl: None, }, ); @@ -599,6 +602,7 @@ fn generate_missing( vref: None, comp: None, flashctl: None, + sysctl: None, }, ); @@ -644,6 +648,7 @@ fn generate_missing( vref: None, comp: None, flashctl: None, + sysctl: None, }); let pin = device_pin @@ -712,6 +717,7 @@ fn generate_missing( vref: None, comp: None, flashctl: None, + sysctl: None, }, ); } @@ -1068,6 +1074,7 @@ fn apply_unicomm( vref: None, comp: None, flashctl: None, + sysctl: None, }); } } @@ -1154,6 +1161,17 @@ fn apply_comp( /// `__MSPM0_HAS_ECC__`; the sector size is the datasheet's, curated in `parts.yaml`. Not from the /// SVDs, which describe `CMDWEPROTA` on parts whose header gives it zero width, nor from /// driverlib, whose `DL_FLASHCTL_SECTOR_SIZE` is one portfolio-wide constant. +fn apply_sysctl(family: &PartFamily, peripherals: &mut BTreeMap<String, Peripheral>) { + for peripheral in peripherals + .values_mut() + .filter(|peripheral| peripheral.ty == PeripheralType::Sysctl) + { + peripheral.sysctl = Some(Sysctl { + bor_warning_levels: family.bor_warning_levels, + }); + } +} + fn apply_flashctl( family: &PartFamily, header: &Header, diff --git a/mspm0-data-gen/src/parts.rs b/mspm0-data-gen/src/parts.rs index d89954f..378b0c9 100644 --- a/mspm0-data-gen/src/parts.rs +++ b/mspm0-data-gen/src/parts.rs @@ -59,6 +59,11 @@ pub struct PartFamily { /// bullet. 1024 on every family so far; stated per device so the question stays closed. pub flash_sector_bytes: u32, + /// Whether the BOR warning thresholds BOR1โ€“BOR3 exist, from the datasheet's `VBOR1`โ€“`VBOR3` + /// rows. False only on mspm0h321x so far. The register interface and the SVDs cannot say, + /// and driverlib's per-family enums contradict the datasheets in both directions. + pub bor_warning_levels: bool, + /// The parts of the clock tree with no machine-readable source. pub clock_tree: ClockTreeSpec, diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 58ef752..d10d204 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -566,6 +566,35 @@ pub struct Peripheral { /// `None` for peripherals which are not the FLASHCTL. #[serde(skip_serializing_if = "Option::is_none")] pub flashctl: Option<Flashctl>, + + /// The parts of the SYSCTL which its register block does not describe. + /// + /// `None` for peripherals which are not the SYSCTL. + #[serde(skip_serializing_if = "Option::is_none")] + pub sysctl: Option<Sysctl>, +} + +/// The parts of the SYSCTL which its register block does not describe. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Sysctl { + /// Whether the BOR warning thresholds BOR1โ€“BOR3 exist. When true, `BORTHRESHOLD.LEVEL` + /// values 1โ€“3 arm an early-warning interrupt above the BOR0 reset level; when false the + /// device implements only BOR0 and the upper `LEVEL` encodings select nothing the datasheet + /// documents. False only on the MSPM0H321x so far, whose datasheet publishes no + /// `VBOR1`โ€“`VBOR3` rows and qualifies its BOR hysteresis "Level 0"; every other family's + /// datasheet states all three with rising, falling and STANDBY figures. The register + /// interface cannot say: the field is two bits and the SVDs enumerate four levels + /// everywhere, and driverlib's per-family enums contradict the datasheets in *both* + /// directions (`dl_sysctl_mspm0c110x.h` allows only level 0 against the C1104's full table, + /// `dl_sysctl_mspm0h321x.h` offers all four against the H3216's one). + /// + /// A new threshold takes effect only after `BORCLRCMD` is written with its key and `GO` โ€” + /// `BORTHRESHOLD.LEVEL` alone changes nothing, and no TI code performs the second write. + /// Confirm via `SYSSTATUS.BORCURTHRESHOLD` after the documented ~15ยตs; do not poll it in + /// place of the delay, since it reads the old level until the new one lands and a refused + /// change would hang the poll. On mspm0l110x/l130x/l134x, `PMCU_ERR_03`: BOR1โ€“3 are not + /// functional in STANDBY. + pub bor_warning_levels: bool, } /// How the flash controller writes and protects its flash. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 253c569..b23056f 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -265,6 +265,34 @@ pub struct Peripheral { /// /// `None` for peripherals which are not the FLASHCTL. pub flashctl: Option<Flashctl>, + + /// The parts of the SYSCTL which its register block does not describe. + /// + /// `None` for peripherals which are not the SYSCTL. + pub sysctl: Option<Sysctl>, +} + +/// The parts of the SYSCTL which its register block does not describe. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct Sysctl { + /// Whether the BOR warning thresholds BOR1โ€“BOR3 exist. When true, `BORTHRESHOLD.LEVEL` + /// values 1โ€“3 arm an early-warning interrupt above the BOR0 reset level; when false the + /// device implements only BOR0 and the upper `LEVEL` encodings select nothing the datasheet + /// documents. False only on the MSPM0H321x so far, whose datasheet publishes no + /// `VBOR1`โ€“`VBOR3` rows and qualifies its BOR hysteresis "Level 0"; every other family's + /// datasheet states all three with rising, falling and STANDBY figures. The register + /// interface cannot say: the field is two bits and the SVDs enumerate four levels + /// everywhere, and driverlib's per-family enums contradict the datasheets in *both* + /// directions (`dl_sysctl_mspm0c110x.h` allows only level 0 against the C1104's full table, + /// `dl_sysctl_mspm0h321x.h` offers all four against the H3216's one). + /// + /// A new threshold takes effect only after `BORCLRCMD` is written with its key and `GO` โ€” + /// `BORTHRESHOLD.LEVEL` alone changes nothing, and no TI code performs the second write. + /// Confirm via `SYSSTATUS.BORCURTHRESHOLD` after the documented ~15ยตs; do not poll it in + /// place of the delay, since it reads the old level until the new one lands and a refused + /// change would hang the poll. On mspm0l110x/l130x/l134x, `PMCU_ERR_03`: BOR1โ€“3 are not + /// functional in STANDBY. + pub bor_warning_levels: bool, } /// How the flash controller writes and protects its flash. diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index e5dda9b..8dd05e1 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -518,6 +518,16 @@ fn generate_peripheral( None => quote! { None }, }; + let sysctl = match peripheral.sysctl { + Some(sysctl) => { + let bor_warning_levels = sysctl.bor_warning_levels; + quote! { Some(Sysctl { + bor_warning_levels: #bor_warning_levels, + }) } + } + None => quote! { None }, + }; + Some(quote! { Peripheral { name: #name, @@ -540,6 +550,7 @@ fn generate_peripheral( vref: #vref, comp: #comp, flashctl: #flashctl, + sysctl: #sysctl, } }) } From 64d9b5677fb854ce42aa238ef03e24ca769b1755 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Tue, 11 Aug 2026 20:28:25 +0100 Subject: [PATCH 104/134] dma: record whether the 128-bit transfer width exists Nothing in the metadata distinguished a DMA that honours DMACTL's LONGLONG encoding from one that ignores it, and DmaChannel::full answers a different question: where the width exists the basic channels have it too. The header states it per device, the SVDs and the datasheets agree on every family, so apply_dma reads the header and fails generation on a disagreement with the SVD. --- README.md | 3 ++ mspm0-data-gen/src/generate.rs | 47 +++++++++++++++++++++++++++++-- mspm0-data-gen/src/header.rs | 8 ++++++ mspm0-data-gen/src/svd.rs | 15 +++++++++- mspm0-data-gen/src/verify.rs | 19 +++++++++++++ mspm0-data-types/src/lib.rs | 31 ++++++++++++++++++++ mspm0-metapac-gen/res/metadata.rs | 30 ++++++++++++++++++++ mspm0-metapac-gen/src/metadata.rs | 11 ++++++++ 8 files changed, 160 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d9bf7c8..b35a167 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,13 @@ These are the data sources currently used. * Flash geometry: the programming word width, the `CMDWEPROT*` register widths and whether the flash has ECC (`FLASHCTL_SYS_*`, `__MSPM0_HAS_ECC__`). Not from the SVDs, which describe `CMDWEPROTA` on parts whose header gives it zero width + * Whether the DMA implements the 128-bit transfer width (`DMA_SYS_MMR_LLONG`), cross-checked + against the SVD and matching every datasheet's "Long long (128-bit) transfer" row * mspm0 SVDs * Register blocks * Which peripheral instances have a `CLKCFG.BLOCKASYNC` bit. TI does not publish an SVD for every family, so this is optional per family. + * A cross-check on the DMA's 128-bit transfer width, which the header states directly. * Device datasheets, read by the scripts in [`tools/`](./tools) * How deep a sleep each PD1 peripheral is retained through, and how deep each peripheral stays usable, from the "Supported Functionality by Operating Mode" table diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 5f612e4..730fdf4 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -8,9 +8,9 @@ use std::{ use anyhow::{anyhow, bail, ensure, Context}; use mspm0_data_types::{ - Adc, Chip, Comp, DmaChannel, Flashctl, Interrupt, Memory, MemoryKind, Package, PackagePin, - Peripheral, PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, Timer, - Sysctl, Uart, Unicomm, Vref, + Adc, Chip, Comp, Dma, DmaChannel, Flashctl, Interrupt, Memory, MemoryKind, Package, PackagePin, + Peripheral, PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, Sysctl, + Timer, Uart, Unicomm, Vref, }; use regex::Regex; @@ -89,6 +89,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu apply_comp(sysconfig, comp, &mut peripherals); apply_flashctl(family, header, &mut peripherals); apply_sysctl(family, &mut peripherals); + apply_dma(family, header, svd, &mut peripherals)?; for part_number in family.part_numbers.iter() { // Filter for package types available on the part number. @@ -325,6 +326,7 @@ fn generate_peripherals2( comp: None, flashctl: None, sysctl: None, + dma: None, }; // Lookup the pins @@ -565,6 +567,7 @@ fn generate_missing( comp: None, flashctl: None, sysctl: None, + dma: None, }, ); @@ -603,6 +606,7 @@ fn generate_missing( comp: None, flashctl: None, sysctl: None, + dma: None, }, ); @@ -649,6 +653,7 @@ fn generate_missing( comp: None, flashctl: None, sysctl: None, + dma: None, }); let pin = device_pin @@ -718,6 +723,7 @@ fn generate_missing( comp: None, flashctl: None, sysctl: None, + dma: None, }, ); } @@ -1075,6 +1081,7 @@ fn apply_unicomm( comp: None, flashctl: None, sysctl: None, + dma: None, }); } } @@ -1172,6 +1179,40 @@ fn apply_sysctl(family: &PartFamily, peripherals: &mut BTreeMap<String, Peripher } } +/// Record the DMA-wide facts the register block cannot carry. +/// +/// The width is a property of the DMA, not of a channel: where it exists the datasheet ticks it for +/// the basic channels as well as the full ones. +fn apply_dma( + family: &PartFamily, + header: &Header, + svd: Option<&Svd>, + peripherals: &mut BTreeMap<String, Peripheral>, +) -> anyhow::Result<()> { + // The SVD enumerates `LONGLONG` exactly where the header defines the constant, on all fifteen + // families which have one, so a disagreement means a source bump changed one of them. + if let Some(svd) = svd { + ensure!( + svd.dma_long_long == header.dma_long_long, + "{}: the header {} 128-bit DMA transfers but the SVD {}", + family.family, + if header.dma_long_long { "has" } else { "lacks" }, + if svd.dma_long_long { "has" } else { "lacks" }, + ); + } + + for peripheral in peripherals + .values_mut() + .filter(|peripheral| peripheral.ty == PeripheralType::Dma) + { + peripheral.dma = Some(Dma { + long_long_transfers: header.dma_long_long, + }); + } + + Ok(()) +} + fn apply_flashctl( family: &PartFamily, header: &Header, diff --git a/mspm0-data-gen/src/header.rs b/mspm0-data-gen/src/header.rs index f769e13..e987db2 100644 --- a/mspm0-data-gen/src/header.rs +++ b/mspm0-data-gen/src/header.rs @@ -50,6 +50,13 @@ pub struct Header { /// The flash-controller facts the header states as `FLASHCTL_SYS_*` constants. pub flash: HeaderFlash, + + /// Whether the DMA implements 128-bit transfers, from `DMA_SYS_MMR_LLONG`. + /// + /// The constant is defined, always as `1`, exactly on the devices whose datasheet gives the + /// "Long long (128-bit) transfer" row a tick. driverlib gates `DL_DMA_WIDTH_LONG_LONG` on the + /// same constant. + pub dma_long_long: bool, // TODO: Available IOMUX indices // TODO: PF values (for non-analog) // TODO: DMA triggers (used for dma transfers) @@ -99,6 +106,7 @@ impl Header { irq_numbers, nvic_priority_bits, flash, + dma_long_long: content.contains("DMA_SYS_MMR_LLONG"), }) } diff --git a/mspm0-data-gen/src/svd.rs b/mspm0-data-gen/src/svd.rs index 2cdc8df..92dc801 100644 --- a/mspm0-data-gen/src/svd.rs +++ b/mspm0-data-gen/src/svd.rs @@ -21,6 +21,11 @@ use regex::Regex; /// `SYSCTL.SYSOSCCFG.BLOCKASYNCALL`, which masks the requests of every peripheral at once. const BLOCK_ASYNC: &str = "BLOCKASYNC"; +/// The `DMACTL.DMASRCWDTH`/`DMADSTWDTH` value which selects a 128-bit transfer. +/// +/// Enumerated only by the SVDs of devices which implement the width. +const LONG_LONG: &str = "LONGLONG"; + /// One `<peripheral>` element, capturing its `derivedFrom` if it has one. static PERIPHERAL: LazyLock<Regex> = LazyLock::new(|| { Regex::new( @@ -64,6 +69,11 @@ pub struct Svd { /// This varies per instance rather than per peripheral type: on mspm0g120x only `UC0`, `UC2`, /// `UC4`, `UC5` and `UC9` have the bit, despite every `UC` instance being the same IP. pub block_async: BTreeSet<String>, + + /// Whether `DMACTL.DMASRCWDTH` enumerates the 128-bit `LONGLONG` value. + /// + /// A cross-check on the header's `DMA_SYS_MMR_LLONG`, not a source: four families have no SVD. + pub dma_long_long: bool, } impl Svd { @@ -101,6 +111,9 @@ impl Svd { .cloned() .collect::<BTreeSet<_>>(); - Ok(Self { block_async }) + Ok(Self { + block_async, + dma_long_long: content.contains(&format!("<name>{LONG_LONG}</name>")), + }) } } diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 421a918..928e412 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -20,6 +20,7 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { opa_input_sources_exist, comp_features_known, flashctl_known, + dma_widths_known, // Peripherals which don't actually exist no_gpamp_c110x_l151x, // Low power data which is only as complete as the data sources @@ -348,6 +349,24 @@ fn flashctl_known(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// The DMA says which transfer widths it implements. +/// +/// Every chip has a DMA and the header states the fact for every device, so an instance without it +/// means the peripheral stopped being typed `Dma` rather than that a source is missing data. +fn dma_widths_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + for peripheral in chip + .peripherals + .values() + .filter(|peripheral| peripheral.ty == PeripheralType::Dma) + { + if peripheral.dma.is_none() { + bail!("{name}: {} has no transfer width data", peripheral.name); + } + } + + Ok(()) +} + /// Verify all core peripherals are present. fn core_peripherals(chip: &Chip, name: &str) -> anyhow::Result<()> { if !chip.peripherals.contains_key("CPUSS") { diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index d10d204..bd40c79 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -572,6 +572,12 @@ pub struct Peripheral { /// `None` for peripherals which are not the SYSCTL. #[serde(skip_serializing_if = "Option::is_none")] pub sysctl: Option<Sysctl>, + + /// The parts of the DMA which its register block does not describe. + /// + /// `None` for peripherals which are not the DMA. + #[serde(skip_serializing_if = "Option::is_none")] + pub dma: Option<Dma>, } /// The parts of the SYSCTL which its register block does not describe. @@ -597,6 +603,31 @@ pub struct Sysctl { pub bor_warning_levels: bool, } +/// The parts of the DMA which its register block does not describe. +/// +/// A device-wide statement, not a per-channel one. Where the widest transfer exists it is +/// available on the basic channels as well as the full ones, so [`DmaChannel::full`] answers a +/// different question and cannot stand in for this one. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Dma { + /// Whether `DMACTL.DMASRCWDTH` and `DMADSTWDTH` honour the 128-bit `LONGLONG` encoding (`4h`). + /// The other four widths โ€” 8, 16, 32 and 64 bits โ€” exist on every device. + /// + /// TI builds the DMA in two variants and only DMA_B has the wide transfer: true on + /// mspm0c1105_c1106, mspm0g151x, mspm0g351x, mspm0g518x, mspm0h321x, mspm0l112x and + /// mspm0l211x, false on the other eleven families. What a device without it does with a `4h` + /// written to either field is documented nowhere, so a driver has to gate on this rather than + /// try the width and check. + /// + /// Three sources state it per device and agree on all 18 families: the header's + /// `DMA_SYS_MMR_LLONG`, the `LONGLONG` enumerated value in the SVDs which have one, and the + /// datasheet's "Long long (128-bit) transfer" row (older datasheets carry no such table and + /// say it in the DMA feature list, which stops at "long word (64-bit)"). Not sysconfig, whose + /// `DMAChannel.syscfg.js` gates the option on a family list missing four families that have + /// it. + pub long_long_transfers: bool, +} + /// How the flash controller writes and protects its flash. /// /// One `flashctl` register block serves every device, so these are the per-device facts a flash diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index b23056f..98eca54 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -270,6 +270,36 @@ pub struct Peripheral { /// /// `None` for peripherals which are not the SYSCTL. pub sysctl: Option<Sysctl>, + + /// The parts of the DMA which its register block does not describe. + /// + /// `None` for peripherals which are not the DMA. + pub dma: Option<Dma>, +} + +/// The parts of the DMA which its register block does not describe. +/// +/// A device-wide statement, not a per-channel one. Where the widest transfer exists it is +/// available on the basic channels as well as the full ones, so [`DmaChannel::full`] answers a +/// different question and cannot stand in for this one. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct Dma { + /// Whether `DMACTL.DMASRCWDTH` and `DMADSTWDTH` honour the 128-bit `LONGLONG` encoding (`4h`). + /// The other four widths โ€” 8, 16, 32 and 64 bits โ€” exist on every device. + /// + /// TI builds the DMA in two variants and only DMA_B has the wide transfer: true on + /// mspm0c1105_c1106, mspm0g151x, mspm0g351x, mspm0g518x, mspm0h321x, mspm0l112x and + /// mspm0l211x, false on the other eleven families. What a device without it does with a `4h` + /// written to either field is documented nowhere, so a driver has to gate on this rather than + /// try the width and check. + /// + /// Three sources state it per device and agree on all 18 families: the header's + /// `DMA_SYS_MMR_LLONG`, the `LONGLONG` enumerated value in the SVDs which have one, and the + /// datasheet's "Long long (128-bit) transfer" row (older datasheets carry no such table and + /// say it in the DMA feature list, which stops at "long word (64-bit)"). Not sysconfig, whose + /// `DMAChannel.syscfg.js` gates the option on a family list missing four families that have + /// it. + pub long_long_transfers: bool, } /// The parts of the SYSCTL which its register block does not describe. diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 8dd05e1..99caf2b 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -528,6 +528,16 @@ fn generate_peripheral( None => quote! { None }, }; + let dma = match peripheral.dma { + Some(dma) => { + let long_long_transfers = dma.long_long_transfers; + quote! { Some(Dma { + long_long_transfers: #long_long_transfers, + }) } + } + None => quote! { None }, + }; + Some(quote! { Peripheral { name: #name, @@ -551,6 +561,7 @@ fn generate_peripheral( comp: #comp, flashctl: #flashctl, sysctl: #sysctl, + dma: #dma, } }) } From b46ed97d3c852aa48906bd7322194e2091c97ea4 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Tue, 11 Aug 2026 20:28:38 +0100 Subject: [PATCH 105/134] flash: correct the HeaderFlash datawidth doc The schema was fixed when the G518x turned out to program two flash words in one command, but the generator's own doc still called the constant the minimum programming unit. --- mspm0-data-gen/src/header.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mspm0-data-gen/src/header.rs b/mspm0-data-gen/src/header.rs index e987db2..1c71132 100644 --- a/mspm0-data-gen/src/header.rs +++ b/mspm0-data-gen/src/header.rs @@ -68,7 +68,9 @@ pub struct Header { /// header gives it zero width, the same superset-IP pattern as elsewhere. #[derive(Debug, Clone, Copy)] pub struct HeaderFlash { - /// Bits in one flash word, the minimum programming unit. 64 everywhere except the G518x's 128. + /// Bits the widest single program command writes. 64 everywhere except the G518x's 128, which + /// programs two flash words at once โ€” the flash word itself is 64 bits on every device, and + /// the header's own comment calling this the word width is wrong. pub datawidth_bits: u8, /// Implemented bits in `CMDWEPROTA`, one sector each. 0 means the register does not exist and From e5ff0a33dd02a63a3e5a518e0e7d27c3775352f4 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Tue, 11 Aug 2026 20:46:36 +0100 Subject: [PATCH 106/134] dma: give the transfer width its third bit, and record automatic enable The block gave SRCWDTH and DSTWDTH two bits, from the SVDs of the families which implement four widths. hw_dma.h masks both 0x700 on every device and all four TRMs number the field 10-8, so a device with the 128-bit width could not express it and read a 4h back as BYTE. AUTOEN has the same shape as the width: the field is in the block for every device and does nothing on the older DMA, so Peripheral::dma now says whether it is implemented. --- data/registers/dma_v1.yaml | 17 ++++++++++++----- mspm0-data-gen/src/generate.rs | 29 ++++++++++++++++++++--------- mspm0-data-gen/src/header.rs | 6 ++++++ mspm0-data-gen/src/svd.rs | 13 +++++++++++++ mspm0-data-types/src/lib.rs | 13 +++++++++++++ mspm0-metapac-gen/res/metadata.rs | 13 +++++++++++++ mspm0-metapac-gen/src/metadata.rs | 2 ++ 7 files changed, 79 insertions(+), 14 deletions(-) diff --git a/data/registers/dma_v1.yaml b/data/registers/dma_v1.yaml index 4676739..65ccf7e 100644 --- a/data/registers/dma_v1.yaml +++ b/data/registers/dma_v1.yaml @@ -124,15 +124,19 @@ fieldset/CTL: bit_offset: 4 bit_size: 3 enum: PREIRQ + # Three bits, not the two the older SVDs give it: hw_dma.h masks both fields 0x700/0x7000 for + # every device and all four TRMs number the field 10-8, while the SVDs of families without the + # 128-bit width describe only the encodings those devices implement. Peripheral::dma says which + # devices honour LONGLONG. - name: SRCWDTH - description: DMA source width. This bit selects the source data width as a byte, half word, word or long word. + description: DMA source width. This bit selects the source data width as a byte, half word, word, long word or long-long word. bit_offset: 8 - bit_size: 2 + bit_size: 3 enum: WDTH - name: DSTWDTH - description: DMA destination width. This bit selects the destination as a byte, half word, word or long word. + description: DMA destination width. This bit selects the destination as a byte, half word, word, long word or long-long word. bit_offset: 12 - bit_size: 2 + bit_size: 3 enum: WDTH - name: SRCINCR description: DMA source increment. This bit selects automatic incrementing or decrementing of the source address DMASA for each transfer. The amount of change to the DMASA is based on the definitin in the DMASRCWDTH. For example an increment of 1 (+1) on a WORD transfer will increment the DMASA by 4. @@ -504,7 +508,7 @@ enum/TM: description: Repeated block transfer. Each trigger transfers the complete block defined in DMASZ. After the last transfer the DMASA, DMADA, DAMSZ registers are restored to its initial value and the DMAEN stays enabled. value: 3 enum/WDTH: - bit_size: 2 + bit_size: 3 variants: - name: BYTE description: Destination data width is BYTE (8-bit). @@ -518,3 +522,6 @@ enum/WDTH: - name: LONG description: Destination data width is LONG-WORD (64-bit). value: 3 + - name: LONGLONG + description: Destination data width is LONG-LONG-WORD (128-bit). Only on devices whose Peripheral::dma reports long_long_transfers. + value: 4 diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 730fdf4..6b722f7 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -1189,16 +1189,26 @@ fn apply_dma( svd: Option<&Svd>, peripherals: &mut BTreeMap<String, Peripheral>, ) -> anyhow::Result<()> { - // The SVD enumerates `LONGLONG` exactly where the header defines the constant, on all fifteen - // families which have one, so a disagreement means a source bump changed one of them. + // The SVD enumerates `LONGLONG` and carries `DMAAUTOEN` exactly where the header defines the + // matching constant, on all fifteen families which have one, so a disagreement means a source + // bump changed one of them. if let Some(svd) = svd { - ensure!( - svd.dma_long_long == header.dma_long_long, - "{}: the header {} 128-bit DMA transfers but the SVD {}", - family.family, - if header.dma_long_long { "has" } else { "lacks" }, - if svd.dma_long_long { "has" } else { "lacks" }, - ); + for (feature, from_header, from_svd) in [ + ("128-bit DMA transfers", header.dma_long_long, svd.dma_long_long), + ( + "the DMA's automatic enable", + header.dma_auto_enable, + svd.dma_auto_enable, + ), + ] { + ensure!( + from_header == from_svd, + "{}: the header {} {feature} but the SVD {}", + family.family, + if from_header { "has" } else { "lacks" }, + if from_svd { "has" } else { "lacks" }, + ); + } } for peripheral in peripherals @@ -1207,6 +1217,7 @@ fn apply_dma( { peripheral.dma = Some(Dma { long_long_transfers: header.dma_long_long, + auto_enable: header.dma_auto_enable, }); } diff --git a/mspm0-data-gen/src/header.rs b/mspm0-data-gen/src/header.rs index 1c71132..2e5abf5 100644 --- a/mspm0-data-gen/src/header.rs +++ b/mspm0-data-gen/src/header.rs @@ -57,6 +57,11 @@ pub struct Header { /// "Long long (128-bit) transfer" row a tick. driverlib gates `DL_DMA_WIDTH_LONG_LONG` on the /// same constant. pub dma_long_long: bool, + + /// Whether the DMA implements automatic channel enable, from `DMA_SYS_MMR_AUTO`. + /// + /// Read separately from `dma_long_long` although the two constants have never disagreed. + pub dma_auto_enable: bool, // TODO: Available IOMUX indices // TODO: PF values (for non-analog) // TODO: DMA triggers (used for dma transfers) @@ -109,6 +114,7 @@ impl Header { nvic_priority_bits, flash, dma_long_long: content.contains("DMA_SYS_MMR_LLONG"), + dma_auto_enable: content.contains("DMA_SYS_MMR_AUTO"), }) } diff --git a/mspm0-data-gen/src/svd.rs b/mspm0-data-gen/src/svd.rs index 92dc801..e016cf7 100644 --- a/mspm0-data-gen/src/svd.rs +++ b/mspm0-data-gen/src/svd.rs @@ -26,6 +26,11 @@ const BLOCK_ASYNC: &str = "BLOCKASYNC"; /// Enumerated only by the SVDs of devices which implement the width. const LONG_LONG: &str = "LONGLONG"; +/// The `DMACTL` field which enables a channel on a write to its address or size registers. +/// +/// Present only in the SVDs of devices which implement it. +const AUTO_ENABLE: &str = "DMAAUTOEN"; + /// One `<peripheral>` element, capturing its `derivedFrom` if it has one. static PERIPHERAL: LazyLock<Regex> = LazyLock::new(|| { Regex::new( @@ -74,6 +79,13 @@ pub struct Svd { /// /// A cross-check on the header's `DMA_SYS_MMR_LLONG`, not a source: four families have no SVD. pub dma_long_long: bool, + + /// Whether `DMACTL` has the `DMAAUTOEN` field, cross-checking `DMA_SYS_MMR_AUTO` the same way. + /// + /// The SVDs only describe fields the device implements here, which is what makes both checks + /// worth having โ€” `DMASRCINCR`'s stride encodings and `DMAEM` are in every SVD, including the + /// families whose header says those two features are absent. + pub dma_auto_enable: bool, } impl Svd { @@ -114,6 +126,7 @@ impl Svd { Ok(Self { block_async, dma_long_long: content.contains(&format!("<name>{LONG_LONG}</name>")), + dma_auto_enable: content.contains(&format!("<name>{AUTO_ENABLE}</name>")), }) } } diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index bd40c79..b5780ec 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -626,6 +626,19 @@ pub struct Dma { /// `DMAChannel.syscfg.js` gates the option on a family list missing four families that have /// it. pub long_long_transfers: bool, + + /// Whether `DMACTL.AUTOEN` does anything. The field is in the register block on every device, + /// because one block serves them all, but the older DMA implements no automatic enable and + /// writing it there is accepted and ignored. + /// + /// True on exactly the same seven families as [`Dma::long_long_transfers`] โ€” the two features + /// arrived together, and the header states them as separate constants + /// (`DMA_SYS_MMR_AUTO`) which have never disagreed. Recorded separately anyway: nothing says + /// TI must keep shipping them as a pair. + /// + /// Same three sources, agreeing on all 18 families: the header constant, the presence of the + /// `DMAAUTOEN` field in the SVDs which have one, and the datasheet's "Auto enable" row. + pub auto_enable: bool, } /// How the flash controller writes and protects its flash. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 98eca54..d8acd01 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -300,6 +300,19 @@ pub struct Dma { /// `DMAChannel.syscfg.js` gates the option on a family list missing four families that have /// it. pub long_long_transfers: bool, + + /// Whether `DMACTL.AUTOEN` does anything. The field is in the register block on every device, + /// because one block serves them all, but the older DMA implements no automatic enable and + /// writing it there is accepted and ignored. + /// + /// True on exactly the same seven families as [`Dma::long_long_transfers`] โ€” the two features + /// arrived together, and the header states them as separate constants + /// (`DMA_SYS_MMR_AUTO`) which have never disagreed. Recorded separately anyway: nothing says + /// TI must keep shipping them as a pair. + /// + /// Same three sources, agreeing on all 18 families: the header constant, the presence of the + /// `DMAAUTOEN` field in the SVDs which have one, and the datasheet's "Auto enable" row. + pub auto_enable: bool, } /// The parts of the SYSCTL which its register block does not describe. diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 99caf2b..1b4ac21 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -531,8 +531,10 @@ fn generate_peripheral( let dma = match peripheral.dma { Some(dma) => { let long_long_transfers = dma.long_long_transfers; + let auto_enable = dma.auto_enable; quote! { Some(Dma { long_long_transfers: #long_long_transfers, + auto_enable: #auto_enable, }) } } None => quote! { None }, From 18d6a267c2fccebd2c3f6810daa270e3a5624ae9 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Tue, 11 Aug 2026 20:55:00 +0100 Subject: [PATCH 107/134] gpio: record which IO structure each pin is Nothing said which PINCM fields a pin implements. DRV is high-drive and high-speed only, HYSTEN is open-drain only, and open drain has no pullup, so a driver writing one of those on the wrong pin gets a setting that reads back and does nothing. sysconfig states the structure per pin and the datasheets' pin tables agree on every pin of every family. Wake stays with wakeup_pins: the mspm0c110x's open-drain pins have no wakeup logic, so the structure does not answer it. --- README.md | 2 + mspm0-data-gen/src/generate.rs | 55 ++++++++++++++++++++++++-- mspm0-data-gen/src/verify.rs | 34 +++++++++++++++- mspm0-data-types/src/lib.rs | 66 +++++++++++++++++++++++++++++++ mspm0-metapac-gen/res/metadata.rs | 64 ++++++++++++++++++++++++++++++ mspm0-metapac-gen/src/metadata.rs | 17 ++++++-- 6 files changed, 231 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b35a167..56ca200 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ These are the data sources currently used. * Peripheral PF (pin function) mappings. * Peripheral pin names. * Which pins have wakeup logic (`io_wakeup`). + * Which IO structure each pin is built from (`io_type`), and so which of its PINCM fields do + anything. Corroborated per pin by the datasheets' pin-attributes tables. * Number of ADC conversion channels (`SYS_ADC_MEMCTL_DIM`). * How many counters a basic timer instance has (`SYS_NUM_COUNTERS`), and a cross-check of the datasheet's capture/compare channel counts (`SYS_NUM_CC`). diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 6b722f7..2d74a06 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -8,9 +8,9 @@ use std::{ use anyhow::{anyhow, bail, ensure, Context}; use mspm0_data_types::{ - Adc, Chip, Comp, Dma, DmaChannel, Flashctl, Interrupt, Memory, MemoryKind, Package, PackagePin, - Peripheral, PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, PowerMode, Sysctl, - Timer, Uart, Unicomm, Vref, + Adc, Chip, Comp, Dma, DmaChannel, Flashctl, Interrupt, IoStructure, Memory, MemoryKind, Package, + PackagePin, Peripheral, PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, + PowerMode, Sysctl, Timer, Uart, Unicomm, Vref, }; use regex::Regex; @@ -65,6 +65,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu // Data shared across all chips in a family. let packages = get_packages(&family.family, sysconfig)?; let iomux = generate_pincm(sysconfig)?; + let io_structure = generate_io_structure(&family.family, sysconfig)?; let wakeup_pins = generate_wakeup_pins(sysconfig); let mut peripherals = generate_peripherals2(&family.family, header, sysconfig)?; let interrupts = generate_irqs(&family.family, header, int_groups)?; @@ -127,6 +128,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu .collect::<anyhow::Result<_>>()?, packages: packages.collect(), iomux: iomux.clone(), + io_structure: io_structure.clone(), wakeup_pins: wakeup_pins.clone(), peripherals: peripherals.clone(), interrupts: interrupts.clone(), @@ -201,6 +203,53 @@ fn get_packages(family: &str, sysconfig: &SysconfigFile) -> anyhow::Result<Vec<P Ok(packages) } +/// Read each pin's IO structure from sysconfig's `io_type`. +/// +/// An unrecognised value is an error rather than a skipped pin: the structure decides which PINCM +/// fields do anything, so a pin quietly missing from the map would read as a pin with no +/// restrictions. +fn generate_io_structure( + family: &str, + sysconfig: &SysconfigFile, +) -> anyhow::Result<BTreeMap<String, IoStructure>> { + let mut structures = BTreeMap::new(); + + for device_pin in sysconfig.device_pins.values() { + // Multi-bonded pins are listed separately under each function, as in generate_pincm. + if device_pin.name.contains('/') { + continue; + } + + // A pin with no PINCM is not usable as I/O, and sysconfig gives those no io_type either. + if device_pin.attributes.iomux_pincm.parse::<u32>().is_err() { + continue; + } + + let io_type = device_pin.attributes.io_type.as_deref().context(format!( + "{family}: {} has a PINCM but no io_type", + device_pin.name + ))?; + + let structure = match io_type { + "SD" => IoStructure::Standard, + "SDL" => IoStructure::StandardLowLeakage, + "SDW" => IoStructure::StandardWithWake, + "HD" => IoStructure::HighDrive, + "HS" => IoStructure::HighSpeed, + "OD" => IoStructure::OpenDrain, + "USB" => IoStructure::Usb, + other => bail!( + "{family}: {} has the unknown io_type {other}", + device_pin.name + ), + }; + + structures.insert(device_pin.name.to_string(), structure); + } + + Ok(structures) +} + fn generate_pincm(sysconfig: &SysconfigFile) -> anyhow::Result<BTreeMap<String, u32>> { let mut pins = BTreeMap::new(); diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 928e412..363d162 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -1,7 +1,7 @@ use std::{collections::HashSet, sync::LazyLock}; use anyhow::bail; -use mspm0_data_types::{AdcInternalSource, Chip, PeripheralType, PowerDomain}; +use mspm0_data_types::{AdcInternalSource, Chip, IoStructure, PeripheralType, PowerDomain}; use regex::Regex; /// Run every check, returning one error per failure. @@ -35,6 +35,7 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { clock_frequencies_consistent, retention_known, wakeup_pins_known, + io_structures_known, ]; CHECKS @@ -349,6 +350,37 @@ fn flashctl_known(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// Every pin has an IO structure, and no pin wakes the device without the logic to do it. +/// +/// The second half is a subset check, not an equality: a structure which can carry wakeup logic +/// does not always have it โ€” the mspm0c110x's open-drain pins do not. +fn io_structures_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + for pin in chip.iomux.keys() { + if !chip.io_structure.contains_key(pin) { + bail!("{name}: {pin} has a PINCM but no IO structure"); + } + } + + let Some(wakeup_pins) = &chip.wakeup_pins else { + return Ok(()); + }; + + for pin in wakeup_pins { + let structure = chip.io_structure.get(pin); + if !matches!( + structure, + Some(IoStructure::StandardWithWake | IoStructure::HighDrive | IoStructure::OpenDrain) + ) { + bail!( + "{name}: {pin} wakes the device from SHUTDOWN but is {structure:?}, which has no \ + wakeup logic" + ); + } + } + + Ok(()) +} + /// The DMA says which transfer widths it implements. /// /// Every chip has a DMA and the header states the fact for every device, so an instance without it diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index b5780ec..3bea9f4 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -34,6 +34,11 @@ pub struct Chip { /// Mapping from device pin to IOMUX register index. pub iomux: BTreeMap<String, u32>, + /// Which IO structure each device pin is built from, keyed the same way as `iomux`. + /// + /// Every pin with a PINCM has one. + pub io_structure: BTreeMap<String, IoStructure>, + /// Device pins which have wakeup logic and can therefore wake the device from SHUTDOWN. /// /// The `FASTWAKE` mechanism, which wakes the device from STOP and STANDBY, works on any GPIO @@ -230,6 +235,67 @@ pub struct Package { pub pins: Vec<PackagePin>, } +/// The IO structure a pin is built from, which decides what its PINCM fields do. +/// +/// The IOMUX register is the same for every pin, so a field a pin's structure does not implement +/// is written, read back, and ignored. What each structure implements: +/// +/// | structure | `INV` | `DRV` | `HYSTEN` | `PIPU` | `PIPD` | wake | +/// |---|---|---|---|---|---|---| +/// | [`Standard`](IoStructure::Standard), [`StandardLowLeakage`](IoStructure::StandardLowLeakage) | yes | | | yes | yes | | +/// | [`StandardWithWake`](IoStructure::StandardWithWake) | yes | | | yes | yes | yes | +/// | [`HighDrive`](IoStructure::HighDrive) | yes | yes | | yes | yes | yes | +/// | [`HighSpeed`](IoStructure::HighSpeed) | yes | yes | | yes | yes | | +/// | [`OpenDrain`](IoStructure::OpenDrain) | yes | | yes | | yes | yes | +/// +/// The table is SLAU846 Table 8-1, and TI's own `GPIOPin.syscfg.js` gates its options by exactly +/// these rules. Three caveats before treating it as complete: +/// +/// - **Wake is not derivable from the structure.** On mspm0c110x and msps003fx the open-drain +/// pins have no wakeup logic โ€” sysconfig marks `io_wakeup` false on them, and the C1104 +/// datasheet's feature table has no wakeup column at all. Use [`Chip::wakeup_pins`]. +/// - **The per-device feature tables are not reliable in either direction.** The MSPM0G3519's +/// omits the open-drain row although its own pin table gives PA0 and PA1 that structure; the +/// MSPM0L2117's carries two low-drive rows although no pin on the device is low-drive; the +/// MSPM0L2117's also leaves high-drive's drive-strength cell empty against every other +/// datasheet, the TRM and TI's own tool; and the MSPM0H3216's marks no structure as having a +/// pulldown. Per-pin data is the reliable part. +/// - **Not every device has every structure**, and no device has all of them. +/// +/// The source is sysconfig's per-pin `io_type`, which the datasheets' per-pin tables corroborate +/// with no disagreement on any pin of any family. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum IoStructure { + /// Standard drive (`SDIO`). + Standard, + + /// Standard drive, low leakage (`SDL` in sysconfig, which TI's tool calls "Low-leakage + /// Standard"). The datasheets' pin tables print it as plain standard drive, and every rule in + /// TI's tool treats the two identically, so the difference is leakage current rather than + /// anything the IOMUX can express. + /// + /// One pin per family on the older G and L families โ€” PA2 everywhere it appears โ€” and every + /// pin of msps003fx. + StandardLowLeakage, + + /// Standard drive with wakeup logic (`SDIO` with wake). + StandardWithWake, + + /// High drive (`HDIO`), the 20mA output. + HighDrive, + + /// High speed (`HSIO`). + HighSpeed, + + /// 5V-tolerant open drain (`ODIO`). The only structure with hysteresis control, and the only + /// one with no pullup: `PIPU` on one of these pins does nothing. + OpenDrain, + + /// A USB 2.0 full-speed pin (`USBIO`), on mspm0g518x only. Powered from `VUSB33` rather than + /// `VDD`, and treated as standard drive by TI's tool. + Usb, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PackagePin { /// The position by pin name. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index d8acd01..d91b07b 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -807,6 +807,70 @@ pub struct Pin { /// `None` when the vendor data does not describe wakeup logic for this chip, which is not the /// same as `Some(false)`. pub wakeup: Option<bool>, + + /// Which IO structure the pin is built from, and so which of its PINCM fields do anything. + pub structure: IoStructure, +} + +/// The IO structure a pin is built from, which decides what its PINCM fields do. +/// +/// The IOMUX register is the same for every pin, so a field a pin's structure does not implement +/// is written, read back, and ignored. What each structure implements: +/// +/// | structure | `INV` | `DRV` | `HYSTEN` | `PIPU` | `PIPD` | wake | +/// |---|---|---|---|---|---|---| +/// | [`Standard`](IoStructure::Standard), [`StandardLowLeakage`](IoStructure::StandardLowLeakage) | yes | | | yes | yes | | +/// | [`StandardWithWake`](IoStructure::StandardWithWake) | yes | | | yes | yes | yes | +/// | [`HighDrive`](IoStructure::HighDrive) | yes | yes | | yes | yes | yes | +/// | [`HighSpeed`](IoStructure::HighSpeed) | yes | yes | | yes | yes | | +/// | [`OpenDrain`](IoStructure::OpenDrain) | yes | | yes | | yes | yes | +/// +/// The table is SLAU846 Table 8-1, and TI's own `GPIOPin.syscfg.js` gates its options by exactly +/// these rules. Three caveats before treating it as complete: +/// +/// - **Wake is not derivable from the structure.** On mspm0c110x and msps003fx the open-drain +/// pins have no wakeup logic โ€” sysconfig marks `io_wakeup` false on them, and the C1104 +/// datasheet's feature table has no wakeup column at all. Use [`Pin::wakeup`]. +/// - **The per-device feature tables are not reliable in either direction.** The MSPM0G3519's +/// omits the open-drain row although its own pin table gives PA0 and PA1 that structure; the +/// MSPM0L2117's carries two low-drive rows although no pin on the device is low-drive; the +/// MSPM0L2117's also leaves high-drive's drive-strength cell empty against every other +/// datasheet, the TRM and TI's own tool; and the MSPM0H3216's marks no structure as having a +/// pulldown. Per-pin data is the reliable part. +/// - **Not every device has every structure**, and no device has all of them. +/// +/// The source is sysconfig's per-pin `io_type`, which the datasheets' per-pin tables corroborate +/// with no disagreement on any pin of any family. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum IoStructure { + /// Standard drive (`SDIO`). + Standard, + + /// Standard drive, low leakage (`SDL` in sysconfig, which TI's tool calls "Low-leakage + /// Standard"). The datasheets' pin tables print it as plain standard drive, and every rule in + /// TI's tool treats the two identically, so the difference is leakage current rather than + /// anything the IOMUX can express. + /// + /// One pin per family on the older G and L families โ€” PA2 everywhere it appears โ€” and every + /// pin of msps003fx. + StandardLowLeakage, + + /// Standard drive with wakeup logic (`SDIO` with wake). + StandardWithWake, + + /// High drive (`HDIO`), the 20mA output. + HighDrive, + + /// High speed (`HSIO`). + HighSpeed, + + /// 5V-tolerant open drain (`ODIO`). The only structure with hysteresis control, and the only + /// one with no pullup: `PIPU` on one of these pins does nothing. + OpenDrain, + + /// A USB 2.0 full-speed pin (`USBIO`), on mspm0g518x only. Powered from `VUSB33` rather than + /// `VDD`, and treated as standard drive by TI's tool. + Usb, } #[derive(Debug, Eq, PartialEq, Clone, Copy)] diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 1b4ac21..652b070 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -1,8 +1,8 @@ use std::{collections::HashSet, sync::LazyLock}; use mspm0_data_types::{ - AdcInternalSource, Chip, MemoryKind, Package, Peripheral, PeripheralType, PowerDomain, - PowerMode, + AdcInternalSource, Chip, IoStructure, MemoryKind, Package, Peripheral, PeripheralType, + PowerDomain, PowerMode, }; use proc_macro2::{Literal, TokenStream}; use quote::quote; @@ -36,7 +36,18 @@ pub fn pins(chip: &Chip, package: &Package) -> TokenStream { None => quote! { None }, }; - Some(quote! { Pin { pin: #signal, pincm: #pincm, wakeup: #wakeup } }) + let structure = match chip.io_structure.get(signal) { + Some(IoStructure::Standard) => quote! { IoStructure::Standard }, + Some(IoStructure::StandardLowLeakage) => quote! { IoStructure::StandardLowLeakage }, + Some(IoStructure::StandardWithWake) => quote! { IoStructure::StandardWithWake }, + Some(IoStructure::HighDrive) => quote! { IoStructure::HighDrive }, + Some(IoStructure::HighSpeed) => quote! { IoStructure::HighSpeed }, + Some(IoStructure::OpenDrain) => quote! { IoStructure::OpenDrain }, + Some(IoStructure::Usb) => quote! { IoStructure::Usb }, + None => panic!("{signal} has a PINCM but no IO structure"), + }; + + Some(quote! { Pin { pin: #signal, pincm: #pincm, wakeup: #wakeup, structure: #structure } }) }); quote! { &[#(#pins),*] } From 9a8c4f1baaf43b52bcef8845be0839b90d7449f9 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Tue, 11 Aug 2026 20:59:56 +0100 Subject: [PATCH 108/134] gpio: state how much of the IO structure data the datasheets cover "No disagreement on any pin" read as if every pin had been checked. 723 of 847 were; the rest are rows the table extraction did not recover. --- mspm0-data-types/src/lib.rs | 5 +++-- mspm0-metapac-gen/res/metadata.rs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 3bea9f4..c598d73 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -262,8 +262,9 @@ pub struct Package { /// pulldown. Per-pin data is the reliable part. /// - **Not every device has every structure**, and no device has all of them. /// -/// The source is sysconfig's per-pin `io_type`, which the datasheets' per-pin tables corroborate -/// with no disagreement on any pin of any family. +/// The source is sysconfig's per-pin `io_type`. The datasheets' per-pin tables corroborate it on +/// 723 of the 847 pins, across all 18 families, with no disagreement on any of them; the shortfall +/// is rows the table extraction did not recover, not pins the two sources describe differently. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum IoStructure { /// Standard drive (`SDIO`). diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index d91b07b..2b8f5d7 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -839,8 +839,9 @@ pub struct Pin { /// pulldown. Per-pin data is the reliable part. /// - **Not every device has every structure**, and no device has all of them. /// -/// The source is sysconfig's per-pin `io_type`, which the datasheets' per-pin tables corroborate -/// with no disagreement on any pin of any family. +/// The source is sysconfig's per-pin `io_type`. The datasheets' per-pin tables corroborate it on +/// 723 of the 847 pins, across all 18 families, with no disagreement on any of them; the shortfall +/// is rows the table extraction did not recover, not pins the two sources describe differently. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum IoStructure { /// Standard drive (`SDIO`). From fc3fc1484a62ba82e456ea8afafedcdceaf3e287 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Tue, 11 Aug 2026 22:13:16 +0100 Subject: [PATCH 109/134] temp_sensor: extract the conversion constants from the datasheets TEMP_SENSE0 is one ADC code and means nothing without the trim temperature, the sensor's slope, the sample window and the reference the factory measured against. None of the four has a machine-readable source. The reference varies per device across three values, and three datasheets contradict themselves about it. The overrides file records which half hardware agreed with. --- data/temp_sensor/mspm0c1105_c1106.yaml | 19 ++ data/temp_sensor/mspm0c110x.yaml | 19 ++ data/temp_sensor/mspm0g110x.yaml | 19 ++ data/temp_sensor/mspm0g150x.yaml | 19 ++ data/temp_sensor/mspm0g151x.yaml | 19 ++ data/temp_sensor/mspm0g310x.yaml | 19 ++ data/temp_sensor/mspm0g350x.yaml | 19 ++ data/temp_sensor/mspm0g351x.yaml | 19 ++ data/temp_sensor/mspm0g518x.yaml | 19 ++ data/temp_sensor/mspm0h321x.yaml | 19 ++ data/temp_sensor/mspm0l110x.yaml | 25 ++ data/temp_sensor/mspm0l112x.yaml | 19 ++ data/temp_sensor/mspm0l122x.yaml | 21 ++ data/temp_sensor/mspm0l130x.yaml | 25 ++ data/temp_sensor/mspm0l134x.yaml | 25 ++ data/temp_sensor/mspm0l211x.yaml | 19 ++ data/temp_sensor/mspm0l222x.yaml | 21 ++ data/temp_sensor/msps003fx.yaml | 19 ++ data/temp_sensor_overrides.yaml | 31 ++ tools/temp_sensor.py | 396 +++++++++++++++++++++++++ 20 files changed, 791 insertions(+) create mode 100644 data/temp_sensor/mspm0c1105_c1106.yaml create mode 100644 data/temp_sensor/mspm0c110x.yaml create mode 100644 data/temp_sensor/mspm0g110x.yaml create mode 100644 data/temp_sensor/mspm0g150x.yaml create mode 100644 data/temp_sensor/mspm0g151x.yaml create mode 100644 data/temp_sensor/mspm0g310x.yaml create mode 100644 data/temp_sensor/mspm0g350x.yaml create mode 100644 data/temp_sensor/mspm0g351x.yaml create mode 100644 data/temp_sensor/mspm0g518x.yaml create mode 100644 data/temp_sensor/mspm0h321x.yaml create mode 100644 data/temp_sensor/mspm0l110x.yaml create mode 100644 data/temp_sensor/mspm0l112x.yaml create mode 100644 data/temp_sensor/mspm0l122x.yaml create mode 100644 data/temp_sensor/mspm0l130x.yaml create mode 100644 data/temp_sensor/mspm0l134x.yaml create mode 100644 data/temp_sensor/mspm0l211x.yaml create mode 100644 data/temp_sensor/mspm0l222x.yaml create mode 100644 data/temp_sensor/msps003fx.yaml create mode 100644 data/temp_sensor_overrides.yaml create mode 100644 tools/temp_sensor.py diff --git a/data/temp_sensor/mspm0c1105_c1106.yaml b/data/temp_sensor/mspm0c1105_c1106.yaml new file mode 100644 index 0000000..5feb8cd --- /dev/null +++ b/data/temp_sensor/mspm0c1105_c1106.yaml @@ -0,0 +1,19 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0c1106 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +tstrim_c: 30 +tsc_uv_per_c: -1890 +settling_ns: 10000 +calibration_sample_ns: 10000 +calibration_reference: internal_1v4 diff --git a/data/temp_sensor/mspm0c110x.yaml b/data/temp_sensor/mspm0c110x.yaml new file mode 100644 index 0000000..65f8c16 --- /dev/null +++ b/data/temp_sensor/mspm0c110x.yaml @@ -0,0 +1,19 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0c1104 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +tstrim_c: 30 +tsc_uv_per_c: -1800 +settling_ns: 10000 +calibration_sample_ns: null +calibration_reference: internal_1v4 diff --git a/data/temp_sensor/mspm0g110x.yaml b/data/temp_sensor/mspm0g110x.yaml new file mode 100644 index 0000000..9427295 --- /dev/null +++ b/data/temp_sensor/mspm0g110x.yaml @@ -0,0 +1,19 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0g1107 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +tstrim_c: 30 +tsc_uv_per_c: -1800 +settling_ns: 12500 +calibration_sample_ns: 12500 +calibration_reference: vdd diff --git a/data/temp_sensor/mspm0g150x.yaml b/data/temp_sensor/mspm0g150x.yaml new file mode 100644 index 0000000..1a57506 --- /dev/null +++ b/data/temp_sensor/mspm0g150x.yaml @@ -0,0 +1,19 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0g1507 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +tstrim_c: 30 +tsc_uv_per_c: -1800 +settling_ns: 12500 +calibration_sample_ns: 12500 +calibration_reference: vdd diff --git a/data/temp_sensor/mspm0g151x.yaml b/data/temp_sensor/mspm0g151x.yaml new file mode 100644 index 0000000..071998e --- /dev/null +++ b/data/temp_sensor/mspm0g151x.yaml @@ -0,0 +1,19 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0g1519 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +tstrim_c: 30 +tsc_uv_per_c: -1900 +settling_ns: 12500 +calibration_sample_ns: 12500 +calibration_reference: internal_1v4 diff --git a/data/temp_sensor/mspm0g310x.yaml b/data/temp_sensor/mspm0g310x.yaml new file mode 100644 index 0000000..dcc00f5 --- /dev/null +++ b/data/temp_sensor/mspm0g310x.yaml @@ -0,0 +1,19 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0g3107 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +tstrim_c: 30 +tsc_uv_per_c: -1800 +settling_ns: 12500 +calibration_sample_ns: 12500 +calibration_reference: vdd diff --git a/data/temp_sensor/mspm0g350x.yaml b/data/temp_sensor/mspm0g350x.yaml new file mode 100644 index 0000000..6a8bbfb --- /dev/null +++ b/data/temp_sensor/mspm0g350x.yaml @@ -0,0 +1,19 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0g3507 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +tstrim_c: 30 +tsc_uv_per_c: -1800 +settling_ns: 12500 +calibration_sample_ns: 12500 +calibration_reference: vdd diff --git a/data/temp_sensor/mspm0g351x.yaml b/data/temp_sensor/mspm0g351x.yaml new file mode 100644 index 0000000..33cfb49 --- /dev/null +++ b/data/temp_sensor/mspm0g351x.yaml @@ -0,0 +1,19 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0g3519 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +tstrim_c: 30 +tsc_uv_per_c: -1900 +settling_ns: 12500 +calibration_sample_ns: 12500 +calibration_reference: internal_1v4 diff --git a/data/temp_sensor/mspm0g518x.yaml b/data/temp_sensor/mspm0g518x.yaml new file mode 100644 index 0000000..c864591 --- /dev/null +++ b/data/temp_sensor/mspm0g518x.yaml @@ -0,0 +1,19 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0g5187 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +tstrim_c: 30 +tsc_uv_per_c: -1900 +settling_ns: 12500 +calibration_sample_ns: 12500 +calibration_reference: internal_1v4 diff --git a/data/temp_sensor/mspm0h321x.yaml b/data/temp_sensor/mspm0h321x.yaml new file mode 100644 index 0000000..656b57a --- /dev/null +++ b/data/temp_sensor/mspm0h321x.yaml @@ -0,0 +1,19 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0h3216 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +tstrim_c: 30 +tsc_uv_per_c: -1900 +settling_ns: 10000 +calibration_sample_ns: null +calibration_reference: internal_4v05 diff --git a/data/temp_sensor/mspm0l110x.yaml b/data/temp_sensor/mspm0l110x.yaml new file mode 100644 index 0000000..3669598 --- /dev/null +++ b/data/temp_sensor/mspm0l110x.yaml @@ -0,0 +1,25 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0l1106 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +# +# The mspm0l1106 datasheet contradicts itself here: the peripheral section says VRSEL=0h and the electrical table says VRSEL=2h. +# Resolved to internal_1v4 by data/temp_sensor_overrides.yaml (L1306 hardware, 2026-08-11), which holds the reasoning. +tstrim_c: 30 +tsc_uv_per_c: -1750 +settling_ns: 10000 +calibration_sample_ns: 12500 +calibration_reference: internal_1v4 +conflicting_references: +- vdd +- internal_1v4 diff --git a/data/temp_sensor/mspm0l112x.yaml b/data/temp_sensor/mspm0l112x.yaml new file mode 100644 index 0000000..49bc4f7 --- /dev/null +++ b/data/temp_sensor/mspm0l112x.yaml @@ -0,0 +1,19 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0l2117 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +tstrim_c: 30 +tsc_uv_per_c: -1900 +settling_ns: 10000 +calibration_sample_ns: 10000 +calibration_reference: internal_1v4 diff --git a/data/temp_sensor/mspm0l122x.yaml b/data/temp_sensor/mspm0l122x.yaml new file mode 100644 index 0000000..1acda92 --- /dev/null +++ b/data/temp_sensor/mspm0l122x.yaml @@ -0,0 +1,21 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0l1228 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +# +# The mspm0l1228 datasheet contradicts itself here: no tSET,TS row. +tstrim_c: 30 +tsc_uv_per_c: -1800 +settling_ns: null +calibration_sample_ns: 12500 +calibration_reference: internal_1v4 diff --git a/data/temp_sensor/mspm0l130x.yaml b/data/temp_sensor/mspm0l130x.yaml new file mode 100644 index 0000000..9b5b3cd --- /dev/null +++ b/data/temp_sensor/mspm0l130x.yaml @@ -0,0 +1,25 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0l1306 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +# +# The mspm0l1306 datasheet contradicts itself here: the peripheral section says VRSEL=0h and the electrical table says VRSEL=2h. +# Resolved to internal_1v4 by data/temp_sensor_overrides.yaml (L1306 hardware, 2026-08-11), which holds the reasoning. +tstrim_c: 30 +tsc_uv_per_c: -1750 +settling_ns: 10000 +calibration_sample_ns: 12500 +calibration_reference: internal_1v4 +conflicting_references: +- vdd +- internal_1v4 diff --git a/data/temp_sensor/mspm0l134x.yaml b/data/temp_sensor/mspm0l134x.yaml new file mode 100644 index 0000000..ea597d7 --- /dev/null +++ b/data/temp_sensor/mspm0l134x.yaml @@ -0,0 +1,25 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0l1346 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +# +# The mspm0l1346 datasheet contradicts itself here: the peripheral section says VRSEL=0h and the electrical table says VRSEL=2h. +# Resolved to internal_1v4 by data/temp_sensor_overrides.yaml (L1306 hardware, 2026-08-11), which holds the reasoning. +tstrim_c: 30 +tsc_uv_per_c: -1750 +settling_ns: 10000 +calibration_sample_ns: 12500 +calibration_reference: internal_1v4 +conflicting_references: +- vdd +- internal_1v4 diff --git a/data/temp_sensor/mspm0l211x.yaml b/data/temp_sensor/mspm0l211x.yaml new file mode 100644 index 0000000..49bc4f7 --- /dev/null +++ b/data/temp_sensor/mspm0l211x.yaml @@ -0,0 +1,19 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0l2117 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +tstrim_c: 30 +tsc_uv_per_c: -1900 +settling_ns: 10000 +calibration_sample_ns: 10000 +calibration_reference: internal_1v4 diff --git a/data/temp_sensor/mspm0l222x.yaml b/data/temp_sensor/mspm0l222x.yaml new file mode 100644 index 0000000..26c78db --- /dev/null +++ b/data/temp_sensor/mspm0l222x.yaml @@ -0,0 +1,21 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0l2228 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +# +# The mspm0l2228 datasheet contradicts itself here: no tSET,TS row. +tstrim_c: 30 +tsc_uv_per_c: -1800 +settling_ns: null +calibration_sample_ns: 12500 +calibration_reference: internal_1v4 diff --git a/data/temp_sensor/msps003fx.yaml b/data/temp_sensor/msps003fx.yaml new file mode 100644 index 0000000..65f8c16 --- /dev/null +++ b/data/temp_sensor/msps003fx.yaml @@ -0,0 +1,19 @@ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the mspm0c1104 datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself +tstrim_c: 30 +tsc_uv_per_c: -1800 +settling_ns: 10000 +calibration_sample_ns: null +calibration_reference: internal_1v4 diff --git a/data/temp_sensor_overrides.yaml b/data/temp_sensor_overrides.yaml new file mode 100644 index 0000000..1485543 --- /dev/null +++ b/data/temp_sensor_overrides.yaml @@ -0,0 +1,31 @@ +# Corrections to what `tools/temp_sensor.py` reads out of a datasheet, applied last. +# +# Only for a contradiction a better source settles. Every entry states the evidence, as +# `data/operating_mode_overrides.yaml` does: an override with no evidence is a guess wearing a +# file name. + +mspm0l110x: + calibration_reference: internal_1v4 + # The MSPM0L1105/L1106 datasheet states the calibration twice and contradicts itself: the 7.13 + # electrical table's test conditions say `VRSEL=2h (internal VREF), BUFCONFIG=1h (1.4V VREF)`, + # the 8.13 peripheral section says `VRSEL=0h (VDD)`. Same measurement, same document. + # + # Settled on hardware for the L1306, which carries the identical pair of statements (2026-08-11, + # embassy-mspm0, R18): `TEMP_SENSE0` reads 1885, which is 644mV against a 1.4V reference and + # 1519mV against VDD. Only the first is a temperature sensor output. Converting a live reading + # taken against VDDA (code 806, stable) gives 26.8degC against the 1.4V trim and 527degC against + # the VDD trim. The other two combinations give 241degC and 211degC, so the answer does not rest + # on the reading's own reference having taken effect. + evidence: L1306 hardware, 2026-08-11 + +mspm0l130x: + calibration_reference: internal_1v4 + # The measurement above was taken on this family. See mspm0l110x for the arithmetic. + evidence: L1306 hardware, 2026-08-11 + +mspm0l134x: + calibration_reference: internal_1v4 + # The MSPM0L1343-L1346 datasheet is the L1303-L1306 datasheet's twin and carries the same + # contradiction in the same two places. Same silicon generation, same sensor rows (TSc -1.75 + # mV/degC, TSTRIM 30degC), so the L1306 measurement covers it. + evidence: L1306 hardware, 2026-08-11 diff --git a/tools/temp_sensor.py b/tools/temp_sensor.py new file mode 100644 index 0000000..03fc3f8 --- /dev/null +++ b/tools/temp_sensor.py @@ -0,0 +1,396 @@ +"""Extract the temperature sensor's conversion constants from MSPM0 datasheets. + +`FACTORYREGION.TEMP_SENSE0` holds one ADC code per device: the sensor's output at the factory trim +temperature. Turning a later reading into a temperature needs three numbers that live only in the +datasheet -- the trim temperature itself, the sensor's slope, and how long the ADC has to sample -- +plus one fact about how the factory measurement was taken. This writes +`data/temp_sensor/<family>.yaml`. + +Like the other `tools/` scripts this is an offline aid rather than part of the build. Re-run it +after a data source bump rather than editing the YAML by hand. + +Usage: + uv run tools/temp_sensor.py <datasheet.pdf> [...] # print what it finds + uv run tools/temp_sensor.py --write <dir-of-datasheets> # regenerate data/temp_sensor + +`uv run` installs the dependencies below on its own; a bare `python` needs them on the path. + +Four things about these rows are worth knowing: + +- **The settling time is a MAX, and it is a minimum ADC sample time.** The datasheets' own footnote + says so: "This is the maximum time required for the temperature sensor to settle when measured by + the ADC. It may be used to specify the minimum ADC sample time." A driver sampling faster gets a + plausible-looking wrong reading, and the figure ranges 10us to 12.5us across the portfolio. + +- **The sample time the factory used is a separate number** and is not always the settling time. The + older L datasheets state a 12.5us calibration sample against their own 10us settling maximum, so + reproducing the factory measurement and merely letting the sensor settle are two different + requirements. + +- **The reference the factory calibrated against varies per device**, and there is no portfolio + default: the older G families and TI's own worked example use VDD, seven datasheets name the 1.4V + internal reference, and the H3216 names 4.05V. A code taken against a different reference has to + be rescaled before it is compared with `TEMP_SENSE0`, so this is the fact that decides whether a + driver is right or merely plausible. + +- **Three datasheets contradict themselves about it.** MSPM0L1106, MSPM0L1306 and MSPM0L1346 give + the electrical table's test conditions as `VRSEL=2h (internal VREF), BUFCONFIG=1h (1.4V VREF)` + and the peripheral section's prose as `VRSEL=0h (VDD)`, for the same measurement in the same + document. Neither half is a wording artifact -- both name `VRSEL`. The tool reports the conflict + and keeps both readings; `data/temp_sensor_overrides.yaml` resolves it, with the evidence. + +No document settles that one. SLAU847 says "the 1.4V internal voltage reference" outright, but +SLAU846 and SLAU923 carry the same sentence with the reference edited out -- leaving the tell-tale +"the a voltage reference used during factory calibration" -- so the specific value there is stale +text rather than an L-series statement. All three then share one worked example, at 1.4V, with a +slope matching no device in any datasheet. driverlib repeats the 1.4V claim in a single +portfolio-wide header, while TI's own G3507 and G3519 examples compute against 3.3V. + +**Hardware did settle it, and the electrical table was the half that was right.** An L1306's +`TEMP_SENSE0` reads 1885, which is 644mV against a 1.4V reference and 1519mV against VDD; only the +first is a temperature sensor output, and converting a live reading against the 1.4V trim gives +room temperature where the VDD trim gives 527degC. Worth remembering the next time a specification +table and a peripheral-section paragraph disagree: the table won. +""" + +# /// script +# requires-python = ">=3.10" +# dependencies = ["pdfplumber", "pypdfium2", "pyyaml"] +# /// + +import re +import sys +from dataclasses import dataclass +from pathlib import Path + +try: + import pdfplumber + import pypdfium2 + import yaml +except ImportError as e: # pragma: no cover + raise SystemExit(f"{e.name} is missing; run this with `uv run` instead of `python`") + +LATTICE = {"vertical_strategy": "lines", "horizontal_strategy": "lines"} + +#: Time units the sample and settling figures may be given in, as a multiplier to nanoseconds. +UNITS = {"ns": 1, "us": 1_000, "ยตs": 1_000, "ms": 1_000_000} + +NUMBER = re.compile(r"-?\d+(?:\.\d+)?") + +#: A cell holding nothing but the MIN/TYP/MAX figures. The three columns are merged into one cell +#: by the lattice read, and a footnote marker in the neighbouring description is a number too. +FIGURES_ONLY = re.compile(r"\s*-?\d+(?:\.\d+)?(?:\s+-?\d+(?:\.\d+)?)*\s*") + +#: The paragraph of the peripheral section which describes the factory measurement. Its wording +#: drifts between datasheets ("with the 3.3V VDD reference", "with VDD = 3.3V", "with the 1.4V +#: internal VREF"), so the reference is read from the `VRSEL=` it quotes rather than from the prose. +CALIBRATION = re.compile( + r"A unit-specific single-point calibration value.*?trim value\.", re.S +) + +#: `VRSEL=<n>h`, wherever it appears. 0h is VDD, 2h the internal reference buffer, 4h the H3216's +#: 4.05V one. The datasheets write it with and without a space after the equals sign. +VRSEL = re.compile(r"VRSEL\s*=\s*(\d)h") + +#: The ADC sample time the factory measurement used, from the same paragraph. +SAMPLE_TIME = re.compile(r"t\s*(?:Sample|sample)\s*=\s*(\d+(?:\.\d+)?)\s*(us|ยตs|ns|ms)") + +#: The same figure as the electrical table's test conditions state it. The subscript comes back +#: either attached (`tsample = 12.5uS`) or displaced to the end of the cell (`ADC t = 12.5uS +#: sample`), so the word is optional here where the prose always has it. +TABLE_SAMPLE_TIME = re.compile( + r"ADC\s*t\s*(?:sample)?\s*=\s*(\d+(?:\.\d+)?)\s*(us|ยตs|ns|ms)", re.I +) + +#: Corrections a better source settles, applied after the datasheet read. +OVERRIDES = Path("data/temp_sensor_overrides.yaml") + +#: What each `VRSEL` encoding selects, for the generated file and for the metadata. +REFERENCES = {0: "vdd", 2: "internal_1v4", 4: "internal_4v05"} + +#: The newer datasheets name the reference in prose and quote no ADC configuration, so the phrase +#: has to be read too. "with VDD = 3.3V" is deliberately absent: it names the supply rather than the +#: reference, and every datasheet which uses it also quotes a `VRSEL`. +PHRASES = ( + (re.compile(r"with the 1\.4-?V internal", re.I), 2), + (re.compile(r"with the 4\.05-?V internal", re.I), 4), + (re.compile(r"with the 3\.3-?V VDD reference", re.I), 0), +) + + +@dataclass +class TempSensor: + """One family's temperature sensor constants.""" + + tstrim_c: int + tsc_uv_per_c: int + #: `None` on the two families whose datasheet has no tSET,TS row. + settling_ns: int | None + calibration_sample_ns: int | None + #: `VRSEL` per the peripheral section's prose, and per the electrical table's test conditions. + #: They disagree on three families, which is why both are kept. + reference_prose: int | None + reference_table: int | None + + @property + def reference(self) -> str | None: + """The reference both halves agree on, or `None` where the datasheet contradicts itself. + + Either half alone is enough: the newer datasheets name the reference in prose and quote no + ADC configuration, and the C1104's electrical table quotes one against a bare TSTRIM row. + """ + if self.conflicting: + return None + + stated = self.reference_prose if self.reference_prose is not None else self.reference_table + + return REFERENCES.get(stated) if stated is not None else None + + @property + def conflicting(self) -> bool: + """Whether the datasheet's two statements of the reference disagree.""" + return ( + self.reference_prose is not None + and self.reference_table is not None + and self.reference_prose != self.reference_table + ) + + +def page_text(path: Path) -> list[str]: + """The text of every page, read with pypdfium2 as the other tools do.""" + doc = pypdfium2.PdfDocument(str(path)) + try: + return [doc[i].get_textpage().get_text_range() for i in range(len(doc))] + finally: + doc.close() + + +def figures(cell: str) -> list[float]: + """Every number in a merged MIN/TYP/MAX cell, in column order.""" + return [float(n) for n in NUMBER.findall(cell)] + + +def typical(cell: str) -> float | None: + """The TYP figure of a merged MIN/TYP/MAX cell. + + A full row gives three numbers and the middle one is TYP; a row which fills only one column + gives one. Anything else means the layout changed and the caller should report it. + """ + found = figures(cell) + if len(found) == 3: + return found[1] + if len(found) == 1: + return found[0] + + return None + + +def maximum(cell: str) -> float | None: + """The MAX figure of a merged cell, which is the last column that carries a number.""" + found = figures(cell) + + return found[-1] if found else None + + +def read(path: Path) -> tuple[TempSensor | None, str]: + """Read one datasheet. Returns the constants and a note about anything unusual.""" + pages = page_text(path) + + # Searched over the whole document rather than page by page: on the L1105/L1106 datasheet the + # paragraph straddles a page break, and a per-page search silently found no statement there and + # fell back to the electrical table -- which is the half those datasheets contradict. + prose_vrsel = sample_ns = None + flat = " ".join(" ".join(pages).split()) + match = CALIBRATION.search(flat) + if match: + found = VRSEL.search(match.group(0)) + if found: + prose_vrsel = int(found.group(1)) + else: + prose_vrsel = next( + (vrsel for phrase, vrsel in PHRASES if phrase.search(match.group(0))), None + ) + sample = SAMPLE_TIME.search(match.group(0)) + if sample: + sample_ns = round(float(sample.group(1)) * UNITS[sample.group(2).lower()]) + + rows: dict[str, tuple[str, str]] = {} + table_vrsel = None + for index, text in enumerate(pages): + if "TSTRIM" not in text.replace(" ", ""): + continue + + with pdfplumber.open(str(path)) as pdf: + for table in pdf.pages[index].extract_tables(LATTICE): + for row in table: + cells = [(c or "").replace("\n", " ").strip() for c in row] + joined = " ".join(cells) + squashed = joined.replace(" ", "") + markers = (("tstrim", "TSTRIM"), ("tsc", "TSc"), ("tset", "tSET,")) + hit = [key for key, marker in markers if marker in squashed] + if not hit: + continue + + found = VRSEL.search(joined) + if found and table_vrsel is None: + table_vrsel = int(found.group(1)) + + # The table states the calibration sample time on the datasheets whose prose + # quotes no ADC configuration. + sample = TABLE_SAMPLE_TIME.search(joined) + if sample and sample_ns is None: + sample_ns = round( + float(sample.group(1)) * UNITS[sample.group(2).lower()] + ) + + unit = next((c for c in reversed(cells) if c.lower() in UNITS), "") + # The figures share one cell. Pick the cell which is nothing but numbers, so a + # footnote marker in the description ("Factory trim temperature (2)") cannot be + # mistaken for one. + values = next((c for c in cells if FIGURES_ONLY.fullmatch(c)), "") + for key in hit: + if key not in rows: + rows[key] = (values, unit) + if rows: + break + + missing = [key for key in ("tstrim", "tsc") if key not in rows] + if missing: + return None, f"no {', '.join(missing)} row" + + tstrim = typical(rows["tstrim"][0]) + tsc = typical(rows["tsc"][0]) + if tstrim is None or tsc is None: + return None, "a row did not parse into MIN/TYP/MAX" + + # The L1227/L1228/L2227/L2228 table has no tSET,TS row at all, so the minimum sample time is + # unstated for those parts rather than misread. + settling_ns = None + if "tset" in rows: + settling, unit = maximum(rows["tset"][0]), rows["tset"][1] + if settling is None or unit.lower() not in UNITS: + return None, "the tSET,TS row did not parse" + settling_ns = round(settling * UNITS[unit.lower()]) + + sensor = TempSensor( + tstrim_c=round(tstrim), + tsc_uv_per_c=round(tsc * 1000), + settling_ns=settling_ns, + calibration_sample_ns=sample_ns, + reference_prose=prose_vrsel, + reference_table=table_vrsel, + ) + + note = "" + if sensor.conflicting: + note = ( + f"the peripheral section says VRSEL={prose_vrsel}h and the electrical table says " + f"VRSEL={table_vrsel}h" + ) + elif sensor.reference is None: + note = "no calibration reference stated" + if sensor.settling_ns is None: + note = f"{note}; no tSET,TS row".lstrip("; ") + + return sensor, note + + +HEADER = """\ +# The temperature sensor's conversion constants for this family. +# +# GENERATED by `tools/temp_sensor.py --write` from the {ds} datasheet's Temperature Sensor rows. +# Re-run the tool rather than editing by hand. +# +# tstrim_c the factory trim temperature, in degrees Celsius (the TYP column; the +# datasheet's MIN and MAX bound where the trim was actually taken) +# tsc_uv_per_c the sensor's slope in microvolts per degree Celsius, always negative +# settling_ns the MAX of tSET,TS. The datasheet's own footnote calls this the minimum ADC +# sample time for the sensor, not a figure to note and move past +# calibration_sample_ns the ADC sample time the factory measurement used, which is not always the +# same number +# calibration_reference which reference TEMP_SENSE0 was measured against; null where the datasheet +# contradicts itself{conflict} +""" + + +def emit(sensor: TempSensor, resolved: str | None = None) -> dict: + """The YAML body. A contradiction keeps both readings whether or not an override settles it.""" + body = { + "tstrim_c": sensor.tstrim_c, + "tsc_uv_per_c": sensor.tsc_uv_per_c, + "settling_ns": sensor.settling_ns, + "calibration_sample_ns": sensor.calibration_sample_ns, + "calibration_reference": resolved or sensor.reference, + } + + if sensor.conflicting: + body["conflicting_references"] = [ + REFERENCES.get(sensor.reference_prose), + REFERENCES.get(sensor.reference_table), + ] + + return body + + +def write(datasheets: str) -> int: + """Regenerate data/temp_sensor/*.yaml. Returns the number of problems reported.""" + parts = yaml.safe_load(Path("data/parts.yaml").read_text(encoding="utf-8")) + families = [(f["family"], f["datasheet_url"].rsplit("/", 1)[-1]) for f in parts["families"]] + + overrides = yaml.safe_load(OVERRIDES.read_text(encoding="utf-8")) if OVERRIDES.exists() else {} + + Path("data/temp_sensor").mkdir(parents=True, exist_ok=True) + problems = 0 + + for family, gpn in families: + pdf = Path(datasheets) / f"{gpn}_datasheet.pdf" + if not pdf.exists(): + print(f"{family}: no datasheet for {gpn} in {datasheets}") + problems += 1 + continue + + sensor, note = read(pdf) + if sensor is None: + print(f"{family}: {note} in {pdf.name}") + problems += 1 + continue + + override = (overrides or {}).get(family, {}) + resolved = override.get("calibration_reference") + + conflict = "" + if note: + # A recorded conflict is data, not a failure: it is the same on every revision of the + # three datasheets which have it, and a consumer needs to know rather than be told a + # value the document does not support. + print(f"{family}: {note}") + conflict = f"\n#\n# The {gpn} datasheet contradicts itself here: {note}." + if resolved: + conflict += ( + f"\n# Resolved to {resolved} by data/temp_sensor_overrides.yaml" + f" ({override.get('evidence', 'no evidence stated')}), which holds the reasoning." + ) + + body = yaml.safe_dump(emit(sensor, resolved), sort_keys=False) + out = Path("data/temp_sensor") / f"{family}.yaml" + out.write_text(HEADER.format(ds=gpn, conflict=conflict) + body, encoding="utf-8") + + return problems + + +def main(argv: list[str]) -> int: + if len(argv) >= 2 and argv[0] == "--write": + return 1 if write(argv[1]) else 0 + + if not argv: + raise SystemExit(__doc__) + + for name in argv: + sensor, note = read(Path(name)) + print(f"{Path(name).name}: {sensor}{' -- ' + note if note else ''}") + + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) From 4126d08e689d67e00f7ab0f0702edbfc105d5b99 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Tue, 11 Aug 2026 22:13:16 +0100 Subject: [PATCH 110/134] temp_sensor: carry the conversion constants through to the PAC metadata Also reports a family with no constants, and a positive temperature coefficient, which is what a misread MIN/TYP/MAX column looks like. --- README.md | 11 +++- mspm0-data-gen/src/generate.rs | 2 + mspm0-data-gen/src/main.rs | 3 ++ mspm0-data-gen/src/sources.rs | 5 +- mspm0-data-gen/src/temp_sensor.rs | 18 +++++++ mspm0-data-gen/src/verify.rs | 25 +++++++++ mspm0-data-types/src/lib.rs | 88 +++++++++++++++++++++++++++++++ mspm0-metapac-gen/res/metadata.rs | 80 ++++++++++++++++++++++++++++ mspm0-metapac-gen/src/main.rs | 27 +++++++++- 9 files changed, 255 insertions(+), 4 deletions(-) create mode 100644 mspm0-data-gen/src/temp_sensor.rs diff --git a/README.md b/README.md index 56ca200..1a5a9e5 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,13 @@ These are the data sources currently used. internal and pin-loaded โ€” from the `ten` and `tdac_settle` rows ([`data/comp/`](./data/comp)). Neither has a status bit behind it, so waiting them out is the only way to know the output is meaningful + * The temperature sensor's conversion constants โ€” the factory trim temperature, the sensor's + slope, the minimum ADC sample window and the reference the factory calibrated against + ([`data/temp_sensor/`](./data/temp_sensor)). `FACTORYREGION.TEMP_SENSE0` means nothing without + them, and the calibration reference varies per device: VDD on the older G families, the 1.4-V + internal reference on most, 4.05 V on the MSPM0H321x. Three datasheets contradict themselves + about it; [`data/temp_sensor_overrides.yaml`](./data/temp_sensor_overrides.yaml) records which + half hardware agreed with * Which timers stay clocked in STANDBY1 (`standby1_timers` in [`parts.yaml`](./data/parts.yaml)) * MCLK and ULPCLK ceilings, the SYSOSC base frequency, the flash wait-state bands, `fADCCLK` and `TRNGCLKF` (all in [`parts.yaml`](./data/parts.yaml)) @@ -120,8 +127,8 @@ the `tools/` scripts read them from there. 4. Check the peripheral mapping in [`perimap.rs`](./mspm0-data-gen/src/perimap.rs) to use the correct peripherals. 5. Fetch the documents with `./d download-docs`, then regenerate the extracted data: `tools/operating_modes.py`, `tools/timers.py`, `tools/wakeup.py`, `tools/vref.py`, - `tools/adc_channels.py`, `tools/uart.py`, `tools/comp.py` and `tools/errata.py`, each with - `--write files`. + `tools/adc_channels.py`, `tools/uart.py`, `tools/comp.py`, `tools/errata.py` and + `tools/temp_sensor.py`, each with `--write files`. 6. Run `./d gen` and read its output. `verify.rs` reports every per-chip gap it can detect, including a family with no timer, errata or operating-mode data. diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 2d74a06..5b00385 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -59,6 +59,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu wake, vref, comp, + temp_sensor, int_groups, } = *sources; @@ -142,6 +143,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu clock_tree, errata: errata.map(|e| e.errata.clone()).unwrap_or_default(), wake_ns: wake.unwrap_or_default(), + temperature_sensor: temp_sensor, }; for err in verify::verify(&chip, &part_number.name) { diff --git a/mspm0-data-gen/src/main.rs b/mspm0-data-gen/src/main.rs index f166b15..26583d0 100644 --- a/mspm0-data-gen/src/main.rs +++ b/mspm0-data-gen/src/main.rs @@ -16,6 +16,7 @@ mod timers; mod uart; mod util; mod verify; +mod temp_sensor; mod vref; mod wakeup; @@ -87,6 +88,7 @@ fn main() -> anyhow::Result<()> { let wake = wakeup::parse()?; let vref = vref::parse()?; let comp = comp::parse()?; + let temp_sensor = temp_sensor::parse()?; let parts = parts::PartsFile::read()?; // TODO: Expanded family names (ex. C110X -> C1103 & C1104) @@ -107,6 +109,7 @@ fn main() -> anyhow::Result<()> { wake, vref, comp, + temp_sensor, }; stopwatch.section("Generate data"); diff --git a/mspm0-data-gen/src/sources.rs b/mspm0-data-gen/src/sources.rs index e28af86..44cec86 100644 --- a/mspm0-data-gen/src/sources.rs +++ b/mspm0-data-gen/src/sources.rs @@ -6,7 +6,7 @@ use std::collections::BTreeMap; -use mspm0_data_types::{Vref, WakeTimes}; +use mspm0_data_types::{TemperatureSensor, Vref, WakeTimes}; use crate::{ adc_channels::AdcChannels, @@ -41,6 +41,7 @@ pub struct Sources { pub wake: BTreeMap<String, WakeTimes>, pub vref: BTreeMap<String, Vref>, pub comp: BTreeMap<String, CompTiming>, + pub temp_sensor: BTreeMap<String, TemperatureSensor>, } /// What one family is described by. @@ -61,6 +62,7 @@ pub struct FamilySources<'a> { pub wake: Option<WakeTimes>, pub vref: Option<Vref>, pub comp: Option<CompTiming>, + pub temp_sensor: Option<TemperatureSensor>, /// Not narrowed to the family: `generate_irqs` looks groups up per chip, not per family. pub int_groups: &'a BTreeMap<String, Groups>, @@ -104,6 +106,7 @@ impl Sources { wake: self.wake.get(family).copied(), vref: self.vref.get(family).copied(), comp: self.comp.get(family).copied(), + temp_sensor: self.temp_sensor.get(family).copied(), int_groups: &self.int_groups, }) } diff --git a/mspm0-data-gen/src/temp_sensor.rs b/mspm0-data-gen/src/temp_sensor.rs new file mode 100644 index 0000000..4d59252 --- /dev/null +++ b/mspm0-data-gen/src/temp_sensor.rs @@ -0,0 +1,18 @@ +//! Reads `data/temp_sensor/<family>.yaml`, which `tools/temp_sensor.py` extracts from the +//! datasheets and `data/temp_sensor_overrides.yaml` corrects. +//! +//! `FACTORYREGION.TEMP_SENSE0` is one ADC code per device and means nothing on its own: turning it +//! and a later reading into a temperature needs the trim temperature, the sensor's slope, how long +//! the ADC has to sample, and which reference the factory measured against. None of the four is in +//! a register, a header constant or a sysconfig attribute. + +use std::collections::BTreeMap; + +use mspm0_data_types::TemperatureSensor; + +use crate::util; + +/// Read every `data/temp_sensor/<family>.yaml`, keyed by family name. +pub fn parse() -> anyhow::Result<BTreeMap<String, TemperatureSensor>> { + util::per_family("temp_sensor") +} diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 363d162..4556ee7 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -36,6 +36,7 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { retention_known, wakeup_pins_known, io_structures_known, + temperature_sensor_known, ]; CHECKS @@ -350,6 +351,30 @@ fn flashctl_known(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// The temperature sensor's conversion constants are present and the slope has the right sign. +/// +/// Every datasheet so far states all of them, so an absent set means the extraction lost a family +/// rather than a device without a sensor. The sign check is the cheap guard against a MIN/TYP/MAX +/// column misread: the sensor's output falls as the die warms on every device. +fn temperature_sensor_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + let Some(sensor) = chip.temperature_sensor else { + bail!( + "{name}: no temperature sensor constants; data/temp_sensor/{}.yaml is missing", + chip.family + ); + }; + + if sensor.tsc_uv_per_c >= 0 { + bail!( + "{name}: the temperature coefficient is {}uV/C, but the sensor's output falls as the \ + die warms on every device", + sensor.tsc_uv_per_c, + ); + } + + Ok(()) +} + /// Every pin has an IO structure, and no pin wakes the device without the logic to do it. /// /// The second half is a subset check, not an equality: a structure which can carry wakeup logic diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index c598d73..ae62f07 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -114,6 +114,94 @@ pub struct Chip { /// How long this device takes to reach RUN from each sleep mode. pub wake_ns: WakeTimes, + + /// What `FACTORYREGION.TEMP_SENSE0` has to be combined with to give a temperature. + /// + /// `None` when `data/temp_sensor/<family>.yaml` is missing, which `verify.rs` reports. + #[serde(skip_serializing_if = "Option::is_none")] + pub temperature_sensor: Option<TemperatureSensor>, +} + +/// The temperature sensor's conversion constants. +/// +/// `FACTORYREGION.TEMP_SENSE0` holds one ADC code per device: the sensor's output at the factory +/// trim temperature, in 12-bit mode. It means nothing on its own. The temperature of a later +/// reading is +/// +/// ```text +/// T = (V_sample - V_trim) / tsc + tstrim +/// ``` +/// +/// where `V_trim` is `TEMP_SENSE0` scaled by the reference the factory used, and `V_sample` is the +/// live code scaled by the reference the driver used. Those two references are not always the same +/// one, which is what [`TemperatureSensor::calibration_reference`] is for. +/// +/// None of these has a machine-readable source: no register holds them, no SDK header defines them +/// and sysconfig has no attribute for them. They come from the datasheet's Temperature Sensor +/// rows, extracted by `tools/temp_sensor.py`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct TemperatureSensor { + /// The factory trim temperature in degrees Celsius, from the `TSTRIM` row's TYP column. 30 on + /// every device so far; the MIN and MAX columns say the trim was taken somewhere in 27โ€“33, so + /// a consumer wanting an error bound has one. + pub tstrim_c: i16, + + /// The sensor's slope in microvolts per degree Celsius, from the `TSc` row's TYP column. + /// Always negative โ€” the output falls as the die warms. Ranges โˆ’1750 to โˆ’1900 across the + /// portfolio, so it cannot be assumed. + pub tsc_uv_per_c: i32, + + /// The ADC sample window the sensor needs, in nanoseconds: the MAX of the datasheet's + /// `tSET,TS` row. + /// + /// **This is a minimum sample time, not a settling figure to note and move past.** The + /// datasheets' own footnote says so: "This is the maximum time required for the temperature + /// sensor to settle when measured by the ADC. It may be used to specify the minimum ADC sample + /// time." Sampling faster returns a plausible number that is wrong, with nothing to indicate + /// it. 10ยตs or 12.5ยตs depending on the family. + /// + /// `None` on mspm0l122x and mspm0l222x, whose shared datasheet has no such row. + #[serde(skip_serializing_if = "Option::is_none")] + pub settling_ns: Option<u32>, + + /// The ADC sample window the factory measurement itself used, in nanoseconds. + /// + /// Not always [`TemperatureSensor::settling_ns`]: the older L datasheets state a 12.5ยตs + /// calibration sample against their own 10ยตs settling maximum. Reproducing the factory + /// measurement and merely letting the sensor settle are different requirements, and the wider + /// of the two is the safe window for both. + /// + /// `None` where the datasheet does not state it. + #[serde(skip_serializing_if = "Option::is_none")] + pub calibration_sample_ns: Option<u32>, + + /// Which ADC reference `TEMP_SENSE0` was measured against. + /// + /// **A driver reading the sensor against a different reference has to scale `TEMP_SENSE0` by + /// this one, not by its own.** Getting it wrong does not produce a small error: on an L1306, + /// converting against VDD instead of the 1.4V reference turns 26.8ยฐC into 527ยฐC. + /// + /// It genuinely varies per device โ€” VDD on the four older G families, the 1.4V internal + /// reference on ten families, 4.05V on mspm0h321x โ€” so there is no portfolio default to fall + /// back on, and no register or header states it. + pub calibration_reference: CalibrationReference, +} + +/// The ADC reference a device's temperature sensor was calibrated against. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum CalibrationReference { + /// `VRSEL = 0h`, the supply, stated as 3.3V. mspm0g110x, mspm0g150x, mspm0g310x and + /// mspm0g350x. + #[serde(rename = "vdd")] + Vdd, + + /// `VRSEL = 2h` with the 1.4V internal reference buffer. + #[serde(rename = "internal_1v4")] + Internal1V4, + + /// `VRSEL = 4h`, the 4.05V internal reference. mspm0h321x only. + #[serde(rename = "internal_4v05")] + Internal4V05, } /// Time to reach RUN from each sleep mode, in nanoseconds. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 2b8f5d7..d947cbb 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -65,6 +65,86 @@ pub struct Metadata { /// How long this device takes to reach RUN from each sleep mode. pub wake_ns: WakeTimes, + + /// What `FACTORYREGION.TEMP_SENSE0` has to be combined with to give a temperature. + /// + /// `None` where the datasheet's temperature sensor rows have not been transcribed. + pub temperature_sensor: Option<TemperatureSensor>, +} + +/// The temperature sensor's conversion constants. +/// +/// `FACTORYREGION.TEMP_SENSE0` holds one ADC code per device: the sensor's output at the factory +/// trim temperature, in 12-bit mode. It means nothing on its own. The temperature of a later +/// reading is +/// +/// ```text +/// T = (V_sample - V_trim) / tsc + tstrim +/// ``` +/// +/// where `V_trim` is `TEMP_SENSE0` scaled by the reference the factory used, and `V_sample` is the +/// live code scaled by the reference the driver used. Those two references are not always the same +/// one, which is what `calibration_reference` is for. +/// +/// None of these has a machine-readable source: no register holds them, no SDK header defines them +/// and sysconfig has no attribute for them. They come from the datasheet's Temperature Sensor rows. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct TemperatureSensor { + /// The factory trim temperature in degrees Celsius, from the `TSTRIM` row's TYP column. 30 on + /// every device so far; the MIN and MAX columns say the trim was taken somewhere in 27-33, so + /// a consumer wanting an error bound has one. + pub tstrim_c: i16, + + /// The sensor's slope in microvolts per degree Celsius, from the `TSc` row's TYP column. + /// Always negative -- the output falls as the die warms. Ranges -1750 to -1900 across the + /// portfolio, so it cannot be assumed. + pub tsc_uv_per_c: i32, + + /// The ADC sample window the sensor needs, in nanoseconds: the MAX of the datasheet's + /// `tSET,TS` row. + /// + /// **This is a minimum sample time, not a settling figure to note and move past.** The + /// datasheets' own footnote says so: "This is the maximum time required for the temperature + /// sensor to settle when measured by the ADC. It may be used to specify the minimum ADC sample + /// time." Sampling faster returns a plausible number that is wrong, with nothing to indicate + /// it. 10us or 12.5us depending on the family. + /// + /// `None` on mspm0l122x and mspm0l222x, whose shared datasheet has no such row. + pub settling_ns: Option<u32>, + + /// The ADC sample window the factory measurement itself used, in nanoseconds. + /// + /// Not always `settling_ns`: the older L datasheets state a 12.5us calibration sample against + /// their own 10us settling maximum. Reproducing the factory measurement and merely letting the + /// sensor settle are different requirements, and the wider of the two is safe for both. + /// + /// `None` where the datasheet does not state it. + pub calibration_sample_ns: Option<u32>, + + /// Which ADC reference `TEMP_SENSE0` was measured against. + /// + /// **A driver reading the sensor against a different reference has to scale `TEMP_SENSE0` by + /// this one, not by its own.** Getting it wrong does not produce a small error: on an L1306, + /// converting against VDD instead of the 1.4V reference turns 26.8C into 527C. + /// + /// It genuinely varies per device -- VDD on the four older G families, the 1.4V internal + /// reference on ten families, 4.05V on mspm0h321x -- so there is no portfolio default to fall + /// back on, and no register or header states it. + pub calibration_reference: CalibrationReference, +} + +/// The ADC reference a device's temperature sensor was calibrated against. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum CalibrationReference { + /// `VRSEL = 0h`, the supply, stated as 3.3V. mspm0g110x, mspm0g150x, mspm0g310x and + /// mspm0g350x. + Vdd, + + /// `VRSEL = 2h` with the 1.4V internal reference buffer. + Internal1V4, + + /// `VRSEL = 4h`, the 4.05V internal reference. mspm0h321x only. + Internal4V05, } /// Time to reach RUN from each sleep mode, in nanoseconds. diff --git a/mspm0-metapac-gen/src/main.rs b/mspm0-metapac-gen/src/main.rs index 6d0a4a3..a9badc8 100644 --- a/mspm0-metapac-gen/src/main.rs +++ b/mspm0-metapac-gen/src/main.rs @@ -6,7 +6,7 @@ use std::{ }; use anyhow::Context; -use mspm0_data_types::{Chip, Package}; +use mspm0_data_types::{CalibrationReference, Chip, Package}; use proc_macro2::Literal; use quote::quote; @@ -195,6 +195,30 @@ fn generate_chip_metadata( } }; + let temperature_sensor = match &chip.temperature_sensor { + Some(sensor) => { + let tstrim_c = Literal::i16_unsuffixed(sensor.tstrim_c); + let tsc_uv_per_c = Literal::i32_unsuffixed(sensor.tsc_uv_per_c); + let settling_ns = ns(sensor.settling_ns); + let calibration_sample_ns = ns(sensor.calibration_sample_ns); + let reference = match sensor.calibration_reference { + CalibrationReference::Vdd => quote!(CalibrationReference::Vdd), + CalibrationReference::Internal1V4 => quote!(CalibrationReference::Internal1V4), + CalibrationReference::Internal4V05 => quote!(CalibrationReference::Internal4V05), + }; + quote! { + Some(TemperatureSensor { + tstrim_c: #tstrim_c, + tsc_uv_per_c: #tsc_uv_per_c, + settling_ns: #settling_ns, + calibration_sample_ns: #calibration_sample_ns, + calibration_reference: #reference, + }) + } + } + None => quote!(None), + }; + // Memory is the one part of the metadata which varies by part number rather than by family let memory = metadata::memory(chip); let include = format!("../{deduped_file}"); @@ -222,6 +246,7 @@ fn generate_chip_metadata( clock_tree: #clock_tree, errata: &[#(#errata),*], wake_ns: #wake_ns, + temperature_sensor: #temperature_sensor, }; } .to_string(); From d3032a92ff5d926dfd6a1406d0c28b36af11a541 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Tue, 11 Aug 2026 22:17:56 +0100 Subject: [PATCH 111/134] temp_sensor: say which of the constants fails quietly The calibration sample window is the one worth spelling out: too short but still past settling reads plausibly and drifts, where a wrong reference is off by hundreds of degrees. Also names the g350x/g351x pair, which is the clearest evidence that the reference is a device fact and not a document defect. --- mspm0-data-types/src/lib.rs | 18 +++++++++++++----- mspm0-metapac-gen/res/metadata.rs | 17 +++++++++++++---- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index ae62f07..003ca3c 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -167,9 +167,14 @@ pub struct TemperatureSensor { /// The ADC sample window the factory measurement itself used, in nanoseconds. /// /// Not always [`TemperatureSensor::settling_ns`]: the older L datasheets state a 12.5ยตs - /// calibration sample against their own 10ยตs settling maximum. Reproducing the factory - /// measurement and merely letting the sensor settle are different requirements, and the wider - /// of the two is the safe window for both. + /// calibration sample against their own 10ยตs settling maximum. "Long enough for the sensor to + /// settle" and "the window the stored code was taken with" are different conditions, and only + /// the second makes a live reading directly comparable with `TEMP_SENSE0`. Sample at the wider + /// of the two and both hold. + /// + /// This is the quiet one of the five. A window that is long enough to settle but shorter than + /// the factory's gives a reading which looks entirely reasonable and drifts, where a wrong + /// [`TemperatureSensor::calibration_reference`] gives a number nobody can miss. /// /// `None` where the datasheet does not state it. #[serde(skip_serializing_if = "Option::is_none")] @@ -182,8 +187,11 @@ pub struct TemperatureSensor { /// converting against VDD instead of the 1.4V reference turns 26.8ยฐC into 527ยฐC. /// /// It genuinely varies per device โ€” VDD on the four older G families, the 1.4V internal - /// reference on ten families, 4.05V on mspm0h321x โ€” so there is no portfolio default to fall - /// back on, and no register or header states it. + /// reference on twelve families, 4.05V on mspm0h321x โ€” so there is no portfolio default to + /// fall back on, and no register or header states it. The sharpest case is g350x against + /// g351x: same series, adjacent part numbers, different references. TI's own + /// `adc12_internal_temp_sensor_mathacl` example gets that pair wrong, computing against 3.3V + /// for both. pub calibration_reference: CalibrationReference, } diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index d947cbb..8d8dada 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -115,8 +115,14 @@ pub struct TemperatureSensor { /// The ADC sample window the factory measurement itself used, in nanoseconds. /// /// Not always `settling_ns`: the older L datasheets state a 12.5us calibration sample against - /// their own 10us settling maximum. Reproducing the factory measurement and merely letting the - /// sensor settle are different requirements, and the wider of the two is safe for both. + /// their own 10us settling maximum. "Long enough for the sensor to settle" and "the window the + /// stored code was taken with" are different conditions, and only the second makes a live + /// reading directly comparable with `TEMP_SENSE0`. Sample at the wider of the two and both + /// hold. + /// + /// This is the quiet one of the five. A window that is long enough to settle but shorter than + /// the factory's gives a reading which looks entirely reasonable and drifts, where a wrong + /// `calibration_reference` gives a number nobody can miss. /// /// `None` where the datasheet does not state it. pub calibration_sample_ns: Option<u32>, @@ -128,8 +134,11 @@ pub struct TemperatureSensor { /// converting against VDD instead of the 1.4V reference turns 26.8C into 527C. /// /// It genuinely varies per device -- VDD on the four older G families, the 1.4V internal - /// reference on ten families, 4.05V on mspm0h321x -- so there is no portfolio default to fall - /// back on, and no register or header states it. + /// reference on twelve families, 4.05V on mspm0h321x -- so there is no portfolio default to + /// fall back on, and no register or header states it. The sharpest case is g350x against + /// g351x: same series, adjacent part numbers, different references. TI's own + /// `adc12_internal_temp_sensor_mathacl` example gets that pair wrong, computing against 3.3V + /// for both. pub calibration_reference: CalibrationReference, } From b809a9bfbc841fe107a695acc73dee535617ba35 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Thu, 13 Aug 2026 03:13:04 +0100 Subject: [PATCH 112/134] dma: record which devices implement stride addressing The earlier sweep left stride out because the sources looked split. They are not. The SVDs, the shared hw_dma.h and driverlib all describe the IP superset and carry the stride encodings on every device, so none of the three is a per-device statement; the header constant and the datasheet's feature table are, and they agree on all ten families which state both. The TRMs' DMA_A/DMA_B comparison settles nothing either way. It grants DMA_A stride, against both per-device sources, and denies it a repeated mode which its own transfer-mode table types full-feature. --- mspm0-data-gen/src/generate.rs | 6 ++++++ mspm0-data-gen/src/header.rs | 9 +++++++++ mspm0-data-types/src/lib.rs | 24 ++++++++++++++++++++++++ mspm0-metapac-gen/res/metadata.rs | 24 ++++++++++++++++++++++++ mspm0-metapac-gen/src/metadata.rs | 2 ++ 5 files changed, 65 insertions(+) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 5b00385..d338a3c 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -1243,6 +1243,11 @@ fn apply_dma( // The SVD enumerates `LONGLONG` and carries `DMAAUTOEN` exactly where the header defines the // matching constant, on all fifteen families which have one, so a disagreement means a source // bump changed one of them. + // + // Stride is deliberately not checked this way. Every SVD enumerates `STRIDE_2`-`STRIDE_9` in + // `DMASRCINCR`, including the families whose header and datasheet both deny stride, so the + // check would fail on every DMA_A device. The datasheet's "Stride mode" row is the second + // source there, and it agrees with the header on all ten families which state both. if let Some(svd) = svd { for (feature, from_header, from_svd) in [ ("128-bit DMA transfers", header.dma_long_long, svd.dma_long_long), @@ -1269,6 +1274,7 @@ fn apply_dma( peripheral.dma = Some(Dma { long_long_transfers: header.dma_long_long, auto_enable: header.dma_auto_enable, + stride_mode: header.dma_stride, }); } diff --git a/mspm0-data-gen/src/header.rs b/mspm0-data-gen/src/header.rs index 2e5abf5..df68265 100644 --- a/mspm0-data-gen/src/header.rs +++ b/mspm0-data-gen/src/header.rs @@ -62,6 +62,14 @@ pub struct Header { /// /// Read separately from `dma_long_long` although the two constants have never disagreed. pub dma_auto_enable: bool, + + /// Whether the DMA implements stride addressing, from `DMA_SYS_MMR_STRIDE`. + /// + /// The constant is defined, always as `1`, exactly on the devices whose datasheet gives the + /// "Stride mode" row a tick โ€” ten families state both and agree. Unlike `dma_long_long`, + /// driverlib does not gate on this one: `DL_DMA_ADDR_STRIDE_2` through `_9` are declared on + /// every device. + pub dma_stride: bool, // TODO: Available IOMUX indices // TODO: PF values (for non-analog) // TODO: DMA triggers (used for dma transfers) @@ -115,6 +123,7 @@ impl Header { flash, dma_long_long: content.contains("DMA_SYS_MMR_LLONG"), dma_auto_enable: content.contains("DMA_SYS_MMR_AUTO"), + dma_stride: content.contains("DMA_SYS_MMR_STRIDE"), }) } diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 003ca3c..559defd 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -802,6 +802,30 @@ pub struct Dma { /// Same three sources, agreeing on all 18 families: the header constant, the presence of the /// `DMAAUTOEN` field in the SVDs which have one, and the datasheet's "Auto enable" row. pub auto_enable: bool, + + /// Whether `DMACTL.DMASRCINCR` and `DMADSTINCR` implement the stride encodings, `8h` + /// (`STRIDE_2`) through `Fh` (`STRIDE_9`), which advance the address by 2 to 9 times the + /// transfer width instead of by one. + /// + /// True on the same seven families as [`Dma::long_long_transfers`] and [`Dma::auto_enable`]. + /// Recorded separately for the same reason: the three arrived together and nothing says TI + /// must keep shipping them as a set. Do not read one off another. + /// + /// **Only two sources can answer, and neither is the SVD.** The header's `DMA_SYS_MMR_STRIDE` + /// and the datasheet's "Stride mode" row state it per device and agree on all ten families + /// which have both. Everything else describes the IP superset and ticks stride everywhere: all + /// 21 SVDs enumerate `STRIDE_2`โ€“`STRIDE_9` whatever the device, the shared `hw_dma.h` defines + /// the encodings unconditionally, and driverlib declares `DL_DMA_ADDR_STRIDE_*` without the + /// `#ifdef` it puts on `DL_DMA_WIDTH_LONG_LONG`. The SVDs do discriminate `LONGLONG` and + /// `DMAAUTOEN`, so their stride values are unmaintained rather than a second opinion. + /// + /// The TRMs' Table 5-1 ticks stride for DMA_A and is wrong. That same table denies DMA_A a + /// repeated mode, which its own Table 5-2 contradicts by typing `DMATM` 2 and 3 full-feature, + /// as does every DMA_A datasheet's "Repeat Mode: Yes". + /// + /// The field is four bits wide on every device, so a stride value can always be written. What + /// a device without stride does with one is not documented anywhere. + pub stride_mode: bool, } /// How the flash controller writes and protects its flash. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 8d8dada..d1f4716 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -402,6 +402,30 @@ pub struct Dma { /// Same three sources, agreeing on all 18 families: the header constant, the presence of the /// `DMAAUTOEN` field in the SVDs which have one, and the datasheet's "Auto enable" row. pub auto_enable: bool, + + /// Whether `DMACTL.DMASRCINCR` and `DMADSTINCR` implement the stride encodings, `8h` + /// (`STRIDE_2`) through `Fh` (`STRIDE_9`), which advance the address by 2 to 9 times the + /// transfer width instead of by one. + /// + /// True on the same seven families as [`Dma::long_long_transfers`] and [`Dma::auto_enable`]. + /// Recorded separately for the same reason: the three arrived together and nothing says TI + /// must keep shipping them as a set. Do not read one off another. + /// + /// **Only two sources can answer, and neither is the SVD.** The header's `DMA_SYS_MMR_STRIDE` + /// and the datasheet's "Stride mode" row state it per device and agree on all ten families + /// which have both. Everything else describes the IP superset and ticks stride everywhere: all + /// 21 SVDs enumerate `STRIDE_2`โ€“`STRIDE_9` whatever the device, the shared `hw_dma.h` defines + /// the encodings unconditionally, and driverlib declares `DL_DMA_ADDR_STRIDE_*` without the + /// `#ifdef` it puts on `DL_DMA_WIDTH_LONG_LONG`. The SVDs do discriminate `LONGLONG` and + /// `DMAAUTOEN`, so their stride values are unmaintained rather than a second opinion. + /// + /// The TRMs' Table 5-1 ticks stride for DMA_A and is wrong. That same table denies DMA_A a + /// repeated mode, which its own Table 5-2 contradicts by typing `DMATM` 2 and 3 full-feature, + /// as does every DMA_A datasheet's "Repeat Mode: Yes". + /// + /// The field is four bits wide on every device, so a stride value can always be written. What + /// a device without stride does with one is not documented anywhere. + pub stride_mode: bool, } /// The parts of the SYSCTL which its register block does not describe. diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 652b070..94ead90 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -543,9 +543,11 @@ fn generate_peripheral( Some(dma) => { let long_long_transfers = dma.long_long_transfers; let auto_enable = dma.auto_enable; + let stride_mode = dma.stride_mode; quote! { Some(Dma { long_long_transfers: #long_long_transfers, auto_enable: #auto_enable, + stride_mode: #stride_mode, }) } } None => quote! { None }, From fe7482f182ae0b93f79855fc034dc65c7af116b1 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Thu, 13 Aug 2026 03:26:22 +0100 Subject: [PATCH 113/134] dma: record gather mode, which is not the same as extended mode DMA_SYS_MMR_EM reads like a switch for the whole of DMACTL.EM and is not one. The field selects four modes and the older DMA implements three: every datasheet carrying the feature table ticks table and fill on its full channels whether or not the constant is defined. Only gather follows it. Recording the constant as an extended-mode bool would have denied fill and table to the eleven families which have them. --- mspm0-data-gen/src/generate.rs | 3 +++ mspm0-data-gen/src/header.rs | 9 +++++++++ mspm0-data-types/src/lib.rs | 21 +++++++++++++++++++++ mspm0-metapac-gen/res/metadata.rs | 21 +++++++++++++++++++++ mspm0-metapac-gen/src/metadata.rs | 2 ++ 5 files changed, 56 insertions(+) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index d338a3c..83057b4 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -1275,6 +1275,9 @@ fn apply_dma( long_long_transfers: header.dma_long_long, auto_enable: header.dma_auto_enable, stride_mode: header.dma_stride, + // `DMA_SYS_MMR_EM` tracks gather alone. Fill and table are on every device's full + // channels, ticked by every datasheet which carries the table. + gather_mode: header.dma_extended_mode, }); } diff --git a/mspm0-data-gen/src/header.rs b/mspm0-data-gen/src/header.rs index df68265..8d54dfb 100644 --- a/mspm0-data-gen/src/header.rs +++ b/mspm0-data-gen/src/header.rs @@ -70,6 +70,14 @@ pub struct Header { /// driverlib does not gate on this one: `DL_DMA_ADDR_STRIDE_2` through `_9` are declared on /// every device. pub dma_stride: bool, + + /// Whether the DMA implements gather mode, from `DMA_SYS_MMR_EM`. + /// + /// **The constant is named for extended mode and does not mean the device has none.** + /// `DMACTL.EM` selects four modes and the older DMA implements three of them: every datasheet + /// with the table gives "Table & Fill Mode" a tick on its full channels, `DMA_SYS_MMR_EM` or + /// not. Only gather, `EM` value 1, follows the constant, on all ten families which state both. + pub dma_extended_mode: bool, // TODO: Available IOMUX indices // TODO: PF values (for non-analog) // TODO: DMA triggers (used for dma transfers) @@ -124,6 +132,7 @@ impl Header { dma_long_long: content.contains("DMA_SYS_MMR_LLONG"), dma_auto_enable: content.contains("DMA_SYS_MMR_AUTO"), dma_stride: content.contains("DMA_SYS_MMR_STRIDE"), + dma_extended_mode: content.contains("DMA_SYS_MMR_EM"), }) } diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 559defd..eb73583 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -826,6 +826,27 @@ pub struct Dma { /// The field is four bits wide on every device, so a stride value can always be written. What /// a device without stride does with one is not documented anywhere. pub stride_mode: bool, + + /// Whether `DMACTL.EM` implements gather mode, value `1h`, which walks a table of addresses + /// and copies from each to one destination. + /// + /// **This is not "the device has extended modes".** `EM` selects four, and every device + /// implements three: fill (`2h`) and table (`3h`) are ticked on the full channels of every + /// datasheet carrying the table, whether or not the part has gather. A driver keyed on + /// "extended mode" as one bit would refuse fill and table on the eleven families that have + /// them. + /// + /// **Extended modes are full-channel only**, on every device. The TRM says "In FULL channels, + /// the DMA controller has two extended modes", and every datasheet marks both the gather and + /// the table-and-fill rows `-` in its BASIC column. Combine with [`DmaChannel::full`] before + /// offering any of them โ€” unlike [`Dma::stride_mode`], [`Dma::long_long_transfers`] and + /// [`Dma::auto_enable`], which the datasheets tick for basic channels too. + /// + /// Three sources state gather per device and agree: the header's `DMA_SYS_MMR_EM`, the + /// datasheet's "Gather Mode" row, and the TRMs' Table 5-1. That last one is right about this + /// row and wrong about two others in the same table, so it corroborates rather than settles โ€” + /// read Table 5-1 a row at a time, never as a whole. + pub gather_mode: bool, } /// How the flash controller writes and protects its flash. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index d1f4716..e035db0 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -426,6 +426,27 @@ pub struct Dma { /// The field is four bits wide on every device, so a stride value can always be written. What /// a device without stride does with one is not documented anywhere. pub stride_mode: bool, + + /// Whether `DMACTL.EM` implements gather mode, value `1h`, which walks a table of addresses + /// and copies from each to one destination. + /// + /// **This is not "the device has extended modes".** `EM` selects four, and every device + /// implements three: fill (`2h`) and table (`3h`) are ticked on the full channels of every + /// datasheet carrying the table, whether or not the part has gather. A driver keyed on + /// "extended mode" as one bit would refuse fill and table on the eleven families that have + /// them. + /// + /// **Extended modes are full-channel only**, on every device. The TRM says "In FULL channels, + /// the DMA controller has two extended modes", and every datasheet marks both the gather and + /// the table-and-fill rows `-` in its BASIC column. Combine with [`DmaChannel::full`] before + /// offering any of them โ€” unlike [`Dma::stride_mode`], [`Dma::long_long_transfers`] and + /// [`Dma::auto_enable`], which the datasheets tick for basic channels too. + /// + /// Three sources state gather per device and agree: the header's `DMA_SYS_MMR_EM`, the + /// datasheet's "Gather Mode" row, and the TRMs' Table 5-1. That last one is right about this + /// row and wrong about two others in the same table, so it corroborates rather than settles โ€” + /// read Table 5-1 a row at a time, never as a whole. + pub gather_mode: bool, } /// The parts of the SYSCTL which its register block does not describe. diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 94ead90..37999ca 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -544,10 +544,12 @@ fn generate_peripheral( let long_long_transfers = dma.long_long_transfers; let auto_enable = dma.auto_enable; let stride_mode = dma.stride_mode; + let gather_mode = dma.gather_mode; quote! { Some(Dma { long_long_transfers: #long_long_transfers, auto_enable: #auto_enable, stride_mode: #stride_mode, + gather_mode: #gather_mode, }) } } None => quote! { None }, From 890f9295f4efcab1cd1cec2ed6fac256d2fcb143 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Thu, 13 Aug 2026 03:55:43 +0100 Subject: [PATCH 114/134] adc: record how long the ADC needs to wake from power-down CTL0.PWRDN resets to automatic power down, and the TRM pays for the wake out of the programmed sample window rather than in hardware: SCOMPx has to cover Twakeup on top of the sampling the signal needs. A window sized for the signal alone samples an input that has not finished waking, and nothing reports it. Nothing machine-readable carries the figure. sysconfig only warns that it exists and points at the datasheet. The maximum and the typical are separate keys because the datasheets fill different columns: fifteen families state a ceiling, three state a typical and no ceiling at all. One field would let a typical be read as a bound on the three that publish none. The table is unruled, so the column is decided by comparing the figure's position against the MIN/TYP/MAX headers. --- data/adc_wakeup/mspm0c1105_c1106.yaml | 16 +++ data/adc_wakeup/mspm0c110x.yaml | 16 +++ data/adc_wakeup/mspm0g110x.yaml | 16 +++ data/adc_wakeup/mspm0g150x.yaml | 16 +++ data/adc_wakeup/mspm0g151x.yaml | 16 +++ data/adc_wakeup/mspm0g310x.yaml | 16 +++ data/adc_wakeup/mspm0g350x.yaml | 16 +++ data/adc_wakeup/mspm0g351x.yaml | 16 +++ data/adc_wakeup/mspm0g518x.yaml | 16 +++ data/adc_wakeup/mspm0h321x.yaml | 16 +++ data/adc_wakeup/mspm0l110x.yaml | 16 +++ data/adc_wakeup/mspm0l112x.yaml | 16 +++ data/adc_wakeup/mspm0l122x.yaml | 16 +++ data/adc_wakeup/mspm0l130x.yaml | 16 +++ data/adc_wakeup/mspm0l134x.yaml | 16 +++ data/adc_wakeup/mspm0l211x.yaml | 16 +++ data/adc_wakeup/mspm0l222x.yaml | 16 +++ data/adc_wakeup/msps003fx.yaml | 16 +++ mspm0-data-gen/src/adc_wakeup.rs | 33 +++++ mspm0-data-gen/src/generate.rs | 9 +- mspm0-data-gen/src/main.rs | 3 + mspm0-data-gen/src/sources.rs | 4 + mspm0-data-gen/src/verify.rs | 38 +++++ mspm0-data-types/src/lib.rs | 31 ++++ mspm0-metapac-gen/res/metadata.rs | 29 ++++ mspm0-metapac-gen/src/metadata.rs | 10 ++ tools/adc_wakeup.py | 196 ++++++++++++++++++++++++++ 27 files changed, 640 insertions(+), 1 deletion(-) create mode 100644 data/adc_wakeup/mspm0c1105_c1106.yaml create mode 100644 data/adc_wakeup/mspm0c110x.yaml create mode 100644 data/adc_wakeup/mspm0g110x.yaml create mode 100644 data/adc_wakeup/mspm0g150x.yaml create mode 100644 data/adc_wakeup/mspm0g151x.yaml create mode 100644 data/adc_wakeup/mspm0g310x.yaml create mode 100644 data/adc_wakeup/mspm0g350x.yaml create mode 100644 data/adc_wakeup/mspm0g351x.yaml create mode 100644 data/adc_wakeup/mspm0g518x.yaml create mode 100644 data/adc_wakeup/mspm0h321x.yaml create mode 100644 data/adc_wakeup/mspm0l110x.yaml create mode 100644 data/adc_wakeup/mspm0l112x.yaml create mode 100644 data/adc_wakeup/mspm0l122x.yaml create mode 100644 data/adc_wakeup/mspm0l130x.yaml create mode 100644 data/adc_wakeup/mspm0l134x.yaml create mode 100644 data/adc_wakeup/mspm0l211x.yaml create mode 100644 data/adc_wakeup/mspm0l222x.yaml create mode 100644 data/adc_wakeup/msps003fx.yaml create mode 100644 mspm0-data-gen/src/adc_wakeup.rs create mode 100644 tools/adc_wakeup.py diff --git a/data/adc_wakeup/mspm0c1105_c1106.yaml b/data/adc_wakeup/mspm0c1105_c1106.yaml new file mode 100644 index 0000000..a65556d --- /dev/null +++ b/data/adc_wakeup/mspm0c1105_c1106.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0C1106 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +max_ns: 5000 diff --git a/data/adc_wakeup/mspm0c110x.yaml b/data/adc_wakeup/mspm0c110x.yaml new file mode 100644 index 0000000..f59ddd3 --- /dev/null +++ b/data/adc_wakeup/mspm0c110x.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0C1104 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +max_ns: 5000 diff --git a/data/adc_wakeup/mspm0g110x.yaml b/data/adc_wakeup/mspm0g110x.yaml new file mode 100644 index 0000000..6179564 --- /dev/null +++ b/data/adc_wakeup/mspm0g110x.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0G1107 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +max_ns: 5000 diff --git a/data/adc_wakeup/mspm0g150x.yaml b/data/adc_wakeup/mspm0g150x.yaml new file mode 100644 index 0000000..202a108 --- /dev/null +++ b/data/adc_wakeup/mspm0g150x.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0G1507 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +max_ns: 5000 diff --git a/data/adc_wakeup/mspm0g151x.yaml b/data/adc_wakeup/mspm0g151x.yaml new file mode 100644 index 0000000..67ebcd4 --- /dev/null +++ b/data/adc_wakeup/mspm0g151x.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0G1519 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +max_ns: 5000 diff --git a/data/adc_wakeup/mspm0g310x.yaml b/data/adc_wakeup/mspm0g310x.yaml new file mode 100644 index 0000000..bcdf42b --- /dev/null +++ b/data/adc_wakeup/mspm0g310x.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0G3107 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +max_ns: 5000 diff --git a/data/adc_wakeup/mspm0g350x.yaml b/data/adc_wakeup/mspm0g350x.yaml new file mode 100644 index 0000000..a5db035 --- /dev/null +++ b/data/adc_wakeup/mspm0g350x.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0G3507 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +max_ns: 5000 diff --git a/data/adc_wakeup/mspm0g351x.yaml b/data/adc_wakeup/mspm0g351x.yaml new file mode 100644 index 0000000..7bb509f --- /dev/null +++ b/data/adc_wakeup/mspm0g351x.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0G3519 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +max_ns: 5000 diff --git a/data/adc_wakeup/mspm0g518x.yaml b/data/adc_wakeup/mspm0g518x.yaml new file mode 100644 index 0000000..97057cc --- /dev/null +++ b/data/adc_wakeup/mspm0g518x.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0G5187 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +max_ns: 5000 diff --git a/data/adc_wakeup/mspm0h321x.yaml b/data/adc_wakeup/mspm0h321x.yaml new file mode 100644 index 0000000..420c0f4 --- /dev/null +++ b/data/adc_wakeup/mspm0h321x.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0H3216 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +max_ns: 5000 diff --git a/data/adc_wakeup/mspm0l110x.yaml b/data/adc_wakeup/mspm0l110x.yaml new file mode 100644 index 0000000..73b4e05 --- /dev/null +++ b/data/adc_wakeup/mspm0l110x.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0L1106 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +typ_ns: 1000 diff --git a/data/adc_wakeup/mspm0l112x.yaml b/data/adc_wakeup/mspm0l112x.yaml new file mode 100644 index 0000000..560793b --- /dev/null +++ b/data/adc_wakeup/mspm0l112x.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0L2117 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +max_ns: 5000 diff --git a/data/adc_wakeup/mspm0l122x.yaml b/data/adc_wakeup/mspm0l122x.yaml new file mode 100644 index 0000000..9b1698a --- /dev/null +++ b/data/adc_wakeup/mspm0l122x.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0L1228 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +max_ns: 5000 diff --git a/data/adc_wakeup/mspm0l130x.yaml b/data/adc_wakeup/mspm0l130x.yaml new file mode 100644 index 0000000..e67c9de --- /dev/null +++ b/data/adc_wakeup/mspm0l130x.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0L1306 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +typ_ns: 1000 diff --git a/data/adc_wakeup/mspm0l134x.yaml b/data/adc_wakeup/mspm0l134x.yaml new file mode 100644 index 0000000..ed52ea3 --- /dev/null +++ b/data/adc_wakeup/mspm0l134x.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0L1346 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +typ_ns: 1000 diff --git a/data/adc_wakeup/mspm0l211x.yaml b/data/adc_wakeup/mspm0l211x.yaml new file mode 100644 index 0000000..560793b --- /dev/null +++ b/data/adc_wakeup/mspm0l211x.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0L2117 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +max_ns: 5000 diff --git a/data/adc_wakeup/mspm0l222x.yaml b/data/adc_wakeup/mspm0l222x.yaml new file mode 100644 index 0000000..78d4e4e --- /dev/null +++ b/data/adc_wakeup/mspm0l222x.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0L2228 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +max_ns: 5000 diff --git a/data/adc_wakeup/msps003fx.yaml b/data/adc_wakeup/msps003fx.yaml new file mode 100644 index 0000000..f59ddd3 --- /dev/null +++ b/data/adc_wakeup/msps003fx.yaml @@ -0,0 +1,16 @@ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the MSPM0C1104 datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# Assumes internal reference is active +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. + +max_ns: 5000 diff --git a/mspm0-data-gen/src/adc_wakeup.rs b/mspm0-data-gen/src/adc_wakeup.rs new file mode 100644 index 0000000..8711170 --- /dev/null +++ b/mspm0-data-gen/src/adc_wakeup.rs @@ -0,0 +1,33 @@ +//! Reads `data/adc_wakeup/<family>.yaml`, which `tools/adc_wakeup.py` extracts from the datasheets. +//! +//! `CTL0.PWRDN` resets to automatic power down, and the TRM makes the wake the caller's problem: +//! `SCOMPx` has to cover it on top of the sampling the signal itself needs. Nothing machine-readable +//! carries the figure โ€” sysconfig only warns that it exists and points at the datasheet โ€” so it is +//! transcribed like the VREF startup time. +//! +//! The two keys are separate because the datasheets fill different columns. A family with only +//! `typ_ns` has no published ceiling, which a consumer needing a guarantee has to notice rather than +//! round away. + +use std::collections::BTreeMap; + +use serde::Deserialize; + +use crate::util; + +/// One family's ADC wake-up figure, as the datasheet states it. +#[derive(Debug, Clone, Copy, Deserialize)] +pub struct AdcWakeup { + /// The MAX column, in nanoseconds. Absent where the datasheet publishes no ceiling. + #[serde(default)] + pub max_ns: Option<u32>, + + /// The TYP column, in nanoseconds. Absent where the datasheet states a ceiling instead. + #[serde(default)] + pub typ_ns: Option<u32>, +} + +/// Read every `data/adc_wakeup/<family>.yaml`, keyed by family name. +pub fn parse() -> anyhow::Result<BTreeMap<String, AdcWakeup>> { + util::per_family("adc_wakeup") +} diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 83057b4..f412903 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -17,6 +17,7 @@ use regex::Regex; use crate::comp::CompTiming; use crate::{ adc_channels::AdcChannels, + adc_wakeup::AdcWakeup, header::Header, int_group::Groups, opa::Opas, @@ -49,6 +50,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu header, sysconfig, adc_channels, + adc_wakeup, svd, clock_tree, operating_modes, @@ -83,7 +85,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu apply_standby1_timers(family, &mut peripherals)?; apply_timers(family, sysconfig, timers, &mut peripherals)?; apply_clock_ranges(family, &mut peripherals); - apply_adc(family, sysconfig, adc_channels, &mut peripherals)?; + apply_adc(family, sysconfig, adc_channels, adc_wakeup, &mut peripherals)?; apply_unicomm(family, header, &mut peripherals)?; apply_uart(family, sysconfig, uart, &mut peripherals)?; apply_opa(opa, &mut peripherals); @@ -1378,6 +1380,7 @@ fn apply_adc( family: &PartFamily, sysconfig: &SysconfigFile, adc_channels: Option<&AdcChannels>, + adc_wakeup: Option<AdcWakeup>, peripherals: &mut BTreeMap<String, Peripheral>, ) -> anyhow::Result<()> { let chip_name = &family.family; @@ -1424,6 +1427,10 @@ fn apply_adc( memctl, vrsel, internal_channels, + // Only the column the datasheet fills is set. A `None` maximum is the statement that + // the family publishes no ceiling, so it must not be filled in from the typical. + wakeup_max_ns: adc_wakeup.and_then(|w| w.max_ns), + wakeup_typ_ns: adc_wakeup.and_then(|w| w.typ_ns), }); } diff --git a/mspm0-data-gen/src/main.rs b/mspm0-data-gen/src/main.rs index 26583d0..6dae77d 100644 --- a/mspm0-data-gen/src/main.rs +++ b/mspm0-data-gen/src/main.rs @@ -1,4 +1,5 @@ mod adc_channels; +mod adc_wakeup; mod clock_tree; mod comp; mod errata; @@ -79,6 +80,7 @@ fn main() -> anyhow::Result<()> { stopwatch.section("Read interrupt group mappings"); let adc_channels = adc_channels::parse()?; + let adc_wakeup = adc_wakeup::parse()?; let int_groups = int_group::parse()?; let operating_modes = operating_modes::parse()?; let timers = timers::parse()?; @@ -97,6 +99,7 @@ fn main() -> anyhow::Result<()> { parts, headers, adc_channels, + adc_wakeup, sysconfig, svds, clock_trees, diff --git a/mspm0-data-gen/src/sources.rs b/mspm0-data-gen/src/sources.rs index 44cec86..884c8a2 100644 --- a/mspm0-data-gen/src/sources.rs +++ b/mspm0-data-gen/src/sources.rs @@ -10,6 +10,7 @@ use mspm0_data_types::{TemperatureSensor, Vref, WakeTimes}; use crate::{ adc_channels::AdcChannels, + adc_wakeup::AdcWakeup, clock_tree::{ClockTreeFile, ClockTrees}, comp::CompTiming, errata::Errata, @@ -29,6 +30,7 @@ pub struct Sources { pub parts: PartsFile, pub headers: Headers, pub adc_channels: BTreeMap<String, AdcChannels>, + pub adc_wakeup: BTreeMap<String, AdcWakeup>, pub sysconfig: Sysconfig, pub svds: Svds, pub clock_trees: ClockTrees, @@ -52,6 +54,7 @@ pub struct FamilySources<'a> { pub header: &'a Header, pub sysconfig: &'a SysconfigFile, pub adc_channels: Option<&'a AdcChannels>, + pub adc_wakeup: Option<AdcWakeup>, pub svd: Option<&'a Svd>, pub clock_tree: Option<&'a ClockTreeFile>, pub operating_modes: Option<&'a OperatingModes>, @@ -96,6 +99,7 @@ impl Sources { header, sysconfig, adc_channels: self.adc_channels.get(family), + adc_wakeup: self.adc_wakeup.get(family).copied(), svd: self.svds.files.get(family), clock_tree: self.clock_trees.files.get(family), operating_modes: self.operating_modes.get(family), diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 4556ee7..4c11226 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -14,6 +14,7 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { register_blocks_exist, vref_startup_known, adc_channels_known, + adc_wakeup_known, adc_internal_sources_exist, uart_features_known, opa_inputs_known, @@ -152,6 +153,43 @@ fn adc_channels_known(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// Report an ADC instance with no wake-up figure. +/// +/// All sixteen datasheets state `Twakeup`, so a missing figure means +/// `data/adc_wakeup/<family>.yaml` is absent or the extraction failed. Exactly one of the two +/// columns is filled: a family stating both, or neither, is a change in the source worth looking at +/// rather than something to average. +fn adc_wakeup_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + for peripheral in chip + .peripherals + .values() + .filter(|peripheral| peripheral.ty == PeripheralType::Adc) + { + let Some(adc) = peripheral.adc.as_ref() else { + continue; + }; + + if adc.wakeup_max_ns.is_none() && adc.wakeup_typ_ns.is_none() { + bail!( + "{name}: {} has no ADC wake-up time; data/adc_wakeup/{}.yaml is missing", + peripheral.name, + chip.family + ); + } + + if adc.wakeup_max_ns.is_some() && adc.wakeup_typ_ns.is_some() { + bail!( + "{name}: {} states both a typical and a maximum ADC wake-up time; the datasheets \ + fill one column, so check which data/adc_wakeup/{}.yaml means", + peripheral.name, + chip.family + ); + } + } + + Ok(()) +} + /// An internal channel naming a peripheral instance requires that instance to exist. /// /// This is the check that separates the datasheets from the SDK's family-superset tables, which diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index eb73583..7280eeb 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -999,6 +999,37 @@ pub struct Adc { /// Empty when the family's datasheet has no such table, which none so far lacks. #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub internal_channels: BTreeMap<u8, AdcInternalSource>, + + /// Guaranteed ceiling on the time the ADC needs to wake from automatic power down, in + /// nanoseconds, from the datasheet's `Twakeup` MAX column. + /// + /// `CTL0.PWRDN` resets to `0`, automatic power down, where the ADC switches off after each + /// conversion. The TRM makes the wake the caller's problem rather than the hardware's: it is + /// paid for out of the programmed sample window, so `SCOMPx` has to cover this figure **plus** + /// the sampling the signal itself needs. A window sized only for the signal samples an input + /// that has not finished waking, and nothing reports it. With `PWRDN = 1` the ADC stays + /// powered and none of this applies. + /// + /// **`None` means the datasheet publishes no ceiling, not that the wake is free.** On + /// mspm0l110x, mspm0l130x and mspm0l134x the figure appears in the TYP column instead and is in + /// [`Adc::wakeup_typ_ns`]. There is no worst case to design against on those three, which a + /// consumer needing a guarantee has to decide about rather than round away โ€” the two columns + /// are separate fields so that a typical cannot be mistaken for a bound. + /// + /// 5000 on the fifteen families which state a maximum. Stated once per datasheet, under the + /// condition "assumes internal reference is active" -- thirteen datasheets, two of which serve + /// two families each. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub wakeup_max_ns: Option<u32>, + + /// Typical time the ADC needs to wake from automatic power down, in nanoseconds, from the + /// datasheet's `Twakeup` TYP column. + /// + /// 1000 on mspm0l110x, mspm0l130x and mspm0l134x, which publish no maximum. `None` on the + /// fifteen families which state a ceiling instead โ€” see [`Adc::wakeup_max_ns`] for what this + /// figure is for and why the two are not merged. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub wakeup_typ_ns: Option<u32>, } /// An internal signal an ADC channel samples instead of a package pin. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index e035db0..ad613ab 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -651,6 +651,35 @@ pub struct Adc { /// channel 13 on mspm0g350x and channel 12 on mspm0l130x -- so do not key it on the instance /// name. pub internal_channels: &'static [AdcInternalChannel], + + /// Guaranteed ceiling on the time the ADC needs to wake from automatic power down, in + /// nanoseconds, from the datasheet's `Twakeup` MAX column. + /// + /// `CTL0.PWRDN` resets to `0`, automatic power down, where the ADC switches off after each + /// conversion. The TRM makes the wake the caller's problem rather than the hardware's: it is + /// paid for out of the programmed sample window, so `SCOMPx` has to cover this figure **plus** + /// the sampling the signal itself needs. A window sized only for the signal samples an input + /// that has not finished waking, and nothing reports it. With `PWRDN = 1` the ADC stays + /// powered and none of this applies. + /// + /// **`None` means the datasheet publishes no ceiling, not that the wake is free.** On + /// mspm0l110x, mspm0l130x and mspm0l134x the figure appears in the TYP column instead and is in + /// [`Adc::wakeup_typ_ns`]. There is no worst case to design against on those three, which a + /// consumer needing a guarantee has to decide about rather than round away โ€” the two columns + /// are separate fields so that a typical cannot be mistaken for a bound. + /// + /// 5000 on the fifteen families which state a maximum. Stated once per datasheet, under the + /// condition "assumes internal reference is active" -- thirteen datasheets, two of which serve + /// two families each. + pub wakeup_max_ns: Option<u32>, + + /// Typical time the ADC needs to wake from automatic power down, in nanoseconds, from the + /// datasheet's `Twakeup` TYP column. + /// + /// 1000 on mspm0l110x, mspm0l130x and mspm0l134x, which publish no maximum. `None` on the + /// fifteen families which state a ceiling instead โ€” see [`Adc::wakeup_max_ns`] for what this + /// figure is for and why the two are not merged. + pub wakeup_typ_ns: Option<u32>, } /// One ADC channel and the internal signal it samples. diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 37999ca..e531b99 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -409,11 +409,21 @@ fn generate_peripheral( let source = adc_internal_source(*source); quote! { AdcInternalChannel { channel: #channel, source: #source } } }); + let ns = |value: Option<u32>| match value { + Some(ns) => { + let ns = Literal::u32_unsuffixed(ns); + quote! { Some(#ns) } + } + None => quote! { None }, + }; + let (wakeup_max_ns, wakeup_typ_ns) = (ns(adc.wakeup_max_ns), ns(adc.wakeup_typ_ns)); quote! { Some(Adc { memctl: #memctl, vrsel: #vrsel, internal_channels: &[#(#internal_channels),*], + wakeup_max_ns: #wakeup_max_ns, + wakeup_typ_ns: #wakeup_typ_ns, }) } } diff --git a/tools/adc_wakeup.py b/tools/adc_wakeup.py new file mode 100644 index 0000000..de34877 --- /dev/null +++ b/tools/adc_wakeup.py @@ -0,0 +1,196 @@ +"""Extract the ADC wake-up time from MSPM0 datasheets. + +`CTL0.PWRDN` resets to `0`, which is automatic power down: the ADC powers off after each conversion +and has to wake again for the next one. The TRM's note under its AUTO-mode timing diagram says the +wake costs part of the sample window and has to be paid for out of `SCOMPx` -- "if the maximum ADC +wakeup/enable time is 5uS, it means the duration set by SCOMPx should be > (5uS + Duration for sample +window)". Nothing machine-readable carries the figure: no header constant, nothing in driverlib, and +sysconfig only emits a warning telling you to read the datasheet. This writes +`data/adc_wakeup/<family>.yaml`. + +Like `tools/vref.py`, this is an offline aid rather than part of the build. Re-run it after a data +source bump rather than editing the YAML by hand. + +Usage: + uv run tools/adc_wakeup.py <datasheet.pdf> [...] # print what it finds + uv run tools/adc_wakeup.py --write <dir-of-datasheets> # regenerate data/adc_wakeup + +`uv run` installs the dependencies below on its own; a bare `python` needs them on the path. + +**Which column the figure sits in is the point, and it differs.** Thirteen datasheets put it in MAX; +mspm0l110x, mspm0l130x and mspm0l134x put 1us in TYP and publish no maximum at all. A driver that +has to guarantee a settled input needs the ceiling, and on those three families the datasheet does +not give one -- so the two are recorded as separate keys rather than as one number with a footnote. +Reading a typical as a bound is the failure this shape prevents. + +The table is not ruled, so a lattice read returns nothing and the column has to be decided +geometrically: the MIN/TYP/MAX header positions are found on the same page and the figure is +assigned to whichever header its centre is nearest. Layout text cannot answer this -- it collapses +the three columns into whitespace, which is how the difference went unnoticed until it was checked. +""" + +# /// script +# requires-python = ">=3.10" +# dependencies = ["pdfplumber", "pyyaml"] +# /// + +import re +import sys +from pathlib import Path + +try: + import pdfplumber + import yaml +except ImportError as e: # pragma: no cover + raise SystemExit(f"{e.name} is missing; run this with `uv run` instead of `python`") + +#: The row's description cell. Specific enough not to catch `tWAKEUP`, the SHUTDOWN wake-up time, +#: which is a different figure in a different table. +CAPTION = "ADC Wakeup Time" + +#: Time units the figure may be given in, as a multiplier to nanoseconds. +UNITS = {"ns": 1, "us": 1_000, "ยตs": 1_000, "ms": 1_000_000} + +NUMBER = re.compile(r"\d+(?:\.\d+)?") + +#: Rows are grouped by rounding the word top to this many points. Large enough to hold a row whose +#: cells sit a fraction of a point apart, small enough not to merge two rows. +ROW_TOLERANCE = 3 + + +def centre(word: dict) -> float: + return (word["x0"] + word["x1"]) / 2 + + +def rows_of(page) -> dict[int, list[dict]]: + """Words grouped into visual rows, keyed by rounded vertical position.""" + grouped: dict[int, list[dict]] = {} + for word in page.extract_words(): + grouped.setdefault(round(word["top"] / ROW_TOLERANCE), []).append(word) + return grouped + + +def read(path: Path) -> tuple[str, int, str] | None: + """Return `(column, nanoseconds, conditions)` for the ADC wake-up row, or `None`. + + `column` is `MIN`, `TYP` or `MAX` -- whichever header the figure sits under. + """ + with pdfplumber.open(str(path)) as pdf: + for page in pdf.pages: + text = page.extract_text() or "" + if CAPTION not in text: + continue + + grouped = rows_of(page) + headers: dict[str, float] = {} + + for key in sorted(grouped): + words = sorted(grouped[key], key=lambda w: w["x0"]) + line = " ".join(w["text"] for w in words) + + # The column header row precedes the figures on the same page. + if "MIN" in line and "MAX" in line: + headers = { + w["text"]: centre(w) for w in words if w["text"] in ("MIN", "TYP", "MAX") + } + + if CAPTION.replace(" ", "") not in line.replace(" ", "") or not headers: + continue + + unit = next((w["text"] for w in reversed(words) if w["text"].lower() in UNITS), "") + figures = [w for w in words if NUMBER.fullmatch(w["text"])] + if not unit or not figures: + continue + + figure = figures[-1] + column = min(headers, key=lambda c: abs(headers[c] - centre(figure))) + conditions = " ".join( + w["text"] + for w in words + if w is not figure + and w["text"] != unit + and w["text"] not in CAPTION.split() + and not w["text"].startswith("T") + ) + ns = round(float(figure["text"]) * UNITS[unit.lower()]) + return column, ns, " ".join(conditions.split()) + + return None + + +HEADER = """\ +# How long this family's ADC needs to wake from automatic power down, in nanoseconds. +# +# GENERATED by `tools/adc_wakeup.py --write` from the `Twakeup` row of the {ds} datasheet. +# Re-run the tool rather than editing by hand. +# +# CTL0.PWRDN resets to 0 (automatic power down), and the TRM requires this to be paid for out of the +# programmed sample window: SCOMPx must cover the wake plus the sampling the signal actually needs. +# +# Stated under one condition, quoted here: +# +# {conditions} +# +# Only the column the datasheet fills is written. A missing max_ns means the datasheet publishes no +# ceiling for this family, not that the wake is free. +""" + + +def write(datasheets: str) -> int: + """Regenerate data/adc_wakeup/*.yaml. Returns the number of problems reported.""" + parts = yaml.safe_load(Path("data/parts.yaml").read_text(encoding="utf-8")) + families = [(f["family"], f["datasheet_url"].rsplit("/", 1)[-1]) for f in parts["families"]] + + Path("data/adc_wakeup").mkdir(parents=True, exist_ok=True) + problems = 0 + + for family, gpn in families: + pdf = Path(datasheets) / f"{gpn}_datasheet.pdf" + if not pdf.exists(): + print(f"{family}: no datasheet for {gpn} in {datasheets}") + problems += 1 + continue + + found = read(pdf) + if found is None: + print(f"{family}: no ADC wake-up time in {pdf.name}") + problems += 1 + continue + + column, ns, conditions = found + if column == "MIN": + print(f"{family}: ADC wake-up time is a MIN in {pdf.name}, which says nothing useful") + problems += 1 + continue + + key = "max_ns" if column == "MAX" else "typ_ns" + path = Path(f"data/adc_wakeup/{family}.yaml") + with open(path, "w", encoding="utf-8", newline="\n") as out: + out.write(HEADER.format(ds=gpn.upper(), conditions=conditions or "none stated")) + out.write(f"\n{key}: {ns}\n") + print(f"{family}: {ns / 1000:g} us ({column}), from {pdf.name}") + + return problems + + +def main(argv: list[str]) -> None: + if "--write" in argv: + i = argv.index("--write") + raise SystemExit(1 if write(argv[i + 1] if i + 1 < len(argv) else ".") else 0) + + paths = [Path(a) for a in argv if not a.startswith("--")] + if not paths: + raise SystemExit(__doc__) + + for path in paths: + found = read(path) + if found is None: + print(f"########## {path.name}: no ADC wake-up time") + continue + column, ns, conditions = found + print(f"########## {path.name}") + print(f" {ns / 1000:>8g} us {column} {conditions}") + + +if __name__ == "__main__": + main(sys.argv[1:]) From 3cceb3b6fc3c6415d0d159f6ce89f2997b4db5ed Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 17 Aug 2026 11:36:05 +0100 Subject: [PATCH 115/134] registers: correct the DMA INT_EVENT array stride The array is based at 0x1020 with a stride of 44, so index 1 - the generic event group - resolves to 0x104c instead of 0x1050. hw_dma.h places DMA_GEN_EVENT.IIDX at 0x1050, and the eleven SVDs which describe the two groups as separate clusters agree. The ten which state one two-element array give a dimIncrement of 44, which is the block's own size rather than the distance between instances. --- data/registers/dma_v1.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/data/registers/dma_v1.yaml b/data/registers/dma_v1.yaml index 65ccf7e..1754328 100644 --- a/data/registers/dma_v1.yaml +++ b/data/registers/dma_v1.yaml @@ -35,10 +35,14 @@ block/DMA: description: Peripheral Debug Control. byte_offset: 4120 fieldset: PDBGCTL + # Stride 48, not the 44 every SVD's dimIncrement gives: 44 is the block's own size, so index 1 + # landed four bytes low. hw_dma.h puts DMA_CPU_INT.IIDX at 0x1020 and DMA_GEN_EVENT.IIDX at + # 0x1050, and the eleven SVDs which describe the two groups as separate clusters agree. The ten + # which state one two-element array are the only dissenters. - name: INT_EVENT array: len: 2 - stride: 44 + stride: 48 byte_offset: 4128 block: INT_EVENT - name: EVT_MODE From 51161a152f9f38a67b7b21d45e3f01121ec20ba3 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 17 Aug 2026 12:06:49 +0100 Subject: [PATCH 116/134] sysctl: BLOCKASYNC comes from the peripheral's own CLKCFG SYSCTL's SVD body carries every other peripheral's clock config as SYSCTL_MGMT_<other>_CLKCFG, twelve registers on a G350x, each with the field. Testing for it anywhere in the body reported SYSCTL as having a bit it does not have, on 32 of 43 chips. No SYSCTL in the SDK defines a CLKCFG at all. The only BLOCKASYNC-family constant there is SYSOSCCFG.BLOCKASYNCALL, which masks every peripheral at once and is a different thing. --- mspm0-data-gen/src/svd.rs | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/mspm0-data-gen/src/svd.rs b/mspm0-data-gen/src/svd.rs index e016cf7..5ab9345 100644 --- a/mspm0-data-gen/src/svd.rs +++ b/mspm0-data-gen/src/svd.rs @@ -2,8 +2,9 @@ //! come from the register YAMLs. //! //! The SVDs are scanned textually rather than parsed, since every fact read here is a presence test -//! on a field name. Parsing properly would mean depending on `svd-parser` directly: chiptool does not -//! re-export it, and is itself pinned to a commit, so the dependency would have to track that commit. +//! on a field name, in one case narrowed to the register holding it. Parsing properly would mean +//! depending on `svd-parser` directly: chiptool does not re-export it, and is itself pinned to a +//! commit, so the dependency would have to track that commit. use std::{ collections::{BTreeMap, BTreeSet}, @@ -19,8 +20,16 @@ use regex::Regex; /// /// Only peripherals which can raise such a request have it. Note that this is distinct from /// `SYSCTL.SYSOSCCFG.BLOCKASYNCALL`, which masks the requests of every peripheral at once. +/// +/// It is read from the peripheral's own `<instance>_CLKCFG` and nowhere else, because SYSCTL's body +/// also contains every other peripheral's clock config as `SYSCTL_MGMT_<other>_CLKCFG` โ€” twelve such +/// registers on a G350x, each carrying the field. A test over the whole body reports SYSCTL as having +/// a bit it does not have: no SYSCTL in the SDK defines a `CLKCFG` at all. const BLOCK_ASYNC: &str = "BLOCKASYNC"; +/// The register carrying [`BLOCK_ASYNC`], suffixed onto the instance name. +const CLKCFG: &str = "_CLKCFG"; + /// The `DMACTL.DMASRCWDTH`/`DMADSTWDTH` value which selects a 128-bit transfer. /// /// Enumerated only by the SVDs of devices which implement the width. @@ -39,7 +48,12 @@ static PERIPHERAL: LazyLock<Regex> = LazyLock::new(|| { .unwrap() }); -/// The first `<name>` of a peripheral, which is its instance name. +/// One `<register>` element. Never carries attributes in any of these files. +static REGISTER: LazyLock<Regex> = + LazyLock::new(|| Regex::new(r"(?s)<register>(?<body>.*?)</register>").unwrap()); + +/// The first `<name>` of a peripheral, which is its instance name. Within a `<register>` body the +/// same match is the register's own name, since it precedes the fields. static NAME: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"<name>([^<]+)</name>").unwrap()); #[derive(Debug)] @@ -102,10 +116,15 @@ impl Svd { .context(format!("{path:?}: peripheral without a name"))?[1] .to_string(); - // The field name only ever appears as a `<name>`, so a substring test over the - // peripheral's body is enough to tell whether one of its registers has the field. + let clkcfg = format!("{name}{CLKCFG}"); let tag = format!("<name>{BLOCK_ASYNC}</name>"); - own.insert(name.clone(), body.contains(&tag)); + let has_block_async = REGISTER.captures_iter(body).any(|register| { + let register = ®ister["body"]; + + NAME.captures(register).is_some_and(|n| n[1] == *clkcfg) && register.contains(&tag) + }); + + own.insert(name.clone(), has_block_async); // Peripherals in these SVDs are always fully expanded, but resolve `derivedFrom` // anyway so a source bump which starts using it does not silently drop instances. From 0e6170c181f6c3f69efdb44608b9f53f7949785d Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Fri, 21 Aug 2026 11:33:03 +0100 Subject: [PATCH 117/134] adc: record the minimum sample window, bare pin and per OPA gain SAMPCLK has no published ceiling, so what bounds a conversion is the window. A SCOMPx shorter than tSample converts an input that has not finished settling, and nothing reports it. It adds to Twakeup rather than replacing it. Every figure sits in the MIN column on all eighteen families. tSample and tSample_step are one quantity: only mspm0h321x states a Vstep, and it is the only 4.5-5.5V part, so the older documents left the step implicit at the device's own full scale. That family states the row twice and the larger is recorded, since 188ns is not a bound at its own rail. Figures round up, so 62.5ns stores as 63. tSample_PGA is keyed by gain rather than by channel, and at x32 it is an order of magnitude above the bare-pin figure. Neither of its unpublished axes is derivable: the G datasheets print six gains and the L datasheets two, and the curves cross; and every row is conditioned GBW = 0x1, which nothing gives a counterpart for. Seven datasheets print the table for the four families that have an OPA, so the map is attached only where an instance exists. --- data/adc_sample/mspm0c1105_c1106.yaml | 14 ++ data/adc_sample/mspm0c110x.yaml | 14 ++ data/adc_sample/mspm0g110x.yaml | 33 +++ data/adc_sample/mspm0g150x.yaml | 33 +++ data/adc_sample/mspm0g151x.yaml | 14 ++ data/adc_sample/mspm0g310x.yaml | 33 +++ data/adc_sample/mspm0g350x.yaml | 33 +++ data/adc_sample/mspm0g351x.yaml | 14 ++ data/adc_sample/mspm0g518x.yaml | 14 ++ data/adc_sample/mspm0h321x.yaml | 18 ++ data/adc_sample/mspm0l110x.yaml | 29 +++ data/adc_sample/mspm0l112x.yaml | 14 ++ data/adc_sample/mspm0l122x.yaml | 14 ++ data/adc_sample/mspm0l130x.yaml | 29 +++ data/adc_sample/mspm0l134x.yaml | 29 +++ data/adc_sample/mspm0l211x.yaml | 14 ++ data/adc_sample/mspm0l222x.yaml | 14 ++ data/adc_sample/msps003fx.yaml | 14 ++ mspm0-data-gen/src/adc_sample.rs | 40 ++++ mspm0-data-gen/src/generate.rs | 36 ++- mspm0-data-gen/src/main.rs | 5 +- mspm0-data-gen/src/sources.rs | 4 + mspm0-data-gen/src/verify.rs | 72 ++++++ mspm0-data-types/src/lib.rs | 53 +++++ mspm0-metapac-gen/res/metadata.rs | 56 +++++ mspm0-metapac-gen/src/metadata.rs | 8 + tools/adc_sample.py | 316 ++++++++++++++++++++++++++ 27 files changed, 961 insertions(+), 6 deletions(-) create mode 100644 data/adc_sample/mspm0c1105_c1106.yaml create mode 100644 data/adc_sample/mspm0c110x.yaml create mode 100644 data/adc_sample/mspm0g110x.yaml create mode 100644 data/adc_sample/mspm0g150x.yaml create mode 100644 data/adc_sample/mspm0g151x.yaml create mode 100644 data/adc_sample/mspm0g310x.yaml create mode 100644 data/adc_sample/mspm0g350x.yaml create mode 100644 data/adc_sample/mspm0g351x.yaml create mode 100644 data/adc_sample/mspm0g518x.yaml create mode 100644 data/adc_sample/mspm0h321x.yaml create mode 100644 data/adc_sample/mspm0l110x.yaml create mode 100644 data/adc_sample/mspm0l112x.yaml create mode 100644 data/adc_sample/mspm0l122x.yaml create mode 100644 data/adc_sample/mspm0l130x.yaml create mode 100644 data/adc_sample/mspm0l134x.yaml create mode 100644 data/adc_sample/mspm0l211x.yaml create mode 100644 data/adc_sample/mspm0l222x.yaml create mode 100644 data/adc_sample/msps003fx.yaml create mode 100644 mspm0-data-gen/src/adc_sample.rs create mode 100644 tools/adc_sample.py diff --git a/data/adc_sample/mspm0c1105_c1106.yaml b/data/adc_sample/mspm0c1105_c1106.yaml new file mode 100644 index 0000000..e199229 --- /dev/null +++ b/data/adc_sample/mspm0c1105_c1106.yaml @@ -0,0 +1,14 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0C1106 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample_step` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 188 diff --git a/data/adc_sample/mspm0c110x.yaml b/data/adc_sample/mspm0c110x.yaml new file mode 100644 index 0000000..c052112 --- /dev/null +++ b/data/adc_sample/mspm0c110x.yaml @@ -0,0 +1,14 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0C1104 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample_step` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 166 diff --git a/data/adc_sample/mspm0g110x.yaml b/data/adc_sample/mspm0g110x.yaml new file mode 100644 index 0000000..f11ddb2 --- /dev/null +++ b/data/adc_sample/mspm0g110x.yaml @@ -0,0 +1,33 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0G1107 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 63 + +# pga_ns is the `tSample_PGA` row: the window when the channel is an OPA output, keyed by PGA gain. +# It is per gain and not per channel, and at high gain it is an order of magnitude above min_ns. +# +# Every published figure is conditioned GBW = 0x1 (OA_CFGBASE_GBW_HIGHGAIN). The reset value is 0 +# and TI's DL_OPA_init leaves it there, so a caller which has not set it is outside every published +# figure. No datasheet publishes a row for GBW = 0, and the low setting is slower, so the figures +# become an underestimate rather than a bound there. +# +# A gain absent here is unpublished, not derivable: the G datasheets print six gains and the L +# datasheets two, and the curves cross, so neither can be interpolated onto the other. + +pga_ns: + 1: 220 + 2: 480 + 4: 550 + 8: 850 + 16: 1600 + 32: 2600 diff --git a/data/adc_sample/mspm0g150x.yaml b/data/adc_sample/mspm0g150x.yaml new file mode 100644 index 0000000..15632c3 --- /dev/null +++ b/data/adc_sample/mspm0g150x.yaml @@ -0,0 +1,33 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0G1507 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 63 + +# pga_ns is the `tSample_PGA` row: the window when the channel is an OPA output, keyed by PGA gain. +# It is per gain and not per channel, and at high gain it is an order of magnitude above min_ns. +# +# Every published figure is conditioned GBW = 0x1 (OA_CFGBASE_GBW_HIGHGAIN). The reset value is 0 +# and TI's DL_OPA_init leaves it there, so a caller which has not set it is outside every published +# figure. No datasheet publishes a row for GBW = 0, and the low setting is slower, so the figures +# become an underestimate rather than a bound there. +# +# A gain absent here is unpublished, not derivable: the G datasheets print six gains and the L +# datasheets two, and the curves cross, so neither can be interpolated onto the other. + +pga_ns: + 1: 220 + 2: 480 + 4: 550 + 8: 850 + 16: 1600 + 32: 2600 diff --git a/data/adc_sample/mspm0g151x.yaml b/data/adc_sample/mspm0g151x.yaml new file mode 100644 index 0000000..0f14ee8 --- /dev/null +++ b/data/adc_sample/mspm0g151x.yaml @@ -0,0 +1,14 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0G1519 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 63 diff --git a/data/adc_sample/mspm0g310x.yaml b/data/adc_sample/mspm0g310x.yaml new file mode 100644 index 0000000..45341ac --- /dev/null +++ b/data/adc_sample/mspm0g310x.yaml @@ -0,0 +1,33 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0G3107 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 63 + +# pga_ns is the `tSample_PGA` row: the window when the channel is an OPA output, keyed by PGA gain. +# It is per gain and not per channel, and at high gain it is an order of magnitude above min_ns. +# +# Every published figure is conditioned GBW = 0x1 (OA_CFGBASE_GBW_HIGHGAIN). The reset value is 0 +# and TI's DL_OPA_init leaves it there, so a caller which has not set it is outside every published +# figure. No datasheet publishes a row for GBW = 0, and the low setting is slower, so the figures +# become an underestimate rather than a bound there. +# +# A gain absent here is unpublished, not derivable: the G datasheets print six gains and the L +# datasheets two, and the curves cross, so neither can be interpolated onto the other. + +pga_ns: + 1: 220 + 2: 480 + 4: 550 + 8: 850 + 16: 1600 + 32: 2600 diff --git a/data/adc_sample/mspm0g350x.yaml b/data/adc_sample/mspm0g350x.yaml new file mode 100644 index 0000000..22ae834 --- /dev/null +++ b/data/adc_sample/mspm0g350x.yaml @@ -0,0 +1,33 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0G3507 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 63 + +# pga_ns is the `tSample_PGA` row: the window when the channel is an OPA output, keyed by PGA gain. +# It is per gain and not per channel, and at high gain it is an order of magnitude above min_ns. +# +# Every published figure is conditioned GBW = 0x1 (OA_CFGBASE_GBW_HIGHGAIN). The reset value is 0 +# and TI's DL_OPA_init leaves it there, so a caller which has not set it is outside every published +# figure. No datasheet publishes a row for GBW = 0, and the low setting is slower, so the figures +# become an underestimate rather than a bound there. +# +# A gain absent here is unpublished, not derivable: the G datasheets print six gains and the L +# datasheets two, and the curves cross, so neither can be interpolated onto the other. + +pga_ns: + 1: 220 + 2: 480 + 4: 550 + 8: 850 + 16: 1600 + 32: 2600 diff --git a/data/adc_sample/mspm0g351x.yaml b/data/adc_sample/mspm0g351x.yaml new file mode 100644 index 0000000..e4b0342 --- /dev/null +++ b/data/adc_sample/mspm0g351x.yaml @@ -0,0 +1,14 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0G3519 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 63 diff --git a/data/adc_sample/mspm0g518x.yaml b/data/adc_sample/mspm0g518x.yaml new file mode 100644 index 0000000..8fc838f --- /dev/null +++ b/data/adc_sample/mspm0g518x.yaml @@ -0,0 +1,14 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0G5187 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample_step` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 188 diff --git a/data/adc_sample/mspm0h321x.yaml b/data/adc_sample/mspm0h321x.yaml new file mode 100644 index 0000000..b03c25c --- /dev/null +++ b/data/adc_sample/mspm0h321x.yaml @@ -0,0 +1,18 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0H3216 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample_step` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# This datasheet states the row twice, once per input step: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF, Vstep=4V: 188 ns +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF, Vstep=5V: 400 ns +# +# The larger is written. The recommended supply reaches 5.5V, so the smaller figure is not a bound +# for the part. + +min_ns: 400 diff --git a/data/adc_sample/mspm0l110x.yaml b/data/adc_sample/mspm0l110x.yaml new file mode 100644 index 0000000..0b4b9bc --- /dev/null +++ b/data/adc_sample/mspm0l110x.yaml @@ -0,0 +1,29 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0L1106 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 156 + +# pga_ns is the `tSample_PGA` row: the window when the channel is an OPA output, keyed by PGA gain. +# It is per gain and not per channel, and at high gain it is an order of magnitude above min_ns. +# +# Every published figure is conditioned GBW = 0x1 (OA_CFGBASE_GBW_HIGHGAIN). The reset value is 0 +# and TI's DL_OPA_init leaves it there, so a caller which has not set it is outside every published +# figure. No datasheet publishes a row for GBW = 0, and the low setting is slower, so the figures +# become an underestimate rather than a bound there. +# +# A gain absent here is unpublished, not derivable: the G datasheets print six gains and the L +# datasheets two, and the curves cross, so neither can be interpolated onto the other. + +pga_ns: + 1: 310 + 32: 1500 diff --git a/data/adc_sample/mspm0l112x.yaml b/data/adc_sample/mspm0l112x.yaml new file mode 100644 index 0000000..7454789 --- /dev/null +++ b/data/adc_sample/mspm0l112x.yaml @@ -0,0 +1,14 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0L2117 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample_step` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 188 diff --git a/data/adc_sample/mspm0l122x.yaml b/data/adc_sample/mspm0l122x.yaml new file mode 100644 index 0000000..1925949 --- /dev/null +++ b/data/adc_sample/mspm0l122x.yaml @@ -0,0 +1,14 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0L1228 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 156 diff --git a/data/adc_sample/mspm0l130x.yaml b/data/adc_sample/mspm0l130x.yaml new file mode 100644 index 0000000..ddf42a6 --- /dev/null +++ b/data/adc_sample/mspm0l130x.yaml @@ -0,0 +1,29 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0L1306 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 156 + +# pga_ns is the `tSample_PGA` row: the window when the channel is an OPA output, keyed by PGA gain. +# It is per gain and not per channel, and at high gain it is an order of magnitude above min_ns. +# +# Every published figure is conditioned GBW = 0x1 (OA_CFGBASE_GBW_HIGHGAIN). The reset value is 0 +# and TI's DL_OPA_init leaves it there, so a caller which has not set it is outside every published +# figure. No datasheet publishes a row for GBW = 0, and the low setting is slower, so the figures +# become an underestimate rather than a bound there. +# +# A gain absent here is unpublished, not derivable: the G datasheets print six gains and the L +# datasheets two, and the curves cross, so neither can be interpolated onto the other. + +pga_ns: + 1: 310 + 32: 1500 diff --git a/data/adc_sample/mspm0l134x.yaml b/data/adc_sample/mspm0l134x.yaml new file mode 100644 index 0000000..b794eec --- /dev/null +++ b/data/adc_sample/mspm0l134x.yaml @@ -0,0 +1,29 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0L1346 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 156 + +# pga_ns is the `tSample_PGA` row: the window when the channel is an OPA output, keyed by PGA gain. +# It is per gain and not per channel, and at high gain it is an order of magnitude above min_ns. +# +# Every published figure is conditioned GBW = 0x1 (OA_CFGBASE_GBW_HIGHGAIN). The reset value is 0 +# and TI's DL_OPA_init leaves it there, so a caller which has not set it is outside every published +# figure. No datasheet publishes a row for GBW = 0, and the low setting is slower, so the figures +# become an underestimate rather than a bound there. +# +# A gain absent here is unpublished, not derivable: the G datasheets print six gains and the L +# datasheets two, and the curves cross, so neither can be interpolated onto the other. + +pga_ns: + 1: 310 + 32: 1500 diff --git a/data/adc_sample/mspm0l211x.yaml b/data/adc_sample/mspm0l211x.yaml new file mode 100644 index 0000000..7454789 --- /dev/null +++ b/data/adc_sample/mspm0l211x.yaml @@ -0,0 +1,14 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0L2117 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample_step` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 188 diff --git a/data/adc_sample/mspm0l222x.yaml b/data/adc_sample/mspm0l222x.yaml new file mode 100644 index 0000000..d769c7c --- /dev/null +++ b/data/adc_sample/mspm0l222x.yaml @@ -0,0 +1,14 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0L2228 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 156 diff --git a/data/adc_sample/msps003fx.yaml b/data/adc_sample/msps003fx.yaml new file mode 100644 index 0000000..c052112 --- /dev/null +++ b/data/adc_sample/msps003fx.yaml @@ -0,0 +1,14 @@ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the MSPM0C1104 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `tSample_step` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +# +# Stated under one condition, quoted here: +# +# 12-bit mode, RS = 50ฮฉ, Cpext = 10pF + +min_ns: 166 diff --git a/mspm0-data-gen/src/adc_sample.rs b/mspm0-data-gen/src/adc_sample.rs new file mode 100644 index 0000000..6f16c71 --- /dev/null +++ b/mspm0-data-gen/src/adc_sample.rs @@ -0,0 +1,40 @@ +//! Reads `data/adc_sample/<family>.yaml`, which `tools/adc_sample.py` extracts from the datasheets. +//! +//! `SAMPCLK` has no published ceiling, so what bounds an ADC conversion is the sample *window*. +//! Nothing machine-readable carries it: no header constant, nothing in driverlib, and sysconfig has +//! no attribute for it. Transcribed like the VREF startup time and the ADC wake-up figure. +//! +//! Two rows, because they answer different questions. `min_ns` is the bare-pin minimum and applies +//! to every channel that reaches a package pin. `pga_ns` is the window when the channel is an OPA +//! output, and it is keyed by PGA gain rather than by channel โ€” at x32 it is an order of magnitude +//! above `min_ns`, so a driver using the bare-pin figure there is short by that much. +//! +//! `pga_ns` is present in seven of the eighteen files and only four of those families have an OPA: +//! the datasheet row carries the footnote "Only applies for devices with OPA" because one document +//! covers several devices. `apply_adc` attaches the map only where an OPA instance exists, so the +//! superset the document prints does not reach the metadata. + +use std::collections::BTreeMap; + +use serde::Deserialize; + +use crate::util; + +/// One family's ADC sample-window figures, as the datasheet states them. +#[derive(Debug, Clone, Deserialize)] +pub struct AdcSample { + /// The `tSample`/`tSample_step` MIN column, in nanoseconds, rounded up. Every datasheet states + /// one, so an absent file is an extraction failure rather than a device without a minimum. + pub min_ns: u32, + + /// The `tSample_PGA` MIN column, in nanoseconds, keyed by PGA gain. Absent where the datasheet + /// prints no such table. A gain missing from a family that has the table is unpublished for + /// that family and cannot be interpolated โ€” the G and L curves cross. + #[serde(default)] + pub pga_ns: BTreeMap<u8, u32>, +} + +/// Read every `data/adc_sample/<family>.yaml`, keyed by family name. +pub fn parse() -> anyhow::Result<BTreeMap<String, AdcSample>> { + util::per_family("adc_sample") +} diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index f412903..9e020c6 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -8,15 +8,16 @@ use std::{ use anyhow::{anyhow, bail, ensure, Context}; use mspm0_data_types::{ - Adc, Chip, Comp, Dma, DmaChannel, Flashctl, Interrupt, IoStructure, Memory, MemoryKind, Package, - PackagePin, Peripheral, PeripheralInterrupt, PeripheralPin, PeripheralType, PowerDomain, - PowerMode, Sysctl, Timer, Uart, Unicomm, Vref, + Adc, Chip, Comp, Dma, DmaChannel, Flashctl, Interrupt, IoStructure, Memory, MemoryKind, + Package, PackagePin, Peripheral, PeripheralInterrupt, PeripheralPin, PeripheralType, + PowerDomain, PowerMode, Sysctl, Timer, Uart, Unicomm, Vref, }; use regex::Regex; use crate::comp::CompTiming; use crate::{ adc_channels::AdcChannels, + adc_sample::AdcSample, adc_wakeup::AdcWakeup, header::Header, int_group::Groups, @@ -50,6 +51,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu header, sysconfig, adc_channels, + adc_sample, adc_wakeup, svd, clock_tree, @@ -85,7 +87,14 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu apply_standby1_timers(family, &mut peripherals)?; apply_timers(family, sysconfig, timers, &mut peripherals)?; apply_clock_ranges(family, &mut peripherals); - apply_adc(family, sysconfig, adc_channels, adc_wakeup, &mut peripherals)?; + apply_adc( + family, + sysconfig, + adc_channels, + adc_sample, + adc_wakeup, + &mut peripherals, + )?; apply_unicomm(family, header, &mut peripherals)?; apply_uart(family, sysconfig, uart, &mut peripherals)?; apply_opa(opa, &mut peripherals); @@ -1252,7 +1261,11 @@ fn apply_dma( // source there, and it agrees with the header on all ten families which state both. if let Some(svd) = svd { for (feature, from_header, from_svd) in [ - ("128-bit DMA transfers", header.dma_long_long, svd.dma_long_long), + ( + "128-bit DMA transfers", + header.dma_long_long, + svd.dma_long_long, + ), ( "the DMA's automatic enable", header.dma_auto_enable, @@ -1380,12 +1393,20 @@ fn apply_adc( family: &PartFamily, sysconfig: &SysconfigFile, adc_channels: Option<&AdcChannels>, + adc_sample: Option<&AdcSample>, adc_wakeup: Option<AdcWakeup>, peripherals: &mut BTreeMap<String, Peripheral>, ) -> anyhow::Result<()> { let chip_name = &family.family; let vrsel = adc_vrsel_mapping(&family.adc_vrsel)?; + // Seven datasheets print the `tSample_PGA` table and only four of those families have an OPA: + // the row is footnoted "Only applies for devices with OPA" because one document covers several + // devices. Gating on the instance is what keeps that superset out of the metadata. + let has_opa = peripherals + .values() + .any(|peripheral| peripheral.ty == PeripheralType::Opa); + let mut memctl = BTreeMap::new(); for peripheral in sysconfig .peripherals @@ -1431,6 +1452,11 @@ fn apply_adc( // the family publishes no ceiling, so it must not be filled in from the typical. wakeup_max_ns: adc_wakeup.and_then(|w| w.max_ns), wakeup_typ_ns: adc_wakeup.and_then(|w| w.typ_ns), + sample_min_ns: adc_sample.map(|s| s.min_ns), + pga_sample_ns: match (has_opa, adc_sample) { + (true, Some(sample)) => sample.pga_ns.clone(), + _ => BTreeMap::new(), + }, }); } diff --git a/mspm0-data-gen/src/main.rs b/mspm0-data-gen/src/main.rs index 6dae77d..b7375c8 100644 --- a/mspm0-data-gen/src/main.rs +++ b/mspm0-data-gen/src/main.rs @@ -1,4 +1,5 @@ mod adc_channels; +mod adc_sample; mod adc_wakeup; mod clock_tree; mod comp; @@ -13,11 +14,11 @@ mod perimap; mod sources; mod svd; mod sysconfig; +mod temp_sensor; mod timers; mod uart; mod util; mod verify; -mod temp_sensor; mod vref; mod wakeup; @@ -80,6 +81,7 @@ fn main() -> anyhow::Result<()> { stopwatch.section("Read interrupt group mappings"); let adc_channels = adc_channels::parse()?; + let adc_sample = adc_sample::parse()?; let adc_wakeup = adc_wakeup::parse()?; let int_groups = int_group::parse()?; let operating_modes = operating_modes::parse()?; @@ -99,6 +101,7 @@ fn main() -> anyhow::Result<()> { parts, headers, adc_channels, + adc_sample, adc_wakeup, sysconfig, svds, diff --git a/mspm0-data-gen/src/sources.rs b/mspm0-data-gen/src/sources.rs index 884c8a2..10d76ba 100644 --- a/mspm0-data-gen/src/sources.rs +++ b/mspm0-data-gen/src/sources.rs @@ -10,6 +10,7 @@ use mspm0_data_types::{TemperatureSensor, Vref, WakeTimes}; use crate::{ adc_channels::AdcChannels, + adc_sample::AdcSample, adc_wakeup::AdcWakeup, clock_tree::{ClockTreeFile, ClockTrees}, comp::CompTiming, @@ -30,6 +31,7 @@ pub struct Sources { pub parts: PartsFile, pub headers: Headers, pub adc_channels: BTreeMap<String, AdcChannels>, + pub adc_sample: BTreeMap<String, AdcSample>, pub adc_wakeup: BTreeMap<String, AdcWakeup>, pub sysconfig: Sysconfig, pub svds: Svds, @@ -54,6 +56,7 @@ pub struct FamilySources<'a> { pub header: &'a Header, pub sysconfig: &'a SysconfigFile, pub adc_channels: Option<&'a AdcChannels>, + pub adc_sample: Option<&'a AdcSample>, pub adc_wakeup: Option<AdcWakeup>, pub svd: Option<&'a Svd>, pub clock_tree: Option<&'a ClockTreeFile>, @@ -99,6 +102,7 @@ impl Sources { header, sysconfig, adc_channels: self.adc_channels.get(family), + adc_sample: self.adc_sample.get(family), adc_wakeup: self.adc_wakeup.get(family).copied(), svd: self.svds.files.get(family), clock_tree: self.clock_trees.files.get(family), diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 4c11226..26be421 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -15,6 +15,8 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { vref_startup_known, adc_channels_known, adc_wakeup_known, + adc_sample_window_known, + adc_pga_sample_matches_opa, adc_internal_sources_exist, uart_features_known, opa_inputs_known, @@ -190,6 +192,76 @@ fn adc_wakeup_known(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// Report an ADC instance with no minimum sample window. +/// +/// Every one of the sixteen datasheets states the row, under one name or the other, so a missing +/// figure means `data/adc_sample/<family>.yaml` is absent or the extraction failed. It is never a +/// device which can sample for as little as it likes. +fn adc_sample_window_known(chip: &Chip, name: &str) -> anyhow::Result<()> { + for peripheral in chip + .peripherals + .values() + .filter(|peripheral| peripheral.ty == PeripheralType::Adc) + { + let missing = peripheral + .adc + .as_ref() + .is_none_or(|adc| adc.sample_min_ns.is_none()); + if missing { + bail!( + "{name}: {} has no minimum ADC sample window; data/adc_sample/{}.yaml is missing", + peripheral.name, + chip.family + ); + } + } + + Ok(()) +} + +/// The PGA sample windows exist exactly where an OPA does. +/// +/// Both directions matter and both have been wrong in a source. Seven datasheets print the +/// `tSample_PGA` table while only four families have the amplifier, so carrying the map on a chip +/// with no OPA would repeat the ADC channel map's superset mistake. A chip which *has* an OPA and +/// no map is the other half: its OPA output channels then read as needing only the bare-pin window, +/// which is short by an order of magnitude at high gain. +fn adc_pga_sample_matches_opa(chip: &Chip, name: &str) -> anyhow::Result<()> { + let has_opa = chip + .peripherals + .values() + .any(|peripheral| peripheral.ty == PeripheralType::Opa); + + for peripheral in chip + .peripherals + .values() + .filter(|peripheral| peripheral.ty == PeripheralType::Adc) + { + let Some(adc) = peripheral.adc.as_ref() else { + continue; + }; + + if has_opa && adc.pga_sample_ns.is_empty() { + bail!( + "{name}: {} has no PGA sample windows but the chip has an OPA; \ + data/adc_sample/{}.yaml lost the tSample_PGA table", + peripheral.name, + chip.family + ); + } + + if !has_opa && !adc.pga_sample_ns.is_empty() { + bail!( + "{name}: {} carries PGA sample windows but the chip has no OPA; the datasheet row \ + is footnoted \"Only applies for devices with OPA\"", + peripheral.name, + ); + } + } + + Ok(()) +} + /// An internal channel naming a peripheral instance requires that instance to exist. /// /// This is the check that separates the datasheets from the SDK's family-superset tables, which diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 7280eeb..27d4c3c 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -1030,6 +1030,59 @@ pub struct Adc { /// figure is for and why the two are not merged. #[serde(default, skip_serializing_if = "Option::is_none")] pub wakeup_typ_ns: Option<u32>, + + /// The shortest sample window the datasheet supports, in nanoseconds, from the `tSample` row + /// (older documents) or `tSample_step` (newer), MIN column. + /// + /// `SAMPCLK` has no published ceiling; the window is what the datasheets bound. A `SCOMPx` + /// shorter than this converts an input which has not finished settling, and nothing reports it. + /// This is the bare-pin figure. An OPA output channel needs [`Adc::pga_sample_ns`] instead, and + /// where `CTL0.PWRDN` leaves automatic power down on, [`Adc::wakeup_max_ns`] is paid out of the + /// same window and adds to this. + /// + /// **The two row names are one quantity.** Only mspm0h321x prints a `Vstep`, and it is the only + /// 4.5โ€“5.5V part, so the older documents left the step implicit at the device's own full scale. + /// No datasheet prints both rows, so that reading is the best supported rather than proven. + /// Where a datasheet states the row twice the larger is recorded: mspm0h321x is 188ns at a 4V + /// step and 400ns at 5V, and its recommended supply reaches 5.5V, so only the 5V figure bounds + /// the part. + /// + /// Whole nanoseconds, rounded **up** โ€” 62.5ns on the G families becomes 63. Rounding a lower + /// bound down would hand out a window the datasheet does not support. + /// + /// Five values across eighteen families, and it follows neither the family nor the series: 63 + /// on the six G families, 156 on l110x/l122x/l130x/l134x/l222x, 166 on c110x and msps003fx, 188 + /// on c1105_c1106/g518x/l112x/l211x, 400 on h321x. `None` means the extraction is missing, not + /// that the device has no minimum โ€” every datasheet states one. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sample_min_ns: Option<u32>, + + /// The sample window an OPA output channel needs, in nanoseconds, keyed by PGA gain. + /// + /// From the datasheet's `tSample_PGA` row, MIN column. It is per *gain* and not per channel, + /// and at high gain it is an order of magnitude above [`Adc::sample_min_ns`]: 1500ns at x32 + /// against a 156ns bare pin on mspm0l130x. A driver programming the bare-pin figure for an OPA + /// channel is short by that much, with nothing to report it. + /// + /// **Every published figure is conditioned `CFGBASE.GBW = 1`, the high-bandwidth setting.** The + /// field resets to `0` and TI's `DL_OPA_init` leaves it there, so a caller which has not set it + /// is outside every published figure. No datasheet publishes a row for `GBW = 0`, and the low + /// setting is slower โ€” the OPA's own `tSETTLE` is 2.5ยตs typical against 1.3ยตs โ€” so at `GBW = 0` + /// these become an underestimate rather than a bound. What a given driver leaves the field at + /// is the driver's own fact, not the device's. + /// + /// **A gain absent from the map is unpublished, not derivable.** The G datasheets print six + /// gains (x1, x2, x4, x8, x16, x32) and the L datasheets print two, the endpoints only. The two + /// curves cross โ€” L is slower at x1 (310ns against 220ns) and faster at x32 (1500ns against + /// 2600ns) โ€” so neither can be interpolated onto the other, and nothing bounds the L families + /// between x1 and x32. + /// + /// Empty on every chip with no OPA. Seven datasheets print the table but only four families + /// have the amplifier: the row carries the footnote "Only applies for devices with OPA" because + /// one document covers several devices, so the map is attached only where an OPA instance + /// exists. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub pga_sample_ns: BTreeMap<u8, u32>, } /// An internal signal an ADC channel samples instead of a package pin. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index ad613ab..6b86076 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -680,6 +680,62 @@ pub struct Adc { /// fifteen families which state a ceiling instead โ€” see [`Adc::wakeup_max_ns`] for what this /// figure is for and why the two are not merged. pub wakeup_typ_ns: Option<u32>, + + /// The shortest sample window the datasheet supports, in nanoseconds, from the `tSample` row + /// (older documents) or `tSample_step` (newer), MIN column. + /// + /// `SAMPCLK` has no published ceiling; the window is what the datasheets bound. A `SCOMPx` + /// shorter than this converts an input which has not finished settling, and nothing reports it. + /// This is the bare-pin figure. An OPA output channel needs [`Adc::pga_sample_ns`] instead, and + /// where `CTL0.PWRDN` leaves automatic power down on, [`Adc::wakeup_max_ns`] is paid out of the + /// same window and adds to this. + /// + /// **The two row names are one quantity.** Only mspm0h321x prints a `Vstep`, and it is the only + /// 4.5-5.5V part, so the older documents left the step implicit at the device's own full scale. + /// Where a datasheet states the row twice the larger is recorded: mspm0h321x is 188ns at a 4V + /// step and 400ns at 5V, and its recommended supply reaches 5.5V, so only the 5V figure bounds + /// the part. + /// + /// Whole nanoseconds, rounded **up** -- 62.5ns on the G families becomes 63. Rounding a lower + /// bound down would hand out a window the datasheet does not support. + /// + /// Five values across eighteen families, and it follows neither the family nor the series. + /// `None` means the extraction is missing, not that the device has no minimum. + pub sample_min_ns: Option<u32>, + + /// The sample window an OPA output channel needs, in nanoseconds, keyed by PGA gain and sorted + /// by gain. + /// + /// From the datasheet's `tSample_PGA` row, MIN column. It is per *gain* and not per channel, + /// and at high gain it is an order of magnitude above [`Adc::sample_min_ns`]: 1500ns at x32 + /// against a 156ns bare pin on mspm0l130x. Programming the bare-pin figure for an OPA channel + /// is short by that much, with nothing to report it. + /// + /// **Every published figure is conditioned `CFGBASE.GBW = 1`, the high-bandwidth setting.** The + /// field resets to `0` and TI's `DL_OPA_init` leaves it there, so a caller which has not set it + /// is outside every published figure. No datasheet publishes a row for `GBW = 0`, and the low + /// setting is slower -- the OPA's own `tSETTLE` is 2.5us typical against 1.3us -- so at + /// `GBW = 0` these become an underestimate rather than a bound. What a given driver leaves the + /// field at is the driver's own fact, not the device's. + /// + /// **A gain absent from the slice is unpublished, not derivable.** The G datasheets print six + /// gains (x1, x2, x4, x8, x16, x32) and the L datasheets print two, the endpoints only. The two + /// curves cross -- L is slower at x1 (310ns against 220ns) and faster at x32 (1500ns against + /// 2600ns) -- so neither can be interpolated onto the other, and nothing bounds the L families + /// between x1 and x32. + /// + /// Empty on every chip with no OPA. + pub pga_sample_ns: &'static [AdcPgaSample], +} + +/// One PGA gain and the ADC sample window an OPA output channel needs at it. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct AdcPgaSample { + /// The OPA's programmed gain, as the datasheet writes it: 1, 2, 4, 8, 16 or 32. + pub gain: u8, + + /// The minimum sample window at that gain, in nanoseconds. + pub ns: u32, } /// One ADC channel and the internal signal it samples. diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index e531b99..2e46838 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -417,6 +417,12 @@ fn generate_peripheral( None => quote! { None }, }; let (wakeup_max_ns, wakeup_typ_ns) = (ns(adc.wakeup_max_ns), ns(adc.wakeup_typ_ns)); + let sample_min_ns = ns(adc.sample_min_ns); + let pga_sample_ns = adc.pga_sample_ns.iter().map(|(gain, sample_ns)| { + let gain = Literal::u8_unsuffixed(*gain); + let sample_ns = Literal::u32_unsuffixed(*sample_ns); + quote! { AdcPgaSample { gain: #gain, ns: #sample_ns } } + }); quote! { Some(Adc { memctl: #memctl, @@ -424,6 +430,8 @@ fn generate_peripheral( internal_channels: &[#(#internal_channels),*], wakeup_max_ns: #wakeup_max_ns, wakeup_typ_ns: #wakeup_typ_ns, + sample_min_ns: #sample_min_ns, + pga_sample_ns: &[#(#pga_sample_ns),*], }) } } diff --git a/tools/adc_sample.py b/tools/adc_sample.py new file mode 100644 index 0000000..3e5f133 --- /dev/null +++ b/tools/adc_sample.py @@ -0,0 +1,316 @@ +"""Extract the ADC's minimum sample window from MSPM0 datasheets. + +`SAMPCLK` has no published ceiling; what the datasheets bound is the *window*, and a window shorter +than this returns a conversion of an input that has not finished settling, with nothing to report it. +Nothing machine-readable carries the figure. This writes `data/adc_sample/<family>.yaml`. + +Two rows are read from the ADC's switching-characteristics table: + +`tSample` / `tSample_step`, the bare-pin minimum. Every figure in every datasheet sits in the MIN +column, which is what makes it safe to assert against rather than merely document. The two names are +one quantity: only mspm0h321x prints a `Vstep`, and it is the only 4.5-5.5V part, so the older +documents left the step implicit at the device's own full scale. The datasheets' own input-network +derivation (T = K x Tau) is a step-settling calculation and nothing else. No datasheet prints both +rows, so this is the best-supported reading rather than a proven one. + +`tSample_PGA`, the window when the channel is an OPA output. It is per *gain*, not per channel, and +the bare-pin figure is short by an order of magnitude at high gain -- 0.31us against 1.5us at x32 on +mspm0l130x. The gain axis is a different length on the two series: the G datasheets print six gains +and the L datasheets print two, the endpoints only. The curves cross -- L is slower at x1 and faster +at x32 -- so the G shape cannot be interpolated onto the L families, and an absent gain is absent +rather than derivable. + +**Every `tSample_PGA` row is conditioned `GBW = 0x1`**, which is `OA_CFGBASE_GBW_HIGHGAIN`. The reset +value is 0 and TI's `DL_OPA_init` leaves it there, so a caller which has not set it is outside every +published figure. No datasheet publishes a row for `GBW = 0`, and the low setting is slower, so the +figures become an underestimate rather than a bound there. + +Like `tools/adc_wakeup.py`, this is an offline aid rather than part of the build. Re-run it after a +data source bump rather than editing the YAML by hand. + +Usage: + uv run tools/adc_sample.py <datasheet.pdf> [...] # print what it finds + uv run tools/adc_sample.py --write <dir-of-datasheets> # regenerate data/adc_sample + +`uv run` installs the dependencies below on its own; a bare `python` needs them on the path. + +The table is not ruled, so a lattice read returns nothing and the column has to be decided +geometrically: the MIN/TYP/MAX header positions are found on the same page and each figure is +assigned to whichever header its centre is nearest. Layout text cannot answer this -- it collapses +the three columns into whitespace, which is how a MIN reads as a bare number with no column at all. + +Two rows are expected on mspm0h321x, one per `Vstep`. The **larger** is written: its recommended +supply reaches 5.5V, so the 4V figure is not a bound for the part. Both conditions are quoted in the +generated file. + +A datasheet with no `tSample_PGA` table is not a failure -- most families have no OPA. The seven that +print one include mspm0g110x, mspm0g310x and mspm0l110x, which have no OPA either: the row carries +the footnote "Only applies for devices with OPA" because one document covers several devices. The +generator attaches the map only to a chip that has an OPA instance, the same superset trap the ADC +channel map already guards against. +""" + +# /// script +# requires-python = ">=3.10" +# dependencies = ["pdfplumber", "pyyaml"] +# /// + +import math +import re +import sys +from pathlib import Path + +try: + import pdfplumber + import yaml +except ImportError as e: # pragma: no cover + raise SystemExit(f"{e.name} is missing; run this with `uv run` instead of `python`") + +#: Time units the figures may be given in, as a multiplier to nanoseconds. +UNITS = {"ns": 1, "us": 1_000, "ยตs": 1_000, "ms": 1_000_000} + +NUMBER = re.compile(r"\d+(?:\.\d+)?") + +#: The bare-pin row's description cell. `\bwith\b` excludes the qualified rows -- `with OPA`, +#: `with GPAMP`, `with DAC as input` -- while leaving `without OPA` and `for step input`. +BARE = re.compile(r"Sampl\w*\s+time(?!.*\bwith\b)", re.IGNORECASE) + +#: One sub-row of the `tSample_PGA` block. The gain is the key; the GBW value is captured so a +#: datasheet that ever prints `GBW = 0x0` is noticed rather than silently folded in. +PGA = re.compile(r"GBW\s*=\s*(0x[0-9A-Fa-f]+).*?PGA\s+gain\s*=\s*x(\d+)", re.IGNORECASE) + +#: Parameter-name and caption words, dropped so the quoted conditions read as conditions. `Vstep=4V` +#: survives because it is one token rather than the bare word `step`. +DROP = re.compile( + r"t?Sampl\w*|time|without|OPA|for|step|input|\(\d+\)|\d+(?:\.\d+)?", re.IGNORECASE +) + +#: Rows are grouped by rounding the word top to this many points. +ROW_TOLERANCE = 3 + + +def centre(word: dict) -> float: + return (word["x0"] + word["x1"]) / 2 + + +def rows_of(page) -> dict[int, list[dict]]: + """Words grouped into visual rows, keyed by rounded vertical position.""" + grouped: dict[int, list[dict]] = {} + for word in page.extract_words(): + grouped.setdefault(round(word["top"] / ROW_TOLERANCE), []).append(word) + return grouped + + +def figure_in(words: list[dict], headers: dict[str, float]) -> tuple[str, float] | None: + """The rightmost numeric cell of a row, as `(column, value)`. The unit is the caller's.""" + lo = min(headers.values()) - 20 + figures = [w for w in words if NUMBER.fullmatch(w["text"]) and centre(w) > lo] + if not figures: + return None + figure = figures[-1] + column = min(headers, key=lambda c: abs(headers[c] - centre(figure))) + return column, float(figure["text"]) + + +def nanoseconds(value: float, unit: str) -> int: + """Convert to whole nanoseconds, rounding **up**. + + Every figure here is a lower bound on a sample window, so rounding a fraction down would hand a + consumer a window the datasheet does not support. Only 62.5ns on the G families is not already + whole. + """ + return math.ceil(value * UNITS[unit.lower()]) + + +def unit_of(words: list[dict]) -> str: + return next((w["text"] for w in reversed(words) if w["text"].lower() in UNITS), "") + + +def read(path: Path) -> tuple[list[tuple[str, int, str]], dict[int, tuple[str, int, str]]]: + """Return the bare-pin rows and the per-gain PGA rows found in one datasheet. + + Bare rows are `(column, nanoseconds, conditions, stepped)`, where `stepped` is whether the row + is TI's newer `tSample_step` -- told by its description cell, since only mspm0h321x's conditions + mention a step. PGA rows are keyed by gain and carry `(column, nanoseconds, gbw)`. + """ + bare: list[tuple[str, int, str, bool]] = [] + pga: dict[int, tuple[str, int, str]] = {} + + with pdfplumber.open(str(path)) as pdf: + for page in pdf.pages: + text = page.extract_text() or "" + if "Sampling time" not in text and "PGA gain" not in text: + continue + + grouped = rows_of(page) + headers: dict[str, float] = {} + # The unit cell sits on the `tSample_PGA` parameter row, which the G datasheets print + # *below* its gain sub-rows, so the page's rows are resolved together at the end. + page_unit = "" + raw: list[tuple[int, str, float, str]] = [] + + for key in sorted(grouped): + words = sorted(grouped[key], key=lambda w: w["x0"]) + line = " ".join(w["text"] for w in words) + + if "MIN" in line and "MAX" in line: + headers = { + w["text"]: centre(w) for w in words if w["text"] in ("MIN", "TYP", "MAX") + } + if not headers: + continue + + page_unit = unit_of(words) or page_unit + + gain = PGA.search(line) + if gain: + found = figure_in(words, headers) + if found: + raw.append((int(gain.group(2)), found[0], found[1], unit_of(words))) + continue + + if not BARE.search(line): + continue + unit = unit_of(words) + found = figure_in(words, headers) if unit else None + if not found: + continue + column, ns = found[0], nanoseconds(found[1], unit) + conditions = " ".join( + w["text"] for w in words if w["text"] != unit and not DROP.fullmatch(w["text"]) + ) + stepped = "step input" in line.lower() + bare.append((column, ns, " ".join(conditions.split()), stepped)) + + for gain, column, value, own_unit in raw: + unit = own_unit or page_unit + if unit: + pga[gain] = (column, nanoseconds(value, unit), "0x1") + + return bare, pga + + +HEADER = """\ +# The minimum ADC sample window for this family, in nanoseconds. +# +# GENERATED by `tools/adc_sample.py --write` from the {ds} datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# min_ns is the `{row}` row, MIN column: the shortest window that samples a full-scale step to +# within the converter's resolution. A shorter window returns a conversion of an input that has not +# settled, and nothing reports it. +{conditions}""" + +SINGLE_CONDITION = """\ +# +# Stated under one condition, quoted here: +# +# {conditions} +""" + +PGA_HEADER = """ +# pga_ns is the `tSample_PGA` row: the window when the channel is an OPA output, keyed by PGA gain. +# It is per gain and not per channel, and at high gain it is an order of magnitude above min_ns. +# +# Every published figure is conditioned GBW = 0x1 (OA_CFGBASE_GBW_HIGHGAIN). The reset value is 0 +# and TI's DL_OPA_init leaves it there, so a caller which has not set it is outside every published +# figure. No datasheet publishes a row for GBW = 0, and the low setting is slower, so the figures +# become an underestimate rather than a bound there. +# +# A gain absent here is unpublished, not derivable: the G datasheets print six gains and the L +# datasheets two, and the curves cross, so neither can be interpolated onto the other. +""" + +VSTEP_NOTE = """\ +# +# This datasheet states the row twice, once per input step: +# +{rows} +# +# The larger is written. The recommended supply reaches 5.5V, so the smaller figure is not a bound +# for the part. +""" + + +def write(datasheets: str) -> int: + """Regenerate data/adc_sample/*.yaml. Returns the number of problems reported.""" + parts = yaml.safe_load(Path("data/parts.yaml").read_text(encoding="utf-8")) + families = [(f["family"], f["datasheet_url"].rsplit("/", 1)[-1]) for f in parts["families"]] + + Path("data/adc_sample").mkdir(parents=True, exist_ok=True) + problems = 0 + + for family, gpn in families: + pdf = Path(datasheets) / f"{gpn}_datasheet.pdf" + if not pdf.exists(): + print(f"{family}: no datasheet for {gpn} in {datasheets}") + problems += 1 + continue + + bare, pga = read(pdf) + if not bare: + print(f"{family}: no ADC sampling-time row in {pdf.name}") + problems += 1 + continue + + off_column = [c for c, _, _, _ in bare if c != "MIN"] + if off_column: + print(f"{family}: sampling time is a {off_column[0]} in {pdf.name}, not a MIN") + problems += 1 + continue + + wrong_gbw = sorted({g for _, _, g in pga.values() if g != "0x1"}) + if wrong_gbw: + print(f"{family}: tSample_PGA rows at GBW {', '.join(wrong_gbw)} in {pdf.name}") + problems += 1 + continue + + _, ns, conditions, _ = max(bare, key=lambda r: r[1]) + row = "tSample_step" if any(stepped for _, _, _, stepped in bare) else "tSample" + # Where the row is stated twice the Vstep note quotes both, so a second block saying "one + # condition" would contradict it. + if len(bare) > 1: + quoted = "\n".join(f"# {c or 'none stated'}: {n} ns" for _, n, c, _ in bare) + conditions = VSTEP_NOTE.format(rows=quoted) + else: + conditions = SINGLE_CONDITION.format(conditions=conditions or "none stated") + + path = Path(f"data/adc_sample/{family}.yaml") + with open(path, "w", encoding="utf-8", newline="\n") as out: + out.write(HEADER.format(ds=gpn.upper(), row=row, conditions=conditions)) + out.write(f"\nmin_ns: {ns}\n") + if pga: + out.write(PGA_HEADER) + out.write("\npga_ns:\n") + for gain in sorted(pga): + out.write(f" {gain}: {pga[gain][1]}\n") + + gains = f", pga x{'/x'.join(str(g) for g in sorted(pga))}" if pga else "" + print(f"{family}: {ns} ns ({row}){gains}, from {pdf.name}") + + return problems + + +def main(argv: list[str]) -> None: + if "--write" in argv: + i = argv.index("--write") + raise SystemExit(1 if write(argv[i + 1] if i + 1 < len(argv) else ".") else 0) + + paths = [Path(a) for a in argv if not a.startswith("--")] + if not paths: + raise SystemExit(__doc__) + + for path in paths: + bare, pga = read(path) + print(f"########## {path.name}") + if not bare: + print(" no ADC sampling-time row") + for column, ns, conditions, _ in bare: + print(f" {ns:>8} ns {column} {conditions}") + for gain in sorted(pga): + column, ns, gbw = pga[gain] + print(f" {ns:>8} ns {column} PGA gain x{gain}, GBW {gbw}") + + +if __name__ == "__main__": + main(sys.argv[1:]) From d665320b0ea8cc2528e04a8d7903591ea7ef44e6 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 24 Aug 2026 11:33:06 +0100 Subject: [PATCH 118/134] adc: record the sample window each internal channel needs An internal signal needs far longer than a package pin: 10us for the internal reference against a 156ns pin on mspm0l211x. A window sized for the pin samples a signal that has not settled, and nothing reports it. Keyed by signal in the YAML because that is how the datasheets state it, and attached per channel because the signal reaches a different channel number on different families and two channels on some devices. The temperature sensor and the OPA outputs are deliberately absent, so their None means look elsewhere rather than unpublished. The sensor states two figures which differ and are independently absent, so no scalar can carry it; the OPA window is keyed by PGA gain. The tSample_VREF row states the channel it was measured on, and generation now fails if that disagrees with the channel mapping table. One page of the datasheet against another, and the only independent check available on a channel map. --- .../adc_internal_sample/mspm0c1105_c1106.yaml | 28 ++ data/adc_internal_sample/mspm0c110x.yaml | 21 ++ data/adc_internal_sample/mspm0g110x.yaml | 25 ++ data/adc_internal_sample/mspm0g150x.yaml | 25 ++ data/adc_internal_sample/mspm0g151x.yaml | 23 ++ data/adc_internal_sample/mspm0g310x.yaml | 25 ++ data/adc_internal_sample/mspm0g350x.yaml | 25 ++ data/adc_internal_sample/mspm0g351x.yaml | 23 ++ data/adc_internal_sample/mspm0g518x.yaml | 30 ++ data/adc_internal_sample/mspm0h321x.yaml | 28 ++ data/adc_internal_sample/mspm0l110x.yaml | 23 ++ data/adc_internal_sample/mspm0l112x.yaml | 28 ++ data/adc_internal_sample/mspm0l122x.yaml | 30 ++ data/adc_internal_sample/mspm0l130x.yaml | 23 ++ data/adc_internal_sample/mspm0l134x.yaml | 23 ++ data/adc_internal_sample/mspm0l211x.yaml | 28 ++ data/adc_internal_sample/mspm0l222x.yaml | 30 ++ data/adc_internal_sample/msps003fx.yaml | 21 ++ mspm0-data-gen/src/adc_internal_sample.rs | 47 +++ mspm0-data-gen/src/generate.rs | 49 ++- mspm0-data-gen/src/main.rs | 3 + mspm0-data-gen/src/sources.rs | 4 + mspm0-data-gen/src/verify.rs | 4 +- mspm0-data-types/src/lib.rs | 34 +- mspm0-metapac-gen/res/metadata.rs | 24 ++ mspm0-metapac-gen/src/metadata.rs | 17 +- tools/adc_internal_sample.py | 293 ++++++++++++++++++ 27 files changed, 921 insertions(+), 13 deletions(-) create mode 100644 data/adc_internal_sample/mspm0c1105_c1106.yaml create mode 100644 data/adc_internal_sample/mspm0c110x.yaml create mode 100644 data/adc_internal_sample/mspm0g110x.yaml create mode 100644 data/adc_internal_sample/mspm0g150x.yaml create mode 100644 data/adc_internal_sample/mspm0g151x.yaml create mode 100644 data/adc_internal_sample/mspm0g310x.yaml create mode 100644 data/adc_internal_sample/mspm0g350x.yaml create mode 100644 data/adc_internal_sample/mspm0g351x.yaml create mode 100644 data/adc_internal_sample/mspm0g518x.yaml create mode 100644 data/adc_internal_sample/mspm0h321x.yaml create mode 100644 data/adc_internal_sample/mspm0l110x.yaml create mode 100644 data/adc_internal_sample/mspm0l112x.yaml create mode 100644 data/adc_internal_sample/mspm0l122x.yaml create mode 100644 data/adc_internal_sample/mspm0l130x.yaml create mode 100644 data/adc_internal_sample/mspm0l134x.yaml create mode 100644 data/adc_internal_sample/mspm0l211x.yaml create mode 100644 data/adc_internal_sample/mspm0l222x.yaml create mode 100644 data/adc_internal_sample/msps003fx.yaml create mode 100644 mspm0-data-gen/src/adc_internal_sample.rs create mode 100644 tools/adc_internal_sample.py diff --git a/data/adc_internal_sample/mspm0c1105_c1106.yaml b/data/adc_internal_sample/mspm0c1105_c1106.yaml new file mode 100644 index 0000000..e157b78 --- /dev/null +++ b/data/adc_internal_sample/mspm0c1105_c1106.yaml @@ -0,0 +1,28 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0C1106 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +# The channel the tSample_VREF condition names. Cross-checked against the +# channel data/adc_channels/ routes Vref to -- one page of the datasheet +# corroborating another, which is the only independent check on a channel map. +vref_channel: 29 + +sample_min_ns: + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 5000 + # 12-bit mode, VDD as reference -- the internal reference cannot measure itself + Vref: 10000 diff --git a/data/adc_internal_sample/mspm0c110x.yaml b/data/adc_internal_sample/mspm0c110x.yaml new file mode 100644 index 0000000..8cd5b3b --- /dev/null +++ b/data/adc_internal_sample/mspm0c110x.yaml @@ -0,0 +1,21 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0C1104 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +sample_min_ns: + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 3000 diff --git a/data/adc_internal_sample/mspm0g110x.yaml b/data/adc_internal_sample/mspm0g110x.yaml new file mode 100644 index 0000000..4b6f7f5 --- /dev/null +++ b/data/adc_internal_sample/mspm0g110x.yaml @@ -0,0 +1,25 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0G1107 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +sample_min_ns: + # 12-bit mode + Dac0: 500 + # 12-bit mode + Gpamp: 3000 + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 5000 diff --git a/data/adc_internal_sample/mspm0g150x.yaml b/data/adc_internal_sample/mspm0g150x.yaml new file mode 100644 index 0000000..8dedb6e --- /dev/null +++ b/data/adc_internal_sample/mspm0g150x.yaml @@ -0,0 +1,25 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0G1507 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +sample_min_ns: + # 12-bit mode + Dac0: 500 + # 12-bit mode + Gpamp: 3000 + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 5000 diff --git a/data/adc_internal_sample/mspm0g151x.yaml b/data/adc_internal_sample/mspm0g151x.yaml new file mode 100644 index 0000000..e1f9148 --- /dev/null +++ b/data/adc_internal_sample/mspm0g151x.yaml @@ -0,0 +1,23 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0G1519 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +sample_min_ns: + # 12-bit mode + Dac0: 500 + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 5000 diff --git a/data/adc_internal_sample/mspm0g310x.yaml b/data/adc_internal_sample/mspm0g310x.yaml new file mode 100644 index 0000000..e1abdd5 --- /dev/null +++ b/data/adc_internal_sample/mspm0g310x.yaml @@ -0,0 +1,25 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0G3107 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +sample_min_ns: + # 12-bit mode + Dac0: 500 + # 12-bit mode + Gpamp: 3000 + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 5000 diff --git a/data/adc_internal_sample/mspm0g350x.yaml b/data/adc_internal_sample/mspm0g350x.yaml new file mode 100644 index 0000000..45ac9d2 --- /dev/null +++ b/data/adc_internal_sample/mspm0g350x.yaml @@ -0,0 +1,25 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0G3507 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +sample_min_ns: + # 12-bit mode + Dac0: 500 + # 12-bit mode + Gpamp: 3000 + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 5000 diff --git a/data/adc_internal_sample/mspm0g351x.yaml b/data/adc_internal_sample/mspm0g351x.yaml new file mode 100644 index 0000000..ed1c2b4 --- /dev/null +++ b/data/adc_internal_sample/mspm0g351x.yaml @@ -0,0 +1,23 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0G3519 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +sample_min_ns: + # 12-bit mode + Dac0: 500 + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 5000 diff --git a/data/adc_internal_sample/mspm0g518x.yaml b/data/adc_internal_sample/mspm0g518x.yaml new file mode 100644 index 0000000..24d0c91 --- /dev/null +++ b/data/adc_internal_sample/mspm0g518x.yaml @@ -0,0 +1,30 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0G5187 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +# The channel the tSample_VREF condition names. Cross-checked against the +# channel data/adc_channels/ routes Vref to -- one page of the datasheet +# corroborating another, which is the only independent check on a channel map. +vref_channel: 28 + +sample_min_ns: + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 5000 + # 12-bit mode, VDD as reference -- the internal reference cannot measure itself + Vref: 4000 + # 12-bit mode, internal reference (VRSEL=1h or 2h) + VusbMonitor: 5000 diff --git a/data/adc_internal_sample/mspm0h321x.yaml b/data/adc_internal_sample/mspm0h321x.yaml new file mode 100644 index 0000000..5fd79ba --- /dev/null +++ b/data/adc_internal_sample/mspm0h321x.yaml @@ -0,0 +1,28 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0H3216 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +# The channel the tSample_VREF condition names. Cross-checked against the +# channel data/adc_channels/ routes Vref to -- one page of the datasheet +# corroborating another, which is the only independent check on a channel map. +vref_channel: 29 + +sample_min_ns: + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 3000 + # 12-bit mode, VDD as reference -- the internal reference cannot measure itself + Vref: 10000 diff --git a/data/adc_internal_sample/mspm0l110x.yaml b/data/adc_internal_sample/mspm0l110x.yaml new file mode 100644 index 0000000..91b17a3 --- /dev/null +++ b/data/adc_internal_sample/mspm0l110x.yaml @@ -0,0 +1,23 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0L1106 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +sample_min_ns: + # 12-bit mode + Gpamp: 2500 + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 3000 diff --git a/data/adc_internal_sample/mspm0l112x.yaml b/data/adc_internal_sample/mspm0l112x.yaml new file mode 100644 index 0000000..8d67b33 --- /dev/null +++ b/data/adc_internal_sample/mspm0l112x.yaml @@ -0,0 +1,28 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0L2117 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +# The channel the tSample_VREF condition names. Cross-checked against the +# channel data/adc_channels/ routes Vref to -- one page of the datasheet +# corroborating another, which is the only independent check on a channel map. +vref_channel: 29 + +sample_min_ns: + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 5000 + # 12-bit mode, VDD as reference -- the internal reference cannot measure itself + Vref: 10000 diff --git a/data/adc_internal_sample/mspm0l122x.yaml b/data/adc_internal_sample/mspm0l122x.yaml new file mode 100644 index 0000000..8ffd484 --- /dev/null +++ b/data/adc_internal_sample/mspm0l122x.yaml @@ -0,0 +1,30 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0L1228 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +# The channel the tSample_VREF condition names. Cross-checked against the +# channel data/adc_channels/ routes Vref to -- one page of the datasheet +# corroborating another, which is the only independent check on a channel map. +vref_channel: 28 + +sample_min_ns: + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 5000 + # 12-bit mode; the VBAT divider, not the VDD one the footnote describes + VbatMonitor: 5000 + # 12-bit mode, VDD as reference -- the internal reference cannot measure itself + Vref: 4000 diff --git a/data/adc_internal_sample/mspm0l130x.yaml b/data/adc_internal_sample/mspm0l130x.yaml new file mode 100644 index 0000000..fe6ffc3 --- /dev/null +++ b/data/adc_internal_sample/mspm0l130x.yaml @@ -0,0 +1,23 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0L1306 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +sample_min_ns: + # 12-bit mode + Gpamp: 2500 + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 3000 diff --git a/data/adc_internal_sample/mspm0l134x.yaml b/data/adc_internal_sample/mspm0l134x.yaml new file mode 100644 index 0000000..dd04d90 --- /dev/null +++ b/data/adc_internal_sample/mspm0l134x.yaml @@ -0,0 +1,23 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0L1346 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +sample_min_ns: + # 12-bit mode + Gpamp: 2500 + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 3000 diff --git a/data/adc_internal_sample/mspm0l211x.yaml b/data/adc_internal_sample/mspm0l211x.yaml new file mode 100644 index 0000000..8d67b33 --- /dev/null +++ b/data/adc_internal_sample/mspm0l211x.yaml @@ -0,0 +1,28 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0L2117 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +# The channel the tSample_VREF condition names. Cross-checked against the +# channel data/adc_channels/ routes Vref to -- one page of the datasheet +# corroborating another, which is the only independent check on a channel map. +vref_channel: 29 + +sample_min_ns: + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 5000 + # 12-bit mode, VDD as reference -- the internal reference cannot measure itself + Vref: 10000 diff --git a/data/adc_internal_sample/mspm0l222x.yaml b/data/adc_internal_sample/mspm0l222x.yaml new file mode 100644 index 0000000..da1c8f7 --- /dev/null +++ b/data/adc_internal_sample/mspm0l222x.yaml @@ -0,0 +1,30 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0L2228 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +# The channel the tSample_VREF condition names. Cross-checked against the +# channel data/adc_channels/ routes Vref to -- one page of the datasheet +# corroborating another, which is the only independent check on a channel map. +vref_channel: 28 + +sample_min_ns: + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 5000 + # 12-bit mode; the VBAT divider, not the VDD one the footnote describes + VbatMonitor: 5000 + # 12-bit mode, VDD as reference -- the internal reference cannot measure itself + Vref: 4000 diff --git a/data/adc_internal_sample/msps003fx.yaml b/data/adc_internal_sample/msps003fx.yaml new file mode 100644 index 0000000..8cd5b3b --- /dev/null +++ b/data/adc_internal_sample/msps003fx.yaml @@ -0,0 +1,21 @@ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the MSPM0C1104 datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. + +sample_min_ns: + # 12-bit mode; internal reference (VRSEL=1h or 2h) where stated + SupplyMonitor: 3000 diff --git a/mspm0-data-gen/src/adc_internal_sample.rs b/mspm0-data-gen/src/adc_internal_sample.rs new file mode 100644 index 0000000..9c8aaf8 --- /dev/null +++ b/mspm0-data-gen/src/adc_internal_sample.rs @@ -0,0 +1,47 @@ +//! Reads `data/adc_internal_sample/<family>.yaml`, which `tools/adc_internal_sample.py` extracts +//! from the datasheets. +//! +//! The ADC's switching-characteristics table states a separate `tSample_<signal>` row for most of +//! the signals routed to an internal channel, and those minimums are far above the bare-pin +//! `tSample` in `adc_sample.rs` โ€” 10ยตs for the internal reference against a 156ns pin on +//! mspm0l211x. Nothing machine-readable carries any of it. +//! +//! **Keyed by signal, because that is how the datasheets state it**: one row per signal per family, +//! with the signal reaching different channel numbers on different families and sometimes two +//! channels on one device. `apply_adc` copies each figure onto every channel routing that signal, +//! which is where a consumer looks for it. +//! +//! Two signals are deliberately absent. The temperature sensor states *two* figures which differ +//! and are independently absent (`data/temp_sensor/`), so no single number here could carry it. The +//! OPA outputs are keyed by PGA gain rather than being scalar, and live in `Adc::pga_sample_ns`. + +use std::collections::BTreeMap; + +use mspm0_data_types::AdcInternalSource; +use serde::Deserialize; + +use crate::util; + +/// One family's per-signal ADC sample minimums, as the datasheet states them. +#[derive(Debug, Clone, Deserialize)] +pub struct AdcInternalSample { + /// The MIN column of each `tSample_<signal>` row, in nanoseconds, keyed by the signal the row + /// names. A signal absent here has no such row in this family's datasheet. + #[serde(default)] + pub sample_min_ns: BTreeMap<AdcInternalSource, u32>, + + /// The ADC channel named inside the `tSample_VREF` row's test condition. + /// + /// Not used to build anything โ€” `verify.rs` compares it against the channel + /// `data/adc_channels/` routes [`AdcInternalSource::Vref`] to. It is one page of the datasheet + /// corroborating another, and the only independent check available on a channel map. + /// + /// `None` where the family's datasheet has no `tSample_VREF` row. + #[serde(default)] + pub vref_channel: Option<u8>, +} + +/// Read every `data/adc_internal_sample/<family>.yaml`, keyed by family name. +pub fn parse() -> anyhow::Result<BTreeMap<String, AdcInternalSample>> { + util::per_family("adc_internal_sample") +} diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 9e020c6..67867fc 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -8,15 +8,16 @@ use std::{ use anyhow::{anyhow, bail, ensure, Context}; use mspm0_data_types::{ - Adc, Chip, Comp, Dma, DmaChannel, Flashctl, Interrupt, IoStructure, Memory, MemoryKind, - Package, PackagePin, Peripheral, PeripheralInterrupt, PeripheralPin, PeripheralType, - PowerDomain, PowerMode, Sysctl, Timer, Uart, Unicomm, Vref, + Adc, AdcInternalChannel, AdcInternalSource, Chip, Comp, Dma, DmaChannel, Flashctl, Interrupt, + IoStructure, Memory, MemoryKind, Package, PackagePin, Peripheral, PeripheralInterrupt, + PeripheralPin, PeripheralType, PowerDomain, PowerMode, Sysctl, Timer, Uart, Unicomm, Vref, }; use regex::Regex; use crate::comp::CompTiming; use crate::{ adc_channels::AdcChannels, + adc_internal_sample::AdcInternalSample, adc_sample::AdcSample, adc_wakeup::AdcWakeup, header::Header, @@ -51,6 +52,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu header, sysconfig, adc_channels, + adc_internal_sample, adc_sample, adc_wakeup, svd, @@ -91,6 +93,7 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu family, sysconfig, adc_channels, + adc_internal_sample, adc_sample, adc_wakeup, &mut peripherals, @@ -1393,6 +1396,7 @@ fn apply_adc( family: &PartFamily, sysconfig: &SysconfigFile, adc_channels: Option<&AdcChannels>, + adc_internal_sample: Option<&AdcInternalSample>, adc_sample: Option<&AdcSample>, adc_wakeup: Option<AdcWakeup>, peripherals: &mut BTreeMap<String, Peripheral>, @@ -1438,11 +1442,46 @@ fn apply_adc( .get(name) .context(format!("{chip_name}: {name} has no MEMCTL count"))?; - // Absent data is a gap verify.rs reports, like the other datasheet extractions. + // Absent data is a gap verify.rs reports, like the other datasheet extractions. The + // per-signal sample minimums are joined on here: the datasheets state one row per signal, + // so every channel routing that signal on this device carries the same figure, which is + // what a dual-ADC part reaching one signal from both ADCs needs. let internal_channels = adc_channels .and_then(|family| family.get(name)) .cloned() - .unwrap_or_default(); + .unwrap_or_default() + .into_iter() + .map(|(channel, source)| { + let sample_min_ns = adc_internal_sample + .and_then(|family| family.sample_min_ns.get(&source)) + .copied(); + + ( + channel, + AdcInternalChannel { + source, + sample_min_ns, + }, + ) + }) + .collect::<BTreeMap<_, _>>(); + + // One page of the datasheet against another: the `tSample_VREF` row states the channel it + // was measured on, and it has to be the channel the ADC Channel Mapping table routes VREF + // to. It is the only independent check available on a channel map, so a disagreement fails + // generation rather than being reported and passed over. + if let Some(stated) = adc_internal_sample.and_then(|family| family.vref_channel) { + let mapped = internal_channels + .iter() + .find(|(_, route)| route.source == AdcInternalSource::Vref) + .map(|(channel, _)| *channel); + + ensure!( + mapped == Some(stated), + "{chip_name}: {name}'s tSample_VREF row is measured on channel {stated}, but the \ + channel mapping table routes VREF to {mapped:?}" + ); + } peripheral.adc = Some(Adc { memctl, diff --git a/mspm0-data-gen/src/main.rs b/mspm0-data-gen/src/main.rs index b7375c8..edc2534 100644 --- a/mspm0-data-gen/src/main.rs +++ b/mspm0-data-gen/src/main.rs @@ -1,4 +1,5 @@ mod adc_channels; +mod adc_internal_sample; mod adc_sample; mod adc_wakeup; mod clock_tree; @@ -81,6 +82,7 @@ fn main() -> anyhow::Result<()> { stopwatch.section("Read interrupt group mappings"); let adc_channels = adc_channels::parse()?; + let adc_internal_sample = adc_internal_sample::parse()?; let adc_sample = adc_sample::parse()?; let adc_wakeup = adc_wakeup::parse()?; let int_groups = int_group::parse()?; @@ -101,6 +103,7 @@ fn main() -> anyhow::Result<()> { parts, headers, adc_channels, + adc_internal_sample, adc_sample, adc_wakeup, sysconfig, diff --git a/mspm0-data-gen/src/sources.rs b/mspm0-data-gen/src/sources.rs index 10d76ba..d1d92d9 100644 --- a/mspm0-data-gen/src/sources.rs +++ b/mspm0-data-gen/src/sources.rs @@ -10,6 +10,7 @@ use mspm0_data_types::{TemperatureSensor, Vref, WakeTimes}; use crate::{ adc_channels::AdcChannels, + adc_internal_sample::AdcInternalSample, adc_sample::AdcSample, adc_wakeup::AdcWakeup, clock_tree::{ClockTreeFile, ClockTrees}, @@ -31,6 +32,7 @@ pub struct Sources { pub parts: PartsFile, pub headers: Headers, pub adc_channels: BTreeMap<String, AdcChannels>, + pub adc_internal_sample: BTreeMap<String, AdcInternalSample>, pub adc_sample: BTreeMap<String, AdcSample>, pub adc_wakeup: BTreeMap<String, AdcWakeup>, pub sysconfig: Sysconfig, @@ -56,6 +58,7 @@ pub struct FamilySources<'a> { pub header: &'a Header, pub sysconfig: &'a SysconfigFile, pub adc_channels: Option<&'a AdcChannels>, + pub adc_internal_sample: Option<&'a AdcInternalSample>, pub adc_sample: Option<&'a AdcSample>, pub adc_wakeup: Option<AdcWakeup>, pub svd: Option<&'a Svd>, @@ -102,6 +105,7 @@ impl Sources { header, sysconfig, adc_channels: self.adc_channels.get(family), + adc_internal_sample: self.adc_internal_sample.get(family), adc_sample: self.adc_sample.get(family), adc_wakeup: self.adc_wakeup.get(family).copied(), svd: self.svds.files.get(family), diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 26be421..a288d78 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -276,8 +276,8 @@ fn adc_internal_sources_exist(chip: &Chip, name: &str) -> anyhow::Result<()> { continue; }; - for (channel, source) in &adc.internal_channels { - let instance = match source { + for (channel, route) in &adc.internal_channels { + let instance = match route.source { AdcInternalSource::Opa0 => "OPA0", AdcInternalSource::Opa1 => "OPA1", AdcInternalSource::Gpamp => "GPAMP", diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index 27d4c3c..da02910 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -998,7 +998,7 @@ pub struct Adc { /// /// Empty when the family's datasheet has no such table, which none so far lacks. #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] - pub internal_channels: BTreeMap<u8, AdcInternalSource>, + pub internal_channels: BTreeMap<u8, AdcInternalChannel>, /// Guaranteed ceiling on the time the ADC needs to wake from automatic power down, in /// nanoseconds, from the datasheet's `Twakeup` MAX column. @@ -1085,6 +1085,38 @@ pub struct Adc { pub pga_sample_ns: BTreeMap<u8, u32>, } +/// One ADC channel's route to an internal signal, and what that route costs to sample. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct AdcInternalChannel { + /// The signal this channel samples. + pub source: AdcInternalSource, + + /// The shortest sample window this route supports, in nanoseconds, from the datasheet's + /// `tSample_<signal>` row, MIN column. + /// + /// These are far above [`Adc::sample_min_ns`], which bounds a package pin: 10000 for the + /// internal reference against a 156ns pin on mspm0l211x. A window sized for the pin samples an + /// internal signal which has not settled, and nothing reports it. + /// + /// The datasheets state one row per signal per family, so every channel routing a given signal + /// on a given device carries the same figure โ€” a dual-ADC part reaching one signal from both + /// gets it on both routes. It is stored per channel because that is where a consumer looks. + /// + /// **The condition differs per signal and is load-bearing.** The internal reference is measured + /// with VDD as the ADC reference, because it cannot be measured against itself: sampling that + /// channel with `VRSEL` on the internal reference is outside the published figure entirely. The + /// supply and USB monitors are the other way round, measured against the internal reference. + /// + /// **`None` on the temperature-sensor route means "look elsewhere", not "unpublished".** That + /// signal states two figures rather than one โ€” [`TemperatureSensor::settling_ns`] and + /// [`TemperatureSensor::calibration_sample_ns`], which differ on the older L datasheets and are + /// independently absent โ€” so no single number here could carry it. Everywhere else `None` is + /// the ordinary meaning: the datasheet publishes no row for that signal. The OPA outputs are + /// also `None` here; their window is per PGA gain, in [`Adc::pga_sample_ns`]. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sample_min_ns: Option<u32>, +} + /// An internal signal an ADC channel samples instead of a package pin. /// /// Variants exist for the signals the current datasheets route; a new family may add more. diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 6b86076..11cbb8f 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -743,6 +743,30 @@ pub struct AdcPgaSample { pub struct AdcInternalChannel { pub channel: u8, pub source: AdcInternalSource, + + /// The shortest sample window this route supports, in nanoseconds, from the datasheet's + /// `tSample_<signal>` row, MIN column. + /// + /// These are far above [`Adc::sample_min_ns`], which bounds a package pin: 10000 for the + /// internal reference against a 156ns pin on mspm0l211x. A window sized for the pin samples an + /// internal signal which has not settled, and nothing reports it. + /// + /// The datasheets state one row per signal per family, so every channel routing a given signal + /// on a device carries the same figure -- a dual-ADC part reaching one signal from both ADCs + /// gets it on both routes. + /// + /// **The condition differs per signal and is load-bearing.** The internal reference is measured + /// with VDD as the ADC reference, because it cannot be measured against itself: sampling that + /// channel with `VRSEL` on the internal reference is outside the published figure entirely. The + /// supply and USB monitors are the other way round, measured against the internal reference. + /// + /// **`None` on the temperature-sensor route means "look elsewhere", not "unpublished".** That + /// signal states two figures rather than one -- [`TemperatureSensor::settling_ns`] and + /// [`TemperatureSensor::calibration_sample_ns`], which differ on the older L datasheets and are + /// independently absent -- so no single number here could carry it. The OPA outputs are also + /// `None`; their window is per PGA gain, in [`Adc::pga_sample_ns`]. Everywhere else `None` is + /// the ordinary meaning: the datasheet publishes no row for that signal. + pub sample_min_ns: Option<u32>, } /// An internal signal an ADC channel samples instead of a package pin. diff --git a/mspm0-metapac-gen/src/metadata.rs b/mspm0-metapac-gen/src/metadata.rs index 2e46838..4e8a499 100644 --- a/mspm0-metapac-gen/src/metadata.rs +++ b/mspm0-metapac-gen/src/metadata.rs @@ -404,11 +404,6 @@ fn generate_peripheral( Some(adc) => { let memctl = Literal::u8_unsuffixed(adc.memctl); let vrsel = Literal::u8_unsuffixed(adc.vrsel); - let internal_channels = adc.internal_channels.iter().map(|(channel, source)| { - let channel = Literal::u8_unsuffixed(*channel); - let source = adc_internal_source(*source); - quote! { AdcInternalChannel { channel: #channel, source: #source } } - }); let ns = |value: Option<u32>| match value { Some(ns) => { let ns = Literal::u32_unsuffixed(ns); @@ -416,6 +411,18 @@ fn generate_peripheral( } None => quote! { None }, }; + let internal_channels = adc.internal_channels.iter().map(|(channel, route)| { + let channel = Literal::u8_unsuffixed(*channel); + let source = adc_internal_source(route.source); + let sample_min_ns = ns(route.sample_min_ns); + quote! { + AdcInternalChannel { + channel: #channel, + source: #source, + sample_min_ns: #sample_min_ns, + } + } + }); let (wakeup_max_ns, wakeup_typ_ns) = (ns(adc.wakeup_max_ns), ns(adc.wakeup_typ_ns)); let sample_min_ns = ns(adc.sample_min_ns); let pga_sample_ns = adc.pga_sample_ns.iter().map(|(gain, sample_ns)| { diff --git a/tools/adc_internal_sample.py b/tools/adc_internal_sample.py new file mode 100644 index 0000000..dcb4d8b --- /dev/null +++ b/tools/adc_internal_sample.py @@ -0,0 +1,293 @@ +"""Extract the ADC's minimum sample window for each internal signal, from MSPM0 datasheets. + +The ADC's switching-characteristics table states a separate `tSample_<signal>` row for most of the +signals routed to an internal channel, and those minimums are far above the bare-pin `tSample`: +10us for the internal reference against a 156ns pin on mspm0l211x. This writes +`data/adc_internal_sample/<family>.yaml`. Nothing machine-readable carries any of it. + +Keyed by **signal**, not by channel, because that is how the datasheets state it -- one row per +signal per family. `apply_adc` copies the figure onto every channel routing that signal, which is +where a consumer looks for it, and a dual-ADC part reaching one signal from both ADCs gets the same +number on both routes. + +Two signals are deliberately not extracted here: + +- **The temperature sensor**, whose rows are in `data/temp_sensor/` already and are *two* quantities + rather than one: `tSET,TS` (the settling maximum) and the `ADC tSample` the factory calibration + used, which differ -- 10us against 12.5us on the older L datasheets -- and which are independently + absent. One scalar cannot hold both, and dropping the calibration figure is the quiet failure, + since a window long enough to settle but shorter than the factory's reads plausibly and drifts. +- **The OPA outputs**, whose `tSample_PGA` row is keyed by PGA gain rather than being a scalar and + lives in `Adc::pga_sample_ns` (`tools/adc_sample.py`). + +Usage: + uv run tools/adc_internal_sample.py <datasheet.pdf> [...] # print what it finds + uv run tools/adc_internal_sample.py --write <dir-of-datasheets> # regenerate + +`uv run` installs the dependencies below on its own; a bare `python` needs them on the path. + +**Every figure is in the MIN column**, which is what makes them safe to assert against. The table is +not ruled, so the column is decided geometrically against the MIN/TYP/MAX header positions, the same +way `adc_wakeup.py` and `adc_sample.py` do it. + +Three traps this had to survive, all silent: + +- **A unit cell can be blank.** The MSPM0L2117 `tSample_VREF` row prints `10` with no `us` where + every neighbouring row has one, so the unit falls back to the table's rather than the row's. +- **A figure and its condition can sit on different lines**, the condition wrapping above and below + it. That is why the row is assembled from a small window of lines rather than from one. +- **A footnote can contradict the row it annotates.** The MSPM0L1228/L2228 footnote calls both + supply-monitor dividers `VDD/3` while the row itself is named `Supply Monitor (VBAT/3)`. The row + wins -- a monitor dividing a rail other than its own reports nothing about that rail -- so the + signal is taken from the parameter name and never from the footnote. +""" + +# /// script +# requires-python = ">=3.10" +# dependencies = ["pdfplumber", "pyyaml"] +# /// + +import math +import re +import sys +from pathlib import Path + +try: + import pdfplumber + import yaml +except ImportError as e: # pragma: no cover + raise SystemExit(f"{e.name} is missing; run this with `uv run` instead of `python`") + +#: Time units the figures may be given in, as a multiplier to nanoseconds. +UNITS = {"ns": 1, "us": 1_000, "ยตs": 1_000, "ms": 1_000_000} + +NUMBER = re.compile(r"^\d+(?:\.\d+)?$") + +#: The channel named inside the `tSample_VREF` condition. Extracted on its own rather than as part +#: of the condition text, because it is a cross-check worth having and a single token is robust +#: where the whole wrapped condition is not: it must equal the channel `data/adc_channels/` routes +#: `Vref` to, which is one page of the datasheet corroborating another. +CHANNEL = re.compile(r"CHANNEL\s*=\s*(\d+)", re.IGNORECASE) + +#: The row has to be a sampling-time row. Without this, the signal names also match the accuracy and +#: supply-current rows of the electrical table -- `VSupplyMon ADC input channel: Supply Monitor` sits +#: on the same page and would otherwise be read as a 1.5us MAX sample window. +SAMPLE_ROW = re.compile(r"Sampl\w*\s+time\s+with\b", re.IGNORECASE) + +#: Each `tSample_<signal>` row, mapped to its `AdcInternalSource` variant. Order matters: the VBAT +#: monitor's row name contains the VDD monitor's, so it has to be tested first. `tSample_PGA` and +#: the temperature sensor rows are deliberately absent -- see the module docs. +SIGNALS = [ + (re.compile(r"Supply\s+Monitor\s*\(?\s*VBAT", re.IGNORECASE), "VbatMonitor"), + (re.compile(r"VUSB\s+Monitor|USBMon", re.IGNORECASE), "VusbMonitor"), + (re.compile(r"Supply\s+Monitor", re.IGNORECASE), "SupplyMonitor"), + (re.compile(r"\bwith\s+(internal\s+)?VREF\b", re.IGNORECASE), "Vref"), + (re.compile(r"\bwith\s+GPAMP\b", re.IGNORECASE), "Gpamp"), + (re.compile(r"\bwith\s+DAC\b", re.IGNORECASE), "Dac0"), +] + +#: The test condition each row is stated under. Uniform across every family that states the row, so +#: it is recorded once here rather than extracted per family. +#: +#: The reference matters and differs per signal. The internal reference is measured with VDD as the +#: ADC reference because it cannot be measured against itself, so a caller sampling that channel +#: with `VRSEL` on the internal reference is outside the published figure. The supply and USB +#: monitors are the other way round, measured against the internal reference. +CONDITIONS = { + "SupplyMonitor": "12-bit mode; internal reference (VRSEL=1h or 2h) where stated", + "VbatMonitor": "12-bit mode; the VBAT divider, not the VDD one the footnote describes", + "VusbMonitor": "12-bit mode, internal reference (VRSEL=1h or 2h)", + "Vref": "12-bit mode, VDD as reference -- the internal reference cannot measure itself", + "Gpamp": "12-bit mode", + "Dac0": "12-bit mode", +} + +#: Rows are grouped by rounding the word top to this many points. +ROW_TOLERANCE = 3 + +#: How many lines below a parameter row its figure may wrap to. +WRAP = 2 + + +def centre(word: dict) -> float: + return (word["x0"] + word["x1"]) / 2 + + +def rows_of(page) -> dict[int, list[dict]]: + """Words grouped into visual rows, keyed by rounded vertical position.""" + grouped: dict[int, list[dict]] = {} + for word in page.extract_words(): + grouped.setdefault(round(word["top"] / ROW_TOLERANCE), []).append(word) + return grouped + + +def read(path: Path) -> dict[str, tuple[str, int]]: + """Return `{signal: (column, nanoseconds)}` for one datasheet. + + The test conditions are deliberately not extracted. They are uniform per signal across every + family that states the row, so a per-family reading of them adds nothing and is fragile: TI + centres the condition on its row, wrapping it above *and* below the figure, so no line window + separates one row's condition from its neighbour's reliably. They are recorded once, as prose, + in `CONDITIONS` below. + """ + found: dict[str, tuple[str, int]] = {} + vref_channel: int | None = None + + with pdfplumber.open(str(path)) as pdf: + for page in pdf.pages: + text = page.extract_text() or "" + if "Sample time with" not in text and "Sampling time with" not in text: + continue + + grouped = rows_of(page) + keys = sorted(grouped) + headers: dict[str, float] = {} + # The unit cell is blank on one row of one datasheet, so the table's unit carries. + page_unit = "" + + for index, key in enumerate(keys): + words = sorted(grouped[key], key=lambda w: w["x0"]) + line = " ".join(w["text"] for w in words) + + if "MIN" in line and "MAX" in line: + headers = { + w["text"]: centre(w) for w in words if w["text"] in ("MIN", "TYP", "MAX") + } + + if not headers: + continue + + unit = next((w["text"] for w in reversed(words) if w["text"].lower() in UNITS), "") + page_unit = unit or page_unit + + if not SAMPLE_ROW.search(line): + continue + signal = next((name for pattern, name in SIGNALS if pattern.search(line)), None) + if signal is None or signal in found: + continue + + low = min(headers.values()) - 20 + # The figure may wrap onto a following line, taking its unit with it. + for follow in keys[index : index + 1 + WRAP]: + segment = sorted(grouped[follow], key=lambda w: w["x0"]) + figures = [ + w for w in segment if NUMBER.fullmatch(w["text"]) and centre(w) > low + ] + if not figures: + continue + figure = figures[-1] + row_unit = next( + (w["text"] for w in reversed(segment) if w["text"].lower() in UNITS), "" + ) + scale = UNITS[(row_unit or unit or page_unit or "us").lower()] + column = min(headers, key=lambda c: abs(headers[c] - centre(figure))) + found[signal] = (column, math.ceil(float(figure["text"]) * scale)) + if signal == "Vref": + # Nearest `CHANNEL=n` to this row. A wrong pick shows up immediately as a + # verify.rs failure rather than as silently wrong data, since every family + # stating the row agrees with the channel map today. + near = [ + (abs(k - key), m.group(1)) + for k in keys[max(0, index - WRAP) : index + 1 + WRAP] + for m in [CHANNEL.search(" ".join(w["text"] for w in grouped[k]))] + if m + ] + if near: + vref_channel = int(min(near)[1]) + break + + return found, vref_channel + + +HEADER = """\ +# The minimum ADC sample window for each internal signal on this family, in nanoseconds. +# +# GENERATED by `tools/adc_internal_sample.py --write` from the {ds} datasheet's ADC switching +# characteristics. Re-run the tool rather than editing by hand. +# +# Keyed by signal because that is how the datasheet states it; the generator copies each figure onto +# every channel routing that signal. Every figure is the MIN column of a `tSample_<signal>` row. +# +# These are far above the bare-pin minimum in data/adc_sample/: the internal reference alone can be +# 10us against a 156ns pin. A window sized for the pin samples an internal signal that has not +# settled, and nothing reports it. +# +# The temperature sensor is not here -- its two figures live in data/temp_sensor/ and differ from +# each other. Nor are the OPA outputs, whose row is keyed by PGA gain in data/adc_sample/. +# +# Conditions are quoted per signal because they differ per signal and are load-bearing: the internal +# reference is measured with VDD as the ADC reference, since it cannot be measured against itself. +""" + + +def write(datasheets: str) -> int: + """Regenerate data/adc_internal_sample/*.yaml. Returns the number of problems reported.""" + parts = yaml.safe_load(Path("data/parts.yaml").read_text(encoding="utf-8")) + families = [(f["family"], f["datasheet_url"].rsplit("/", 1)[-1]) for f in parts["families"]] + + Path("data/adc_internal_sample").mkdir(parents=True, exist_ok=True) + problems = 0 + + for family, gpn in families: + pdf = Path(datasheets) / f"{gpn}_datasheet.pdf" + if not pdf.exists(): + print(f"{family}: no datasheet for {gpn} in {datasheets}") + problems += 1 + continue + + found, vref_channel = read(pdf) + if not found: + print(f"{family}: no tSample_<signal> rows in {pdf.name}") + problems += 1 + continue + + off_column = {s: c for s, (c, _) in found.items() if c != "MIN"} + if off_column: + print(f"{family}: {off_column} is not a MIN in {pdf.name}") + problems += 1 + continue + + path = Path(f"data/adc_internal_sample/{family}.yaml") + with open(path, "w", encoding="utf-8", newline="\n") as out: + out.write(HEADER.format(ds=gpn.upper())) + if vref_channel is not None: + out.write( + "\n# The channel the tSample_VREF condition names. Cross-checked against the\n" + "# channel data/adc_channels/ routes Vref to -- one page of the datasheet\n" + "# corroborating another, which is the only independent check on a channel map.\n" + f"vref_channel: {vref_channel}\n" + ) + out.write("\nsample_min_ns:\n") + for signal in sorted(found): + out.write(f" # {CONDITIONS[signal]}\n") + out.write(f" {signal}: {found[signal][1]}\n") + + summary = ", ".join(f"{s} {found[s][1]}" for s in sorted(found)) + print(f"{family}: {summary}, from {pdf.name}") + + return problems + + +def main(argv: list[str]) -> None: + if "--write" in argv: + i = argv.index("--write") + raise SystemExit(1 if write(argv[i + 1] if i + 1 < len(argv) else ".") else 0) + + paths = [Path(a) for a in argv if not a.startswith("--")] + if not paths: + raise SystemExit(__doc__) + + for path in paths: + found, vref_channel = read(path) + print(f"########## {path.name}") + if vref_channel is not None: + print(f" tSample_VREF condition names ADC channel {vref_channel}") + if not found: + print(" no tSample_<signal> rows") + for signal in sorted(found): + column, ns = found[signal] + print(f" {signal:16} {ns:>7} ns {column} {CONDITIONS[signal]}") + + +if __name__ == "__main__": + main(sys.argv[1:]) From 2e9ea23cf7857aa9386e367a8284394dd299618d Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Wed, 26 Aug 2026 13:10:44 +0100 Subject: [PATCH 119/134] parts: correct the MSPM0G1507 flash size Recorded as 64KB against a 128KB part. SLASEW9E's device comparison table, its family-members bullet and the SDK's mspm0g1507.cmd all say 128; G1506 is the 64KB part. --- data/parts.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/parts.yaml b/data/parts.yaml index 6901caa..db5e2d3 100644 --- a/data/parts.yaml +++ b/data/parts.yaml @@ -319,7 +319,7 @@ families: - name: mspm0g1507 memory: - name: FLASH - length: 64 + length: 128 address: 0x00000000 - name: RAM length: 32 From 42607cd39599680d6f2fafa1003311056a7c02c0 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Wed, 26 Aug 2026 13:10:44 +0100 Subject: [PATCH 120/134] tools: check part memory sizes against the datasheets parts.yaml states flash and SRAM by hand because sysconfig describes neither, and nothing checked either. The datasheets' device comparison table states both per orderable part, so the transcription is verifiable after the fact. Checks rather than writes: the rest of a part number's entry has no machine-readable source. Entries are summed by kind, since the Gx51x parts spend one datasheet figure over RAM and RAM_BANK. --- tools/memory.py | 128 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 tools/memory.py diff --git a/tools/memory.py b/tools/memory.py new file mode 100644 index 0000000..39b64da --- /dev/null +++ b/tools/memory.py @@ -0,0 +1,128 @@ +"""Check `data/parts.yaml`'s flash and SRAM sizes against the datasheets. + +`data/parts.yaml` is hand-maintained and sysconfig says nothing about memory per part number, so +until now nothing checked these two numbers at all. This reads each datasheet's "Device Comparison" +table, whose `FLASH / SRAM (KB)` column states both per orderable part, and reports every +disagreement. It found MSPM0G1507 recorded as 64KB of flash against the datasheet's 128KB. + +This checks rather than writes: the rest of a part number's entry -- packages, the curated +frequencies -- has no machine-readable source, so there is nothing to regenerate. + +Usage: + uv run tools/memory.py <datasheet.pdf> [...] # print each part the datasheet states + uv run tools/memory.py --check <dir-of-pdfs> # compare against data/parts.yaml + +`uv run` installs the dependencies below on its own; a bare `python` needs them on the path. + +**Sum the entries by kind rather than reading the one named `RAM`.** The Gx51x parts split their +128KB of SRAM into `RAM` and `RAM_BANK`, contiguous at 0x20200000 and 0x20210000, because the upper +bank is wiped by any mode deeper than SLEEP and carries its own `retained_through`. Comparing the +first entry against the datasheet's single figure reports all four as half their real size. + +The table is ruled, so a lattice read finds it. What needs care is the leading column: a row states +its part number and size, and the columns shared by every part of the family are stated once against +the first row and left blank below, so a row cannot be skipped for having empty cells. +""" + +# /// script +# requires-python = ">=3.10" +# dependencies = ["pdfplumber", "pyyaml"] +# /// + +import re +import sys +from pathlib import Path + +import pdfplumber +import yaml + +PART = re.compile(r"\b(MSPM0[A-Z]?\d{4}|MSPS\d{3}F\d)", re.IGNORECASE) +SIZE = re.compile(r"^\s*(\d+)\s*/\s*(\d+)\s*$") + + +def read(path: Path) -> dict[str, set[tuple[int, int]]]: + """Every part number the datasheet's comparison table states, to its (flash, sram) in KB. + + A set, because one part number appears once per package it is offered in, and every row has to + agree. Two sizes for one part means the table was misread. + """ + found: dict[str, set[tuple[int, int]]] = {} + + with pdfplumber.open(path) as pdf: + for page in pdf.pages[:14]: + for table in page.extract_tables(): + if not table: + continue + header = " ".join(str(cell) for cell in table[0] if cell).upper() + if "FLASH" not in header or "SRAM" not in header: + continue + + for row in table[1:]: + cells = [(cell or "").replace("\n", " ").strip() for cell in row] + name = next((PART.search(c) for c in cells if PART.search(c)), None) + size = next((SIZE.match(c) for c in cells if SIZE.match(c)), None) + if name and size: + part = name.group(1).lower() + found.setdefault(part, set()).add((int(size[1]), int(size[2]))) + + return found + + +def check(datasheets: str) -> int: + """Compare every part in data/parts.yaml against its datasheet. Returns problems reported.""" + parts = yaml.safe_load(Path("data/parts.yaml").read_text(encoding="utf-8")) + problems = 0 + checked = 0 + + for family in parts["families"]: + gpn = family["datasheet_url"].rsplit("/", 1)[-1] + pdf = Path(datasheets) / f"{gpn}_datasheet.pdf" + if not pdf.exists(): + print(f"{family['family']}: no datasheet for {gpn} in {datasheets}") + problems += 1 + continue + + stated = read(pdf) + for part in family["part_numbers"]: + # Summed by kind: a part with an upper SRAM bank spends its total over two entries. + flash = sum(m["length"] for m in part["memory"] if m["name"].startswith("FLASH")) + ram = sum(m["length"] for m in part["memory"] if m["name"].startswith("RAM")) + + sizes = stated.get(part["name"]) + if not sizes: + print(f"{part['name']}: no row in {pdf.name}") + problems += 1 + elif len(sizes) > 1: + print(f"{part['name']}: {pdf.name} states {sorted(sizes)} for one part") + problems += 1 + elif (size := next(iter(sizes))) != (flash, ram): + print( + f"{part['name']}: parts.yaml says {flash}KB flash and {ram}KB SRAM, " + f"{pdf.name} says {size[0]}KB and {size[1]}KB" + ) + problems += 1 + else: + checked += 1 + + print(f"{checked} part numbers agree with their datasheet, {problems} do not") + return problems + + +def main(argv: list[str]) -> None: + if "--check" in argv: + i = argv.index("--check") + raise SystemExit(1 if check(argv[i + 1] if i + 1 < len(argv) else ".") else 0) + + paths = [Path(a) for a in argv if not a.startswith("--")] + if not paths: + raise SystemExit(__doc__) + + for path in paths: + print(f"########## {path.name}") + for part, sizes in sorted(read(path).items()): + for flash, ram in sorted(sizes): + print(f" {part:<12} {flash:>4}KB flash {ram:>4}KB SRAM") + + +if __name__ == "__main__": + main(sys.argv[1:]) From 68b6c41e59c043835baf0da6dc95a184a41ab854 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Wed, 26 Aug 2026 13:10:55 +0100 Subject: [PATCH 121/134] dac: add the DAC12 register block DAC0 has been in the metadata on ten chips with an address, pins and an interrupt, and no register block behind any of it. Extracted from the G151x SVD. The G150x and G350x SVDs give the block an FSUB_1 at 404h which neither hw_dac12.h nor SLAU846 Table 20-7 has, and fold the two interrupt groups into one array where the newer ones split CPU_INT and GEN_EVENT. Both groups have the same layout and the same IIDX values, so they share one fieldset. --- data/registers/dac_v1.yaml | 546 ++++++++++++++++++++++++++++ mspm0-data-gen/src/perimap.rs | 3 + mspm0-metapac-gen/src/peripheral.rs | 1 + transforms/DAC.yaml | 150 ++++++++ 4 files changed, 700 insertions(+) create mode 100644 data/registers/dac_v1.yaml create mode 100644 transforms/DAC.yaml diff --git a/data/registers/dac_v1.yaml b/data/registers/dac_v1.yaml new file mode 100644 index 0000000..45bffc5 --- /dev/null +++ b/data/registers/dac_v1.yaml @@ -0,0 +1,546 @@ +block/CPU_INT: + items: + - name: IIDX + description: Interrupt index. + byte_offset: 0 + access: Read + fieldset: IIDX + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: INT + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: INT + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: INT + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: INT + - name: ICLR + description: Interrupt clear. + byte_offset: 40 + access: Write + fieldset: INT +block/DAC: + description: PERIPHERALREGION. + # Extracted from the G151x SVD. The G150x and G350x SVDs describe the same block with one + # INT_EVENT array of two instead of separate CPU_INT and GEN_EVENT, and give it an FSUB_1 at + # 404h which neither hw_dac12.h nor SLAU846 Table 20-7 lists. One subscriber port, at 400h. + items: + - name: FSUB + description: Subscriber Port 0. + byte_offset: 1024 + fieldset: FPORT + - name: FPUB + description: Publisher port 1. + byte_offset: 1092 + fieldset: FPORT + - name: GPRCM + array: + len: 1 + stride: 24 + byte_offset: 2048 + block: GPRCM + - name: CPU_INT + array: + len: 1 + stride: 44 + byte_offset: 4128 + block: CPU_INT + - name: GEN_EVENT + array: + len: 1 + stride: 44 + byte_offset: 4176 + block: GEN_EVENT + - name: EVT_MODE + description: Event Mode. + byte_offset: 4320 + fieldset: EVT_MODE + - name: DESC + description: Module Description. + byte_offset: 4348 + access: Read + fieldset: DESC + - name: CTL0 + description: Control 0. + byte_offset: 4352 + fieldset: CTL0 + - name: CTL1 + description: Control 1. + byte_offset: 4368 + fieldset: CTL1 + - name: CTL2 + description: Control 2. + byte_offset: 4384 + fieldset: CTL2 + - name: CTL3 + description: Control 3. + byte_offset: 4400 + fieldset: CTL3 + - name: CALCTL + description: Calibration control. + byte_offset: 4416 + fieldset: CALCTL + - name: CALDATA + description: Calibration data. + byte_offset: 4448 + access: Read + fieldset: CALDATA + - name: DATA0 + description: Data 0. + byte_offset: 4608 + fieldset: DATA0 +block/GEN_EVENT: + items: + - name: IIDX + description: Interrupt index. + byte_offset: 0 + access: Read + fieldset: IIDX + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: INT + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: INT + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: INT + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: INT + - name: ICLR + description: Interrupt clear. + byte_offset: 40 + access: Write + fieldset: INT +block/GPRCM: + items: + - name: PWREN + description: Power enable. + byte_offset: 0 + fieldset: PWREN + - name: RSTCTL + description: Reset Control. + byte_offset: 4 + access: Write + fieldset: RSTCTL + - name: STAT + description: Status Register. + byte_offset: 20 + access: Read + fieldset: STAT +fieldset/CALCTL: + description: Calibration control. + fields: + - name: CALON + description: This bit when set initiates the DAC offset error calibration sequence and is automatically reset when the offset error calibration completes. + bit_offset: 0 + bit_size: 1 + - name: CALSEL + description: This bit is used to select between factory trim or self calibration trim. + bit_offset: 1 + bit_size: 1 + enum: CALSEL +fieldset/CALDATA: + description: Calibration data. + fields: + - name: DATA + description: DAC offset error calibration data. The DAC offset error calibration data is represented in twos complement format providing a range of 64 to +63. This is read-only bit, reflecting the calibration data. Writing to this register will have no effect, it will not change the calibration value. + bit_offset: 0 + bit_size: 7 +fieldset/CTL0: + description: Control 0. + fields: + - name: ENABLE + description: This bit enables the DAC module. + bit_offset: 0 + bit_size: 1 + - name: RES + description: These bits define the DAC output voltage resolution. + bit_offset: 8 + bit_size: 1 + enum: RES + - name: DFM + description: This bit defines the DAC input data format. + bit_offset: 16 + bit_size: 1 + enum: DFM +fieldset/CTL1: + description: Control 1. + fields: + - name: AMPEN + description: 'AMP_EN - output amplifier enabled or disabled 0 : disabled 1 : enabled.' + bit_offset: 0 + bit_size: 1 + - name: AMPHIZ + description: 'AMPHIZ - amplifier output value 0 : amplifier output is high impedance 1 : amplifier output is pulled down to ground.' + bit_offset: 1 + bit_size: 1 + enum: AMPHIZ + - name: REFSP + description: This bit selects the DAC voltage reference source + input. + bit_offset: 8 + bit_size: 1 + enum: REFSP + - name: REFSN + description: This bit selects the DAC voltage reference source + input. + bit_offset: 9 + bit_size: 1 + enum: REFSN + - name: OPS + description: These bits select the DAC output on device pin. + bit_offset: 24 + bit_size: 1 + enum: OPS +fieldset/CTL2: + description: Control 2. + fields: + - name: FIFOEN + description: This bit enables the FIFO and the FIFO hardware control state machine. + bit_offset: 0 + bit_size: 1 + - name: FIFOTH + description: These bits determine the FIFO threshold. In case of DMA based operation, DAC generates new DMA trigger when the number of empty locations in FIFO match the selected FIFO threshold level. In case of CPU based operation, the FIFO threshold bits are don't care and FIFO level is directly indicated through the respective bits in the RIS register. + bit_offset: 8 + bit_size: 2 + enum: FIFOTH + - name: FIFOTRIGSEL + description: These bits select the source for FIFO read trigger. When the selected FIFO read trigger is asserted, the data from FIFO (as indicated by read pointer) is moved into internal DAC data register. + bit_offset: 16 + bit_size: 2 + enum: FIFOTRIGSEL + - name: DMATRIGEN + description: This bit enables the DMA trigger generation mechanism. When this bit is set along with FIFOEN, the DMA trigger is generated based on the empty FIFO locations qualified by FIFOTH settings. This bit should be cleared by software to stop further DMA triggers. + bit_offset: 24 + bit_size: 1 +fieldset/CTL3: + description: Control 3. + fields: + - name: STIMEN + description: This bit enables the sample time generator. + bit_offset: 0 + bit_size: 1 + - name: STIMCONFIG + description: These bits are used to configure the trigger rate from the sample time generator. The STIMCONFIG values 10 to 15 are reserved and default to same effect as value 0 (500SPS). + bit_offset: 8 + bit_size: 4 + enum: STIMCONFIG +fieldset/DATA0: + description: Data 0. + fields: + - name: DATA_VALUE + description: This is the data written for digital to analog conversion. + bit_offset: 0 + bit_size: 12 +fieldset/DESC: + description: Module Description. + fields: + - name: MINREV + description: Minor rev of the IP. + bit_offset: 0 + bit_size: 4 + - name: MAJREV + description: Major rev of the IP. + bit_offset: 4 + bit_size: 4 + - name: FEATUREVER + description: Feature Set for the module *instance*. + bit_offset: 12 + bit_size: 4 + - name: MODULEID + description: Module identification contains a unique peripheral identification number. The assignments are maintained in a central database for all of the platform modules to ensure uniqueness. + bit_offset: 16 + bit_size: 16 +fieldset/EVT_MODE: + description: Event Mode. + fields: + - name: CPU + description: Event line mode select for event corresponding to [IPSTANDARD.CPU_INT]. + bit_offset: 0 + bit_size: 2 + enum: INT_CFG + - name: GEN_EVENT + description: Event line mode select for event corresponding to [IPSTANDARD.GEN_EVENT]. + bit_offset: 2 + bit_size: 2 + enum: INT_CFG +fieldset/FPORT: + description: Publisher port 1. + fields: + - name: CHANID + description: 0 = disconnected. others = connected to channel_ID = CHANID. + bit_offset: 0 + bit_size: 4 +fieldset/IIDX: + description: Interrupt index. + fields: + - name: STAT + description: Interrupt index status. + bit_offset: 0 + bit_size: 4 + enum: IIDX_STAT +fieldset/INT: + description: Interrupt clear. + fields: + - name: MODRDYIFG + description: Clears MODRDYIFG in RIS register. + bit_offset: 1 + bit_size: 1 + - name: FIFOFULLIFG + description: Clears FIFOFULLIFG in RIS register. + bit_offset: 8 + bit_size: 1 + - name: FIFO1B4IFG + description: Clears FIFO1B4IFG in RIS register. + bit_offset: 9 + bit_size: 1 + - name: FIFO1B2IFG + description: Clears FIFO1B2IFG in RIS register. + bit_offset: 10 + bit_size: 1 + - name: FIFO3B4IFG + description: Clears FIFO3B4IFG in RIS register. + bit_offset: 11 + bit_size: 1 + - name: FIFOEMPTYIFG + description: Clears FIFOEMPTYIFG in RIS register. + bit_offset: 12 + bit_size: 1 + - name: FIFOURUNIFG + description: Clears FIFOURUNIFG in RIS register. + bit_offset: 13 + bit_size: 1 + - name: DMADONEIFG + description: Clears DMADONEIFG in RIS register. + bit_offset: 14 + bit_size: 1 +fieldset/PWREN: + description: Power enable. + fields: + - name: ENABLE + description: Enable the power. + bit_offset: 0 + bit_size: 1 + - name: KEY + description: KEY to allow Power State Change 26h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: PWREN_KEY +fieldset/RSTCTL: + description: Reset Control. + fields: + - name: RESETASSERT + description: Assert reset to the peripheral. + bit_offset: 0 + bit_size: 1 + - name: RESETSTKYCLR + description: Clear the RESETSTKY bit in the STAT register. + bit_offset: 1 + bit_size: 1 + - name: KEY + description: Unlock key B1h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: RESET_KEY +fieldset/STAT: + description: Status Register. + fields: + - name: RESETSTKY + description: This bit indicates, if the peripheral was reset, since this bit was cleared by RESETSTKYCLR in the RSTCTL register. + bit_offset: 16 + bit_size: 1 +enum/AMPHIZ: + bit_size: 1 + variants: + - name: HIZ + description: HiZ when disable. + value: 0 + - name: PULLDOWN + description: dacout pulldown when disable. + value: 1 +enum/CALSEL: + bit_size: 1 + variants: + - name: FACTORYTRIM + description: Factory Trim Calibration Values are used when calibration is enabled. + value: 0 + - name: SELFCALIBRATIONTRIM + description: Self Calibration Trim Values are used when calibration is enabled. + value: 1 +enum/DFM: + bit_size: 1 + variants: + - name: BINARY + description: Straight binary. + value: 0 + - name: TWOS_COMP + description: Twos complement. + value: 1 +enum/FIFOTH: + bit_size: 2 + variants: + - name: LOW + description: One fourth of the FIFO locations are empty. + value: 0 + - name: MED + description: Half of the FIFO locations are empty. + value: 1 + - name: HIGH + description: Three fourth of the FIFO locations are empty. + value: 2 + - name: SPARE + description: Reserved value. Defaults to same effect as FIFOTH = 0 (One fourth of the FIFO locations are empty). + value: 3 +enum/FIFOTRIGSEL: + bit_size: 2 + variants: + - name: STIM + description: Sample time generator output. + value: 0 + - name: TRIG0 + description: Hardware trigger-0 from event fabric. + value: 1 + - name: SPARE1 + description: Reserved - unimplemented. + value: 2 + - name: SPARE2 + description: Reserved - unimplemented. + value: 3 +enum/IIDX_STAT: + bit_size: 4 + variants: + - name: NO_INTR + description: No pending interrupt. + value: 0 + - name: MODRDYIFG + description: Module ready interrupt. + value: 2 + - name: FIFOFULLIFG + description: FIFO full interrupt. + value: 9 + - name: FIFO1B4IFG + description: FIFO one fourth empty interrupt. + value: 10 + - name: FIFO1B2IFG + description: FIFO half empty interrupt. + value: 11 + - name: FIFO3B4IFG + description: FIFO three fourth empty interrupt. + value: 12 + - name: FIFOEMPTYIFG + description: FIFO empty interrupt. + value: 13 + - name: FIFOURUNIFG + description: FIFO underrun interrupt. + value: 14 + - name: DMADONEIFG + description: DMA done interrupt. + value: 15 +enum/INT_CFG: + bit_size: 2 + variants: + - name: DISABLE + description: The interrupt or event line is disabled. + value: 0 + - name: SOFTWARE + description: Event handled by software. Software must clear the associated RIS flag. + value: 1 + - name: HARDWARE + description: Event handled by hardware. The hardware (another module) clears automatically the associated RIS flag. + value: 2 +enum/OPS: + bit_size: 1 + variants: + - name: NOC0 + description: No connect. Both DAC output switches are open. + value: 0 + - name: OUT0 + description: OUT0 output is selected. + value: 1 +enum/PWREN_KEY: + bit_size: 8 + variants: + - name: KEY + value: 38 +enum/REFSN: + bit_size: 1 + variants: + - name: VEREFN + description: VEREFN pin as VR-. + value: 0 + - name: VSSA + description: Analog supply (VSSA) as VR-. + value: 1 +enum/REFSP: + bit_size: 1 + variants: + - name: VDDA + description: Analog supply (VDDA) as VR+. + value: 0 + - name: VEREFP + description: VEREFP pin as VR+. + value: 1 +enum/RES: + bit_size: 1 + variants: + - name: _8BITS + description: 8-bits resolution. + value: 0 + - name: _12BITS + description: 12-bit resolution. + value: 1 +enum/RESET_KEY: + bit_size: 8 + variants: + - name: KEY + value: 177 +enum/STIMCONFIG: + bit_size: 4 + variants: + - name: _500SPS + description: Trigger rate is 500 sps (clock divide value is 4000). + value: 0 + - name: _1KSPS + description: Trigger rate is 1 ksps (clock divide value is 2000). + value: 1 + - name: _2KSPS + description: Trigger rate is 2 ksps (clock divide value is 1000). + value: 2 + - name: _4KSPS + description: Trigger rate is 4 ksps (clock divide value is 500). + value: 3 + - name: _8KSPS + description: Trigger rate is 8 ksps (clock divide value is 250). + value: 4 + - name: _16KSPS + description: Trigger rate is 16 ksps (clock divide value is 125). + value: 5 + - name: _100KSPS + description: Trigger rate is 100 ksps (clock divide value is 20). + value: 6 + - name: _200KSPS + description: Trigger rate is 200 ksps (clock divide value is 10). + value: 7 + - name: _500KSPS + description: Trigger rate is 500 ksps (clock divide value is 4). + value: 8 + - name: _1MSPS + description: Trigger rate is 1 Msps (clock divide value is 2). + value: 9 diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index 491071e..23ca9a8 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -16,6 +16,9 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ (".*:timb", "btimer"), (".*:tim", "v1"), (".*:adc", "v1"), + // Only g150x, g151x, g350x and g351x have a DAC0, so an ungated entry cannot over-match. + // Not the same four families as the OPA: the Gx51x parts have a DAC and no amplifier. + (".*:dac", "v1"), (".*:wwdt", "v1"), (".*:flashctl", "v1"), (".*:trng", "v1"), diff --git a/mspm0-metapac-gen/src/peripheral.rs b/mspm0-metapac-gen/src/peripheral.rs index e60f457..ef8bd70 100644 --- a/mspm0-metapac-gen/src/peripheral.rs +++ b/mspm0-metapac-gen/src/peripheral.rs @@ -16,6 +16,7 @@ const GENERATE_PERIPHERALS: &[PeripheralType] = &[ PeripheralType::Canfd, PeripheralType::Comp, PeripheralType::Crc, + PeripheralType::Dac, PeripheralType::FactoryRegion, PeripheralType::FlashCtl, PeripheralType::Gpio, diff --git a/transforms/DAC.yaml b/transforms/DAC.yaml new file mode 100644 index 0000000..7c197d1 --- /dev/null +++ b/transforms/DAC.yaml @@ -0,0 +1,150 @@ +# Transform for DAC12 registers based on DAC0 on G151x. +# +# The G150x and G350x SVDs describe the same block with one INT_EVENT array of two rather than +# separate CPU_INT and GEN_EVENT, and add an FSUB_1 at 404h which neither hw_dac12.h nor SLAU846 +# Table 20-7 has. G151x is extracted instead: its register list matches both. +transforms: + - !DeleteUselessEnums + + # DeleteUselessEnums leaves the interrupt flags, whose variants are CLR and SET. + - !DeleteEnumsWithVariants + variants: + 0: CLR + 1: SET + + # CALON is useless + - !DeleteEnumsWithVariants + variants: + 0: INACTIVE + 1: ACTIVE + + # RESETASSERT is useless + - !DeleteEnumsWithVariants + variants: + 0: NOP + 1: ASSERT + + # RESETSTKY is useless + - !DeleteEnumsWithVariants + variants: + 0: NORES + 1: RESET + + # RESETSTKYCLR is useless + - !DeleteEnumsWithVariants + variants: + 0: NOP + 1: CLR + + # TODO: Something better than a cursed rename as merge + - !MergeBlocks + from: DAC0 + main: DAC0 + to: DAC + + # Remove prefixes + - !RenameRegisters + block: .* + from: DAC0_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: DAC0_(.+) + to: $1 + + - !Rename + type: All + from: DAC0_(.+) + to: $1 + + # Both interrupt blocks have the same layout and the same IIDX values, so they share one + # fieldset each, as comp_dacout does. The ADC keeps its three apart because their STAT value + # sets genuinely differ. + - !MergeEnums + from: (CPU_INT|GEN_EVENT)_IIDX_STAT + to: IIDX_STAT + + - !MergeFieldsets + from: (?:CPU_INT|GEN_EVENT)_(?:IMASK|ISET|MIS|RIS|ICLR) + to: INT + check: NoCheck + + - !MergeFieldsets + from: (?:CPU_INT|GEN_EVENT)_IIDX + to: IIDX + + - !RenameRegisters + block: .* + from: (?:CPU_INT|GEN_EVENT)_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: (?:CPU_INT|GEN_EVENT)_(.+) + to: $1 + + ## Publisher/Subscriber ports + ## The CHANID enum only names the disconnected value, which the width already says. + - !DeleteEnums + from: (FSUB_0|FPUB_1)_CHANID + soft: false + + - !MergeFieldsets + from: (FPUB|FSUB)_\d+ + to: FPORT + + - !RenameRegisters + block: .* + from: (FSUB|FPUB)_[0-1] + to: $1 + + ## Add missing keys + - !AddFields + fieldset: RSTCTL + fields: + - name: KEY + description: Unlock key B1h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: RESET_KEY + + - !AddFields + fieldset: PWREN + fields: + - name: KEY + description: KEY to allow Power State Change 26h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: PWREN_KEY + + - !Add + ir: + enum/RESET_KEY: + bit_size: 8 + variants: + - name: KEY + value: 177 + enum/PWREN_KEY: + bit_size: 8 + variants: + - name: KEY + value: 38 + + ## EVT_MODE + # The two event lines take the same three values. + - !MergeEnums + from: (INT0|EVT1)_CFG + to: INT_CFG + + - !RenameFields + fieldset: EVT_MODE + from: INT0_CFG + to: CPU + - !RenameFields + fieldset: EVT_MODE + from: EVT1_CFG + to: GEN_EVENT + + ## Cleanup + - !Sort From 10bb1ab8b0063c48eac09ce6eecef5501dbd5f7f Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Wed, 26 Aug 2026 13:10:56 +0100 Subject: [PATCH 122/134] verify: report a peripheral with no register block The other direction of register_blocks_exist. A peripheral with no perimap entry is described in the metadata and cannot be addressed, and nothing said so. Sixteen types are in that state and are listed; a new one now fails generation rather than joining them. --- mspm0-data-gen/src/verify.rs | 53 +++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index a288d78..a3d0f22 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -1,6 +1,6 @@ use std::{collections::HashSet, sync::LazyLock}; -use anyhow::bail; +use anyhow::{bail, ensure}; use mspm0_data_types::{AdcInternalSource, Chip, IoStructure, PeripheralType, PowerDomain}; use regex::Regex; @@ -12,6 +12,7 @@ pub fn verify(chip: &Chip, name: &str) -> Vec<anyhow::Error> { gpio_no_duplicates, peripheral_types_known, register_blocks_exist, + register_blocks_written, vref_startup_known, adc_channels_known, adc_wakeup_known, @@ -105,6 +106,56 @@ fn register_blocks_exist(chip: &Chip, name: &str) -> anyhow::Result<()> { Ok(()) } +/// A peripheral type with no register block at all, which is not on the list of known gaps. +/// +/// The other direction of [`register_blocks_exist`]. A peripheral with no `perimap` entry gets no +/// version and no register block, so it appears in the metadata with an address, pins and +/// interrupts and cannot be addressed. Nothing said so: DAC0 sat like that on ten chips until the +/// HAL asked for it. Listing the gaps here makes a new one fail generation rather than join them +/// quietly. +/// +/// Adding a type to `UNWRITTEN_BLOCKS` is a decision to ship it unaddressable. Writing the block +/// and its `perimap` entry is the other option, and is what the list is for. +fn register_blocks_written(chip: &Chip, name: &str) -> anyhow::Result<()> { + /// Types with no `data/registers` block yet, each an instance a consumer cannot reach. + const UNWRITTEN_BLOCKS: &[PeripheralType] = &[ + PeripheralType::Aes, + PeripheralType::AesAdv, + PeripheralType::Debugss, + PeripheralType::Event, + PeripheralType::GpAmp, + PeripheralType::I2s, + PeripheralType::Iwdt, + PeripheralType::KeystoreCtl, + PeripheralType::Lcd, + PeripheralType::Lfss, + PeripheralType::Npu, + PeripheralType::Rtc, + PeripheralType::Spgss, + PeripheralType::Spi, + PeripheralType::Usbfs, + PeripheralType::Wuc, + ]; + + let missing: Vec<_> = chip + .peripherals + .values() + .filter(|peripheral| peripheral.version.is_none()) + .filter(|peripheral| peripheral.ty != PeripheralType::Unknown) + .filter(|peripheral| !UNWRITTEN_BLOCKS.contains(&peripheral.ty)) + .map(|peripheral| peripheral.name.as_str()) + .collect(); + + ensure!( + missing.is_empty(), + "{name}: no register block for {}; write one and add a perimap entry, or record the \ + type in UNWRITTEN_BLOCKS", + missing.join(", ") + ); + + Ok(()) +} + /// Report a VREF instance with no startup time. /// /// A device carrying `VREF_ERR_01` cannot trust `CTL1.READY` after the first enable since reset, so From d66f388850c59b6da3c34219a1355be2beffdb1b Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Wed, 26 Aug 2026 13:14:46 +0100 Subject: [PATCH 123/134] generate: report a package sysconfig has no pinout for The package filter cannot tell a package this part is not offered in from one sysconfig has no entry for, so four part numbers across g150x and g5187 have been losing a package silently. Both datasheets sell it: MSPM0G150xSDGS28R and MSPM0G5187SDGS20R. Reported rather than fatal - the source is incomplete, not contradictory, and parts.yaml should keep saying what TI sells. --- mspm0-data-gen/src/generate.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index 67867fc..feae538 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -108,6 +108,28 @@ fn generate_family(family: &PartFamily, sources: &FamilySources) -> anyhow::Resu apply_dma(family, header, svd, &mut peripherals)?; for part_number in family.part_numbers.iter() { + // A package sysconfig has no pinout for produces no chip at all, and the filter below + // cannot tell that from a package this part is simply not offered in. The G150x parts + // each list DGS, which their datasheet sells as MSPM0G150xSDGS28R and which the family's + // sysconfig has no entry for, so three parts lost a package and nothing said so. + let unknown: Vec<_> = part_number + .packages + .iter() + .filter(|wanted| !packages.iter().any(|package| &package.package == *wanted)) + .map(String::as_str) + .collect(); + + // Reported rather than fatal: the source is incomplete, not self-contradictory, and + // parts.yaml should keep saying what TI sells. + if !unknown.is_empty() { + eprintln!( + "{}: parts.yaml lists package {}, which sysconfig has no pinout for, so no chip \ + is generated for it", + part_number.name, + unknown.join(", ") + ); + } + // Filter for package types available on the part number. let packages = packages .iter() From b4976260d6fe83d166848da33dacc0dd7a8ca62b Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Wed, 26 Aug 2026 13:33:16 +0100 Subject: [PATCH 124/134] spi: add the legacy SPI register block 62 instances across fifteen families have been in the metadata with no register block, which is why no legacy SPI driver could be written against the PAC. Extracted from the G151x SVD, which names the three interrupt groups as hw_spi.h does. Twelve SVDs call them INT_EVENT0-2, the C110x and MSPS003FX pair describe only the first, and the three L SVDs drop INTCTL; all of that is SVD incompleteness. Those parts' own device headers declare SPI0 as a full SPI_Regs, driverlib gates none of it per device, and SLAU847's SPI register table lists INTCTL and all three groups. Shaped to match unicommspi_v1, which describes the same SPI function. --- data/registers/spi_v1.yaml | 805 ++++++++++++++++++++++++++++ mspm0-data-gen/src/perimap.rs | 3 + mspm0-data-gen/src/verify.rs | 1 - mspm0-metapac-gen/src/peripheral.rs | 1 + transforms/SPI.yaml | 202 +++++++ 5 files changed, 1011 insertions(+), 1 deletion(-) create mode 100644 data/registers/spi_v1.yaml create mode 100644 transforms/SPI.yaml diff --git a/data/registers/spi_v1.yaml b/data/registers/spi_v1.yaml new file mode 100644 index 0000000..5092c16 --- /dev/null +++ b/data/registers/spi_v1.yaml @@ -0,0 +1,805 @@ +block/CPU_INT: + items: + - name: IIDX + description: Interrupt Index Register. + byte_offset: 0 + access: Read + fieldset: CPU_INT_IIDX + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: CPU_INT + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: CPU_INT + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: CPU_INT + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: CPU_INT + - name: ICLR + description: Interrupt clear. + byte_offset: 40 + access: Write + fieldset: CPU_INT +block/DMA_TRIG_RX: + items: + - name: IIDX + description: Interrupt Index Register. + byte_offset: 0 + access: Read + fieldset: DMA_TRIG_RX_IIDX + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: DMA_TRIG_RX + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: DMA_TRIG_RX + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: DMA_TRIG_RX + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: DMA_TRIG_RX + - name: ICLR + description: Interrupt clear. + byte_offset: 40 + access: Write + fieldset: DMA_TRIG_RX +block/DMA_TRIG_TX: + items: + - name: IIDX + description: Interrupt Index Register. + byte_offset: 0 + access: Read + fieldset: DMA_TRIG_TX_IIDX + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: DMA_TRIG_TX + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: DMA_TRIG_TX + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: DMA_TRIG_TX + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: DMA_TRIG_TX + - name: ICLR + description: Interrupt clear. + byte_offset: 40 + access: Write + fieldset: DMA_TRIG_TX +block/GPRCM: + items: + - name: PWREN + description: Power enable. + byte_offset: 0 + fieldset: PWREN + - name: RSTCTL + description: Reset Control. + byte_offset: 4 + access: Write + fieldset: RSTCTL + - name: CLKCFG + description: Peripheral Clock Configuration Register. + byte_offset: 8 + fieldset: CLKCFG + - name: GPRCM_STAT + description: Status Register. + byte_offset: 20 + access: Read + fieldset: GPRCM_STAT +block/SPI: + description: PERIPHERALREGION. + # Extracted from the G151x SVD, which names the three interrupt groups as hw_spi.h does. Twelve + # SVDs call them INT_EVENT0-2 and the C110x/MSPS003FX pair describe only the first; that is SVD + # incompleteness rather than a narrower device, since those parts' own device header declares + # SPI0 as a full SPI_Regs and driverlib's DL_SPI_DMA_IIDX_RX carries no per-device gate. + items: + - name: GPRCM + array: + len: 1 + stride: 24 + byte_offset: 2048 + block: GPRCM + - name: CLKDIV + description: Clock Divider. + byte_offset: 4096 + fieldset: CLKDIV + - name: CLKSEL + description: Clock Select for Ultra Low Power peripherals. + byte_offset: 4100 + fieldset: CLKSEL + - name: PDBGCTL + description: Peripheral Debug Control. + byte_offset: 4120 + fieldset: PDBGCTL + - name: CPU_INT + array: + len: 1 + stride: 44 + byte_offset: 4128 + block: CPU_INT + - name: DMA_TRIG_RX + array: + len: 1 + stride: 44 + byte_offset: 4176 + block: DMA_TRIG_RX + - name: DMA_TRIG_TX + array: + len: 1 + stride: 44 + byte_offset: 4224 + block: DMA_TRIG_TX + - name: EVT_MODE + description: Event Mode. + byte_offset: 4320 + fieldset: EVT_MODE + - name: INTCTL + description: Interrupt control register. + byte_offset: 4324 + fieldset: INTCTL + - name: CTL0 + description: SPI control register 0. + byte_offset: 4352 + fieldset: CTL0 + - name: CTL1 + description: SPI control register 1. + byte_offset: 4356 + fieldset: CTL1 + - name: CLKCTL + description: Clock prescaler and divider register. + byte_offset: 4360 + fieldset: CLKCTL + - name: IFLS + description: Interrupt FIFO Level Select Register. + byte_offset: 4364 + fieldset: IFLS + - name: STAT + description: Status Register. + byte_offset: 4368 + access: Read + fieldset: STAT + - name: RXDATA + description: RXDATA Register. + byte_offset: 4400 + access: Read + fieldset: RXDATA + - name: TXDATA + description: TXDATA Register. + byte_offset: 4416 + fieldset: TXDATA +fieldset/CLKCFG: + description: Peripheral Clock Configuration Register. + fields: + - name: BLOCKASYNC + description: Async Clock Request is blocked from starting SYSOSC or forcing bus clock to 32MHz. + bit_offset: 8 + bit_size: 1 + - name: KEY + description: KEY to Allow State Change A9h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: CLKCFG_KEY +fieldset/CLKCTL: + description: Clock prescaler and divider register. + fields: + - name: SCR + description: 'Serial clock divider: This is used to generate the transmit and receive bit rate of the SPI. The SPI bit rate is (SPI''s functional clock frequency)/((SCR+1)*2). SCR is a value from 0-1023.' + bit_offset: 0 + bit_size: 10 + - name: DSAMPLE + description: 'Delayed sampling value. In controller mode the data on the input pin will be delayed sampled by the defined clock cycles of internal functional clock hence relaxing the setup time of input data. This setting is useful in systems where the board delays and external peripheral delays are more than the input setup time of the controller. Please refer to the datasheet for values of controller input setup time and assess what DSAMPLE value meets the requirement of the system. Note: High values of DSAMPLE can cause HOLD time violations and must be factored in the calculations.' + bit_offset: 28 + bit_size: 4 +fieldset/CLKDIV: + description: Clock Divider. + fields: + - name: RATIO + description: Selects divide ratio of module clock. + bit_offset: 0 + bit_size: 3 + enum: RATIO +fieldset/CLKSEL: + description: Clock Select for Ultra Low Power peripherals. + fields: + - name: LFCLK_SEL + description: Selects LFCLK as clock source if enabled. + bit_offset: 1 + bit_size: 1 + - name: MFCLK_SEL + description: Selects MFCLK as clock source if enabled. + bit_offset: 2 + bit_size: 1 + - name: SYSCLK_SEL + description: Selects SYSCLK as clock source if enabled. + bit_offset: 3 + bit_size: 1 +fieldset/CPU_INT: + description: Interrupt clear. + fields: + - name: RXFIFO_OVF + description: Clear RXFIFO overflow event. + bit_offset: 0 + bit_size: 1 + - name: PER + description: Clear Parity error event. + bit_offset: 1 + bit_size: 1 + - name: RTOUT + description: Clear SPI Receive Time-Out Event. + bit_offset: 2 + bit_size: 1 + - name: RX + description: Clear Receive FIFO event. + bit_offset: 3 + bit_size: 1 + - name: TX + description: Clear Transmit FIFO event. + bit_offset: 4 + bit_size: 1 + - name: TXEMPTY + description: Clear Transmit FIFO Empty event. + bit_offset: 5 + bit_size: 1 + - name: IDLE + description: Clear SPI IDLE mode event. + bit_offset: 6 + bit_size: 1 + - name: DMA_DONE_RX + description: Clear DMA Done 1 event for RX. + bit_offset: 7 + bit_size: 1 + - name: DMA_DONE_TX + description: Clear DMA Done 1 event for TX. + bit_offset: 8 + bit_size: 1 + - name: TXFIFO_UNF + description: Clear TXFIFO underflow event. + bit_offset: 9 + bit_size: 1 + - name: RXFULL + description: Clear RX FIFO underflow event. + bit_offset: 10 + bit_size: 1 +fieldset/CPU_INT_IIDX: + description: Interrupt Index Register. + fields: + - name: STAT + description: Interrupt index status. + bit_offset: 0 + bit_size: 8 + enum: CPU_INT_IIDX_STAT +fieldset/CTL0: + description: SPI control register 0. + fields: + - name: DSS + description: 'Data Size Select. Values 0 - 2 are reserved and shall not be used. 3h = 4_BIT : 4-bit data SPI allows only values up to 16 Bit.' + bit_offset: 0 + bit_size: 5 + enum: DSS + - name: FRF + description: Frame format Select. + bit_offset: 5 + bit_size: 2 + enum: FRF + - name: PACKEN + description: Packing Enable. When 1, packing feature is enabled inside the IP When 0, packing feature is disabled inside the IP. + bit_offset: 7 + bit_size: 1 + - name: SPO + description: CLKOUT polarity (Motorola SPI frame format only). + bit_offset: 8 + bit_size: 1 + - name: SPH + description: CLKOUT phase (Motorola SPI frame format only) This bit selects the clock edge that captures data and enables it to change state. It has the most impact on the first bit transmitted by either permitting or not permitting a clock transition before the first data capture edge. + bit_offset: 9 + bit_size: 1 + enum: SPH + - name: CSSEL + description: Select the CS line to control on data transfer This bit is applicable for both controller/target mode. + bit_offset: 12 + bit_size: 2 + enum: CSSEL + - name: CSCLR + description: Clear shift register counter on CS inactive This bit is relevant only in the peripheral, CTL1.CP=0. + bit_offset: 14 + bit_size: 1 +fieldset/CTL1: + description: SPI control register 1. + fields: + - name: ENABLE + description: SPI enable. + bit_offset: 0 + bit_size: 1 + - name: LBM + description: Loop back mode. + bit_offset: 1 + bit_size: 1 + - name: CP + description: Controller or peripheral mode select. This bit can be modified only when SPI is disabled, CTL1.ENABLE=0. + bit_offset: 2 + bit_size: 1 + - name: POD + description: 'Peripheral-mode: Data output disabled This bit is relevant only in Peripheral mode. In multiple-peripheral system topologies, SPI controller can broadcast a message to all peripherals, while only one peripheral drives the line. POD can be used by the SPI peripheral to disable driving data on the line.' + bit_offset: 3 + bit_size: 1 + - name: MSB + description: MSB first select. Controls the direction of the receive and transmit shift register. + bit_offset: 4 + bit_size: 1 + - name: PREN + description: Parity receive enable If enabled, parity reception check will be done for both controller and peripheral modes In case of a parity miss-match the parity error flag RIS.PER will be set. + bit_offset: 5 + bit_size: 1 + - name: PES + description: Even Parity Select. + bit_offset: 6 + bit_size: 1 + - name: PTEN + description: Parity transmit enable If enabled, parity transmission will be done for both controller and peripheral modes. + bit_offset: 8 + bit_size: 1 + - name: CDENABLE + description: Command/Data Mode enable. + bit_offset: 11 + bit_size: 1 + - name: CDMODE + description: 'Command/Data Mode Value When CTL1.CDENABLE is 1, CS3 line is used as C/D signal to distinguish between Command (C/D low) and Data (C/D high) information. When a value is written into the CTL1.CDMODE bits, the C/D (CS3) line will go low for the given numbers of byte which are sent by the SPI, starting with the next value to be transmitted after which, C/D line will go high automatically 0: Manual mode with C/D signal as High 1-14: C/D is low while this number of bytes are being sent after which, this field sets to 0 and C/D goes high. Reading this field at any time returns the remaining number of command bytes. 15: Manual mode with C/D signal as Low.' + bit_offset: 12 + bit_size: 4 + enum: CDMODE + - name: REPEATTX + description: 'Counter to repeat last transfer 0: repeat last transfer is disabled. x: repeat the last transfer with the given number. The transfer will be started with writing a data into the TX Buffer. Sending the data will be repeated with the given value, so the data will be transferred X+1 times in total. The behavior is identical as if the data would be written into the TX Buffer that many times as defined by the value here. It can be used to clean a transfer or to pull a certain amount of data by a peripheral.' + bit_offset: 16 + bit_size: 8 + - name: RXTIMEOUT + description: Receive Timeout (only for Peripheral mode) Defines the number of Clock Cycles before after which the Receive Timeout flag RTOUT is set. The time is calculated using the control register for the clock selection and divider in the Controller mode configuration. A value of 0 disables this function. + bit_offset: 24 + bit_size: 6 +fieldset/DMA_TRIG_RX: + description: Interrupt clear. + fields: + - name: RTOUT + description: Clear SPI Receive Time-Out event. + bit_offset: 2 + bit_size: 1 + - name: RX + description: Clear Receive FIFO event. + bit_offset: 3 + bit_size: 1 +fieldset/DMA_TRIG_RX_IIDX: + description: Interrupt Index Register. + fields: + - name: STAT + description: Interrupt index status. + bit_offset: 0 + bit_size: 8 + enum: DMA_TRIG_RX_IIDX_STAT +fieldset/DMA_TRIG_TX: + description: Interrupt clear. + fields: + - name: TX + description: Clear Transmit FIFO event. + bit_offset: 4 + bit_size: 1 +fieldset/DMA_TRIG_TX_IIDX: + description: Interrupt Index Register. + fields: + - name: STAT + description: Interrupt index status. + bit_offset: 0 + bit_size: 8 + enum: DMA_TRIG_TX_IIDX_STAT +fieldset/EVT_MODE: + description: Event Mode. + fields: + - name: CPU + description: Event line mode select for event corresponding to [IPSTANDARD.INT_EVENT0]. + bit_offset: 0 + bit_size: 2 + enum: INT_CFG + - name: DMA_TRIG_RX + description: Event line mode select for event corresponding to [IPSTANDARD.INT_EVENT1]. + bit_offset: 2 + bit_size: 2 + enum: INT_CFG + - name: DMA_TRIG_TX + description: Event line mode select for event corresponding to [IPSTANDARD.INT_EVENT2]. + bit_offset: 4 + bit_size: 2 + enum: INT_CFG +fieldset/GPRCM_STAT: + description: Status Register. + fields: + - name: RESETSTKY + description: This bit indicates, if the peripheral was reset, since this bit was cleared by RESETSTKYCLR in the RSTCTL register. + bit_offset: 16 + bit_size: 1 +fieldset/IFLS: + description: Interrupt FIFO Level Select Register. + fields: + - name: TXIFLSEL + description: SPI Transmit Interrupt FIFO Level Select The trigger points for the transmit interrupt are as follows:. + bit_offset: 0 + bit_size: 3 + enum: TXIFLSEL + - name: RXIFLSEL + description: SPI Receive Interrupt FIFO Level Select The trigger points for the receive interrupt are as follows:. + bit_offset: 3 + bit_size: 3 + enum: RXIFLSEL +fieldset/INTCTL: + description: Interrupt control register. + fields: + - name: INTEVAL + description: Writing a 1 to this field re-evaluates the interrupt sources. + bit_offset: 0 + bit_size: 1 +fieldset/PDBGCTL: + description: Peripheral Debug Control. + fields: + - name: FREE + description: Free run control. + bit_offset: 0 + bit_size: 1 + - name: SOFT + description: Soft halt boundary control. This function is only available, if [FREE] is set to 'STOP'. + bit_offset: 1 + bit_size: 1 + enum: SOFT +fieldset/PWREN: + description: Power enable. + fields: + - name: ENABLE + description: Enable the power. + bit_offset: 0 + bit_size: 1 + - name: KEY + description: KEY to allow Power State Change 26h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: PWREN_KEY +fieldset/RSTCTL: + description: Reset Control. + fields: + - name: RESETASSERT + description: Assert reset to the peripheral. + bit_offset: 0 + bit_size: 1 + - name: RESETSTKYCLR + description: Clear the RESETSTKY bit in the STAT register. + bit_offset: 1 + bit_size: 1 + - name: KEY + description: Unlock key B1h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: RESET_KEY +fieldset/RXDATA: + description: RXDATA Register. + fields: + - name: DATA + description: Received Data When PACKEN=1,two entries of the FIFO are returned as a 32-bit value. When PACKEN=0, 1 entry of FIFO is returned as 16-bit value. As data values are removed by the receive logic from the incoming data frame, they are placed into the entry in the receive FIFO, pointed to by the current FIFO write pointer. Received data less than 16 bits is automatically right justified in the receive buffer. + bit_offset: 0 + bit_size: 16 +fieldset/STAT: + description: Status Register. + fields: + - name: TFE + description: Transmit FIFO empty. + bit_offset: 0 + bit_size: 1 + - name: TNF + description: Transmit FIFO not full. + bit_offset: 1 + bit_size: 1 + - name: RFE + description: Receive FIFO empty. + bit_offset: 2 + bit_size: 1 + - name: RNF + description: Receive FIFO not full. + bit_offset: 3 + bit_size: 1 + - name: BUSY + description: Busy. + bit_offset: 4 + bit_size: 1 +fieldset/TXDATA: + description: TXDATA Register. + fields: + - name: DATA + description: Transmit Data When read, last written value will be returned. If the last write to this field was a 32-bit write (with PACKEN=1), 32-bits will be returned and if the last write was a 16-bit write (PACKEN=0), those 16-bits will be returned. When written, one or two FIFO entries will be written depending on PACKEN value. Data values are removed from the transmit FIFO one value at a time by the transmit logic. It is loaded into the transmit serial shifter, then serially shifted out onto the TXD output pin at the programmed bit rate. When a data size of less than 16 bits is selected, the user must right-justify data written to the transmit FIFO. The transmit logic ignores the unused bits. + bit_offset: 0 + bit_size: 16 +enum/CDMODE: + bit_size: 4 + variants: + - name: DATA + description: 'Manual mode: Data.' + value: 0 + - name: COMMAND + description: 'Manual mode: Command.' + value: 15 +enum/CLKCFG_KEY: + bit_size: 8 + variants: + - name: KEY + value: 169 +enum/CPU_INT_IIDX_STAT: + bit_size: 8 + variants: + - name: NO_INTR + description: No interrupt pending. + value: 0 + - name: RXFIFO_OFV_EVT + description: RX FIFO Overflow Event/interrupt pending. + value: 1 + - name: PER_EVT + description: Transmit Parity Event/interrupt pending. + value: 2 + - name: RTOUT_EVT + description: SPI receive time-out interrupt. + value: 3 + - name: RX_EVT + description: Receive Event/interrupt pending. + value: 4 + - name: TX_EVT + description: Transmit Event/interrupt pending. + value: 5 + - name: TX_EMPTY + description: Transmit Buffer Empty Event/interrupt pending. + value: 6 + - name: IDLE_EVT + description: End of Transmit Event/interrupt pending. + value: 7 + - name: DMA_DONE_RX_EVT + description: DMA Done for Receive Event/interrupt pending. + value: 8 + - name: DMA_DONE_TX_EVT + description: DMA Done for Transmit Event/interrupt pending. + value: 9 + - name: TXFIFO_UNF_EVT + description: TX FIFO underflow interrupt. + value: 10 + - name: RXFULL_EVT + description: RX FIFO Full Interrupt. + value: 11 +enum/CSSEL: + bit_size: 2 + variants: + - name: CSSEL_0 + description: 'CS line select: 0.' + value: 0 + - name: CSSEL_1 + description: 'CS line select: 1.' + value: 1 + - name: CSSEL_2 + description: 'CS line select: 2.' + value: 2 + - name: CSSEL_3 + description: 'CS line select: 3.' + value: 3 +enum/DMA_TRIG_RX_IIDX_STAT: + bit_size: 8 + variants: + - name: NO_INTR + description: No interrupt pending. + value: 0 + - name: RTOUT_EVT + description: SPI receive time-out interrupt. + value: 3 + - name: RX_EVT + description: Receive Event/interrupt pending. + value: 4 +enum/DMA_TRIG_TX_IIDX_STAT: + bit_size: 8 + variants: + - name: NO_INTR + description: No interrupt pending. + value: 0 + - name: TX_EVT + description: Transmit Event/interrupt pending. + value: 5 +enum/DSS: + bit_size: 5 + variants: + - name: DSS_4 + description: 'Data Size Select bits: 4.' + value: 3 + - name: DSS_5 + description: 'Data Size Select bits: 5.' + value: 4 + - name: DSS_6 + description: 'Data Size Select bits: 6.' + value: 5 + - name: DSS_7 + description: 'Data Size Select bits: 7.' + value: 6 + - name: DSS_8 + description: 'Data Size Select bits: 8.' + value: 7 + - name: DSS_9 + description: 'Data Size Select bits: 9.' + value: 8 + - name: DSS_10 + description: 'Data Size Select bits: 10.' + value: 9 + - name: DSS_11 + description: 'Data Size Select bits: 11.' + value: 10 + - name: DSS_12 + description: 'Data Size Select bits: 12.' + value: 11 + - name: DSS_13 + description: 'Data Size Select bits: 13.' + value: 12 + - name: DSS_14 + description: 'Data Size Select bits: 14.' + value: 13 + - name: DSS_15 + description: 'Data Size Select bits: 15.' + value: 14 + - name: DSS_16 + description: 'Data Size Select bits: 16.' + value: 15 +enum/FRF: + bit_size: 2 + variants: + - name: MOTOROLA_3WIRE + description: Motorola SPI frame format (3 wire mode). + value: 0 + - name: MOTOROLA_4WIRE + description: Motorola SPI frame format (4 wire mode). + value: 1 + - name: TI_SYNC + description: TI synchronous serial frame format. + value: 2 + - name: MIRCOWIRE + description: National Microwire frame format. + value: 3 +enum/INT_CFG: + bit_size: 2 + variants: + - name: DISABLE + description: The interrupt or event line is disabled. + value: 0 + - name: SOFTWARE + description: The interrupt or event line is in software mode. Software must clear the RIS. + value: 1 + - name: HARDWARE + description: The interrupt or event line is in hardware mode. The hardware (another module) clears automatically the associated RIS flag. + value: 2 +enum/PWREN_KEY: + bit_size: 8 + variants: + - name: KEY + value: 38 +enum/RATIO: + bit_size: 3 + variants: + - name: DIV_BY_1 + description: Do not divide clock source. + value: 0 + - name: DIV_BY_2 + description: Divide clock source by 2. + value: 1 + - name: DIV_BY_3 + description: Divide clock source by 3. + value: 2 + - name: DIV_BY_4 + description: Divide clock source by 4. + value: 3 + - name: DIV_BY_5 + description: Divide clock source by 5. + value: 4 + - name: DIV_BY_6 + description: Divide clock source by 6. + value: 5 + - name: DIV_BY_7 + description: Divide clock source by 7. + value: 6 + - name: DIV_BY_8 + description: Divide clock source by 8. + value: 7 +enum/RESET_KEY: + bit_size: 8 + variants: + - name: KEY + value: 177 +enum/RXIFLSEL: + bit_size: 3 + variants: + - name: LVL_OFF + description: Reserved. + value: 0 + - name: LVL_1_4 + description: RX FIFO >= 1/4 full. + value: 1 + - name: LVL_1_2 + description: RX FIFO >= 1/2 full (default). + value: 2 + - name: LVL_3_4 + description: RX FIFO >= 3/4 full. + value: 3 + - name: LVL_RES4 + description: Reserved. + value: 4 + - name: LVL_FULL + description: RX FIFO is full. + value: 5 + - name: LVL_RES6 + description: Reserved. + value: 6 + - name: LEVEL_1 + description: Trigger when RX FIFO contains >= 1 frame. + value: 7 +enum/SOFT: + bit_size: 1 + variants: + - name: IMMEDIATE + description: The peripheral will halt immediately, even if the resultant state will result in corruption if the system is restarted. + value: 0 + - name: DELAYED + description: The peripheral blocks the debug freeze until it has reached a boundary where it can resume without corruption. + value: 1 +enum/SPH: + bit_size: 1 + variants: + - name: FIRST + description: Data is captured on the first clock edge transition. + value: 0 + - name: SECOND + description: Data is captured on the second clock edge transition. + value: 1 +enum/TXIFLSEL: + bit_size: 3 + variants: + - name: LVL_OFF + description: Reserved. + value: 0 + - name: LVL_3_4 + description: TX FIFO <= 3/4 empty. + value: 1 + - name: LVL_1_2 + description: TX FIFO <= 1/2 empty (default). + value: 2 + - name: LVL_1_4 + description: TX FIFO <= 1/4 empty. + value: 3 + - name: LVL_RES4 + description: Reserved. + value: 4 + - name: LVL_EMPTY + description: TX FIFO is empty. + value: 5 + - name: LVL_RES6 + description: Reserved. + value: 6 + - name: LEVEL_1 + description: Trigger when TX FIFO has >= 1 frame free Should be used with DMA. + value: 7 diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index 23ca9a8..02ac979 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -2,6 +2,9 @@ use crate::util::RegexMap; pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ (".*:uart", "v1"), + // One version: hw_spi.h is a single shared IP description and every legacy instance has the + // whole of it, including the DMA trigger groups the C110x SVD leaves out. + (".*:spi", "v1"), (".*:gpio", "v1"), (".*:dma", "v1"), (".*:i2c", "v1"), diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index a3d0f22..9554673 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -132,7 +132,6 @@ fn register_blocks_written(chip: &Chip, name: &str) -> anyhow::Result<()> { PeripheralType::Npu, PeripheralType::Rtc, PeripheralType::Spgss, - PeripheralType::Spi, PeripheralType::Usbfs, PeripheralType::Wuc, ]; diff --git a/mspm0-metapac-gen/src/peripheral.rs b/mspm0-metapac-gen/src/peripheral.rs index ef8bd70..823d839 100644 --- a/mspm0-metapac-gen/src/peripheral.rs +++ b/mspm0-metapac-gen/src/peripheral.rs @@ -24,6 +24,7 @@ const GENERATE_PERIPHERALS: &[PeripheralType] = &[ PeripheralType::Iomux, PeripheralType::Mathacl, PeripheralType::Opa, + PeripheralType::Spi, PeripheralType::Sysctl, PeripheralType::Tim, PeripheralType::Trng, diff --git a/transforms/SPI.yaml b/transforms/SPI.yaml new file mode 100644 index 0000000..8f6b00f --- /dev/null +++ b/transforms/SPI.yaml @@ -0,0 +1,202 @@ +# Transform for the standalone SPI register map, based on SPI0 on G151x, whose three interrupt +# groups are already named CPU_INT/DMA_TRIG_RX/DMA_TRIG_TX as hw_spi.h has them. The other twelve +# SVDs call them INT_EVENT0-2, and the C110x/MSPS003FX pair describe only the first; the header is +# one shared IP description, so the widest SVD is the block and the metadata says who has what. +transforms: + - !DeleteUselessEnums + + - !DeleteFieldsets + from: .* + useless: true + + # However DeleteUselessEnums does not delete MIS and IMASK (due to CLR being 0 and SET being 1) + # + # TODO: Add this to chiptool + - !DeleteEnumsWithVariants + variants: + 0: CLR + 1: SET + + # BUSY is useless + - !DeleteEnumsWithVariants + variants: + 0: CLEARED + 1: SET + + # FREE is useless + - !DeleteEnumsWithVariants + variants: + 0: STOP + 1: RUN + + # RESETASSERT is useless + - !DeleteEnumsWithVariants + variants: + 0: NOP + 1: ASSERT + + # RESETSTKY is useless + - !DeleteEnumsWithVariants + variants: + 0: NORES + 1: RESET + + # RESETSTKYCLR is useless + - !DeleteEnumsWithVariants + variants: + 0: NOP + 1: CLR + + # INTEVAL is useless + - !DeleteEnumsWithVariants + variants: + 0: DISABLE + 1: EVAL + + # TODO: Something better than a cursed rename as merge + - !MergeBlocks + from: SPI0 + main: SPI0 + to: SPI + + # Remove prefixes + - !RenameRegisters + block: .* + from: SPI0_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: SPI0_(.+) + to: $1 + + - !Rename + type: All + from: SPI0_(.+) + to: $1 + + ## Interrupts + # The three groups keep separate IIDX enums: their value sets differ, as in uart_v1. + - !MergeFieldsets + from: CPU_INT_(IMASK|ISET|MIS|RIS|ICLR) + to: CPU_INT + check: NoCheck + + - !RenameRegisters + block: .* + from: CPU_INT_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: CPU_INT_(.+) + to: $1 + + - !MergeFieldsets + from: DMA_TRIG_RX_(IMASK|ISET|MIS|RIS|ICLR) + to: DMA_TRIG_RX + check: NoCheck + + - !RenameRegisters + block: .* + from: DMA_TRIG_RX_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: DMA_TRIG_RX_(.+) + to: $1 + + - !MergeFieldsets + from: DMA_TRIG_TX_(IMASK|ISET|MIS|RIS|ICLR) + to: DMA_TRIG_TX + check: NoCheck + + - !RenameRegisters + block: .* + from: DMA_TRIG_TX_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: DMA_TRIG_TX_(.+) + to: $1 + + ## Status flags and the polarity bit. unicommspi_v1 describes the same SPI function and gives + ## none of these an enum; TFE and RFE next to TNF/RNF in the same fieldset already have none, + ## and REPEATTX names only its disabled value. + - !DeleteEnums + from: (BUSY|RNF|TNF|REPEATTX|SPO) + soft: false + + ## CTL1 and STAT describe the command/data framing with the same two values + - !MergeEnums + from: (CTL1|STAT)_CDMODE + to: CDMODE + + ## Add missing keys + - !AddFields + fieldset: RSTCTL + fields: + - name: KEY + description: Unlock key B1h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: RESET_KEY + + - !AddFields + fieldset: PWREN + fields: + - name: KEY + description: KEY to allow Power State Change 26h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: PWREN_KEY + + - !AddFields + fieldset: CLKCFG + fields: + - name: KEY + description: KEY to Allow State Change A9h = KEY to allow write access to this register + bit_offset: 24 + bit_size: 8 + enum: CLKCFG_KEY + + - !Add + ir: + enum/RESET_KEY: + bit_size: 8 + variants: + - name: KEY + value: 177 + enum/PWREN_KEY: + bit_size: 8 + variants: + - name: KEY + value: 38 + enum/CLKCFG_KEY: + bit_size: 8 + variants: + - name: KEY + value: 0xA9 + + ## EVT_MODE + - !MergeEnums + from: INT(\d+)_CFG + to: INT_CFG + + - !RenameFields + fieldset: EVT_MODE + from: INT0_CFG + to: CPU + - !RenameFields + fieldset: EVT_MODE + from: INT1_CFG + to: DMA_TRIG_RX + - !RenameFields + fieldset: EVT_MODE + from: INT2_CFG + to: DMA_TRIG_TX + + ## Cleanup + - !Sort From e252c86dd3877dca28581c32cc01fe1810514f5d Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Wed, 26 Aug 2026 13:45:10 +0100 Subject: [PATCH 125/134] perimap: settle what the RTC split actually is The note said "legacy versus B" and left two register blocks to write. The calendar registers are byte-identical in hw_rtc.h and hw_lfss.h, at the same absolute address, so there are not two RTCs - there is an RTC-only peripheral and an LFSS that wraps the same group with tamper I/O, a watchdog and scratch-pad memory. Two things a writer would otherwise get wrong. The legacy GEN_EVENT group is spaced 4 bytes, alone in the portfolio; hw_rtc.h and SLAU846 SS37.4 both say so and the SVD normalises it. And RTC_B is not a block: sysconfig emits it and LFSS as two instances at one address on five families, while four others have an LFSS and no RTC_B node at all. --- mspm0-data-gen/src/perimap.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index 02ac979..703faf5 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -60,10 +60,26 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ // the TRMs and with each other about CTL0 bits 1 and 2, which the YAML resolves in the TRMs' // favour. (".*:vref", "v1"), - // No RTC entry: a version names a register block, and none has been curated. The variants, - // for whoever writes them: sysconfig calls the standalone pre-LFSS peripheral the "legacy" - // RTC, and the ones inside the LFSS are RTC_A when the LFSS has an independent VBAT supply - // and RTC_B when it is powered from VDD. `mspm0g..0x` has the legacy one, everything else B. + // No RTC entry: a version names a register block, and none has been curated. What the split + // actually is, settled against hw_rtc.h, hw_lfss.h, SLAU846 SS37.4 and the SVDs: + // + // The RTC calendar registers are byte-identical everywhere - CLKCTL through RTCLOCK, 1100h to + // 11FCh, same names and same offsets in both headers, at the same absolute address since every + // instance is based at 0x40094000. The two blocks differ in what surrounds them, not in the + // RTC. The legacy peripheral on `mspm0g..0x` has a GPRCM (PWREN/RSTCTL/CLKCFG/STAT), a + // publisher port and no subscriber, and its GEN_EVENT group is spaced 4 bytes where every + // other MSPM0 interrupt group is spaced 8 - hw_rtc.h and the TRM's register table both say so, + // and the SVD is the one that disagrees. The LFSS has no GPRCM at all, has FSUB_0 as well as + // FPUB_0, spaces both interrupt groups 8 bytes, and wraps the same RTC group together with + // tamper I/O (1200h), a watchdog (1300h) and scratch-pad memory (1400h). + // + // So `rtc_b` should not exist as a block: on g151x, g351x, g518x, l112x and l211x sysconfig + // emits `RTC_B` and `LFSS` as two instances at the same address, and they are one + // peripheral. The work is `rtc_legacy` for the four `mspm0g..0x` families and `lfss_v1` for + // the nine that have an LFSS, after which the duplicate `RTC_B` instance should go. + // + // Nor is sysconfig's `RTC_B` node a reliable "has an RTC" signal: c1105_c1106, h321x, l122x + // and l222x have an LFSS with no `RTC_B` node, and all four datasheets advertise an RTC. (".*:factoryregion", "v1"), // SLAU893 describes two C-series SYSCTLs, "SYSCTL_C1103_C1104" and "SYSCTL_C1105_C1106". The // latter is a superset: it adds the HFXT and LFXT crystal drivers, `MCLKCFG.FLASHWAIT` and the From ae5608f24061ca76fcea70d7fe021ef7270de02e Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Thu, 27 Aug 2026 12:24:22 +0100 Subject: [PATCH 126/134] verify: say which unwritten blocks are not owed one GpAmp and Event are on the list for a different reason from the other thirteen. Neither has registers of its own: the GPAMP is configured through SYSCTL.PMUOPAMP, which every SYSCTL block already generates, and the event fabric is the per-peripheral FSUB/FPUB registers every block already carries. TI ships no base address, no register struct and no driverlib module for either. Both are reachable today, so reading the list as fifteen pending blocks overstates the work by two. --- mspm0-data-gen/src/verify.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 9554673..dbff4ac 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -117,7 +117,19 @@ fn register_blocks_exist(chip: &Chip, name: &str) -> anyhow::Result<()> { /// Adding a type to `UNWRITTEN_BLOCKS` is a decision to ship it unaddressable. Writing the block /// and its `perimap` entry is the other option, and is what the list is for. fn register_blocks_written(chip: &Chip, name: &str) -> anyhow::Result<()> { - /// Types with no `data/registers` block yet, each an instance a consumer cannot reach. + /// Types with no `data/registers` block, for one of two reasons. + /// + /// Most are unwritten: the block exists in the hardware and nobody has curated it, so the + /// instance sits in the metadata unreachable. `GpAmp` and `Event` are not that. They have no + /// registers of their own at all, so no block is owed and writing one is not the fix: + /// + /// - `GpAmp` is configured through `SYSCTL.PMUOPAMP`, which every SYSCTL block already + /// generates. TI ships no `GPAMP_BASE` and no register struct, only `__MSPM0_HAS_GPAMP__`, + /// and driverlib's `dl_gpamp.c` writes `SYSCTL->SOCLOCK.PMUOPAMP`. sysconfig gives the + /// instance no address either. + /// - `Event` has no header, no struct, no base address in any device header and no driverlib + /// module. The event fabric is the per-peripheral `FSUB`/`FPUB` registers, which every block + /// already carries; sysconfig models the fabric as a node with an address anyway. const UNWRITTEN_BLOCKS: &[PeripheralType] = &[ PeripheralType::Aes, PeripheralType::AesAdv, From dd4d1cd866245f2c1075ede2a22342735018d617 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Thu, 27 Aug 2026 12:57:26 +0100 Subject: [PATCH 127/134] wuc: add the wake-up controller register block Two event subscriber ports, which are the whole programmable surface: the wake behaviour itself is automatic and SLAU847 SS3.3.1.3 calls it transparent to software. They let a generic event channel wake the device rather than an NVIC interrupt, and had no accessor. CHANID is four bits, per SLAU847 Table 3-26 and the range in the field's own description. The 21 SVDs give it 2, 3, 4 or 9 and hw_wuc.h masks it 0xFF; adc_v1 and comp_dacout already carry the same field at four. --- data/registers/wuc_v1.yaml | 20 ++++++++++++++++++++ mspm0-data-gen/src/perimap.rs | 2 ++ mspm0-data-gen/src/verify.rs | 1 - mspm0-metapac-gen/src/peripheral.rs | 1 + 4 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 data/registers/wuc_v1.yaml diff --git a/data/registers/wuc_v1.yaml b/data/registers/wuc_v1.yaml new file mode 100644 index 0000000..bcd3ea1 --- /dev/null +++ b/data/registers/wuc_v1.yaml @@ -0,0 +1,20 @@ +block/WUC: + description: 'The wake-up controller. It watches for enabled interrupts while PD1 is power-gated in STOP and STANDBY, when the CPU and NVIC cannot, and hands the device back to the PMCU with the interrupt latched so the core still sees it after powering up. That needs no configuration and SLAU847 SS3.3.1.3 calls it transparent to software: these two subscriber ports are the whole programmable surface, and they let a generic event channel wake the device instead of an NVIC interrupt.' + items: + - name: FSUB + description: Subscriber Port. + array: + len: 2 + stride: 4 + byte_offset: 1024 + fieldset: FPORT +fieldset/FPORT: + description: Subscriber Configuration Register. + # CHANID is four bits: SLAU847 Table 3-26 numbers it 3-0, and the range in its own description + # needs no more. The SVDs give it 2, 3, 4 or 9 bits across the 21 files and hw_wuc.h masks it + # 0xFF; adc_v1 and comp_dacout already carry the same field at four. + fields: + - name: CHANID + description: 0 = disconnected. 1-15 = connected to channelID = CHANID. + bit_offset: 0 + bit_size: 4 diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index 703faf5..33876f2 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -11,6 +11,8 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ (".*:beeper", "v1"), (".*:cpuss", "v1"), (".*:iomux", "v1"), + // Two subscriber ports and nothing else; every family has the same pair. + (".*:wuc", "v1"), (".*:mathacl", "v1"), (".*:opa", "v1"), // A TIMB instance is a basic timer: its counters live at 0x1100 where the general-purpose diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index dbff4ac..8786d76 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -145,7 +145,6 @@ fn register_blocks_written(chip: &Chip, name: &str) -> anyhow::Result<()> { PeripheralType::Rtc, PeripheralType::Spgss, PeripheralType::Usbfs, - PeripheralType::Wuc, ]; let missing: Vec<_> = chip diff --git a/mspm0-metapac-gen/src/peripheral.rs b/mspm0-metapac-gen/src/peripheral.rs index 823d839..7c4a252 100644 --- a/mspm0-metapac-gen/src/peripheral.rs +++ b/mspm0-metapac-gen/src/peripheral.rs @@ -35,6 +35,7 @@ const GENERATE_PERIPHERALS: &[PeripheralType] = &[ PeripheralType::UnicommSpi, PeripheralType::UnicommUart, PeripheralType::Vref, + PeripheralType::Wuc, PeripheralType::Wwdt, ]; From 2348ad67256854a6bf9ff16530f14c59e229a9a7 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Thu, 27 Aug 2026 13:17:34 +0100 Subject: [PATCH 128/134] int_group: name the WUC subscriber ports uniformly Three families called them WUC_FSUB0/1 and eight EVENT_SUB_PORT0/1, so a consumer matching on the name got a different answer per family. WUC_FSUB0 is the TRM's wording rather than the datasheets', and is chosen against the datasheet on purpose: a group member becomes a variant of the generated Group0 enum, and this name maps one-to-one onto the register a driver writes. Existence still comes from the datasheet, which is why c110x, l110x and msps003fx have neither. --- data/int_group/mspm0g110x.yaml | 4 ++-- data/int_group/mspm0g151x.yaml | 4 ++-- data/int_group/mspm0g310x.yaml | 4 ++-- data/int_group/mspm0g518x.yaml | 4 ++-- data/int_group/mspm0l122x.yaml | 4 ++-- data/int_group/mspm0l130x.yaml | 4 ++-- data/int_group/mspm0l134x.yaml | 4 ++-- data/int_group/mspm0l222x.yaml | 4 ++-- mspm0-data-gen/src/int_group.rs | 13 +++++++++++++ 9 files changed, 29 insertions(+), 16 deletions(-) diff --git a/data/int_group/mspm0g110x.yaml b/data/int_group/mspm0g110x.yaml index f824734..95f7e17 100644 --- a/data/int_group/mspm0g110x.yaml +++ b/data/int_group/mspm0g110x.yaml @@ -8,9 +8,9 @@ groups: iidx: 2 - name: FLASHCTL # NVMNW iidx: 3 - - name: EVENT_SUB_PORT0 + - name: WUC_FSUB0 iidx: 4 - - name: EVENT_SUB_PORT1 + - name: WUC_FSUB1 iidx: 5 - name: SYSCTL iidx: 6 diff --git a/data/int_group/mspm0g151x.yaml b/data/int_group/mspm0g151x.yaml index dc7d0a9..0eda1a1 100644 --- a/data/int_group/mspm0g151x.yaml +++ b/data/int_group/mspm0g151x.yaml @@ -8,9 +8,9 @@ groups: iidx: 2 - name: FLASHCTL # NVMNW iidx: 3 - - name: EVENT_SUB_PORT0 + - name: WUC_FSUB0 iidx: 4 - - name: EVENT_SUB_PORT1 + - name: WUC_FSUB1 iidx: 5 - name: SYSCTL iidx: 6 diff --git a/data/int_group/mspm0g310x.yaml b/data/int_group/mspm0g310x.yaml index 82f7fab..e832d60 100644 --- a/data/int_group/mspm0g310x.yaml +++ b/data/int_group/mspm0g310x.yaml @@ -8,9 +8,9 @@ groups: iidx: 2 - name: FLASHCTL # NVMNW iidx: 3 - - name: EVENT_SUB_PORT0 + - name: WUC_FSUB0 iidx: 4 - - name: EVENT_SUB_PORT1 + - name: WUC_FSUB1 iidx: 5 - name: SYSCTL iidx: 6 diff --git a/data/int_group/mspm0g518x.yaml b/data/int_group/mspm0g518x.yaml index 2f95b75..69a9f2a 100644 --- a/data/int_group/mspm0g518x.yaml +++ b/data/int_group/mspm0g518x.yaml @@ -8,9 +8,9 @@ groups: iidx: 2 - name: FLASHCTL # NVMNW iidx: 3 - - name: EVENT_SUB_PORT0 + - name: WUC_FSUB0 iidx: 4 - - name: EVENT_SUB_PORT1 + - name: WUC_FSUB1 iidx: 5 - name: SYSCTL iidx: 6 diff --git a/data/int_group/mspm0l122x.yaml b/data/int_group/mspm0l122x.yaml index 96aa654..a2344e8 100644 --- a/data/int_group/mspm0l122x.yaml +++ b/data/int_group/mspm0l122x.yaml @@ -6,9 +6,9 @@ groups: iidx: 2 - name: FLASHCTL # NVMNW iidx: 3 - - name: EVENT_SUB_PORT0 + - name: WUC_FSUB0 iidx: 4 - - name: EVENT_SUB_PORT1 + - name: WUC_FSUB1 iidx: 5 - name: SYSCTL iidx: 6 diff --git a/data/int_group/mspm0l130x.yaml b/data/int_group/mspm0l130x.yaml index 39e947e..e268283 100644 --- a/data/int_group/mspm0l130x.yaml +++ b/data/int_group/mspm0l130x.yaml @@ -6,9 +6,9 @@ groups: iidx: 2 - name: FLASHCTL # NVMNW iidx: 3 - - name: EVENT_SUB_PORT0 + - name: WUC_FSUB0 iidx: 4 - - name: EVENT_SUB_PORT1 + - name: WUC_FSUB1 iidx: 5 - name: SYSCTL iidx: 6 diff --git a/data/int_group/mspm0l134x.yaml b/data/int_group/mspm0l134x.yaml index 39e947e..e268283 100644 --- a/data/int_group/mspm0l134x.yaml +++ b/data/int_group/mspm0l134x.yaml @@ -6,9 +6,9 @@ groups: iidx: 2 - name: FLASHCTL # NVMNW iidx: 3 - - name: EVENT_SUB_PORT0 + - name: WUC_FSUB0 iidx: 4 - - name: EVENT_SUB_PORT1 + - name: WUC_FSUB1 iidx: 5 - name: SYSCTL iidx: 6 diff --git a/data/int_group/mspm0l222x.yaml b/data/int_group/mspm0l222x.yaml index 8e458f4..1d5e2b8 100644 --- a/data/int_group/mspm0l222x.yaml +++ b/data/int_group/mspm0l222x.yaml @@ -6,9 +6,9 @@ groups: iidx: 2 - name: FLASHCTL # NVMNW iidx: 3 - - name: EVENT_SUB_PORT0 + - name: WUC_FSUB0 iidx: 4 - - name: EVENT_SUB_PORT1 + - name: WUC_FSUB1 iidx: 5 - name: SYSCTL iidx: 6 diff --git a/mspm0-data-gen/src/int_group.rs b/mspm0-data-gen/src/int_group.rs index 1dab366..a08ff38 100644 --- a/mspm0-data-gen/src/int_group.rs +++ b/mspm0-data-gen/src/int_group.rs @@ -3,6 +3,19 @@ //! Several peripherals can share one NVIC line; the handler tells them apart by reading the group's //! `IIDX`. Which value means which peripheral is in the TRM's interrupt tables and nowhere //! machine-readable, so it is transcribed here. +//! +//! **The datasheet decides whether a row exists; the name is chosen for the consumer.** Those are +//! separate questions and the wake-up controller's two subscriber ports are where they come apart. +//! +//! Every datasheet's interrupt table calls them `EVENT SUB PORT0`/`PORT1` and both TRMs call them +//! `WUC FSUB0`/`FSUB1`. Both are TI's own words. `WUC_FSUB0` is recorded because a group member +//! becomes a variant of the generated `Group0` enum, and that name maps one-to-one onto the +//! register a driver writes - `WUC.fsub(0)` - where the datasheet spelling needs a lookup. Do not +//! "correct" it to the datasheet wording; it was chosen against it, deliberately, on 2026-08-27. +//! +//! Existence is the other question, and there the datasheet wins outright. c110x, l110x and +//! msps003fx have no `IIDX` 4 or 5: their datasheets' tables stop at 3 and resume at 6, while both +//! TRMs list the subscriber ports for the whole series. Do not fill those in from the TRM. use std::collections::BTreeMap; From 21aae2de2539709b746637f9bea150dde41424ea Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Thu, 27 Aug 2026 13:17:34 +0100 Subject: [PATCH 129/134] generate: emit interrupt group members the header omits Group membership came from the device header's IRQn entries, with the YAML supplying only the IIDX. IRQn names peripherals, and the wake-up controller's two event subscriber ports are not peripherals - no header on any device lists them, though every datasheet's interrupt table gives them an IIDX. So both entries were dropped on all eleven families which declare them, and nothing could tell a consumer which group member had woken it after arming WUC.FSUB. That is the whole point of the block added in the previous commit. --- mspm0-data-gen/src/generate.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/mspm0-data-gen/src/generate.rs b/mspm0-data-gen/src/generate.rs index feae538..353e079 100644 --- a/mspm0-data-gen/src/generate.rs +++ b/mspm0-data-gen/src/generate.rs @@ -1009,6 +1009,29 @@ fn generate_irqs( )); } } + + // Members the device header does not list at all. `IRQn` names peripherals, and the + // wake-up controller's two event subscriber ports are not peripherals - no header names + // them on any device, though every datasheet's interrupt table gives them an IIDX. Taking + // the header as the membership list drops them, which leaves a consumer unable to tell + // which group member woke it after arming `WUC.FSUB`. + for mapping in group.iter() { + if entries.iter().any(|entry| entry == &mapping.name) { + continue; + } + + if let Some(existing) = interrupt + .group + .insert(mapping.iidx as u32, mapping.name.clone()) + { + return Err(anyhow::anyhow!( + "{chip_name}, {}: IIDX {} maps to both {existing} and {}", + interrupt.name, + mapping.iidx, + mapping.name + )); + } + } } Ok(interrupts) From e0a0775d3a6111d3c47739107d9fafd5ecd4b4d2 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Thu, 27 Aug 2026 13:25:56 +0100 Subject: [PATCH 130/134] dma: name gather mode in the EM enum DMACTL.EM value 1h was absent, so the generated enum said _RESERVED_1 for a mode the device implements and Dma::gather_mode already documents. Reaching it meant Em::from_bits(1). The block was extracted from an SVD of the older generation: those twelve omit the value and the nine newer ones enumerate it, while hw_dma.h has defined DMAEM_GATHERMODE as 1 throughout. Second time that extraction choice has cost this block something, after SRCWDTH/DSTWDTH. Every other enum here was diffed against the union of all 21 SVDs by value; this was the only gap. --- data/registers/dma_v1.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/data/registers/dma_v1.yaml b/data/registers/dma_v1.yaml index 1754328..d156865 100644 --- a/data/registers/dma_v1.yaml +++ b/data/registers/dma_v1.yaml @@ -309,10 +309,22 @@ enum/BURSTSZ: value: 3 enum/EM: bit_size: 2 + # GATHERMODE was missing until 2026-08-27: this block was extracted from an SVD of the older + # generation, and the twelve of those omit the value while the nine newer ones enumerate it. It is + # real - hw_dma.h defines DMA_DMACTL_DMAEM_GATHERMODE as 1, SLAU846 Table 5-3 lists it, and + # `Dma::gather_mode` in the metadata already says which devices implement it. Same cause as + # SRCWDTH/DSTWDTH being two bits here once. + # + # The TRM's prose is no help and contradicts itself twice: SS5.2.4 says the controller "has two + # extended modes" directly above a table of four, and SS5.2.4.1, titled Gather Mode, opens "In + # table mode (DMAEM = 1)". variants: - name: NORMAL description: Normal mode is related to transfers from SRC to DST. value: 0 + - name: GATHERMODE + description: Gather mode will read a data from an address table located at SA, and the data is transfered to the DA. + value: 1 - name: FILLMODE description: Fill mode will copy the SA register content as data to DA. value: 2 From 1798d41f4702b646408e6006ff8ff398ba1a87fa Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Thu, 27 Aug 2026 14:49:27 +0100 Subject: [PATCH 131/134] debugss: add the debug subsystem register block The useful half is the DSSM mailbox at 1100h-110Ch: two 32-bit buffers with flow control carrying messages between a debug probe and the target over SWD, which SLAU847 SS35.2.4 offers for talking to application software when no other interface is present. PWRUPIFG and PWRDWNIFG report a probe attaching and disconnecting, which is the only way software can know it is being debugged. Extracted from the G518x SVD rather than the widest. The C110x one adds a PINCM array holding SWCLK/SWDIO and a CPU_CONNECT_0 at 480h; hw_debugss.h has neither and SLAU893 Table 22-11 - the C-series TRM, for that same family - lists the fourteen registers here and calls every other offset reserved. TX and RX are named from the probe's side, so TXD is read-only to the CPU and reading it is the only way to clear TXCTL.TRANSMIT. TRANSMIT and RECEIVE keep their EMPTY/FULL enums for that reason, where the equivalent bits in spi_v1 were dropped. --- data/registers/debugss_v1.yaml | 262 ++++++++++++++++++++++++++++ mspm0-data-gen/src/perimap.rs | 3 + mspm0-data-gen/src/verify.rs | 1 - mspm0-metapac-gen/src/peripheral.rs | 1 + transforms/DEBUGSS.yaml | 63 +++++++ 5 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 data/registers/debugss_v1.yaml create mode 100644 transforms/DEBUGSS.yaml diff --git a/data/registers/debugss_v1.yaml b/data/registers/debugss_v1.yaml new file mode 100644 index 0000000..e893a24 --- /dev/null +++ b/data/registers/debugss_v1.yaml @@ -0,0 +1,262 @@ +block/CPU_INT: + items: + - name: IIDX + description: Interrupt index. + byte_offset: 0 + access: Read + fieldset: IIDX + - name: IMASK + description: Interrupt mask. + byte_offset: 8 + fieldset: INT + - name: RIS + description: Raw interrupt status. + byte_offset: 16 + access: Read + fieldset: INT + - name: MIS + description: Masked interrupt status. + byte_offset: 24 + access: Read + fieldset: INT + - name: ISET + description: Interrupt set. + byte_offset: 32 + access: Write + fieldset: INT + - name: ICLR + description: Interrupt clear. + byte_offset: 40 + access: Write + fieldset: INT +block/DEBUGSS: + description: 'The debug subsystem. Its useful half is the DSSM mailbox at 1100h-110Ch, a pair of 32-bit buffers with flow control that carry messages between a debug probe and the target over SWD, which SLAU847 SS35.2.4 offers for "communicating with application software running on the target device when no other communication interface is present". PWRUPIFG and PWRDWNIFG report a probe attaching and disconnecting.' + # TX and RX are named from the debug probe's side, not the CPU's. TXD is what the probe writes and + # the target reads - it is read-only here, and reading it is the only way target software can + # clear TXCTL.TRANSMIT. RXD is the other direction. A driver written as though TX means "transmit + # from this CPU" has them exactly backwards, which is why TRANSMIT and RECEIVE keep their + # EMPTY/FULL enums where the equivalent status bits in spi_v1 do not: the polarity is not + # guessable from the field name. + items: + - name: CPU_INT + array: + len: 1 + stride: 44 + byte_offset: 4128 + block: CPU_INT + - name: EVT_MODE + description: Event Mode. + byte_offset: 4320 + access: Read + fieldset: EVT_MODE + - name: DESC + description: Module Description. + byte_offset: 4348 + access: Read + fieldset: DESC + - name: TXD + description: Transmit data register. + byte_offset: 4352 + access: Read + - name: TXCTL + description: Transmit control register. + byte_offset: 4356 + access: Read + fieldset: TXCTL + - name: RXD + description: Receive data register. + byte_offset: 4360 + - name: RXCTL + description: Receive control register. + byte_offset: 4364 + fieldset: RXCTL + - name: SPECIAL_AUTH + description: Special enable authorization register. + byte_offset: 4608 + access: Read + fieldset: SPECIAL_AUTH + - name: APP_AUTH + description: Application CPU0 authorization register. + byte_offset: 4624 + access: Read + fieldset: APP_AUTH +fieldset/APP_AUTH: + description: Application CPU0 authorization register. + fields: + - name: DBGEN + description: Controls invasive debug enable. + bit_offset: 0 + bit_size: 1 + - name: NIDEN + description: Controls non-invasive debug enable. + bit_offset: 1 + bit_size: 1 + - name: SPIDEN + description: Secure invasive debug enable. + bit_offset: 2 + bit_size: 1 + - name: SPNIDEN + description: Secure non-invasive debug enable. + bit_offset: 3 + bit_size: 1 +fieldset/DESC: + description: Module Description. + fields: + - name: MINREV + description: Minor rev of the IP. + bit_offset: 0 + bit_size: 4 + - name: MAJREV + description: Major rev of the IP. + bit_offset: 4 + bit_size: 4 + - name: INSTNUM + description: Instance Number within the device. This will be a parameter to the RTL for modules that can have multiple instances. + bit_offset: 8 + bit_size: 4 + - name: FEATUREVER + description: Feature Set for the module *instance*. + bit_offset: 12 + bit_size: 4 + - name: MODULEID + description: Module identification contains a unique peripheral identification number. The assignments are maintained in a central database for all of the platform modules to ensure uniqueness. + bit_offset: 16 + bit_size: 16 +fieldset/EVT_MODE: + description: Event Mode. + fields: + - name: CPU + description: Event line mode select for peripheral events. + bit_offset: 0 + bit_size: 2 + enum: INT_CFG +fieldset/IIDX: + description: Interrupt index. + fields: + - name: STAT + description: Interrupt index status. + bit_offset: 0 + bit_size: 8 + enum: STAT +fieldset/INT: + description: Interrupt clear. + fields: + - name: TXIFG + description: Clears TXIFG in RIS register. + bit_offset: 0 + bit_size: 1 + - name: RXIFG + description: Clears RXIFG in RIS register. + bit_offset: 1 + bit_size: 1 + - name: PWRUPIFG + description: Clears PWRUPIFG in RIS register. + bit_offset: 2 + bit_size: 1 + - name: PWRDWNIFG + description: Clears PWRDWNIFG in RIS register. + bit_offset: 3 + bit_size: 1 +fieldset/RXCTL: + description: Receive control register. + fields: + - name: RECEIVE + description: Indicates SW write to the DSSM.RXD register. A read of the DSSM.RXD register by SWD Access Port will clear the RX field. + bit_offset: 0 + bit_size: 1 + enum: RECEIVE + - name: RECEIVE_FLAGS + description: Generic RX flags that can be set by SW and read by external debug tool. Functionality is defined by SW. + bit_offset: 1 + bit_size: 7 +fieldset/SPECIAL_AUTH: + description: Special enable authorization register. + fields: + - name: SECAPEN + description: An active high input. When asserted (and SWD access is also permitted), the debug tools can use the Security-AP to communicate with security control logic. When deasserted, a DAPBUS firewall will isolate the AP and prevent access to the Security-AP. + bit_offset: 0 + bit_size: 1 + - name: SWDPORTEN + description: When asserted, the SW-DP functions normally. When deasserted, the SW-DP effectively disables all external debug access. + bit_offset: 1 + bit_size: 1 + - name: DFTAPEN + description: An active high input. When asserted (and SWD access is also permitted), the debug tools can then access the DFT-AP external to the DebugSS lite. When deasserted, a DAPBUS firewall will isolate the AP and prevent access. + bit_offset: 2 + bit_size: 1 + - name: ETAPEN + description: An active high input. When asserted (and SWD access is also permitted), the debug tools can then access an ET-AP external to the DebugSS lite. When deasserted, a DAPBUS firewall will isolate the AP and prevent access. + bit_offset: 3 + bit_size: 1 + - name: CFGAPEN + description: An active high input. When asserted (and SWD access is also permitted), the debug tools can use the Config-AP to read device configuration information. When deasserted, a DAPBUS firewall will isolate the AP and prevent access to the Config-AP. + bit_offset: 4 + bit_size: 1 + - name: AHBAPEN + description: Disabling / enabling debug access to the M0+ Core via the AHB-AP DAP bus isolation. + bit_offset: 5 + bit_size: 1 + - name: PWRAPEN + description: An active high input. When asserted (and SWD access is also permitted), the debug tools can then access the PWR-AP to power and reset state of the CPU. When deasserted, a DAPBUS firewall will isolate the AP and prevent access. + bit_offset: 6 + bit_size: 1 +fieldset/TXCTL: + description: Transmit control register. + fields: + - name: TRANSMIT + description: Indicates data request in DSSM.TXD, set on write via Debug AP to DSSM.TXD. A read of the DSSM.TXD register by SW will clear the TX field. The tool can check that TXD is empty by reading this field. + bit_offset: 0 + bit_size: 1 + enum: TRANSMIT + - name: TRANSMIT_FLAGS + description: Generic TX flags that can be set by external debug tool. Functionality is defined by SW. + bit_offset: 1 + bit_size: 31 +enum/INT_CFG: + bit_size: 2 + variants: + - name: DISABLE + description: The interrupt or event line is disabled. + value: 0 + - name: SOFTWARE + description: The interrupt or event line is in software mode. Software must clear the RIS. + value: 1 + - name: HARDWARE + description: The interrupt or event line is in hardware mode. The hardware (another module) clears automatically the associated RIS flag. + value: 2 +enum/RECEIVE: + bit_size: 1 + variants: + - name: EMPTY + description: RXD empty. + value: 0 + - name: FULL + description: RXD full. + value: 1 +enum/STAT: + bit_size: 8 + variants: + - name: NO_INTR + description: No pending interrupt request. + value: 0 + - name: TXIFG + description: TX interrupt. + value: 1 + - name: RXIFG + description: RX interrupt. + value: 2 + - name: PWRUP + description: Power-up interrupt. A debug session has started. + value: 3 + - name: PWRDWN + description: Power-up interrupt. A debug session has started. + value: 4 +enum/TRANSMIT: + bit_size: 1 + variants: + - name: EMPTY + description: TXD is empty. + value: 0 + - name: FULL + description: TXD is full. + value: 1 diff --git a/mspm0-data-gen/src/perimap.rs b/mspm0-data-gen/src/perimap.rs index 33876f2..e139c03 100644 --- a/mspm0-data-gen/src/perimap.rs +++ b/mspm0-data-gen/src/perimap.rs @@ -13,6 +13,9 @@ pub static PERIMAP: RegexMap<&str> = RegexMap::new(&[ (".*:iomux", "v1"), // Two subscriber ports and nothing else; every family has the same pair. (".*:wuc", "v1"), + // One version: hw_debugss.h is a single shared IP description and SLAU893 Table 22-11 lists + // the same fourteen registers. The C110x SVD's extra PINCM/CPU_CONNECT_0 are not in either. + (".*:debugss", "v1"), (".*:mathacl", "v1"), (".*:opa", "v1"), // A TIMB instance is a basic timer: its counters live at 0x1100 where the general-purpose diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index 8786d76..dddd23f 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -133,7 +133,6 @@ fn register_blocks_written(chip: &Chip, name: &str) -> anyhow::Result<()> { const UNWRITTEN_BLOCKS: &[PeripheralType] = &[ PeripheralType::Aes, PeripheralType::AesAdv, - PeripheralType::Debugss, PeripheralType::Event, PeripheralType::GpAmp, PeripheralType::I2s, diff --git a/mspm0-metapac-gen/src/peripheral.rs b/mspm0-metapac-gen/src/peripheral.rs index 7c4a252..296355b 100644 --- a/mspm0-metapac-gen/src/peripheral.rs +++ b/mspm0-metapac-gen/src/peripheral.rs @@ -12,6 +12,7 @@ const GENERATE_PERIPHERALS: &[PeripheralType] = &[ PeripheralType::Adc, PeripheralType::Beeper, PeripheralType::Cpuss, + PeripheralType::Debugss, PeripheralType::Dma, PeripheralType::Canfd, PeripheralType::Comp, diff --git a/transforms/DEBUGSS.yaml b/transforms/DEBUGSS.yaml new file mode 100644 index 0000000..bb6d587 --- /dev/null +++ b/transforms/DEBUGSS.yaml @@ -0,0 +1,63 @@ +# Transform for the DEBUGSS register map, based on DEBUGSS on G518x, whose interrupt registers are +# already grouped as CPU_INT the way SLAU893 Table 22-11 groups them. +# +# Not the C110x SVD, though it is the widest: that one adds a PINCM array holding SWCLK and SWDIO, +# and a CPU_CONNECT_0 at 480h. hw_debugss.h has none of them, and SLAU893 Table 22-11 - the C-series +# TRM, for the very family whose SVD carries them - lists exactly the fourteen registers here and +# says every other offset is reserved. +transforms: + - !DeleteUselessEnums + + - !DeleteFieldsets + from: .* + useless: true + + # However DeleteUselessEnums does not delete MIS and IMASK (due to CLR being 0 and SET being 1) + # + # TODO: Add this to chiptool + - !DeleteEnumsWithVariants + variants: + 0: CLR + 1: SET + + # TODO: Something better than a cursed rename as merge + - !MergeBlocks + from: DEBUGSS + main: DEBUGSS + to: DEBUGSS + + # Remove prefixes + - !RenameRegisters + block: .* + from: DEBUGSS_(.+) + to: $1 + + - !RenameFields + fieldset: .* + from: DEBUGSS_(.+) + to: $1 + + - !Rename + type: All + from: DEBUGSS_(.+) + to: $1 + + # The five interrupt registers share one layout, as in uart_v1 and i2c_v1. + - !MergeFieldsets + from: (IMASK|ISET|MIS|RIS|ICLR) + to: INT + check: NoCheck + + ## EVT_MODE has one event line here, so the enum needs no merge. + - !RenameFields + fieldset: EVT_MODE + from: INT0_CFG + to: CPU + + - !Rename + type: Enum + from: INT0_CFG + to: INT_CFG + + ## Cleanup + - !Sort From 950e4f9fda8621e37a36eb9079dcac0bffba1845 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 14 Sep 2026 14:34:28 +0100 Subject: [PATCH 132/134] types: say which form Chip::iomux holds "IOMUX register index" left a consumer to guess between three things: the register address, the zero-based accessor index, and TI's own one-based PINCM number. A host tool reading it picked the first and landed one register high on every pin. Also record that it cannot be computed from the pin number. That holds on 14 of the 43 part numbers and on the other 29 the difference spans -27 to +28. --- mspm0-data-types/src/lib.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index da02910..c617c23 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -31,7 +31,23 @@ pub struct Chip { /// Packages which this chip is available in. pub packages: Vec<Package>, - /// Mapping from device pin to IOMUX register index. + /// Mapping from device pin name to its `PINCM` number, as TI numbers them: **one-based**, so + /// `PA0` is `1` on the parts where the two line up at all. + /// + /// The number is neither a byte offset nor an array index, and all three forms are in use, so + /// state which one you mean: + /// + /// - register address: `IOMUX_BASE + pincm * 4`, because the array starts at `IOMUX_BASE + 4` + /// - `iomux_v1` accessor: `IOMUX.pincm(pincm - 1)`, which is zero-based + /// - the value here: TI's own `PINCM<n>` name, matching the datasheet pin tables + /// + /// Adding the array's `+ 4` to this value double-counts and lands one register high on every + /// pin โ€” a real mistake, made by a consumer reading this field for the first time. + /// + /// **Do not compute this from the pin number.** It is `pin + 1` on only 14 of the 43 part + /// numbers, all of them single-port devices; on the other 29 the difference ranges from -27 to + /// +28 and is not affine. `PB0` continues the same numbering space rather than restarting, so + /// a port base plus pin offset is wrong too. This map is the only correct answer. pub iomux: BTreeMap<String, u32>, /// Which IO structure each device pin is built from, keyed the same way as `iomux`. From 3f1221936e7b6178c7a698b10b44b8da65706164 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 14 Sep 2026 15:01:10 +0100 Subject: [PATCH 133/134] gpio: say how often the wake attribute is absent, and where the pin data lands `wakeup_pins` is None on 30 of the 43 part numbers, so a reader who meets it without a count has no way to tell a vendor gap from a negative answer. The obvious fallback is the IO structure, which SLAU846 Table 8-1 gets wrong on the two families whose open-drain pins have no wakeup logic. Both pin maps are top-level in the JSON and per-pin in the PAC, which has already sent a consumer looking for a field that is not there. --- mspm0-data-types/src/lib.rs | 14 +++++++++++++- mspm0-metapac-gen/res/metadata.rs | 9 ++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/mspm0-data-types/src/lib.rs b/mspm0-data-types/src/lib.rs index c617c23..187937d 100644 --- a/mspm0-data-types/src/lib.rs +++ b/mspm0-data-types/src/lib.rs @@ -53,6 +53,9 @@ pub struct Chip { /// Which IO structure each device pin is built from, keyed the same way as `iomux`. /// /// Every pin with a PINCM has one. + /// + /// `mspm0-metapac-gen` flattens this onto each `Pin` as `Pin::structure`, so a consumer of the + /// generated crate reads it there rather than here. pub io_structure: BTreeMap<String, IoStructure>, /// Device pins which have wakeup logic and can therefore wake the device from SHUTDOWN. @@ -61,7 +64,16 @@ pub struct Chip { /// pin and is therefore not described here. /// /// `None` when sysconfig does not describe wakeup logic for this family, which is not the same as - /// the family having no wake-capable pin. + /// the family having no wake-capable pin. **That is the common case**: the attribute is absent + /// on 11 of the 18 families, 30 of the 43 part numbers. The seven which carry it are c110x, + /// c1105_c1106, g151x, g351x, g518x, l112x and l211x. + /// + /// Do not fall back to `io_structure` where this is `None`. SLAU846 Table 8-1 maps structure to + /// wake capability and is wrong on mspm0c110x and msps003fx, whose open-drain pins have no + /// wakeup logic โ€” and msps003fx is one of the families with no attribute to contradict it. + /// + /// `mspm0-metapac-gen` flattens this onto each `Pin` as `Pin::wakeup`, so a consumer of the + /// generated crate reads it there rather than here. #[serde(skip_serializing_if = "Option::is_none")] pub wakeup_pins: Option<BTreeSet<String>>, diff --git a/mspm0-metapac-gen/res/metadata.rs b/mspm0-metapac-gen/res/metadata.rs index 11cbb8f..96b8e11 100644 --- a/mspm0-metapac-gen/res/metadata.rs +++ b/mspm0-metapac-gen/res/metadata.rs @@ -1048,7 +1048,14 @@ pub struct Pin { /// any GPIO pin, but only down to STANDBY. /// /// `None` when the vendor data does not describe wakeup logic for this chip, which is not the - /// same as `Some(false)`. + /// same as `Some(false)`. **That is the common case**: sysconfig carries the attribute on only + /// 7 of the 18 families, so `None` on every pin is the answer for 30 of the 43 part numbers. + /// The seven are c110x, c1105_c1106, g151x, g351x, g518x, l112x and l211x. + /// + /// Do not fall back to [`Pin::structure`] where this is `None`. SLAU846 Table 8-1 maps structure + /// to wake capability and is wrong on mspm0c110x and msps003fx, whose open-drain pins have no + /// wakeup logic. On mspm0c110x this field says so โ€” every pin is `Some(false)`, the two + /// open-drain pins included. On msps003fx it is `None`, so nothing contradicts the table there. pub wakeup: Option<bool>, /// Which IO structure the pin is built from, and so which of its PINCM fields do anything. From 18cd0439a7425011c74fefa8fbf8693811269942 Mon Sep 17 00:00:00 2001 From: CordlessCoder <roman@vm.net.ua> Date: Mon, 14 Sep 2026 15:06:40 +0100 Subject: [PATCH 134/134] verify: take AesAdv off the unwritten list, now that a block exists An entry keeps permitting the absence after the block arrives, so it would swallow a later regression that dropped it again. --- mspm0-data-gen/src/verify.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/mspm0-data-gen/src/verify.rs b/mspm0-data-gen/src/verify.rs index dddd23f..e154c0a 100644 --- a/mspm0-data-gen/src/verify.rs +++ b/mspm0-data-gen/src/verify.rs @@ -132,7 +132,6 @@ fn register_blocks_written(chip: &Chip, name: &str) -> anyhow::Result<()> { /// already carries; sysconfig models the fabric as a node with an address anyway. const UNWRITTEN_BLOCKS: &[PeripheralType] = &[ PeripheralType::Aes, - PeripheralType::AesAdv, PeripheralType::Event, PeripheralType::GpAmp, PeripheralType::I2s,