use std::collections::HashMap;
use crate::error::DataError;
use crate::family::{matches, normalize};
struct Embedded {
family: &'static str,
json: &'static str,
}
const FAMILIES: &[Embedded] = &[
Embedded {
family: "stm32mp25",
json: include_str!("../database/signal/stm32mp25.json"),
},
Embedded {
family: "stm32mp23",
json: include_str!("../database/signal/stm32mp23.json"),
},
Embedded {
family: "stm32mp21",
json: include_str!("../database/signal/stm32mp21.json"),
},
Embedded {
family: "mimx952",
json: include_str!("../database/signal/mimx952.json"),
},
Embedded {
family: "mimx95",
json: include_str!("../database/signal/mimx95.json"),
},
Embedded {
family: "mimx93",
json: include_str!("../database/signal/mimx93.json"),
},
];
#[derive(serde::Deserialize)]
struct RawMap {
packages: Vec<RawPackage>,
pads: Vec<RawPad>,
signals: Vec<RawSignal>,
dma: Vec<DmaRequest>,
}
#[derive(serde::Deserialize)]
struct RawPackage {
name: String,
parts: Vec<String>,
}
#[derive(serde::Deserialize)]
struct RawPad {
name: String,
packages: Option<Vec<String>>,
}
#[derive(serde::Deserialize)]
struct RawSignal {
pad: String,
function: String,
peripheral: Option<String>,
signal: Option<String>,
mux: RawMux,
}
#[derive(serde::Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
enum RawMux {
Af {
af: u8,
},
Additional,
Analog,
Iomuxc {
mux_register: String,
mux_mode: u8,
input_register: Option<String>,
input_daisy: Option<u8>,
config_register: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mux {
Af(u8),
Additional,
Analog,
Iomuxc {
mux_register: u32,
mux_mode: u8,
input: Option<(u32, u8)>,
config_register: u32,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Package {
pub name: String,
pub parts: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Pad {
pub name: String,
pub packages: Option<Vec<String>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Signal {
pub pad: String,
pub function: String,
pub peripheral: Option<String>,
pub signal: Option<String>,
pub mux: Mux,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
pub struct DmaRequest {
pub controller: String,
pub request: u16,
pub function: String,
pub peripheral: Option<String>,
pub signal: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SignalMap {
packages: Vec<Package>,
pads: HashMap<String, Pad>,
signals: Vec<Signal>,
dma: Vec<DmaRequest>,
}
impl SignalMap {
pub fn packages(&self) -> &[Package] {
&self.packages
}
pub fn package_of(&self, chip: &str) -> Option<&Package> {
let chip = chip.to_ascii_uppercase();
self.packages.iter().find(|package| {
package.parts.iter().any(|part| {
let stem = part.strip_suffix(['x', 'X']).unwrap_or(part);
chip.starts_with(&stem.to_ascii_uppercase())
})
})
}
pub fn pad(&self, name: &str) -> Option<&Pad> {
self.pads.get(&name.to_ascii_uppercase())
}
pub fn signals(&self) -> &[Signal] {
&self.signals
}
pub fn signal_routes(&self, peripheral: &str, signal: &str) -> Vec<&Signal> {
let function = format!("{peripheral}_{signal}").to_ascii_uppercase();
self.signals
.iter()
.filter(|s| s.function == function)
.collect()
}
pub fn dma_requests(&self) -> &[DmaRequest] {
&self.dma
}
pub fn dma_requests_for(&self, peripheral: &str) -> Vec<&DmaRequest> {
let prefix = format!("{}_", peripheral.to_ascii_uppercase());
self.dma
.iter()
.filter(|d| {
d.peripheral
.as_deref()
.is_some_and(|p| p.eq_ignore_ascii_case(peripheral))
|| d.function.starts_with(&prefix)
})
.collect()
}
}
pub fn load(chip: &str) -> Result<SignalMap, DataError> {
let needle = normalize(chip);
let entry = FAMILIES
.iter()
.find(|entry| matches(&needle, entry.family))
.ok_or_else(|| DataError::SignalMapUnavailable(chip.to_string()))?;
let parse_err = |detail: String| DataError::SignalMapParse {
chip: chip.to_string(),
detail,
};
let raw: RawMap = serde_json::from_str(entry.json).map_err(|err| parse_err(err.to_string()))?;
let mut signals = Vec::with_capacity(raw.signals.len());
for s in raw.signals {
let register = |value: &str| {
parse_hex(value).ok_or_else(|| {
parse_err(format!(
"{} on {}: bad register '{value}'",
s.function, s.pad
))
})
};
let mux = match &s.mux {
RawMux::Af { af } => Mux::Af(*af),
RawMux::Additional => Mux::Additional,
RawMux::Analog => Mux::Analog,
RawMux::Iomuxc {
mux_register,
mux_mode,
input_register,
input_daisy,
config_register,
} => Mux::Iomuxc {
mux_register: register(mux_register)?,
mux_mode: *mux_mode,
input: match (input_register, input_daisy) {
(Some(reg), Some(daisy)) => Some((register(reg)?, *daisy)),
(None, None) => None,
_ => {
return Err(parse_err(format!(
"{} on {}: input register and daisy disagree",
s.function, s.pad
)));
}
},
config_register: register(config_register)?,
},
};
signals.push(Signal {
pad: s.pad,
function: s.function,
peripheral: s.peripheral,
signal: s.signal,
mux,
});
}
Ok(SignalMap {
packages: raw
.packages
.into_iter()
.map(|p| Package {
name: p.name,
parts: p.parts,
})
.collect(),
pads: raw
.pads
.into_iter()
.map(|p| {
(
p.name.to_ascii_uppercase(),
Pad {
name: p.name,
packages: p.packages,
},
)
})
.collect(),
signals,
dma: raw.dma,
})
}
fn parse_hex(value: &str) -> Option<u32> {
u32::from_str_radix(value.strip_prefix("0x")?, 16).ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_family_parses() {
for chip in [
"stm32mp257fak3",
"stm32mp235fak3",
"stm32mp215fan3",
"mimx9596xvt",
"mimx9529avzxn",
"mimx9352",
] {
let map = load(chip).unwrap_or_else(|err| panic!("{chip}: {err}"));
assert!(!map.signals().is_empty(), "{chip} has no signals");
assert!(!map.dma_requests().is_empty(), "{chip} has no DMA requests");
}
}
#[test]
fn rejects_other_dies_sharing_an_imx_prefix() {
for chip in ["mimx9375", "mimx9371xxvtx"] {
assert!(
matches!(load(chip), Err(DataError::SignalMapUnavailable(_))),
"{chip} should not resolve"
);
}
}
#[test]
fn stm32mp25_i2c1_sda_routes_and_package() {
let map = load("stm32mp257fak3").unwrap();
let mut routes: Vec<(String, Mux)> = map
.signal_routes("i2c1", "sda")
.into_iter()
.map(|s| (s.pad.clone(), s.mux))
.collect();
routes.sort_by(|a, b| a.0.cmp(&b.0));
assert_eq!(
routes,
[
("PA2".into(), Mux::Af(10)),
("PI1".into(), Mux::Af(9)),
("PJ10".into(), Mux::Af(9)),
]
);
let package = map
.package_of("stm32mp257fak3")
.expect("FAK is a known part");
assert_eq!(package.name, "VFBGA424");
assert!(map.package_of("stm32mp257").is_none());
let pa0 = map.pad("pa0").expect("PA0 exists");
assert!(pa0.packages.as_ref().unwrap().contains(&package.name));
}
#[test]
fn mimx93_lpuart1_tx_iomuxc_and_dma() {
let map = load("imx93").unwrap();
let routes = map.signal_routes("LPUART1", "TX");
assert_eq!(routes.len(), 1);
assert_eq!(routes[0].pad, "UART1_TXD");
assert_eq!(
routes[0].mux,
Mux::Iomuxc {
mux_register: 0x543c_0184,
mux_mode: 0,
input: None,
config_register: 0x543c_0334,
}
);
assert!(map.package_of("mimx9352").is_none());
assert_eq!(map.pad("UART1_TXD").unwrap().packages, None);
let dma: Vec<(&str, u16)> = map
.dma_requests_for("lpuart1")
.into_iter()
.map(|d| (d.function.as_str(), d.request))
.collect();
assert_eq!(dma, [("LPUART1_TX", 16), ("LPUART1_RX", 17)]);
}
#[test]
fn rejects_unknown_family() {
assert!(matches!(
load("stm32h747"),
Err(DataError::SignalMapUnavailable(_))
));
}
}