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