Skip to Content
axiomOSmainPlatformRP1 Peripherals

RP1 Peripherals

Relevant source files

RP1 is the Raspberry Pi 5’s southbridge. It is a separate chip reached from the BCM2712 over PCIe (the PCIe2 link), and it holds the 40-pin-header peripherals. axiomOS has no general PCIe or device-tree-driven driver model for it: the kernel uses fixed physical addresses inside a window the firmware has already set up (see Raspberry Pi 5 for the config.txt shortcuts).

Register access

mmio.rs provides MmioReg<T>, a volatile register wrapper with read, write, modify, set_bits, clear_bits, is_set, wait_set and wait_clear (the waits spin).

RP1 registers are addressed as RP1_PERIPHERAL_BASE + offset, where the base is the higher-half alias of physical 0x1F_0000_0000:

BlockRP1 offsetStatus
UART0 (PL011-compatible)0x0003_0000Used by the Shrike control link
UART10x0003_4000Address defined; no driver found
I2C00x0007_0000Address defined; no driver found
SPI00x0005_0000Address defined; no driver found
PWM00x0009_8000Driver
PWM10x0009_C000Driver
IO_BANK0 (GPIO)0x000D_0000Driver
PADS_BANK00x000F_0000Driver (pad control)
PCIe endpoint APB config0x0010_8000Used by the MSI-X route
Clocks0x0001_8000PWM0 clock only

Register layouts in the drivers are cross-checked in comments against the Linux sources pinctrl-rp1.c, pwm-rp1.c and clk-rp1.c. RP1 exposes atomic set (+0x2000) and clear (+0x3000) aliases for ordinary peripheral registers; the driver uses them for the PCIe interrupt-enable mask. The RP1 PCIe config block is the exception, using +0x800 and +0xC00.

GPIO

gpio.rs implements Rp1Gpio for the 28 user GPIOs (NUM_PINS = 28; out-of-range pins assert).

FeatureAPI
Function selectset_function with GpioFunction (Alt0 to Alt4, Gpio = 5, Alt6 to Alt8, Null = 31); get_function
Outputset_high, set_low, toggle, configure_output, force_output_override
Inputread, configure_input
Pullsset_pull with GpioPull, programmed through PADS_BANK0 (schmitt, pull select, input enable, output disable bits)
Edge eventsenable_interrupt(pin, rising, falling), disable_interrupt, clear_interrupt, get_pending_events, has_rising_event, has_falling_event
Diagnosticspad_readback, ctrl_readback, interrupt_state (full readback of status, control, pad, raw interrupts, PCIe enable and status)
UART pinmuxsetup_uart sets GPIO14 and GPIO15 to Alt4

Level-triggered event enable bits are defined but marked unused; the code arms rising and falling edges only.

GPIO interrupt route

RP1 interrupts reach the CPU as PCIe MSI-X messages, so a pin interrupt crosses several stages. rp1_irq.rs documents and programs the route for RP1 vector 0 (IO_BANK0):

initialize_gpio_route validates every stage with readbacks and returns a typed error for each failure mode: PCIe link down, root-bus readback, endpoint unavailable (expects vendor/device 0x0001_1DE4 and chip id 0x2000_1927), missing or invalid MSI-X capability, invalid BAR layout, inbound-window, MIP, MSI-X table and vector readbacks. It repoints the RP1 BARs itself (BAR1 peripherals at PCIe 0, BAR2 SRAM at 4 MiB, BAR0 MSI-X table at 8 MiB, a 9 MiB outbound window), quiesces leftover firmware state, and enables MSI-X globally only after everything downstream is installed.

On the GIC side, interrupts::init unconditionally (on rpi5) marks ID 160 edge-triggered, enables it at priority 0x80, and dispatches it to gpio::handle_interrupt. That handler reads the pending PCIe interrupt bitmap, scans pending pins, reads events, invokes attached BPF programs with a timestamp from the generic counter, clears events, and acknowledges the RP1 vector.

