svd-generator 0.2.0

Converts device information from flattened device tree into an SVD description
Documentation
use crate::tree::{parse_reg, Compatible};
use crate::{Error, Result};

pub mod cdns_qspi_nor;
pub mod dw_apb_i2c;
pub mod dw_apb_uart;
pub mod jh7110_aon_pinctrl;
pub mod jh7110_aon_syscon;
pub mod jh7110_aoncrg;
pub mod jh7110_pmu;
pub mod jh7110_stg_syscon;
pub mod jh7110_stgcrg;
pub mod jh7110_sys_pinctrl;
pub mod jh7110_sys_syscon;
pub mod jh7110_syscrg;
pub mod jh7110_trng;
pub mod oc_pwm;
pub mod oe_omc;
pub mod oe_ophy;
pub mod pl022_ssp_spi;
pub mod riscv_clint;
pub mod riscv_plic;

use cdns_qspi_nor::CdnsQspiNor;
use dw_apb_i2c::DwApbI2c;
use dw_apb_uart::DwApbUart;
use jh7110_aon_pinctrl::Jh7110AonPinctrl;
use jh7110_aon_syscon::Jh7110AonSyscon;
use jh7110_aoncrg::Jh7110AonCrg;
use jh7110_pmu::Jh7110Pmu;
use jh7110_stg_syscon::Jh7110StgSyscon;
use jh7110_stgcrg::Jh7110StgCrg;
use jh7110_sys_pinctrl::Jh7110SysPinctrl;
use jh7110_sys_syscon::Jh7110SysSyscon;
use jh7110_syscrg::Jh7110SysCrg;
use jh7110_trng::Jh7110Trng;
use oc_pwm::OcPwm;
use oe_omc::OeOmc;
use oe_ophy::OeOphy;
use pl022_ssp_spi::Pl022SspSpi;
use riscv_clint::RiscvClint;
use riscv_plic::RiscvPlic;

/// Represents the number of peripherals in the SVD device description.
pub struct PeripheralCount {
    /// Number of I2C peripherals
    pub i2c: usize,
    /// Number of QSPI peripherals
    pub qspi: usize,
    /// Number of SPI peripherals
    pub spi: usize,
    /// Number of UART peripherals
    pub uart: usize,
}

impl PeripheralCount {
    /// Creates a new [PeripheralCount].
    pub const fn new() -> Self {
        Self {
            i2c: 0,
            qspi: 0,
            spi: 0,
            uart: 0,
        }
    }
}

/// Convenience function to create an SVD [`Peripheral`](svd::Peripheral).
pub fn create_peripheral(
    name: &str,
    desc: &str,
    base_address: u64,
    size: u32,
    interrupt: Option<Vec<svd::Interrupt>>,
    registers: Option<Vec<svd::RegisterCluster>>,
    dim_element: Option<svd::DimElement>,
) -> Result<svd::Peripheral> {
    let info = svd::PeripheralInfo::builder()
        .name(name.into())
        .description(Some(desc.into()))
        .base_address(base_address)
        .address_block(Some(
            [svd::AddressBlock::builder()
                .offset(0)
                .size(size)
                .usage(svd::AddressBlockUsage::Registers)
                .build(svd::ValidateLevel::Strict)?]
            .into(),
        ))
        .interrupt(interrupt)
        .registers(registers)
        .build(svd::ValidateLevel::Strict)?;

    match dim_element {
        Some(dim) => Ok(svd::Peripheral::Array(info, dim)),
        None => Ok(svd::Peripheral::Single(info)),
    }
}

/// Parse SVD peripheral information from the DeviceTree root [`Node`](device_tree::Node).
///
/// # Examples
///
/// ```no_run
/// use device_tree::DeviceTree;
/// use svd_generator::svd::parse_peripherals;
///
/// // just an example
/// // this would be bytes loaded from a flattened DeviceTree file
/// let dt_buf = &[0u8];
/// let dt = DeviceTree::load(dt_buf).unwrap();
/// let _peripherals = parse_peripherals(&dt.root).unwrap();
/// ```
pub fn parse_peripherals(root: &device_tree::Node) -> Result<Vec<svd::Peripheral>> {
    let mut count = PeripheralCount::new();

    let addr_cells = root.prop_u32("#address-cells").unwrap_or(1) as usize;
    let size_cells = root.prop_u32("#size-cells").unwrap_or(1) as usize;

    let harts = root
        .children
        .iter()
        .find(|n| n.name == "cpus")
        .ok_or(Error::Svd("no `cpus` node found in DeviceTree".into()))?
        .children
        .len();

    Ok(root
        .children
        .iter()
        .find(|n| n.name == "soc")
        .ok_or(Error::Svd("no `soc` node found in DeviceTree".into()))?
        .children
        .iter()
        .filter(|n| match n.prop_str("compatible") {
            Ok(p) => {
                log::debug!("Found peripheral `compatible` property: {p}");
                Compatible::from(p).is_known()
            }
            Err(_) => false,
        })
        .filter_map(|n| parse_peripheral(n, addr_cells, size_cells, harts, &mut count).ok())
        .collect())
}

