//! Per-peripheral initialization for {{ ir.board.name }}.
//!
//! Only the peripherals actually used by the board are referenced. The
//! generator emits real register writes for UART0 (when it is the console),
//! I2C (full timing setup), SPI master (clock + mode), and timer groups
//! (watchdog disable). Other peripherals get TODO stubs.
use super::pac;
#[allow(unused_imports)]
use super::board::{APB_HZ, XTAL_HZ};
/// Initialize every board peripheral in dependency order.
///
/// Called by [`crate::pac::init`] after clocks and IO MUX are configured.
pub fn init() {
{% for name in peripherals_used %}
init_{{ name }}();
{% endfor %}
}
{% for name in peripherals_used %}
{% set periph = ir.chip.peripherals[name] %}
{% if periph %}
{% if name == "uart0" and ir.board.console and ir.board.console.peripheral == "uart0" %}
/// Bring up UART0 as the console at {{ ir.board.console.baud }} baud, 8N1.
///
/// Clock divider is computed from `APB_HZ` ({{ ir.clocks.apb_hz }} Hz) and
/// the console baud rate ({{ ir.board.console.baud }} baud).
pub fn init_uart0() {
let p = unsafe { pac::Peripherals::steal() };
const BAUD: u32 = {{ ir.board.console.baud }};
// UART0 CLKDIV on ESP32-C3 is Q20.4 fixed point: integer part in bits
// [19:0], fractional part in bits [23:20]. Compute `(APB << 4) / BAUD`,
// then write the integer and fractional halves as raw register bits to
// sidestep field-width type errors in svd2rust 0.31.
let clkdiv: u32 = (APB_HZ << 4) / BAUD;
let integer = clkdiv >> 4;
let frag = clkdiv & 0xF;
p.UART0
.clkdiv()
.write(|w| unsafe { w.bits((integer & 0xF_FFFF) | (frag << 20)) });
// 8 data bits, no parity, 1 stop bit.
p.UART0.conf0().modify(|_, w| unsafe {
w.bit_num().bits(0b11) // 8 data bits
.parity_en().clear_bit()
.stop_bit_num().bits(0b01) // 1 stop bit
});
// Reset FIFOs. Skipping the TX-idle poll: C3's UART0_STATUS layout
// differs across PAC revisions and the FIFO reset alone is enough for
// a cold bring-up.
p.UART0.conf0().modify(|_, w| w.rxfifo_rst().set_bit().txfifo_rst().set_bit());
p.UART0.conf0().modify(|_, w| w.rxfifo_rst().clear_bit().txfifo_rst().clear_bit());
}
{% elif periph.class == "i2c" %}
/// Bring up {{ name }} as an I2C master at {{ ir.board.i2c_configs[name].scl_hz | default(100000) }} Hz.
///
/// Targets the `{{ ir.chip.pac_crate }}::{{ name | upper }}` register block.
/// Clock gating is already done by `crate::clocks::init` — this function
/// only sets up the peripheral-internal timing and leaves it ready for
/// per-transaction command-list writes.
///
/// TRM reference: ESP32-C3 Chapter 16 "I2C Controller", register map and
/// timing formulas. The template writes all *_period / *_hold / *_sample
/// registers as raw half-period counts derived from the source clock
/// (XTAL_HZ = {{ ir.clocks.xtal_hz }} Hz on C3 — the I2C controller uses
/// XTAL directly when `sclk_sel = 0`).
pub fn init_{{ name }}() {
let p = unsafe { pac::Peripherals::steal() };
const SCL_HZ: u32 = {{ ir.board.i2c_configs[name].scl_hz | default(100000) }};
// Full SCL period in XTAL cycles. Split 50/50 between low and high
// halves. For 400 kHz @ 40 MHz XTAL that's 100 cycles, 50 per half.
let period: u32 = XTAL_HZ / SCL_HZ;
let half: u32 = period / 2;
// Select XTAL_CLK as the source (sclk_sel = 0) and enable the clock.
p.{{ name | upper }}.clk_conf().modify(|_, w| unsafe {
w.sclk_sel().clear_bit()
.sclk_active().set_bit()
.sclk_div_num().bits(0)
});
// Master mode, MSB-first on both directions.
p.{{ name | upper }}.ctr().modify(|_, w| {
w.ms_mode().set_bit()
.tx_lsb_first().clear_bit()
.rx_lsb_first().clear_bit()
.clk_en().set_bit()
});
// SCL low/high periods (raw register writes — field widths vary across
// PAC revisions, so stay register-level).
p.{{ name | upper }}.scl_low_period().write(|w| unsafe { w.bits(half) });
p.{{ name | upper }}.scl_high_period().write(|w| unsafe { w.bits(half) });
// SDA hold/sample timings — conservative quarter-period defaults.
let quarter: u32 = half / 2;
p.{{ name | upper }}.sda_hold().write(|w| unsafe { w.bits(quarter) });
p.{{ name | upper }}.sda_sample().write(|w| unsafe { w.bits(quarter) });
// START / repeated-START / STOP setup + hold times.
p.{{ name | upper }}.scl_start_hold().write(|w| unsafe { w.bits(half) });
p.{{ name | upper }}.scl_rstart_setup().write(|w| unsafe { w.bits(half) });
p.{{ name | upper }}.scl_stop_hold().write(|w| unsafe { w.bits(half) });
p.{{ name | upper }}.scl_stop_setup().write(|w| unsafe { w.bits(half) });
// Reset TX + RX FIFOs so subsequent transactions start clean.
p.{{ name | upper }}.fifo_conf().modify(|_, w| {
w.tx_fifo_rst().set_bit().rx_fifo_rst().set_bit()
});
p.{{ name | upper }}.fifo_conf().modify(|_, w| {
w.tx_fifo_rst().clear_bit().rx_fifo_rst().clear_bit()
});
// Publish the config — `conf_upgate` latches the new timings.
p.{{ name | upper }}.ctr().modify(|_, w| w.conf_upgate().set_bit());
}
{% elif periph.class == "spi_master" %}
/// Bring up {{ name }} as an SPI master.
///
/// Targets the `{{ ir.chip.pac_crate }}::{{ name | upper }}` register block.
/// Clock gating is already done by `crate::clocks::init`.
/// Default: 1 MHz, mode 0 (CPOL=0, CPHA=0). Override via `spi_configs`
/// in the board YAML.
///
/// CHIPS-ESP-12: the prior template wrote `wr_byte_order` /
/// `rd_byte_order` on the USER register. Those fields don't exist on any
/// of the six in-tree ESP32 PACs — SPI byte ordering on these chips is
/// fixed at MSB-first. Bit-order lives on the CTRL register
/// (`wr_bit_order` / `rd_bit_order`) and is portable across legacy
/// (esp32c3 0.31, esp32p4 0.2) and modern (svd2rust 0.37+) PACs alike,
/// so the template stays vintage-agnostic.
pub fn init_{{ name }}() {
let p = unsafe { pac::Peripherals::steal() };
{% if ir.board.spi_configs is defined and ir.board.spi_configs[name] is defined %}
const SPI_CLK_HZ: u32 = {{ ir.board.spi_configs[name].clk_hz }};
const SPI_MODE: u8 = {{ ir.board.spi_configs[name].mode }};
{% else %}
const SPI_CLK_HZ: u32 = 1_000_000;
const SPI_MODE: u8 = 0;
{% endif %}
// Clock divider: SPI_CLK = APB_CLK / (clkcnt_n + 1).
let clkcnt_n: u32 = if SPI_CLK_HZ >= APB_HZ { 0 } else { APB_HZ / SPI_CLK_HZ - 1 };
let clkcnt_h = clkcnt_n / 2;
p.{{ name | upper }}.clock().write(|w| unsafe {
w.bits((clkcnt_n & 0x3F) | ((clkcnt_h & 0x3F) << 6) | ((clkcnt_n & 0x3F) << 12))
});
// Full-duplex mode. Byte order is fixed in hardware (MSB-first); bit
// order also defaults to MSB-first at reset on every in-tree ESP32
// PAC vintage (CTRL.wr_bit_order / rd_bit_order both reset to 0) so
// there's nothing to write here. The C3 0.31 PAC types those fields
// as a 1-bit BitWriter while modern svd2rust-0.37+ PACs (P4 0.2,
// C6 0.23, H2 0.19, C5 0.2, C61 0.3) type them as a 2-bit
// FieldWriter; not writing them at all sidesteps the divergence
// and lets the template stay vintage-agnostic.
p.{{ name | upper }}.user().modify(|_, w| w.doutdin().set_bit());
// CPOL via misc.ck_idle_edge, CPHA via user.ck_out_edge.
let cpol = (SPI_MODE >> 1) & 1 != 0;
let cpha = SPI_MODE & 1 != 0;
if cpol {
p.{{ name | upper }}.misc().modify(|_, w| w.ck_idle_edge().set_bit());
} else {
p.{{ name | upper }}.misc().modify(|_, w| w.ck_idle_edge().clear_bit());
}
if cpha {
p.{{ name | upper }}.user().modify(|_, w| w.ck_out_edge().set_bit());
} else {
p.{{ name | upper }}.user().modify(|_, w| w.ck_out_edge().clear_bit());
}
}
{% elif periph.class == "ledc" %}
/// Bring up {{ name }} (LED PWM controller).
///
/// Configures LEDC timers and channels based on board config.
/// Clock gating is already done by `crate::clocks::init`.
pub fn init_{{ name }}() {
let _p = unsafe { pac::Peripherals::steal() };
// LEDC is configured per-channel at runtime by application code.
// Clock gate is enabled; channels are ready for use.
// See the {{ ir.chip.pac_crate }}::LEDC register block for timer/channel
// configuration (timer_conf, ch_conf0, ch_duty, ch_hpoint).
}
{% elif periph.class == "timg" %}
/// Bring up {{ name }} (Timer Group).
///
/// Disables the watchdog and leaves the main timer ready for use.
/// Clock gating is already done by `crate::clocks::init`.
pub fn init_{{ name }}() {
let p = unsafe { pac::Peripherals::steal() };
// Disable the watchdog timer (MWDT) — it's often enabled by bootrom.
// Write the WDT key first, then disable.
p.{{ name | upper }}.wdtwprotect().write(|w| unsafe { w.bits(0x50D8_3AA1) });
p.{{ name | upper }}.wdtconfig0().write(|w| unsafe { w.bits(0) });
p.{{ name | upper }}.wdtwprotect().write(|w| unsafe { w.bits(0) });
}
{% else %}
/// TODO: initialize {{ name }} (class `{{ periph.class }}`, base {{ periph.base }}).
///
/// Populate this function by writing into the `{{ ir.chip.pac_crate }}::{{ name | upper }}`
/// register block. Clock gating is already done by `crate::clocks::init`.
pub fn init_{{ name }}() {
let _ = unsafe { pac::Peripherals::steal() };
// TODO: {{ periph.class }} init sequence for {{ name }}
}
{% endif %}
{% else %}
/// TODO: unknown peripheral `{{ name }}` (no entry in chip IR).
pub fn init_{{ name }}() {}
{% endif %}
{% endfor %}