use super::ir::{EspBoard, EspChip, EspClocksConfig, EspIr};
use anyhow::{Context, Result, anyhow};
use indexmap::IndexMap;
use std::path::Path;
pub fn yaml_to_chip(text: &str) -> Result<EspChip> {
serde_yaml::from_str(text).context("parse esp chip yaml")
}
pub fn yaml_to_board(text: &str) -> Result<EspBoard> {
serde_yaml::from_str(text).context("parse esp board yaml")
}
pub fn load_chip_db(name: &str) -> Result<EspChip> {
let text = rlvgl_chips_esp::chip_yaml(name)
.ok_or_else(|| anyhow!("esp 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<EspBoard> {
let text = rlvgl_chips_esp::board_yaml(name)
.ok_or_else(|| anyhow!("esp 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<EspChip> {
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<EspBoard> {
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: EspChip, board: EspBoard) -> Result<EspIr> {
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)?;
let clocks = default_clocks(&chip);
let pins = board.pins.clone();
Ok(EspIr {
version: "0.1".to_string(),
chip,
board,
clocks,
pins,
metadata: IndexMap::new(),
})
}
fn default_clocks(chip: &EspChip) -> EspClocksConfig {
let cpu_hz = chip
.clock_tree
.cpu_freqs_hz
.iter()
.copied()
.max()
.unwrap_or(0);
let pll_hz = chip
.clock_tree
.pll_freqs_hz
.iter()
.copied()
.max()
.unwrap_or(0);
EspClocksConfig {
cpu_hz,
apb_hz: chip.clock_tree.apb_hz,
xtal_hz: chip.clock_tree.xtal_hz,
pll_hz,
kernels: IndexMap::new(),
}
}
fn validate_pins_against_chip(chip: &EspChip, board: &EspBoard) -> Result<()> {
for pin in &board.pins {
if pin.gpio >= chip.gpio_count {
return Err(anyhow!(
"board '{}' assigns GPIO{} but chip '{}' only exposes GPIO0..{}",
board.name,
pin.gpio,
chip.name,
chip.gpio_count - 1
));
}
if let Some(entry) = chip.io_mux.iter().find(|p| p.gpio == pin.gpio) {
if entry.flash_reserved {
return Err(anyhow!(
"board '{}' assigns GPIO{} ({}) which is reserved for in-package SPI flash on chip '{}'",
board.name,
pin.gpio,
entry.pad_name,
chip.name
));
}
}
}
let mut seen: IndexMap<&str, u8> = IndexMap::new();
for pin in &board.pins {
if let Some(label) = pin.label.as_deref() {
if let Some(prev) = seen.insert(label, pin.gpio) {
return Err(anyhow!(
"board '{}' reuses label '{}' on GPIO{} and GPIO{}",
board.name,
label,
prev,
pin.gpio
));
}
}
}
Ok(())
}