diff options
| author | Felipe Balbi <[email protected]> | 2025-12-04 09:14:24 -0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-12-04 09:14:24 -0800 |
| commit | cc918e997a8709c213c173edb8657ff269dae7a4 (patch) | |
| tree | 761bd20efef324a026bd541135714c86d4b30739 /src | |
| parent | 932d2ef38584b2f4ae926fd484a24a4829fa6cc0 (diff) | |
i2c: controller: ensure bus works after errors (#89)
Fixes: #84
---------
Signed-off-by: Felipe Balbi <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/i2c/controller.rs | 130 |
1 files changed, 91 insertions, 39 deletions
diff --git a/src/i2c/controller.rs b/src/i2c/controller.rs index 818024bc9..182a53aea 100644 --- a/src/i2c/controller.rs +++ b/src/i2c/controller.rs | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | use core::future::Future; | 3 | use core::future::Future; |
| 4 | use core::marker::PhantomData; | 4 | use core::marker::PhantomData; |
| 5 | 5 | ||
| 6 | use embassy_hal_internal::drop::OnDrop; | ||
| 6 | use embassy_hal_internal::Peri; | 7 | use embassy_hal_internal::Peri; |
| 7 | use mcxa_pac::lpi2c0::mtdr::Cmd; | 8 | use mcxa_pac::lpi2c0::mtdr::Cmd; |
| 8 | 9 | ||
| @@ -118,10 +119,9 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { | |||
| 118 | critical_section::with(|_| T::regs().mcr().modify(|_, w| w.men().disabled())); | 119 | critical_section::with(|_| T::regs().mcr().modify(|_, w| w.men().disabled())); |
| 119 | 120 | ||
| 120 | // Soft-reset the controller, read and write FIFOs. | 121 | // Soft-reset the controller, read and write FIFOs. |
| 122 | Self::reset_fifos(); | ||
| 121 | critical_section::with(|_| { | 123 | critical_section::with(|_| { |
| 122 | T::regs() | 124 | T::regs().mcr().modify(|_, w| w.rst().reset()); |
| 123 | .mcr() | ||
| 124 | .modify(|_, w| w.rst().reset().rtf().reset().rrf().reset()); | ||
| 125 | // According to Reference Manual section 40.7.1.4, "There | 125 | // According to Reference Manual section 40.7.1.4, "There |
| 126 | // is no minimum delay required before clearing the | 126 | // is no minimum delay required before clearing the |
| 127 | // software reset", therefore we clear it immediately. | 127 | // software reset", therefore we clear it immediately. |
| @@ -187,20 +187,32 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { | |||
| 187 | } | 187 | } |
| 188 | } | 188 | } |
| 189 | 189 | ||
| 190 | fn is_tx_fifo_full(&mut self) -> bool { | 190 | /// Resets both TX and RX FIFOs dropping their contents. |
| 191 | fn reset_fifos() { | ||
| 192 | critical_section::with(|_| { | ||
| 193 | T::regs().mcr().modify(|_, w| w.rtf().reset().rrf().reset()); | ||
| 194 | }); | ||
| 195 | } | ||
| 196 | |||
| 197 | /// Checks whether the TX FIFO is full | ||
| 198 | fn is_tx_fifo_full() -> bool { | ||
| 191 | let txfifo_size = 1 << T::regs().param().read().mtxfifo().bits(); | 199 | let txfifo_size = 1 << T::regs().param().read().mtxfifo().bits(); |
| 192 | T::regs().mfsr().read().txcount().bits() == txfifo_size | 200 | T::regs().mfsr().read().txcount().bits() == txfifo_size |
| 193 | } | 201 | } |
| 194 | 202 | ||
| 195 | fn is_tx_fifo_empty(&mut self) -> bool { | 203 | /// Checks whether the TX FIFO is empty |
| 204 | fn is_tx_fifo_empty() -> bool { | ||
| 196 | T::regs().mfsr().read().txcount() == 0 | 205 | T::regs().mfsr().read().txcount() == 0 |
| 197 | } | 206 | } |
| 198 | 207 | ||
| 199 | fn is_rx_fifo_empty(&mut self) -> bool { | 208 | /// Checks whether the RX FIFO is empty. |
| 209 | fn is_rx_fifo_empty() -> bool { | ||
| 200 | T::regs().mfsr().read().rxcount() == 0 | 210 | T::regs().mfsr().read().rxcount() == 0 |
| 201 | } | 211 | } |
| 202 | 212 | ||
| 203 | fn status(&mut self) -> Result<()> { | 213 | /// Reads and parses the controller status producing an |
| 214 | /// appropriate `Result<(), Error>` variant. | ||
| 215 | fn status() -> Result<()> { | ||
| 204 | let msr = T::regs().msr().read(); | 216 | let msr = T::regs().msr().read(); |
| 205 | T::regs().msr().write(|w| { | 217 | T::regs().msr().write(|w| { |
| 206 | w.epf() | 218 | w.epf() |
| @@ -234,7 +246,11 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { | |||
| 234 | } | 246 | } |
| 235 | } | 247 | } |
| 236 | 248 | ||
| 237 | fn send_cmd(&mut self, cmd: Cmd, data: u8) { | 249 | /// Inserts the given command into the outgoing FIFO. |
| 250 | /// | ||
| 251 | /// Caller must ensure there is space in the FIFO for the new | ||
| 252 | /// command. | ||
| 253 | fn send_cmd(cmd: Cmd, data: u8) { | ||
| 238 | #[cfg(feature = "defmt")] | 254 | #[cfg(feature = "defmt")] |
| 239 | defmt::trace!( | 255 | defmt::trace!( |
| 240 | "Sending cmd '{}' ({}) with data '{:08x}' MSR: {:08x}", | 256 | "Sending cmd '{}' ({}) with data '{:08x}' MSR: {:08x}", |
| @@ -249,34 +265,46 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { | |||
| 249 | .write(|w| unsafe { w.data().bits(data) }.cmd().variant(cmd)); | 265 | .write(|w| unsafe { w.data().bits(data) }.cmd().variant(cmd)); |
| 250 | } | 266 | } |
| 251 | 267 | ||
| 268 | /// Prepares an appropriate Start condition on bus by issuing a | ||
| 269 | /// `Start` command together with the device address and R/w bit. | ||
| 270 | /// | ||
| 271 | /// Blocks waiting for space in the FIFO to become available, then | ||
| 272 | /// sends the command and blocks waiting for the FIFO to become | ||
| 273 | /// empty ensuring the command was sent. | ||
| 252 | fn start(&mut self, address: u8, read: bool) -> Result<()> { | 274 | fn start(&mut self, address: u8, read: bool) -> Result<()> { |
| 253 | if address >= 0x80 { | 275 | if address >= 0x80 { |
| 254 | return Err(Error::AddressOutOfRange(address)); | 276 | return Err(Error::AddressOutOfRange(address)); |
| 255 | } | 277 | } |
| 256 | 278 | ||
| 257 | // Wait until we have space in the TxFIFO | 279 | // Wait until we have space in the TxFIFO |
| 258 | while self.is_tx_fifo_full() {} | 280 | while Self::is_tx_fifo_full() {} |
| 259 | 281 | ||
| 260 | let addr_rw = address << 1 | if read { 1 } else { 0 }; | 282 | let addr_rw = address << 1 | if read { 1 } else { 0 }; |
| 261 | self.send_cmd(if self.is_hs { Cmd::StartHs } else { Cmd::Start }, addr_rw); | 283 | Self::send_cmd(if self.is_hs { Cmd::StartHs } else { Cmd::Start }, addr_rw); |
| 262 | 284 | ||
| 263 | // Wait for TxFIFO to be drained | 285 | // Wait for TxFIFO to be drained |
| 264 | while !self.is_tx_fifo_empty() {} | 286 | while !Self::is_tx_fifo_empty() {} |
| 265 | 287 | ||
| 266 | // Check controller status | 288 | // Check controller status |
| 267 | self.status() | 289 | Self::status() |
| 268 | } | 290 | } |
| 269 | 291 | ||
| 270 | fn stop(&mut self) -> Result<()> { | 292 | /// Prepares a Stop condition on the bus. |
| 293 | /// | ||
| 294 | /// Analogous to `start`, this blocks waiting for space in the | ||
| 295 | /// FIFO to become available, then sends the command and blocks | ||
| 296 | /// waiting for the FIFO to become empty ensuring the command was | ||
| 297 | /// sent. | ||
| 298 | fn stop() -> Result<()> { | ||
| 271 | // Wait until we have space in the TxFIFO | 299 | // Wait until we have space in the TxFIFO |
| 272 | while self.is_tx_fifo_full() {} | 300 | while Self::is_tx_fifo_full() {} |
| 273 | 301 | ||
| 274 | self.send_cmd(Cmd::Stop, 0); | 302 | Self::send_cmd(Cmd::Stop, 0); |
| 275 | 303 | ||
| 276 | // Wait for TxFIFO to be drained | 304 | // Wait for TxFIFO to be drained |
| 277 | while !self.is_tx_fifo_empty() {} | 305 | while !Self::is_tx_fifo_empty() {} |
| 278 | 306 | ||
| 279 | self.status() | 307 | Self::status() |
| 280 | } | 308 | } |
| 281 | 309 | ||
| 282 | fn blocking_read_internal(&mut self, address: u8, read: &mut [u8], send_stop: SendStop) -> Result<()> { | 310 | fn blocking_read_internal(&mut self, address: u8, read: &mut [u8], send_stop: SendStop) -> Result<()> { |
| @@ -288,20 +316,20 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { | |||
| 288 | self.start(address, true)?; | 316 | self.start(address, true)?; |
| 289 | 317 | ||
| 290 | // Wait until we have space in the TxFIFO | 318 | // Wait until we have space in the TxFIFO |
| 291 | while self.is_tx_fifo_full() {} | 319 | while Self::is_tx_fifo_full() {} |
| 292 | 320 | ||
| 293 | self.send_cmd(Cmd::Receive, (chunk.len() - 1) as u8); | 321 | Self::send_cmd(Cmd::Receive, (chunk.len() - 1) as u8); |
| 294 | 322 | ||
| 295 | for byte in chunk.iter_mut() { | 323 | for byte in chunk.iter_mut() { |
| 296 | // Wait until there's data in the RxFIFO | 324 | // Wait until there's data in the RxFIFO |
| 297 | while self.is_rx_fifo_empty() {} | 325 | while Self::is_rx_fifo_empty() {} |
| 298 | 326 | ||
| 299 | *byte = T::regs().mrdr().read().data().bits(); | 327 | *byte = T::regs().mrdr().read().data().bits(); |
| 300 | } | 328 | } |
| 301 | } | 329 | } |
| 302 | 330 | ||
| 303 | if send_stop == SendStop::Yes { | 331 | if send_stop == SendStop::Yes { |
| 304 | self.stop()?; | 332 | Self::stop()?; |
| 305 | } | 333 | } |
| 306 | 334 | ||
| 307 | Ok(()) | 335 | Ok(()) |
| @@ -327,13 +355,13 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { | |||
| 327 | 355 | ||
| 328 | for byte in write { | 356 | for byte in write { |
| 329 | // Wait until we have space in the TxFIFO | 357 | // Wait until we have space in the TxFIFO |
| 330 | while self.is_tx_fifo_full() {} | 358 | while Self::is_tx_fifo_full() {} |
| 331 | 359 | ||
| 332 | self.send_cmd(Cmd::Transmit, *byte); | 360 | Self::send_cmd(Cmd::Transmit, *byte); |
| 333 | } | 361 | } |
| 334 | 362 | ||
| 335 | if send_stop == SendStop::Yes { | 363 | if send_stop == SendStop::Yes { |
| 336 | self.stop()?; | 364 | Self::stop()?; |
| 337 | } | 365 | } |
| 338 | 366 | ||
| 339 | Ok(()) | 367 | Ok(()) |
| @@ -344,7 +372,6 @@ impl<'d, T: Instance, M: Mode> I2c<'d, T, M> { | |||
| 344 | /// Read from address into buffer blocking caller until done. | 372 | /// Read from address into buffer blocking caller until done. |
| 345 | pub fn blocking_read(&mut self, address: u8, read: &mut [u8]) -> Result<()> { | 373 | pub fn blocking_read(&mut self, address: u8, read: &mut [u8]) -> Result<()> { |
| 346 | self.blocking_read_internal(address, read, SendStop::Yes) | 374 | self.blocking_read_internal(address, read, SendStop::Yes) |
| 347 | // Automatic Stop | ||
| 348 | } | 375 | } |
| 349 | 376 | ||
| 350 | /// Write to address from buffer blocking caller until done. | 377 | /// Write to address from buffer blocking caller until done. |
| @@ -376,6 +403,19 @@ impl<'d, T: Instance> I2c<'d, T, Async> { | |||
| 376 | Self::new_inner(peri, scl, sda, config) | 403 | Self::new_inner(peri, scl, sda, config) |
| 377 | } | 404 | } |
| 378 | 405 | ||
| 406 | fn remediation() { | ||
| 407 | #[cfg(feature = "defmt")] | ||
| 408 | defmt::trace!("Future dropped, issuing stop",); | ||
| 409 | |||
| 410 | // if the FIFO is not empty, drop its contents. | ||
| 411 | if !Self::is_tx_fifo_empty() { | ||
| 412 | Self::reset_fifos(); | ||
| 413 | } | ||
| 414 | |||
| 415 | // send a stop command | ||
| 416 | let _ = Self::stop(); | ||
| 417 | } | ||
| 418 | |||
| 379 | fn enable_rx_ints(&mut self) { | 419 | fn enable_rx_ints(&mut self) { |
| 380 | T::regs().mier().write(|w| { | 420 | T::regs().mier().write(|w| { |
| 381 | w.rdie() | 421 | w.rdie() |
| @@ -413,36 +453,36 @@ impl<'d, T: Instance> I2c<'d, T, Async> { | |||
| 413 | 453 | ||
| 414 | // send the start command | 454 | // send the start command |
| 415 | let addr_rw = address << 1 | if read { 1 } else { 0 }; | 455 | let addr_rw = address << 1 | if read { 1 } else { 0 }; |
| 416 | self.send_cmd(if self.is_hs { Cmd::StartHs } else { Cmd::Start }, addr_rw); | 456 | Self::send_cmd(if self.is_hs { Cmd::StartHs } else { Cmd::Start }, addr_rw); |
| 417 | 457 | ||
| 418 | T::wait_cell() | 458 | T::wait_cell() |
| 419 | .wait_for(|| { | 459 | .wait_for(|| { |
| 420 | // enable interrupts | 460 | // enable interrupts |
| 421 | self.enable_tx_ints(); | 461 | self.enable_tx_ints(); |
| 422 | // if the command FIFO is empty, we're done sending start | 462 | // if the command FIFO is empty, we're done sending start |
| 423 | self.is_tx_fifo_empty() | 463 | Self::is_tx_fifo_empty() |
| 424 | }) | 464 | }) |
| 425 | .await | 465 | .await |
| 426 | .map_err(|_| Error::Other)?; | 466 | .map_err(|_| Error::Other)?; |
| 427 | 467 | ||
| 428 | self.status() | 468 | Self::status() |
| 429 | } | 469 | } |
| 430 | 470 | ||
| 431 | async fn async_stop(&mut self) -> Result<()> { | 471 | async fn async_stop(&mut self) -> Result<()> { |
| 432 | // send the stop command | 472 | // send the stop command |
| 433 | self.send_cmd(Cmd::Stop, 0); | 473 | Self::send_cmd(Cmd::Stop, 0); |
| 434 | 474 | ||
| 435 | T::wait_cell() | 475 | T::wait_cell() |
| 436 | .wait_for(|| { | 476 | .wait_for(|| { |
| 437 | // enable interrupts | 477 | // enable interrupts |
| 438 | self.enable_tx_ints(); | 478 | self.enable_tx_ints(); |
| 439 | // if the command FIFO is empty, we're done sending stop | 479 | // if the command FIFO is empty, we're done sending stop |
| 440 | self.is_tx_fifo_empty() | 480 | Self::is_tx_fifo_empty() |
| 441 | }) | 481 | }) |
| 442 | .await | 482 | .await |
| 443 | .map_err(|_| Error::Other)?; | 483 | .map_err(|_| Error::Other)?; |
| 444 | 484 | ||
| 445 | self.status() | 485 | Self::status() |
| 446 | } | 486 | } |
| 447 | 487 | ||
| 448 | async fn async_read_internal(&mut self, address: u8, read: &mut [u8], send_stop: SendStop) -> Result<()> { | 488 | async fn async_read_internal(&mut self, address: u8, read: &mut [u8], send_stop: SendStop) -> Result<()> { |
| @@ -450,18 +490,21 @@ impl<'d, T: Instance> I2c<'d, T, Async> { | |||
| 450 | return Err(Error::InvalidReadBufferLength); | 490 | return Err(Error::InvalidReadBufferLength); |
| 451 | } | 491 | } |
| 452 | 492 | ||
| 493 | // perform corrective action if the future is dropped | ||
| 494 | let on_drop = OnDrop::new(|| Self::remediation()); | ||
| 495 | |||
| 453 | for chunk in read.chunks_mut(256) { | 496 | for chunk in read.chunks_mut(256) { |
| 454 | self.async_start(address, true).await?; | 497 | self.async_start(address, true).await?; |
| 455 | 498 | ||
| 456 | // send receive command | 499 | // send receive command |
| 457 | self.send_cmd(Cmd::Receive, (chunk.len() - 1) as u8); | 500 | Self::send_cmd(Cmd::Receive, (chunk.len() - 1) as u8); |
| 458 | 501 | ||
| 459 | T::wait_cell() | 502 | T::wait_cell() |
| 460 | .wait_for(|| { | 503 | .wait_for(|| { |
| 461 | // enable interrupts | 504 | // enable interrupts |
| 462 | self.enable_tx_ints(); | 505 | self.enable_tx_ints(); |
| 463 | // if the command FIFO is empty, we're done sending start | 506 | // if the command FIFO is empty, we're done sending start |
| 464 | self.is_tx_fifo_empty() | 507 | Self::is_tx_fifo_empty() |
| 465 | }) | 508 | }) |
| 466 | .await | 509 | .await |
| 467 | .map_err(|_| Error::Other)?; | 510 | .map_err(|_| Error::Other)?; |
| @@ -471,8 +514,8 @@ impl<'d, T: Instance> I2c<'d, T, Async> { | |||
| 471 | .wait_for(|| { | 514 | .wait_for(|| { |
| 472 | // enable interrupts | 515 | // enable interrupts |
| 473 | self.enable_rx_ints(); | 516 | self.enable_rx_ints(); |
| 474 | // it the rx FIFO is not empty, we need to read a byte | 517 | // if the rx FIFO is not empty, we need to read a byte |
| 475 | !self.is_rx_fifo_empty() | 518 | !Self::is_rx_fifo_empty() |
| 476 | }) | 519 | }) |
| 477 | .await | 520 | .await |
| 478 | .map_err(|_| Error::ReadFail)?; | 521 | .map_err(|_| Error::ReadFail)?; |
| @@ -485,12 +528,18 @@ impl<'d, T: Instance> I2c<'d, T, Async> { | |||
| 485 | self.async_stop().await?; | 528 | self.async_stop().await?; |
| 486 | } | 529 | } |
| 487 | 530 | ||
| 531 | // defuse it if the future is not dropped | ||
| 532 | on_drop.defuse(); | ||
| 533 | |||
| 488 | Ok(()) | 534 | Ok(()) |
| 489 | } | 535 | } |
| 490 | 536 | ||
| 491 | async fn async_write_internal(&mut self, address: u8, write: &[u8], send_stop: SendStop) -> Result<()> { | 537 | async fn async_write_internal(&mut self, address: u8, write: &[u8], send_stop: SendStop) -> Result<()> { |
| 492 | self.async_start(address, false).await?; | 538 | self.async_start(address, false).await?; |
| 493 | 539 | ||
| 540 | // perform corrective action if the future is dropped | ||
| 541 | let on_drop = OnDrop::new(|| Self::remediation()); | ||
| 542 | |||
| 494 | // Usually, embassy HALs error out with an empty write, | 543 | // Usually, embassy HALs error out with an empty write, |
| 495 | // however empty writes are useful for writing I2C scanning | 544 | // however empty writes are useful for writing I2C scanning |
| 496 | // logic through write probing. That is, we send a start with | 545 | // logic through write probing. That is, we send a start with |
| @@ -511,10 +560,10 @@ impl<'d, T: Instance> I2c<'d, T, Async> { | |||
| 511 | .wait_for(|| { | 560 | .wait_for(|| { |
| 512 | // enable interrupts | 561 | // enable interrupts |
| 513 | self.enable_tx_ints(); | 562 | self.enable_tx_ints(); |
| 514 | // initiate trasmit | 563 | // initiate transmit |
| 515 | self.send_cmd(Cmd::Transmit, *byte); | 564 | Self::send_cmd(Cmd::Transmit, *byte); |
| 516 | // it the rx FIFO is not empty, we're done transmiting | 565 | // if the tx FIFO is empty, we're done transmiting |
| 517 | self.is_tx_fifo_empty() | 566 | Self::is_tx_fifo_empty() |
| 518 | }) | 567 | }) |
| 519 | .await | 568 | .await |
| 520 | .map_err(|_| Error::WriteFail)?; | 569 | .map_err(|_| Error::WriteFail)?; |
| @@ -524,6 +573,9 @@ impl<'d, T: Instance> I2c<'d, T, Async> { | |||
| 524 | self.async_stop().await?; | 573 | self.async_stop().await?; |
| 525 | } | 574 | } |
| 526 | 575 | ||
| 576 | // defuse it if the future is not dropped | ||
| 577 | on_drop.defuse(); | ||
| 578 | |||
| 527 | Ok(()) | 579 | Ok(()) |
| 528 | } | 580 | } |
| 529 | 581 | ||