fn parse_peripheral(
    node: &device_tree::Node,
    addr_cells: usize,
    size_cells: usize,
    harts: usize,
    count: &mut PeripheralCount,
) -> Result<svd::Peripheral> {
    let name = node.name.as_str();
    let comp_str = node.prop_str("compatible")?;
    let comp = Compatible::from(comp_str);

    let reg_names = node.prop_str("reg-names").unwrap_or("");
    let desc = format!("{name} {comp_str},{reg_names} peripheral generator");

    let (address, size) = parse_reg(node, addr_cells, size_cells)?;

    Ok(match comp {
        Compatible::CdnsQspiNor => {
            let qspi_name = format!("qspi{}", count.qspi);
            count.qspi += 1;

            let interrupt = if node.has_prop("interrupts") {
                Some(vec![svd::Interrupt::builder()
                    .name(qspi_name.to_uppercase())
                    .value(node.prop_u32("interrupts")?)
                    .build(svd::ValidateLevel::Strict)?])
            } else {
                None
            };

            CdnsQspiNor::create(qspi_name.as_str(), address, size, interrupt, 0)?.to_inner()
        }
        Compatible::DwApbI2c => {
            let i2c_name = format!("i2c{}", count.i2c);
            count.i2c += 1;

            let interrupt = if node.has_prop("interrupts") {
                Some(vec![svd::Interrupt::builder()
                    .name(i2c_name.to_uppercase())
                    .value(node.prop_u32("interrupts")?)
                    .build(svd::ValidateLevel::Strict)?])
            } else {
                None
            };

            DwApbI2c::create(i2c_name.as_str(), address, size, interrupt, 0)?.to_inner()
        }
        Compatible::DwApbUart => {
            let uart_name = format!("uart{}", count.uart);
            count.uart += 1;

            let interrupt = if node.has_prop("interrupts") {
                Some(vec![svd::Interrupt::builder()
                    .name(uart_name.to_uppercase())
                    .value(node.prop_u32("interrupts")?)
                    .build(svd::ValidateLevel::Strict)?])
            } else {
                None
            };

            DwApbUart::create(uart_name.as_str(), address, size, interrupt, 0)?.to_inner()
        }
        Compatible::Jh7110AonCrg => {
            let interrupt = if node.has_prop("interrupts") {
                Some(vec![svd::Interrupt::builder()
                    .name("AONCRG".into())
                    .value(node.prop_u32("interrupts")?)
                    .build(svd::ValidateLevel::Strict)?])
            } else {
                None
            };

            Jh7110AonCrg::create("aoncrg", address, size, interrupt)?.to_inner()
        }
        Compatible::Jh7110AonPinctrl => {
            let interrupt = if node.has_prop("interrupts") {
                Some(vec![svd::Interrupt::builder()
                    .name("AON_IOMUX".into())
                    .value(node.prop_u32("interrupts")?)
                    .build(svd::ValidateLevel::Strict)?])
            } else {
                None
            };

            Jh7110AonPinctrl::create("aon_pinctrl", address, size, interrupt)?.to_inner()
        }
        Compatible::Jh7110AonSyscon => {
            let interrupt = if node.has_prop("interrupts") {
                Some(vec![svd::Interrupt::builder()
                    .name("AON_SYSCON".into())
                    .value(node.prop_u32("interrupts")?)
                    .build(svd::ValidateLevel::Strict)?])
            } else {
                None
            };

            Jh7110AonSyscon::create("aon_syscon", address, size, interrupt)?.to_inner()
        }
        Compatible::Jh7110Pmu => {
            let interrupt = if node.has_prop("interrupts") {
                Some(vec![svd::Interrupt::builder()
                    .name("PMU".into())
                    .value(node.prop_u32("interrupts")?)
                    .build(svd::ValidateLevel::Strict)?])
            } else {
                None
            };

            Jh7110Pmu::create("pmu", address, size, interrupt)?.to_inner()
        }
        Compatible::Jh7110StgCrg => {
            let interrupt = if node.has_prop("interrupts") {
                Some(vec![svd::Interrupt::builder()
                    .name("STGCRG".into())
                    .value(node.prop_u32("interrupts")?)
                    .build(svd::ValidateLevel::Strict)?])
            } else {
                None
            };

            Jh7110StgCrg::create("stgcrg", address, size, interrupt)?.to_inner()
        }
        Compatible::Jh7110StgSyscon => {
            let interrupt = if node.has_prop("interrupts") {
                Some(vec![svd::Interrupt::builder()
                    .name("STG_SYSCON".into())
                    .value(node.prop_u32("interrupts")?)
                    .build(svd::ValidateLevel::Strict)?])
            } else {
                None
            };

            Jh7110StgSyscon::create("stg_syscon", address, size, interrupt)?.to_inner()
        }
        Compatible::Jh7110SysCrg => {
            let interrupt = if node.has_prop("interrupts") {
                Some(vec![svd::Interrupt::builder()
                    .name("SYSCRG".into())
                    .value(node.prop_u32("interrupts")?)
                    .build(svd::ValidateLevel::Strict)?])
            } else {
                None
            };

            Jh7110SysCrg::create("syscrg", address, size, interrupt)?.to_inner()
        }
        Compatible::Jh7110SysPinctrl => {
            let interrupt = if node.has_prop("interrupts") {
                Some(vec![svd::Interrupt::builder()
                    .name("SYS_IOMUX".into())
                    .value(node.prop_u32("interrupts")?)
                    .build(svd::ValidateLevel::Strict)?])
            } else {
                None
            };

            Jh7110SysPinctrl::create("sys_pinctrl", address, size, interrupt)?.to_inner()
        }
        Compatible::Jh7110SysSyscon => {
            let interrupt = if node.has_prop("interrupts") {
                Some(vec![svd::Interrupt::builder()
                    .name("SYS_SYSCON".into())
                    .value(node.prop_u32("interrupts")?)
                    .build(svd::ValidateLevel::Strict)?])
            } else {
                None
            };

            Jh7110SysSyscon::create("sys_syscon", address, size, interrupt)?.to_inner()
        }
        Compatible::Jh7110Trng => {
            let interrupt = if node.has_prop("interrupts") {
                Some(vec![svd::Interrupt::builder()
                    .name("TRNG".into())
                    .value(node.prop_u32("interrupts")?)
                    .build(svd::ValidateLevel::Strict)?])
            } else {
                None
            };

            Jh7110Trng::create("trng", address, size, interrupt)?.to_inner()
        }
        Compatible::Pl022SspSpi => {
            let spi_name = format!("spi{}", count.spi);
            count.spi += 1;

            let interrupt = if node.has_prop("interrupts") {
                Some(vec![svd::Interrupt::builder()
                    .name(spi_name.to_uppercase())
                    .value(node.prop_u32("interrupts")?)
                    .build(svd::ValidateLevel::Strict)?])
            } else {
                None
            };

            Pl022SspSpi::create(spi_name.as_str(), address, size, interrupt, 0)?.to_inner()
        }
        Compatible::RiscvClint => RiscvClint::create(address, size, harts)?.to_inner(),
        Compatible::RiscvPlic => {
            RiscvPlic::create(address, size, harts, node.prop_u32("riscv,ndev")? as usize)?
                .to_inner()
        }
        Compatible::OcPwm => {
            let interrupt = if node.has_prop("interrupts") {
                Some(vec![svd::Interrupt::builder()
                    .name("PWM".into())
                    .value(node.prop_u32("interrupts")?)
                    .build(svd::ValidateLevel::Strict)?])
            } else {
                None
            };

            OcPwm::create("pwm", address, size, interrupt, 0)?.to_inner()
        }
        Compatible::OeOmc => OeOmc::create("dmc_ctrl", address, size, None, 0)?.to_inner(),
        Compatible::OeOphy => OeOphy::create("dmc_phy", address, size, None, 0)?.to_inner(),
        _ => create_peripheral(name, desc.as_str(), address, size, None, None, None)?,
    })
}