In the source reviewed, init_pcie_interrupt_route (and enable_pwm0_clock, below) are called from kernel/src/bench.rs, which is compiled only with the bench feature. sys_bpf GPIO attach calls configure_input and enable_interrupt for the pin, but no call to the route initializer appears on that path, and no other call site was found. Whether a non-bench image establishes the MSI-X route some other way was not confirmed. The GPIO interrupt HIL evidence required by ADR-0003 is also a pending checklist item, not a recorded result.

How GPIO is used by the rest of the kernel

  • sys_bpf attach for GPIO edges: after the BPF manager records the route, the pin is configured as input and its edge interrupt enabled.
  • BPF helpers and actuation::guard_gpio write pins through Rp1Gpio, with the actuation monitor deciding first.
  • bench.rs uses GPIO23 as a sensor input and GPIO24 as an e-stop input (see Raspberry Pi 5).

PWM

pwm.rs drives RP1’s own PWM block, which the file states does not use the BCM2835 register layout. Each of PWM0 and PWM1 has a GLOBAL_CTRL register (bit N enables channel N, bit 31 latches) and four channels at a 16-byte stride with CHAN_CTRL, RANGE, PHASE and DUTY.

  • Channel numbering is 1-based in the public API to match the actuation monitor: channels 1 to 4 map to hardware channels 0 to 3. GPIO12 (Alt0) is PWM0 hardware channel 0, so monitor channel 1.
  • API: init (disables all channels), enable, disable, set_range, set_data, set_frequency, set_duty_cycle (percent), debug_regs.
  • Channel default: CHAN_CTRL = 0x101 (trailing-edge mark/space plus FIFO-pop mask), matching the Linux driver.
  • Clock: the PWM counter has a functional clock separate from the APB bus, and firmware leaves it gated. enable_pwm0_clock programs the divider, selects the parent through the AUX source mux and sets the enable bit so the crystal oscillator feeds PWM0 divide-by-one. Per the code comments, an earlier approach that skipped selecting the aux source left the counter frozen.
  • Assumed rate: RP1_PWM_CLOCK_HZ = 50_000_000 is an assumption used to turn a requested frequency into RANGE. A source comment says to calibrate it against a scope before quoting carrier frequencies, and the runbook asks for the physical period to be measured separately.
  • Motor ownership: actuation channels 1 and 2 on chip 0 are owned by the signed Shrike motor link in normal builds (control_link::motor_side), so local RP1 PWM is not what drives the wheels on those channels. See Drivers.

UARTs

RoleHardwareDriver
Kernel consoleBCM2712 PL011 UART10 at 0x10_7D00_1000 (not RP1)uart.rs, no-op init relying on firmware; blocking and non-blocking TX and RX
Shrike control linkRP1 UART0 on GPIO14/15pl011.rs, real init at 115200 8N1 with a 48 MHz UART clock default, polled

The control-link PL011 driver masks all interrupts; enable_rx_irq exists but the comments state IRQ-driven RX is deferred because it needs RP1 peripheral interrupt demultiplexing (issue 65). The rpi5/mod.rs module also creates UART, PWM0 and PWM1 as lazily initialized mutex-guarded globals.

What is missing

The README states there are no I2C, SPI, CAN or Ethernet drivers, and only GPIO, PWM and UART exist on RP1. The source agrees:

PeripheralState
I2C, SPIOnly base offset constants (RP1_I2C0_OFFSET, RP1_SPI0_OFFSET); no driver. The IIO module’s header says it will interface with I2C/SPI drivers in future
CAN, EthernetNo driver
UART1Address constant only
RP1 interrupt demux for peripherals other than IO_BANK0 vector 0Not implemented (IRQ-driven UART RX deferred)
General PCIe enumerationNot implemented; RP1 is configured with fixed constants, relying on pciex4_reset=0
DMA, USB, SD/MMC, ADCNo driver found

Issue references from the README for the missing drivers include 63 and 64 (and others on the roadmap, issue 81).

Last updated on