zephyr/device/
i2c.rs

1//! Device wrappers for Zephyr I2C controllers and targets.
2
3use core::cell::UnsafeCell;
4use core::ffi::c_int;
5
6use super::{NoStatic, Unique};
7use crate::{
8    error::{to_result_void, Error},
9    raw, Result,
10};
11
12// Re-export the raw callback types so users can write `extern "C"` handlers without reaching into
13// `zephyr::raw` directly.
14pub use raw::{i2c_target_callbacks, i2c_target_config};
15
16/// A single operation in a multi-stage I2C transaction.
17///
18/// Used with [`I2c::transfer`] to compose general scatter/gather transfers.  A RESTART is
19/// inserted automatically between adjacent operations of differing direction; the underlying
20/// `i2c_transfer` always emits a STOP after the final operation.
21pub enum Operation<'a> {
22    /// Write the bytes from the buffer to the bus.
23    Write(&'a [u8]),
24    /// Read bytes from the bus into the buffer.
25    Read(&'a mut [u8]),
26}
27
28/// A Zephyr I2C controller device.
29///
30/// This wrapper maps to Zephyr's blocking I2C controller API (`i2c_write`, `i2c_read`,
31/// `i2c_write_read`).  All operations block the calling thread until the transfer completes.
32#[allow(dead_code)]
33pub struct I2c {
34    pub(crate) device: *const raw::device,
35}
36
37// SAFETY: `I2c` holds a raw pointer to a static Zephyr device structure with no thread-affine
38// Rust state.  The Zephyr I2C driver serialises bus access internally via a semaphore.
39unsafe impl Send for I2c {}
40
41impl I2c {
42    /// Constructor, intended to be called by devicetree generated code.
43    #[allow(dead_code)]
44    pub(crate) unsafe fn new(
45        unique: &Unique,
46        _static: &NoStatic,
47        device: *const raw::device,
48    ) -> Option<I2c> {
49        if !unique.once() {
50            return None;
51        }
52
53        Some(I2c { device })
54    }
55
56    /// Verify that the underlying I2C device is ready for use.
57    pub fn is_ready(&self) -> bool {
58        unsafe { raw::device_is_ready(self.device) }
59    }
60
61    /// Write bytes to an I2C device, then read bytes back in a single transaction (RESTART
62    /// between the write and read phases).
63    pub fn write_read(&mut self, addr: u16, write_buf: &[u8], read_buf: &mut [u8]) -> Result<()> {
64        to_result_void(unsafe {
65            raw::zr_i2c_write_read(
66                self.device,
67                addr,
68                write_buf.as_ptr().cast(),
69                write_buf.len(),
70                read_buf.as_mut_ptr().cast(),
71                read_buf.len(),
72            )
73        })
74    }
75
76    /// Write bytes to an I2C device.
77    pub fn write(&mut self, addr: u16, buf: &[u8]) -> Result<()> {
78        to_result_void(unsafe {
79            raw::zr_i2c_write(self.device, buf.as_ptr(), buf.len() as u32, addr)
80        })
81    }
82
83    /// Read bytes from an I2C device.
84    pub fn read(&mut self, addr: u16, buf: &mut [u8]) -> Result<()> {
85        to_result_void(unsafe {
86            raw::zr_i2c_read(self.device, buf.as_mut_ptr(), buf.len() as u32, addr)
87        })
88    }
89
90    /// Maximum number of operations supported in a single [`transfer`](Self::transfer) call.
91    ///
92    /// `transfer` builds the underlying `i2c_msg` array on the stack so the bound is fixed at
93    /// compile time.  Increase this if longer transactions are needed.
94    pub const MAX_TRANSFER_OPS: usize = 8;
95
96    /// Perform a multi-stage I2C transaction.
97    ///
98    /// Wraps Zephyr's `i2c_transfer`, the general-purpose scatter/gather entry point.  Each
99    /// [`Operation`] becomes one `i2c_msg`; a RESTART is inserted between adjacent operations
100    /// whose direction differs, and `i2c_transfer` itself appends a STOP after the final
101    /// message.
102    ///
103    /// Up to [`MAX_TRANSFER_OPS`](Self::MAX_TRANSFER_OPS) operations are supported per call;
104    /// passing more returns `Err(Error(E2BIG))`.  For simple cases prefer
105    /// [`write`](Self::write), [`read`](Self::read), or [`write_read`](Self::write_read).
106    pub fn transfer(&mut self, addr: u16, ops: &mut [Operation<'_>]) -> Result<()> {
107        if ops.len() > Self::MAX_TRANSFER_OPS {
108            return Err(Error(raw::E2BIG));
109        }
110
111        let mut msgs: [raw::i2c_msg; Self::MAX_TRANSFER_OPS] =
112            core::array::from_fn(|_| Default::default());
113        for (i, op) in ops.iter_mut().enumerate() {
114            let (buf_ptr, len, mut flags) = match op {
115                // i2c_msg.buf is `*mut u8` even for writes; the driver does not mutate write
116                // buffers.
117                Operation::Write(buf) => {
118                    (buf.as_ptr() as *mut u8, buf.len(), raw::ZR_I2C_MSG_WRITE)
119                }
120                Operation::Read(buf) => (buf.as_mut_ptr(), buf.len(), raw::ZR_I2C_MSG_READ),
121            };
122            // Insert a RESTART when the direction changes from the previous message, matching
123            // what Zephyr's own `i2c_write_read` helper does for the write -> read transition.
124            if i > 0 && (msgs[i - 1].flags & raw::ZR_I2C_MSG_READ) != (flags & raw::ZR_I2C_MSG_READ)
125            {
126                flags |= raw::ZR_I2C_MSG_RESTART;
127            }
128            msgs[i].buf = buf_ptr;
129            msgs[i].len = len as u32;
130            msgs[i].flags = flags;
131        }
132
133        to_result_void(unsafe {
134            raw::i2c_transfer(self.device, msgs.as_mut_ptr(), ops.len() as u8, addr)
135        })
136    }
137
138    /// Register an I2C target on this bus (low-level).
139    ///
140    /// Prefer [`I2cTargetData::register`] for a fully safe interface.
141    ///
142    /// The caller must supply a fully initialised `i2c_target_config` (including the `callbacks`
143    /// pointer and target address).  The config and the callbacks it points to must remain valid
144    /// for as long as the target is registered — typically they live in a `static`.
145    ///
146    /// All callback function pointers in the `i2c_target_callbacks` struct are invoked from ISR
147    /// context.  The caller is responsible for providing the `extern "C"` callback implementations
148    /// and managing any shared state they access.
149    ///
150    /// Returns an [`I2cTarget`] handle that can be used to unregister.
151    ///
152    /// # Safety
153    ///
154    /// The `config` pointer must remain valid and unmodified until [`I2cTarget::unregister`] is
155    /// called.  The callback functions must be safe to call from ISR context.
156    pub unsafe fn register_target(
157        &mut self,
158        config: &'static mut i2c_target_config,
159    ) -> Result<I2cTarget> {
160        let config_ptr = config as *mut _;
161        to_result_void(raw::zr_i2c_target_register(self.device, config_ptr))?;
162        Ok(I2cTarget {
163            device: self.device,
164            config: config_ptr,
165        })
166    }
167}
168
169// ---------------------------------------------------------------------------
170// Safe I2C target abstraction
171// ---------------------------------------------------------------------------
172
173/// Trait for I2C target callbacks.
174///
175/// Implement this on a type that holds your shared state.  All methods receive
176/// `&self` and are called from **ISR context**, so interior mutability must use
177/// ISR-safe mechanisms such as [`SpinMutex`](crate::sync::SpinMutex) or
178/// atomics.
179///
180/// The implementing type must be [`Send`] + [`Sync`] because it is shared
181/// between ISR callbacks and regular threads.
182pub trait I2cTargetCallbacks: Send + Sync + 'static {
183    /// Called when the controller initiates a write to this target.
184    fn write_requested(&self) -> Result<()> {
185        Ok(())
186    }
187
188    /// Called for each byte received from the controller.
189    fn write_received(&self, val: u8) -> Result<()> {
190        let _ = val;
191        Ok(())
192    }
193
194    /// Called when the controller initiates a read from this target.
195    ///
196    /// Returns the first byte to send to the controller.
197    fn read_requested(&self) -> Result<u8> {
198        Ok(0)
199    }
200
201    /// Called after the controller reads a byte, requesting the next.
202    ///
203    /// Returns the next byte to send to the controller.
204    fn read_processed(&self) -> Result<u8> {
205        Ok(0)
206    }
207
208    /// Called when a STOP condition is detected.
209    fn stop(&self) -> Result<()> {
210        Ok(())
211    }
212}
213
214/// Static storage for an I2C target device.
215///
216/// Wraps a user callback implementation `T` together with the Zephyr
217/// `i2c_target_config` and `i2c_target_callbacks` needed for registration.
218/// Place this in a `static` and call [`register`](Self::register) to activate
219/// the target on an I2C bus.
220///
221/// The [`data`](Self::data) method provides a shared reference to the user
222/// state, usable from any thread.
223///
224/// # Example
225///
226/// ```ignore
227/// use zephyr::device::i2c::{I2cTargetData, I2cTargetCallbacks};
228/// use zephyr::sync::SpinMutex;
229///
230/// struct MyTarget {
231///     inner: SpinMutex<MyState>,
232/// }
233///
234/// impl I2cTargetCallbacks for MyTarget {
235///     fn write_received(&self, val: u8) -> zephyr::Result<()> {
236///         let mut s = self.inner.lock().unwrap();
237///         // handle byte ...
238///         Ok(())
239///     }
240///     // ... other callbacks
241/// #   fn read_requested(&self) -> zephyr::Result<u8> { Ok(0) }
242/// #   fn read_processed(&self) -> zephyr::Result<u8> { Ok(0) }
243/// }
244///
245/// static TARGET: I2cTargetData<MyTarget> = I2cTargetData::new(0x42, MyTarget {
246///     inner: SpinMutex::new(MyState::new()),
247/// });
248///
249/// fn main() {
250///     let mut i2c = /* get I2C device */;
251///     let _handle = TARGET.register(&mut i2c).unwrap();
252///     // Access shared state from any thread:
253///     let state = TARGET.data();
254/// }
255/// ```
256#[repr(C)]
257pub struct I2cTargetData<T: I2cTargetCallbacks> {
258    // First field: the C callbacks receive a `*mut i2c_target_config` that
259    // points here.  Because `UnsafeCell` is `#[repr(transparent)]` and this is
260    // the first field of a `#[repr(C)]` struct, the pointer value equals the
261    // `I2cTargetData` address, enabling a zero-offset container_of cast.
262    config: UnsafeCell<i2c_target_config>,
263    cbs: UnsafeCell<i2c_target_callbacks>,
264    data: T,
265}
266
267// SAFETY: T: Send + Sync.  The config and cbs cells are only written during
268// the single-threaded `register()` call; afterwards they are effectively
269// read-only (the driver reads `callbacks`, and the `node` field is managed by
270// Zephyr's internal linked list under its own lock).
271unsafe impl<T: I2cTargetCallbacks> Send for I2cTargetData<T> {}
272unsafe impl<T: I2cTargetCallbacks> Sync for I2cTargetData<T> {}
273
274impl<T: I2cTargetCallbacks> I2cTargetData<T> {
275    /// Create a new I2C target data instance.
276    ///
277    /// `address` is the 7-bit I2C target address.  `data` is the user state
278    /// that implements [`I2cTargetCallbacks`].
279    ///
280    /// Internal pointers (callback table, config→callbacks link) are set up
281    /// lazily by [`register`](Self::register).
282    pub const fn new(address: u16, data: T) -> Self {
283        Self {
284            config: UnsafeCell::new(i2c_target_config {
285                node: raw::sys_snode_t {
286                    next: core::ptr::null_mut(),
287                },
288                flags: 0,
289                address,
290                callbacks: core::ptr::null(),
291            }),
292            // SAFETY: A zeroed `i2c_target_callbacks` is valid — every field
293            // is an `Option<unsafe extern "C" fn(…)>`, which is `None` when
294            // zero.  Using `zeroed()` avoids enumerating fields that vary with
295            // Kconfig (e.g. CONFIG_I2C_TARGET_BUFFER_MODE).
296            cbs: UnsafeCell::new(unsafe { core::mem::zeroed() }),
297            data,
298        }
299    }
300
301    /// Shared reference to the user data.
302    ///
303    /// This is the same `T` that the ISR callbacks see via `&self`, so both
304    /// sides share the same synchronisation primitives inside `T`.
305    pub fn data(&self) -> &T {
306        &self.data
307    }
308
309    /// Register this target on the given I2C bus.
310    ///
311    /// Populates the C callback trampolines, links the internal config to the
312    /// callback table, and calls `i2c_target_register`.
313    ///
314    /// Returns an [`I2cTarget`] handle whose [`unregister`](I2cTarget::unregister)
315    /// method reverses the registration.
316    ///
317    /// The `&'static self` requirement ensures the backing storage outlives the
318    /// registration.
319    pub fn register(&'static self, i2c: &mut I2c) -> Result<I2cTarget> {
320        // SAFETY: We are the sole writer — `register` is called once during
321        // single-threaded init, before any callbacks can fire.
322        unsafe {
323            let cbs = &mut *self.cbs.get();
324            cbs.write_requested = Some(Self::write_requested_trampoline);
325            cbs.read_requested = Some(Self::read_requested_trampoline);
326            cbs.write_received = Some(Self::write_received_trampoline);
327            cbs.read_processed = Some(Self::read_processed_trampoline);
328            cbs.stop = Some(Self::stop_trampoline);
329
330            let config = &mut *self.config.get();
331            config.callbacks = self.cbs.get() as *const _;
332        }
333
334        to_result_void(unsafe { raw::zr_i2c_target_register(i2c.device, self.config.get()) })?;
335
336        Ok(I2cTarget {
337            device: i2c.device,
338            config: self.config.get(),
339        })
340    }
341
342    // -- internal helpers -----------------------------------------------------
343
344    /// Recover `&Self` from the config pointer passed into C callbacks.
345    ///
346    /// # Safety
347    ///
348    /// `ptr` must originate from the `config` field of a live `I2cTargetData<T>`.
349    unsafe fn from_config(ptr: *mut i2c_target_config) -> &'static Self {
350        unsafe { &*(ptr as *const Self) }
351    }
352
353    // -- C-ABI trampoline functions -------------------------------------------
354
355    unsafe extern "C" fn write_requested_trampoline(config: *mut i2c_target_config) -> c_int {
356        let this = unsafe { Self::from_config(config) };
357        match this.data.write_requested() {
358            Ok(()) => 0,
359            Err(e) => -(e.0 as c_int),
360        }
361    }
362
363    unsafe extern "C" fn write_received_trampoline(
364        config: *mut i2c_target_config,
365        val: u8,
366    ) -> c_int {
367        let this = unsafe { Self::from_config(config) };
368        match this.data.write_received(val) {
369            Ok(()) => 0,
370            Err(e) => -(e.0 as c_int),
371        }
372    }
373
374    unsafe extern "C" fn read_requested_trampoline(
375        config: *mut i2c_target_config,
376        val: *mut u8,
377    ) -> c_int {
378        let this = unsafe { Self::from_config(config) };
379        match this.data.read_requested() {
380            Ok(byte) => {
381                unsafe { *val = byte };
382                0
383            }
384            Err(e) => -(e.0 as c_int),
385        }
386    }
387
388    unsafe extern "C" fn read_processed_trampoline(
389        config: *mut i2c_target_config,
390        val: *mut u8,
391    ) -> c_int {
392        let this = unsafe { Self::from_config(config) };
393        match this.data.read_processed() {
394            Ok(byte) => {
395                unsafe { *val = byte };
396                0
397            }
398            Err(e) => -(e.0 as c_int),
399        }
400    }
401
402    unsafe extern "C" fn stop_trampoline(config: *mut i2c_target_config) -> c_int {
403        let this = unsafe { Self::from_config(config) };
404        match this.data.stop() {
405            Ok(()) => 0,
406            Err(e) => -(e.0 as c_int),
407        }
408    }
409}
410
411/// A registered I2C target.
412///
413/// Created by [`I2cTargetData::register`] or [`I2c::register_target`].  Holds
414/// the device pointer and config so it can unregister cleanly.  Dropping
415/// without calling [`unregister`](I2cTarget::unregister) will **not**
416/// automatically unregister — the caller must manage the lifetime explicitly.
417pub struct I2cTarget {
418    device: *const raw::device,
419    config: *mut i2c_target_config,
420}
421
422// SAFETY: Same justification as `I2c` — the raw device pointer is to a static Zephyr struct.
423unsafe impl Send for I2cTarget {}
424
425impl I2cTarget {
426    /// Unregister this I2C target from the bus.
427    pub fn unregister(self) -> Result<()> {
428        to_result_void(unsafe { raw::zr_i2c_target_unregister(self.device, self.config) })
429    }
430}