ove/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//! I2C bus master driver.
8//!
9//! [`I2c`] wraps an opaque `ove_i2c_t` handle and exposes a method-shaped
10//! API. An `embedded_hal::i2c::I2c` impl is provided when the
11//! `embedded-hal` Cargo feature is enabled.
12
13use crate::bindings;
14use crate::error::{Error, Result};
15
16// SAFETY (module-wide contract for the `unsafe { bindings::ove_*(...) }` FFI
17// calls below): any handle passed to the C API is non-null and refers to a
18// live RTOS object — wrapper constructors establish validity via
19// `Error::from_code`, and `Drop` (or an explicit `deinit`) is the only place
20// a handle is released. Pointer and slice arguments reference caller-owned
21// memory valid for the duration of the call; the C side copies whatever it
22// retains and does not alias them past return (verified against the
23// signatures in `include/ove/*.h`). Blocks that deviate — `transmute`, raw
24// pointer casts from user data, slice reconstruction via `from_raw_parts`,
25// or storing a callback across the FFI boundary — carry their own
26// `// SAFETY:` comment.
27
28/// I2C bus speed grade.
29#[repr(u32)]
30#[derive(Debug, Copy, Clone, PartialEq, Eq)]
31pub enum Speed {
32 /// Standard mode — 100 kHz.
33 Standard = 0,
34 /// Fast mode — 400 kHz.
35 Fast = 1,
36 /// Fast-mode Plus — 1 MHz.
37 FastPlus = 2,
38}
39
40/// I2C bus master.
41///
42/// Wraps an opaque `ove_i2c_t` handle provided by the board configuration
43/// (the substrate manages the handle's lifetime; the binding does not own
44/// it). Construct via [`I2c::from_handle`] with a handle obtained from
45/// the BSP layer or board descriptor.
46#[derive(Debug, Copy, Clone)]
47pub struct I2c {
48 handle: bindings::ove_i2c_t,
49}
50
51impl I2c {
52 /// Wrap an existing `ove_i2c_t` handle.
53 ///
54 /// # Safety
55 /// - `handle` must be a valid I2C handle returned by the substrate
56 /// (typically via the board config / BSP layer).
57 /// - The caller is responsible for ensuring no other `I2c` wrapper
58 /// exists for the same handle concurrently — the substrate
59 /// serialises bus access internally, but constructing two
60 /// wrappers with conflicting trait-impl state is a logic error.
61 #[inline]
62 pub const unsafe fn from_handle(handle: bindings::ove_i2c_t) -> Self {
63 Self { handle }
64 }
65
66 /// Return the underlying handle. Useful when bridging into the
67 /// raw FFI for an operation the binding hasn't surfaced.
68 #[inline]
69 pub fn raw(&self) -> bindings::ove_i2c_t {
70 self.handle
71 }
72
73 /// Write data to an I2C device.
74 pub fn write(&self, addr: u16, data: &[u8], timeout: core::time::Duration) -> Result<()> {
75 let rc = unsafe {
76 bindings::ove_i2c_write(
77 self.handle,
78 addr,
79 data.as_ptr().cast(),
80 data.len(),
81 crate::time::dur_to_ns(timeout),
82 )
83 };
84 Error::from_code(rc)
85 }
86
87 /// Read data from an I2C device.
88 pub fn read(&self, addr: u16, buf: &mut [u8], timeout: core::time::Duration) -> Result<()> {
89 let rc = unsafe {
90 bindings::ove_i2c_read(
91 self.handle,
92 addr,
93 buf.as_mut_ptr().cast(),
94 buf.len(),
95 crate::time::dur_to_ns(timeout),
96 )
97 };
98 Error::from_code(rc)
99 }
100
101 /// Combined write-then-read with I2C repeated start.
102 pub fn write_read(
103 &self,
104 addr: u16,
105 tx: &[u8],
106 rx: &mut [u8],
107 timeout: core::time::Duration,
108 ) -> Result<()> {
109 let rc = unsafe {
110 bindings::ove_i2c_write_read(
111 self.handle,
112 addr,
113 tx.as_ptr().cast(),
114 tx.len(),
115 rx.as_mut_ptr().cast(),
116 rx.len(),
117 crate::time::dur_to_ns(timeout),
118 )
119 };
120 Error::from_code(rc)
121 }
122
123 /// Write to a single-byte-addressed register.
124 pub fn reg_write(
125 &self,
126 addr: u16,
127 reg: u8,
128 data: &[u8],
129 timeout: core::time::Duration,
130 ) -> Result<()> {
131 let rc = unsafe {
132 bindings::ove_i2c_reg_write(
133 self.handle,
134 addr,
135 reg,
136 data.as_ptr().cast(),
137 data.len(),
138 crate::time::dur_to_ns(timeout),
139 )
140 };
141 Error::from_code(rc)
142 }
143
144 /// Read from a single-byte-addressed register.
145 pub fn reg_read(
146 &self,
147 addr: u16,
148 reg: u8,
149 buf: &mut [u8],
150 timeout: core::time::Duration,
151 ) -> Result<()> {
152 let rc = unsafe {
153 bindings::ove_i2c_reg_read(
154 self.handle,
155 addr,
156 reg,
157 buf.as_mut_ptr().cast(),
158 buf.len(),
159 crate::time::dur_to_ns(timeout),
160 )
161 };
162 Error::from_code(rc)
163 }
164
165 /// Probe for a device at the given address (zero-length write, check ACK).
166 pub fn probe(&self, addr: u16, timeout: core::time::Duration) -> Result<()> {
167 let rc =
168 unsafe { bindings::ove_i2c_probe(self.handle, addr, crate::time::dur_to_ns(timeout)) };
169 Error::from_code(rc)
170 }
171}