blob: f05cb3ff5c89830fb45f4a7ea32176b867ff47a6 [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
//! I2C capability, owned by openprot: a bus is an SCL role + an SDA role + a controller.
//! Pins carry chip-local facts (approach D); the controller owns routing + config.
use crate::field_mux::{coalesce_routes, Mmio, MuxRoutes, RegBatch};
use crate::resource::{Capability, Routes};
/// I2C-SCL capability: a pin muxable as a controller's clock line, carrying [`I2cData`].
pub struct I2cScl;
impl Capability for I2cScl {
type Data = I2cData;
}
/// I2C-SDA capability: a pin muxable as a controller's data line, carrying [`I2cData`].
pub struct I2cSda;
impl Capability for I2cSda {
type Data = I2cData;
}
/// One I2C controller's `(I2C, I2CBUFF)` register bases, type-erased to stay PAC-agnostic.
#[derive(Clone, Copy)]
pub struct I2cCtrlRegs {
/// Const-comparable controller identity, so a bus's two pins prove they name one controller.
pub id: usize,
/// Base of the controller's I2C register block.
pub i2c: *const (),
/// Base of the controller's I2CBUFF register block.
pub buff: *const (),
}
/// One I2C pin's datum: its mux route + the controller it wires to.
#[derive(Clone, Copy)]
pub struct I2cData {
/// The controller this pin is wired to.
pub ctrl: I2cCtrlRegs,
}
/// Block-identity marker phantom-typing a controller's I2C applier so it can't stand in for its I2CBUFF applier.
pub struct I2cBlock;
/// Block-identity marker phantom-typing a controller's I2CBUFF applier so it can't stand in for its I2C applier.
pub struct I2cBuffBlock;
/// Const-asserts both pins name one controller — an associated const carries the assert (control flow) a generic const block can't.
trait AssertI2cPins {
const SAME_CONTROLLER: ();
}
impl<Scl: Routes<I2cScl>, Sda: Routes<I2cSda>> AssertI2cPins for (Scl, Sda) {
const SAME_CONTROLLER: () = assert!(
<Scl as Routes<I2cScl>>::DATA.ctrl.id == <Sda as Routes<I2cSda>>::DATA.ctrl.id,
"SCL and SDA pins name different I2C controllers",
);
}
/// Mint a controller's `(I2C, I2CBUFF)` appliers from a bus's two pin tokens; const-asserts both name one controller.
pub const fn i2c_mmios<Scl: Routes<I2cScl>, Sda: Routes<I2cSda>>(
_scl: &Scl,
_sda: &Sda,
) -> (Mmio<I2cBlock>, Mmio<I2cBuffBlock>) {
let () = <(Scl, Sda) as AssertI2cPins>::SAME_CONTROLLER;
(
Mmio::from_raw(<Scl as Routes<I2cScl>>::DATA.ctrl.i2c as *const u8),
Mmio::from_raw(<Scl as Routes<I2cScl>>::DATA.ctrl.buff as *const u8),
)
}
/// A controller that brings up an I2C bus — the chip-side seam. Pins are already muxed by the
/// bus constructor at boot, so this only initializes the controller registers.
pub trait I2cController: Sized {
/// Configuration consumed while opening (e.g. bus timing).
type Config;
/// The ready-to-use driver produced on success.
type Ready;
/// Failure returned if hardware bring-up fails.
type Error: core::fmt::Debug;
/// Bring up the bus, consuming the controller (open-once).
fn open(self, config: &Self::Config) -> Result<Self::Ready, Self::Error>;
}
/// An I2C bus: two role-capable pins + a controller, all owned (the bus is their claim).
pub struct I2cBus<Scl, Sda, C> {
// Held to own the pin claim for the bus's lifetime; never read (routing is done at boot).
#[allow(dead_code)]
scl: Scl,
#[allow(dead_code)]
sda: Sda,
ctrl: C,
}
impl<Scl, Sda, C> I2cBus<Scl, Sda, C> {
/// Token authority: the moved-in pin tokens are the sole right to route these pins; the bus's `MuxRoutes` carries their routes, applied by the SCU `route` applier.
pub const fn new(scl: Scl, sda: Sda, ctrl: C) -> Self {
Self { scl, sda, ctrl }
}
}
/// An I2C bus carries both pins' SCU routes at the type level, folded to one RMW per register at compile time.
impl<Scl: Routes<I2cScl>, Sda: Routes<I2cSda>, C> MuxRoutes for I2cBus<Scl, Sda, C> {
const COALESCED: RegBatch = coalesce_routes(&[
<Scl as Routes<I2cScl>>::ROUTE,
<Sda as Routes<I2cSda>>::ROUTE,
]);
}
impl<Scl, Sda, C> I2cBus<Scl, Sda, C>
where
C: I2cController,
{
/// Bring up the bus, consuming it (open-once). Pins are already muxed by the bus constructor, and
/// role capability was enforced at construction (`Routes<I2cScl>`/`Routes<I2cSda>` bounds); returns
/// the controller's error on bring-up failure.
pub fn open(self, config: &C::Config) -> Result<C::Ready, C::Error> {
self.ctrl.open(config)
}
}