use super::ir::{NrfBoard, NrfChip, NrfClocksConfig, NrfIr};
use anyhow::{Context, Result, anyhow};
use indexmap::IndexMap;
use std::path::Path;
pub fn yaml_to_chip(text: &str) -> Result<NrfChip> {
serde_yaml::from_str(text).context("parse nrf chip yaml")
}
pub fn yaml_to_board(text: &str) -> Result<NrfBoard> {
serde_yaml::from_str(text).context("parse nrf board yaml")
}
pub fn load_chip_db(name: &str) -> Result<NrfChip> {
let text = rlvgl_chips_nrf::chip_yaml(name)
.ok_or_else(|| anyhow!("nrf chipdb has no chip named '{name}'"))?;
yaml_to_chip(text).with_context(|| format!("parsing chipdb chip '{name}'"))
}
pub fn load_board_db(name: &str) -> Result<NrfBoard> {
let text = rlvgl_chips_nrf::board_yaml(name)
.ok_or_else(|| anyhow!("nrf chipdb has no board named '{name}'"))?;
yaml_to_board(text).with_context(|| format!("parsing chipdb board '{name}'"))
}
pub fn load_chip_file(path: &Path) -> Result<NrfChip> {
let text = std::fs::read_to_string(path)
.with_context(|| format!("read chip yaml {}", path.display()))?;
yaml_to_chip(&text).with_context(|| format!("parse chip yaml {}", path.display()))
}
pub fn load_board_file(path: &Path) -> Result<NrfBoard> {
let text = std::fs::read_to_string(path)
.with_context(|| format!("read board yaml {}", path.display()))?;
yaml_to_board(&text).with_context(|| format!("parse board yaml {}", path.display()))
}
pub fn merge(chip: NrfChip, board: NrfBoard) -> Result<NrfIr> {
if board.chip != chip.name {
return Err(anyhow!(
"board '{}' targets chip '{}' but loaded chip spec is '{}'",
board.name,
board.chip,
chip.name
));
}
validate_pins_against_chip(&chip, &board)?;
validate_slot_exclusion(&chip, &board)?;
validate_psel_roles(&chip, &board)?;
let clocks = default_clocks(&chip);
let pins = board.pins.clone();
Ok(NrfIr {
version: "0.1".to_string(),
chip,
board,
clocks,
pins,
metadata: IndexMap::new(),
})
}
fn default_clocks(chip: &NrfChip) -> NrfClocksConfig {
let hfclk_src = if chip.clock_tree.hfclk_src.iter().any(|s| s == "hfxo") {
"hfxo".to_string()
} else {
chip.clock_tree
.hfclk_src
.first()
.cloned()
.unwrap_or_else(|| "hfint".to_string())
};
let lfclk_src = if chip.clock_tree.lfclk_src.iter().any(|s| s == "lfxo") {
"lfxo".to_string()
} else {
chip.clock_tree
.lfclk_src
.first()
.cloned()
.unwrap_or_else(|| "lfrc".to_string())
};
NrfClocksConfig {
hfclk_hz: chip.clock_tree.hfclk_hz,
lfclk_hz: chip.clock_tree.lfclk_hz,
hfclk_src,
lfclk_src,
}
}
fn validate_pins_against_chip(chip: &NrfChip, board: &NrfBoard) -> Result<()> {
for pin in &board.pins {
let port_def = chip
.gpio_ports
.iter()
.find(|p| p.port == pin.port)
.ok_or_else(|| {
anyhow!(
"board '{}' assigns P{}.{:02} but chip '{}' has no port {}",
board.name,
pin.port,
pin.pin,
chip.name,
pin.port
)
})?;
if pin.pin >= port_def.pin_count {
return Err(anyhow!(
"board '{}' assigns P{}.{:02} but port {} only has pins 0..{}",
board.name,
pin.port,
pin.pin,
pin.port,
port_def.pin_count - 1
));
}
}
let mut seen: IndexMap<&str, (u8, u8)> = IndexMap::new();
for pin in &board.pins {
if let Some(label) = pin.label.as_deref() {
if let Some(prev) = seen.insert(label, (pin.port, pin.pin)) {
return Err(anyhow!(
"board '{}' reuses label '{}' on P{}.{:02} and P{}.{:02}",
board.name,
label,
prev.0,
prev.1,
pin.port,
pin.pin
));
}
}
}
Ok(())
}
fn validate_slot_exclusion(chip: &NrfChip, board: &NrfBoard) -> Result<()> {
let mut used: Vec<&str> = Vec::new();
for pin in &board.pins {
if let Some(ref p) = pin.peripheral {
if !used.iter().any(|u| *u == p.as_str()) {
used.push(p);
}
}
}
for slot in &chip.peripheral_slots {
let in_slot: Vec<&&str> = used
.iter()
.filter(|u| slot.instances.iter().any(|i| i == **u))
.collect();
if in_slot.len() > 1 {
return Err(anyhow!(
"board '{}' uses peripherals {} which share slot {} — only one can be enabled",
board.name,
in_slot
.iter()
.map(|s| format!("'{s}'"))
.collect::<Vec<_>>()
.join(", "),
slot.slot,
));
}
}
Ok(())
}
fn validate_psel_roles(chip: &NrfChip, board: &NrfBoard) -> Result<()> {
for pin in &board.pins {
if let (Some(periph_name), Some(role)) = (&pin.peripheral, &pin.role) {
let periph = chip.peripherals.get(periph_name.as_str()).ok_or_else(|| {
anyhow!(
"board '{}' references peripheral '{}' not found in chip '{}'",
board.name,
periph_name,
chip.name
)
})?;
if !periph.psel.iter().any(|p| p.role == *role) {
return Err(anyhow!(
"board '{}' assigns P{}.{:02} role '{}' to peripheral '{}' but that \
peripheral has no PSEL role '{}' (available: {})",
board.name,
pin.port,
pin.pin,
role,
periph_name,
role,
periph
.psel
.iter()
.map(|p| p.role.as_str())
.collect::<Vec<_>>()
.join(", "),
));
}
}
}
Ok(())
}