Skip to main content

ove/async_runtime/
i2c.rs

1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! Async I2C bus wrapper. See [`super::spi`] for the design notes — same
8//! pattern, with an `embedded_hal_async::i2c::I2c<SevenBitAddress>` impl
9//! when the `embedded-hal-async` feature is enabled.
10
11use ::core::cell::UnsafeCell;
12use ::core::ffi::c_void;
13use ::core::future::poll_fn;
14use ::core::task::Poll;
15
16use crate::bindings;
17use crate::error::{Error, Result};
18
19use super::spi::DmaSlot;
20
21unsafe extern "C" fn dma_complete_cb(result: ::core::ffi::c_int, user_data: *mut c_void) {
22    // SAFETY: user_data points to a DmaSlot owned by the calling future.
23    let slot = unsafe { &*(user_data as *const DmaSlot) };
24    slot.result_store(result as i32);
25    slot.wake();
26}
27
28/// Async I2C bus master.
29pub struct AsyncI2c {
30    i2c: bindings::ove_i2c_t,
31    slot: UnsafeCell<DmaSlot>,
32}
33
34// SAFETY: `AsyncI2c` wraps an `ove_i2c_t` handle.  The substrate serialises
35// bus transactions via its driver-level lock; the `UnsafeCell<DmaSlot>` is
36// only touched between `arm()` and the completion ISR, never concurrently
37// from Rust code.
38unsafe impl Send for AsyncI2c {}
39unsafe impl Sync for AsyncI2c {}
40
41impl AsyncI2c {
42    /// Wrap an existing `ove_i2c_t` handle for async use.
43    ///
44    /// # Safety
45    /// `handle` must be a valid I2C handle. Don't use the same handle
46    /// through a sync [`crate::i2c::I2c`] wrapper while async
47    /// transactions are in flight.
48    #[inline]
49    pub const unsafe fn from_handle(handle: bindings::ove_i2c_t) -> Self {
50        Self {
51            i2c: handle,
52            slot: UnsafeCell::new(DmaSlot::new()),
53        }
54    }
55
56    /// Async write-then-read with repeated start.
57    pub async fn write_read(&self, addr: u16, tx: &[u8], rx: &mut [u8]) -> Result<()> {
58        let tx_ptr = if tx.is_empty() {
59            ::core::ptr::null()
60        } else {
61            tx.as_ptr().cast()
62        };
63        let rx_ptr = if rx.is_empty() {
64            ::core::ptr::null_mut()
65        } else {
66            rx.as_mut_ptr().cast()
67        };
68
69        let slot: &DmaSlot = unsafe { &*self.slot.get() };
70        slot.reset();
71
72        let rc = unsafe {
73            bindings::ove_i2c_write_read_async(
74                self.i2c,
75                addr,
76                tx_ptr,
77                tx.len(),
78                rx_ptr,
79                rx.len(),
80                Some(dma_complete_cb),
81                slot as *const _ as *mut c_void,
82            )
83        };
84        Error::from_code(rc)?;
85
86        poll_fn(|cx| {
87            slot.register(cx.waker());
88            let r = slot.result_load();
89            if r == DmaSlot::PENDING {
90                Poll::Pending
91            } else {
92                Poll::Ready(Error::from_code(r))
93            }
94        })
95        .await
96    }
97
98    /// Async write-only transaction.
99    pub async fn write(&self, addr: u16, data: &[u8]) -> Result<()> {
100        let mut empty = [];
101        self.write_read(addr, data, &mut empty).await
102    }
103
104    /// Async read-only transaction.
105    pub async fn read(&self, addr: u16, buf: &mut [u8]) -> Result<()> {
106        let empty: &[u8] = &[];
107        self.write_read(addr, empty, buf).await
108    }
109}
110
111#[cfg(feature = "embedded-hal-async")]
112mod hal_async_impl {
113    use super::AsyncI2c;
114    use crate::error::Error;
115    use embedded_hal::i2c::{ErrorType, Operation, SevenBitAddress};
116    use embedded_hal_async::i2c::I2c;
117
118    impl ErrorType for AsyncI2c {
119        type Error = Error;
120    }
121
122    impl I2c<SevenBitAddress> for AsyncI2c {
123        async fn transaction(
124            &mut self,
125            address: SevenBitAddress,
126            operations: &mut [Operation<'_>],
127        ) -> Result<(), Self::Error> {
128            // Coalesce adjacent Write+Read pairs into a single
129            // ove_i2c_write_read_async call; otherwise issue per
130            // operation. This matches the embedded-hal-async semantics
131            // (each op is a logical transfer; repeated start between).
132            let mut i = 0;
133            while i < operations.len() {
134                match &mut operations[i..] {
135                    [Operation::Write(w), Operation::Read(r), ..] => {
136                        AsyncI2c::write_read(self, address as u16, w, r).await?;
137                        i += 2;
138                    }
139                    [Operation::Write(w), ..] => {
140                        AsyncI2c::write(self, address as u16, w).await?;
141                        i += 1;
142                    }
143                    [Operation::Read(r), ..] => {
144                        AsyncI2c::read(self, address as u16, r).await?;
145                        i += 1;
146                    }
147                    [] => break,
148                }
149            }
150            Ok(())
151        }
152    }
153}