aboutsummaryrefslogtreecommitdiff
path: root/embassy-mspm0/src/i2c_target.rs
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-mspm0/src/i2c_target.rs')
-rw-r--r--embassy-mspm0/src/i2c_target.rs510
1 files changed, 510 insertions, 0 deletions
diff --git a/embassy-mspm0/src/i2c_target.rs b/embassy-mspm0/src/i2c_target.rs
new file mode 100644
index 000000000..e371fa903
--- /dev/null
+++ b/embassy-mspm0/src/i2c_target.rs
@@ -0,0 +1,510 @@
1//! Inter-Integrated-Circuit (I2C) Target
2// The following code is modified from embassy-stm32 and embassy-rp
3// https://github.com/embassy-rs/embassy/tree/main/embassy-stm32
4// https://github.com/embassy-rs/embassy/tree/main/embassy-rp
5
6use core::future::poll_fn;
7use core::marker::PhantomData;
8use core::sync::atomic::Ordering;
9use core::task::Poll;
10
11use embassy_embedded_hal::SetConfig;
12use mspm0_metapac::i2c::vals::CpuIntIidxStat;
13
14use crate::gpio::{AnyPin, SealedPin};
15// Re-use I2c controller types
16use crate::i2c::{ClockSel, ConfigError, Info, Instance, InterruptHandler, SclPin, SdaPin, State};
17use crate::interrupt::InterruptExt;
18use crate::mode::{Async, Blocking, Mode};
19use crate::pac::i2c::vals;
20use crate::pac::{self};
21use crate::{Peri, i2c, i2c_target, interrupt};
22
23#[non_exhaustive]
24#[derive(Clone, Copy, PartialEq, Eq, Debug)]
25/// Config
26pub struct Config {
27 /// 7-bit Target Address
28 pub target_addr: u8,
29
30 /// Control if the target should ack to and report general calls.
31 pub general_call: bool,
32}
33
34impl Default for Config {
35 fn default() -> Self {
36 Self {
37 target_addr: 0x48,
38 general_call: false,
39 }
40 }
41}
42
43/// I2C error
44#[derive(Debug, PartialEq, Eq, Clone, Copy)]
45#[cfg_attr(feature = "defmt", derive(defmt::Format))]
46#[non_exhaustive]
47pub enum Error {
48 /// User passed in a response buffer that was 0 length
49 InvalidResponseBufferLength,
50 /// The response buffer length was too short to contain the message
51 ///
52 /// The length parameter will always be the length of the buffer, and is
53 /// provided as a convenience for matching alongside `Command::Write`.
54 PartialWrite(usize),
55 /// The response buffer length was too short to contain the message
56 ///
57 /// The length parameter will always be the length of the buffer, and is
58 /// provided as a convenience for matching alongside `Command::GeneralCall`.
59 PartialGeneralCall(usize),
60}
61
62/// Received command from the controller.
63#[derive(Debug, Copy, Clone, Eq, PartialEq)]
64#[cfg_attr(feature = "defmt", derive(defmt::Format))]
65pub enum Command {
66 /// General Call Write: Controller sent the General Call address (0x00) followed by data.
67 /// Contains the number of bytes written by the controller.
68 GeneralCall(usize),
69 /// Read: Controller wants to read data from the target.
70 Read,
71 /// Write: Controller sent the target's address followed by data.
72 /// Contains the number of bytes written by the controller.
73 Write(usize),
74 /// Write followed by Read (Repeated Start): Controller wrote data, then issued a repeated
75 /// start and wants to read data. Contains the number of bytes written before the read.
76 WriteRead(usize),
77}
78
79/// Status after responding to a controller read request.
80#[derive(Debug, Copy, Clone, Eq, PartialEq)]
81#[cfg_attr(feature = "defmt", derive(defmt::Format))]
82pub enum ReadStatus {
83 /// Transaction completed successfully. The controller either NACKed the last byte
84 /// or sent a STOP condition.
85 Done,
86 /// Transaction incomplete, controller trying to read more bytes than were provided
87 NeedMoreBytes,
88 /// Transaction complete, but controller stopped reading bytes before we ran out
89 LeftoverBytes(u16),
90}
91
92/// I2C Target driver.
93// Use the same Instance, SclPin, SdaPin traits as the controller
94pub struct I2cTarget<'d, M: Mode> {
95 info: &'static Info,
96 state: &'static State,
97 scl: Option<Peri<'d, AnyPin>>,
98 sda: Option<Peri<'d, AnyPin>>,
99 config: i2c::Config,
100 target_config: i2c_target::Config,
101 _phantom: PhantomData<M>,
102}
103
104impl<'d> SetConfig for I2cTarget<'d, Async> {
105 type Config = (i2c::Config, i2c_target::Config);
106 type ConfigError = ConfigError;
107
108 fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> {
109 self.info.interrupt.disable();
110
111 if let Some(ref sda) = self.sda {
112 sda.update_pf(config.0.sda_pf());
113 }
114
115 if let Some(ref scl) = self.scl {
116 scl.update_pf(config.0.scl_pf());
117 }
118
119 self.config = config.0.clone();
120 self.target_config = config.1.clone();
121
122 self.reset()
123 }
124}
125
126impl<'d> SetConfig for I2cTarget<'d, Blocking> {
127 type Config = (i2c::Config, i2c_target::Config);
128 type ConfigError = ConfigError;
129
130 fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> {
131 if let Some(ref sda) = self.sda {
132 sda.update_pf(config.0.sda_pf());
133 }
134
135 if let Some(ref scl) = self.scl {
136 scl.update_pf(config.0.scl_pf());
137 }
138
139 self.config = config.0.clone();
140 self.target_config = config.1.clone();
141
142 self.reset()
143 }
144}
145
146impl<'d> I2cTarget<'d, Async> {
147 /// Create a new asynchronous I2C target driver using interrupts
148 /// The `config` reuses the i2c controller config to setup the clock while `target_config`
149 /// configures i2c target specific parameters.
150 pub fn new<T: Instance>(
151 peri: Peri<'d, T>,
152 scl: Peri<'d, impl SclPin<T>>,
153 sda: Peri<'d, impl SdaPin<T>>,
154 _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
155 config: i2c::Config,
156 target_config: i2c_target::Config,
157 ) -> Result<Self, ConfigError> {
158 let mut this = Self::new_inner(
159 peri,
160 new_pin!(scl, config.scl_pf()),
161 new_pin!(sda, config.sda_pf()),
162 config,
163 target_config,
164 );
165 this.reset()?;
166 Ok(this)
167 }
168
169 /// Reset the i2c peripheral. If you cancel a respond_to_read, you may stall the bus.
170 /// You can recover the bus by calling this function, but doing so will almost certainly cause
171 /// an i/o error in the controller.
172 pub fn reset(&mut self) -> Result<(), ConfigError> {
173 self.init()?;
174 unsafe { self.info.interrupt.enable() };
175 Ok(())
176 }
177}
178
179impl<'d> I2cTarget<'d, Blocking> {
180 /// Create a new blocking I2C target driver.
181 /// The `config` reuses the i2c controller config to setup the clock while `target_config`
182 /// configures i2c target specific parameters.
183 pub fn new_blocking<T: Instance>(
184 peri: Peri<'d, T>,
185 scl: Peri<'d, impl SclPin<T>>,
186 sda: Peri<'d, impl SdaPin<T>>,
187 config: i2c::Config,
188 target_config: i2c_target::Config,
189 ) -> Result<Self, ConfigError> {
190 let mut this = Self::new_inner(
191 peri,
192 new_pin!(scl, config.scl_pf()),
193 new_pin!(sda, config.sda_pf()),
194 config,
195 target_config,
196 );
197 this.reset()?;
198 Ok(this)
199 }
200
201 /// Reset the i2c peripheral. If you cancel a respond_to_read, you may stall the bus.
202 /// You can recover the bus by calling this function, but doing so will almost certainly cause
203 /// an i/o error in the controller.
204 pub fn reset(&mut self) -> Result<(), ConfigError> {
205 self.init()?;
206 Ok(())
207 }
208}
209
210impl<'d, M: Mode> I2cTarget<'d, M> {
211 fn new_inner<T: Instance>(
212 _peri: Peri<'d, T>,
213 scl: Option<Peri<'d, AnyPin>>,
214 sda: Option<Peri<'d, AnyPin>>,
215 config: i2c::Config,
216 target_config: i2c_target::Config,
217 ) -> Self {
218 if let Some(ref scl) = scl {
219 let pincm = pac::IOMUX.pincm(scl._pin_cm() as usize);
220 pincm.modify(|w| {
221 w.set_hiz1(true);
222 });
223 }
224 if let Some(ref sda) = sda {
225 let pincm = pac::IOMUX.pincm(sda._pin_cm() as usize);
226 pincm.modify(|w| {
227 w.set_hiz1(true);
228 });
229 }
230
231 Self {
232 info: T::info(),
233 state: T::state(),
234 scl,
235 sda,
236 config,
237 target_config,
238 _phantom: PhantomData,
239 }
240 }
241
242 fn init(&mut self) -> Result<(), ConfigError> {
243 let mut config = self.config;
244 let target_config = self.target_config;
245 let regs = self.info.regs;
246
247 config.check_config()?;
248 // Target address must be 7-bit
249 if !(target_config.target_addr < 0x80) {
250 return Err(ConfigError::InvalidTargetAddress);
251 }
252
253 regs.target(0).tctr().modify(|w| {
254 w.set_active(false);
255 });
256
257 // Init power for I2C
258 regs.gprcm(0).rstctl().write(|w| {
259 w.set_resetstkyclr(true);
260 w.set_resetassert(true);
261 w.set_key(vals::ResetKey::KEY);
262 });
263
264 regs.gprcm(0).pwren().write(|w| {
265 w.set_enable(true);
266 w.set_key(vals::PwrenKey::KEY);
267 });
268
269 self.info.interrupt.disable();
270
271 // Init delay from the M0 examples by TI in CCStudio (16 cycles)
272 cortex_m::asm::delay(16);
273
274 // Select and configure the I2C clock using the CLKSEL and CLKDIV registers
275 regs.clksel().write(|w| match config.clock_source {
276 ClockSel::BusClk => {
277 w.set_mfclk_sel(false);
278 w.set_busclk_sel(true);
279 }
280 ClockSel::MfClk => {
281 w.set_mfclk_sel(true);
282 w.set_busclk_sel(false);
283 }
284 });
285 regs.clkdiv().write(|w| w.set_ratio(config.clock_div.into()));
286
287 // Configure at least one target address by writing the 7-bit address to I2Cx.SOAR register. The additional
288 // target address can be enabled and configured by using I2Cx.TOAR2 register.
289 regs.target(0).toar().modify(|w| {
290 w.set_oaren(true);
291 w.set_oar(target_config.target_addr as u16);
292 });
293
294 self.state
295 .clock
296 .store(config.calculate_clock_source(), Ordering::Relaxed);
297
298 regs.target(0).tctr().modify(|w| {
299 w.set_gencall(target_config.general_call);
300 w.set_tclkstretch(true);
301 // Disable target wakeup, follow TI example. (TI note: Workaround for errata I2C_ERR_04.)
302 w.set_twuen(false);
303 w.set_txempty_on_treq(true);
304 });
305
306 // Enable the I2C target mode by setting the ACTIVE bit in I2Cx.TCTR register.
307 regs.target(0).tctr().modify(|w| {
308 w.set_active(true);
309 });
310
311 Ok(())
312 }
313
314 #[inline(always)]
315 fn drain_fifo(&mut self, buffer: &mut [u8], offset: &mut usize) {
316 let regs = self.info.regs;
317
318 for b in &mut buffer[*offset..] {
319 if regs.target(0).tfifosr().read().rxfifocnt() == 0 {
320 break;
321 }
322
323 *b = regs.target(0).trxdata().read().value();
324 *offset += 1;
325 }
326 }
327
328 /// Blocking function to empty the tx fifo
329 ///
330 /// This function can be used to empty the transmit FIFO if data remains after handling a 'read' command (LeftoverBytes).
331 pub fn flush_tx_fifo(&mut self) {
332 self.info.regs.target(0).tfifoctl().modify(|w| {
333 w.set_txflush(true);
334 });
335 while self.info.regs.target(0).tfifosr().read().txfifocnt() as usize != self.info.fifo_size {}
336 self.info.regs.target(0).tfifoctl().modify(|w| {
337 w.set_txflush(false);
338 });
339 }
340}
341
342impl<'d> I2cTarget<'d, Async> {
343 /// Wait asynchronously for commands from an I2C controller.
344 /// `buffer` is provided in case controller does a 'write', 'write read', or 'general call' and is unused for 'read'.
345 pub async fn listen(&mut self, buffer: &mut [u8]) -> Result<Command, Error> {
346 let regs = self.info.regs;
347
348 let mut len = 0;
349
350 // Set the rx fifo interrupt to avoid a fifo overflow
351 regs.target(0).tfifoctl().modify(|r| {
352 r.set_rxtrig(vals::TfifoctlRxtrig::LEVEL_6);
353 });
354
355 self.wait_on(
356 |me| {
357 // Check if address matches the General Call address (0x00)
358 let is_gencall = regs.target(0).tsr().read().addrmatch() == 0;
359
360 if regs.target(0).tfifosr().read().rxfifocnt() > 0 {
361 me.drain_fifo(buffer, &mut len);
362 }
363
364 if buffer.len() == len && regs.target(0).tfifosr().read().rxfifocnt() > 0 {
365 if is_gencall {
366 return Poll::Ready(Err(Error::PartialGeneralCall(buffer.len())));
367 } else {
368 return Poll::Ready(Err(Error::PartialWrite(buffer.len())));
369 }
370 }
371
372 let iidx = regs.cpu_int(0).iidx().read().stat();
373 trace!("ls:{} len:{}", iidx as u8, len);
374 let result = match iidx {
375 CpuIntIidxStat::TTXEMPTY => match len {
376 0 => Poll::Ready(Ok(Command::Read)),
377 w => Poll::Ready(Ok(Command::WriteRead(w))),
378 },
379 CpuIntIidxStat::TSTOPFG => match (is_gencall, len) {
380 (_, 0) => Poll::Pending,
381 (true, w) => Poll::Ready(Ok(Command::GeneralCall(w))),
382 (false, w) => Poll::Ready(Ok(Command::Write(w))),
383 },
384 _ => Poll::Pending,
385 };
386 if !result.is_pending() {
387 regs.cpu_int(0).imask().write(|_| {});
388 }
389 result
390 },
391 |_me| {
392 regs.cpu_int(0).imask().write(|_| {});
393 regs.cpu_int(0).imask().modify(|w| {
394 w.set_tgencall(true);
395 w.set_trxfifotrg(true);
396 w.set_tstop(true);
397 w.set_ttxempty(true);
398 });
399 },
400 )
401 .await
402 }
403
404 /// Respond to an I2C controller 'read' command, asynchronously.
405 pub async fn respond_to_read(&mut self, buffer: &[u8]) -> Result<ReadStatus, Error> {
406 if buffer.is_empty() {
407 return Err(Error::InvalidResponseBufferLength);
408 }
409
410 let regs = self.info.regs;
411 let fifo_size = self.info.fifo_size;
412 let mut chunks = buffer.chunks(self.info.fifo_size);
413
414 self.wait_on(
415 |_me| {
416 if let Some(chunk) = chunks.next() {
417 for byte in chunk {
418 regs.target(0).ttxdata().write(|w| w.set_value(*byte));
419 }
420
421 return Poll::Pending;
422 }
423
424 let iidx = regs.cpu_int(0).iidx().read().stat();
425 let fifo_bytes = fifo_size - regs.target(0).tfifosr().read().txfifocnt() as usize;
426 trace!("rs:{}, fifo:{}", iidx as u8, fifo_bytes);
427
428 let result = match iidx {
429 CpuIntIidxStat::TTXEMPTY => Poll::Ready(Ok(ReadStatus::NeedMoreBytes)),
430 CpuIntIidxStat::TSTOPFG => match fifo_bytes {
431 0 => Poll::Ready(Ok(ReadStatus::Done)),
432 w => Poll::Ready(Ok(ReadStatus::LeftoverBytes(w as u16))),
433 },
434 _ => Poll::Pending,
435 };
436 if !result.is_pending() {
437 regs.cpu_int(0).imask().write(|_| {});
438 }
439 result
440 },
441 |_me| {
442 regs.cpu_int(0).imask().write(|_| {});
443 regs.cpu_int(0).imask().modify(|w| {
444 w.set_ttxempty(true);
445 w.set_tstop(true);
446 });
447 },
448 )
449 .await
450 }
451
452 /// Respond to reads with the fill byte until the controller stops asking
453 pub async fn respond_till_stop(&mut self, fill: u8) -> Result<(), Error> {
454 // The buffer size could be increased to reduce interrupt noise but has higher probability
455 // of LeftoverBytes
456 let buff = [fill];
457 loop {
458 match self.respond_to_read(&buff).await {
459 Ok(ReadStatus::NeedMoreBytes) => (),
460 Ok(_) => break Ok(()),
461 Err(e) => break Err(e),
462 }
463 }
464 }
465
466 /// Respond to a controller read, then fill any remaining read bytes with `fill`
467 pub async fn respond_and_fill(&mut self, buffer: &[u8], fill: u8) -> Result<ReadStatus, Error> {
468 let resp_stat = self.respond_to_read(buffer).await?;
469
470 if resp_stat == ReadStatus::NeedMoreBytes {
471 self.respond_till_stop(fill).await?;
472 Ok(ReadStatus::Done)
473 } else {
474 Ok(resp_stat)
475 }
476 }
477
478 /// Calls `f` to check if we are ready or not.
479 /// If not, `g` is called once(to eg enable the required interrupts).
480 /// The waker will always be registered prior to calling `f`.
481 #[inline(always)]
482 async fn wait_on<F, U, G>(&mut self, mut f: F, mut g: G) -> U
483 where
484 F: FnMut(&mut Self) -> Poll<U>,
485 G: FnMut(&mut Self),
486 {
487 poll_fn(|cx| {
488 // Register prior to checking the condition
489 self.state.waker.register(cx.waker());
490 let r = f(self);
491
492 if r.is_pending() {
493 g(self);
494 }
495
496 r
497 })
498 .await
499 }
500}
501
502impl<'d, M: Mode> Drop for I2cTarget<'d, M> {
503 fn drop(&mut self) {
504 // Ensure peripheral is disabled and pins are reset
505 self.info.regs.target(0).tctr().modify(|w| w.set_active(false));
506
507 self.scl.as_ref().map(|x| x.set_as_disconnected());
508 self.sda.as_ref().map(|x| x.set_as_disconnected());
509 }
510}