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}