ariel_os_rp/i2c/controller/
mod.rs1#![expect(unsafe_code)]
4
5use portable_atomic::{AtomicBool, Ordering};
6
7use ariel_os_embassy_common::{i2c::controller::Kilohertz, impl_async_i2c_for_driver_enum};
8use embassy_rp::{
9 bind_interrupts,
10 i2c::{InterruptHandler, SclPin, SdaPin},
11 peripherals,
12};
13
14const KHZ_TO_HZ: u32 = 1000;
15
16#[derive(Clone)]
20#[non_exhaustive]
21pub struct Config {
22 pub frequency: Frequency,
24}
25
26impl Default for Config {
27 fn default() -> Self {
28 Self {
29 frequency: Frequency::UpTo100k(Kilohertz::kHz(100)),
30 }
31 }
32}
33
34#[derive(Debug, Copy, Clone, PartialEq, Eq)]
38#[cfg_attr(feature = "defmt", derive(defmt::Format))]
39#[repr(u32)]
40pub enum Frequency {
41 UpTo100k(Kilohertz), UpTo400k(Kilohertz), }
46
47#[doc(hidden)]
48impl Frequency {
49 #[must_use]
50 pub const fn first() -> Self {
51 Self::UpTo100k(Kilohertz::kHz(1))
52 }
53
54 #[must_use]
55 pub const fn last() -> Self {
56 Self::UpTo400k(Kilohertz::kHz(400))
57 }
58
59 #[must_use]
60 pub const fn next(self) -> Option<Self> {
61 match self {
62 Self::UpTo100k(f) => {
63 if f.to_kHz() < 100 {
64 Some(Self::UpTo100k(Kilohertz::kHz(f.to_kHz() + 1)))
66 } else {
67 Some(Self::UpTo400k(Kilohertz::kHz(self.khz() + 1)))
68 }
69 }
70 Self::UpTo400k(f) => {
71 if f.to_kHz() < 400 {
72 Some(Self::UpTo400k(Kilohertz::kHz(f.to_kHz() + 1)))
74 } else {
75 None
76 }
77 }
78 }
79 }
80
81 #[must_use]
82 pub const fn prev(self) -> Option<Self> {
83 match self {
84 Self::UpTo100k(f) => {
85 if f.to_kHz() > 1 {
86 Some(Self::UpTo100k(Kilohertz::kHz(f.to_kHz() - 1)))
88 } else {
89 None
90 }
91 }
92 Self::UpTo400k(f) => {
93 if f.to_kHz() > 100 + 1 {
94 Some(Self::UpTo400k(Kilohertz::kHz(f.to_kHz() - 1)))
96 } else {
97 Some(Self::UpTo100k(Kilohertz::kHz(self.khz() - 1)))
98 }
99 }
100 }
101 }
102
103 #[must_use]
104 pub const fn khz(self) -> u32 {
105 match self {
106 Self::UpTo100k(f) | Self::UpTo400k(f) => f.to_kHz(),
107 }
108 }
109}
110
111ariel_os_embassy_common::impl_i2c_from_frequency_up_to!();
112
113macro_rules! define_i2c_drivers {
114 ($( $interrupt:ident => $peripheral:ident ),* $(,)?) => {
115 $(
116 pub struct $peripheral {
118 twim: embassy_rp::i2c::I2c<'static, peripherals::$peripheral, embassy_rp::i2c::Async>,
119 }
120
121 paste::paste! {
123 static [< ACTIVE_ $peripheral >]: AtomicBool = AtomicBool::new(false);
124 }
125
126 impl $peripheral {
127 #[expect(clippy::new_ret_no_self)]
130 #[must_use]
131 pub fn new<SDA: SdaPin<peripherals::$peripheral>, SCL: SclPin<peripherals::$peripheral>>(
132 sda_pin: impl $crate::IntoPeripheral<'static, SDA>,
133 scl_pin: impl $crate::IntoPeripheral<'static, SCL>,
134 config: Config,
135 ) -> I2c {
136 paste::paste! {
139 #[allow(dead_code)]
140 static [<PREVENT_MULTIPLE_ $peripheral>]: () = ();
141 }
142
143 let mut i2c_config = embassy_rp::i2c::Config::default();
144 i2c_config.frequency = config.frequency.khz() * KHZ_TO_HZ;
145
146 bind_interrupts!(
147 struct Irqs {
148 $interrupt => InterruptHandler<peripherals::$peripheral>;
149 }
150 );
151
152 paste::paste! {
154 if [< ACTIVE_ $peripheral >].swap(true, Ordering::AcqRel) {
155 panic!("I2C peripheral already initialized")
156 }
157 }
158
159 let i2c_peripheral = unsafe { peripherals::$peripheral::steal() };
163
164 let i2c = embassy_rp::i2c::I2c::new_async(
167 i2c_peripheral,
168 scl_pin.into_hal_peripheral(),
169 sda_pin.into_hal_peripheral(),
170 Irqs,
171 i2c_config,
172 );
173
174 I2c::$peripheral(Self { twim: i2c })
175 }
176 }
177
178 impl Drop for $peripheral {
179 fn drop(&mut self) {
180 paste::paste! {
181 [< ACTIVE_ $peripheral >].store(false, Ordering::Release);
182 }
183 }
184 }
185 )*
186
187 pub enum I2c {
189 $(
190 #[doc = concat!(stringify!($peripheral), " peripheral.")]
191 $peripheral($peripheral),
192 )*
193 }
194
195 impl embedded_hal_async::i2c::ErrorType for I2c {
196 type Error = ariel_os_embassy_common::i2c::controller::Error;
197 }
198
199 impl_async_i2c_for_driver_enum!(I2c, $( $peripheral ),*);
200 }
201}
202
203fn from_error(err: embassy_rp::i2c::Error) -> ariel_os_embassy_common::i2c::controller::Error {
205 #[allow(deprecated)]
206 use embassy_rp::i2c::{
207 AbortReason,
208 Error::{
209 Abort, AddressOutOfRange, AddressReserved, InvalidReadBufferLength,
210 InvalidWriteBufferLength,
211 },
212 };
213
214 use ariel_os_embassy_common::i2c::controller::{Error, NoAcknowledgeSource};
215
216 match err {
217 Abort(reason) => match reason {
218 AbortReason::NoAcknowledge => Error::NoAcknowledge(NoAcknowledgeSource::Unknown),
219 AbortReason::ArbitrationLoss => Error::ArbitrationLoss,
220 AbortReason::TxNotEmpty(_) | AbortReason::Other(_) => Error::Other,
221 },
222 #[allow(deprecated)]
223 AddressReserved(_)
224 | InvalidWriteBufferLength
225 | AddressOutOfRange(_)
226 | InvalidReadBufferLength => Error::Other,
227 }
228}
229
230define_i2c_drivers!(
232 I2C0_IRQ => I2C0,
233 I2C1_IRQ => I2C1,
234);