diff options
| author | Caleb Jamison <[email protected]> | 2024-02-15 17:43:20 -0500 |
|---|---|---|
| committer | Caleb Jamison <[email protected]> | 2024-02-15 17:56:50 -0500 |
| commit | bd0b450ca4ff36b2b1fe0b3b422cd478f9201ad0 (patch) | |
| tree | 9555d33c741592a04ecab056ba0283c14cc38bb8 /embassy-rp | |
| parent | 5220453d85b1e0f279e94dc1627b7d2434132920 (diff) | |
Improve rp2040 i2c slave
This commit takes the fixes and error reporting improvements from
jcdickinson's work and applies them without overlaying a software state
machine on top of the hardware state machine.
Also allows configuration of response to 'general call' writes.
Diffstat (limited to 'embassy-rp')
| -rw-r--r-- | embassy-rp/src/i2c_slave.rs | 236 |
1 files changed, 147 insertions, 89 deletions
diff --git a/embassy-rp/src/i2c_slave.rs b/embassy-rp/src/i2c_slave.rs index 721b7a1f6..d3cc20f39 100644 --- a/embassy-rp/src/i2c_slave.rs +++ b/embassy-rp/src/i2c_slave.rs | |||
| @@ -4,6 +4,7 @@ use core::marker::PhantomData; | |||
| 4 | use core::task::Poll; | 4 | use core::task::Poll; |
| 5 | 5 | ||
| 6 | use embassy_hal_internal::into_ref; | 6 | use embassy_hal_internal::into_ref; |
| 7 | use embassy_time::{block_for, Duration}; | ||
| 7 | use pac::i2c; | 8 | use pac::i2c; |
| 8 | 9 | ||
| 9 | use crate::i2c::{ | 10 | use crate::i2c::{ |
| @@ -21,6 +22,16 @@ pub enum Error { | |||
| 21 | Abort(AbortReason), | 22 | Abort(AbortReason), |
| 22 | /// User passed in a response buffer that was 0 length | 23 | /// User passed in a response buffer that was 0 length |
| 23 | InvalidResponseBufferLength, | 24 | InvalidResponseBufferLength, |
| 25 | /// The response buffer length was too short to contain the message | ||
| 26 | /// | ||
| 27 | /// The length parameter will always be the length of the buffer, and is | ||
| 28 | /// provided as a convenience for matching alongside `Command::Write`. | ||
| 29 | PartialWrite(usize), | ||
| 30 | /// The response buffer length was too short to contain the message | ||
| 31 | /// | ||
| 32 | /// The length parameter will always be the length of the buffer, and is | ||
| 33 | /// provided as a convenience for matching alongside `Command::GeneralCall`. | ||
| 34 | PartialGeneralCall(usize), | ||
| 24 | } | 35 | } |
| 25 | 36 | ||
| 26 | /// Received command | 37 | /// Received command |
| @@ -56,17 +67,23 @@ pub enum ReadStatus { | |||
| 56 | pub struct Config { | 67 | pub struct Config { |
| 57 | /// Target Address | 68 | /// Target Address |
| 58 | pub addr: u16, | 69 | pub addr: u16, |
| 70 | /// Control if the peripheral should ack to and report general calls. | ||
| 71 | pub general_call: bool, | ||
| 59 | } | 72 | } |
| 60 | 73 | ||
| 61 | impl Default for Config { | 74 | impl Default for Config { |
| 62 | fn default() -> Self { | 75 | fn default() -> Self { |
| 63 | Self { addr: 0x55 } | 76 | Self { |
| 77 | addr: 0x55, | ||
| 78 | general_call: true, | ||
| 79 | } | ||
| 64 | } | 80 | } |
| 65 | } | 81 | } |
| 66 | 82 | ||
| 67 | /// I2CSlave driver. | 83 | /// I2CSlave driver. |
| 68 | pub struct I2cSlave<'d, T: Instance> { | 84 | pub struct I2cSlave<'d, T: Instance> { |
| 69 | phantom: PhantomData<&'d mut T>, | 85 | phantom: PhantomData<&'d mut T>, |
| 86 | pending_byte: Option<u8>, | ||
| 70 | } | 87 | } |
| 71 | 88 | ||
| 72 | impl<'d, T: Instance> I2cSlave<'d, T> { | 89 | impl<'d, T: Instance> I2cSlave<'d, T> { |
| @@ -96,7 +113,19 @@ impl<'d, T: Instance> I2cSlave<'d, T> { | |||
| 96 | w.set_master_mode(false); | 113 | w.set_master_mode(false); |
| 97 | w.set_ic_slave_disable(false); | 114 | w.set_ic_slave_disable(false); |
| 98 | w.set_tx_empty_ctrl(true); | 115 | w.set_tx_empty_ctrl(true); |
| 116 | w.set_rx_fifo_full_hld_ctrl(true); | ||
| 117 | |||
| 118 | // This typically makes no sense for a slave, but it is used to | ||
| 119 | // tune spike suppression, according to the datasheet. | ||
| 120 | w.set_speed(pac::i2c::vals::Speed::FAST); | ||
| 121 | |||
| 122 | // Generate stop interrupts for general calls | ||
| 123 | // This also causes stop interrupts for other devices on the bus but those will not be | ||
| 124 | // propagated up to the application. | ||
| 125 | w.set_stop_det_ifaddressed(!config.general_call); | ||
| 99 | }); | 126 | }); |
| 127 | p.ic_ack_general_call() | ||
| 128 | .write(|w| w.set_ack_gen_call(config.general_call)); | ||
| 100 | 129 | ||
| 101 | // Set FIFO watermarks to 1 to make things simpler. This is encoded | 130 | // Set FIFO watermarks to 1 to make things simpler. This is encoded |
| 102 | // by a register value of 0. Rx watermark should never change, but Tx watermark will be | 131 | // by a register value of 0. Rx watermark should never change, but Tx watermark will be |
| @@ -119,7 +148,10 @@ impl<'d, T: Instance> I2cSlave<'d, T> { | |||
| 119 | T::Interrupt::unpend(); | 148 | T::Interrupt::unpend(); |
| 120 | unsafe { T::Interrupt::enable() }; | 149 | unsafe { T::Interrupt::enable() }; |
| 121 | 150 | ||
| 122 | Self { phantom: PhantomData } | 151 | Self { |
| 152 | phantom: PhantomData, | ||
| 153 | pending_byte: None, | ||
| 154 | } | ||
| 123 | } | 155 | } |
| 124 | 156 | ||
| 125 | /// Calls `f` to check if we are ready or not. | 157 | /// Calls `f` to check if we are ready or not. |
| @@ -133,8 +165,6 @@ impl<'d, T: Instance> I2cSlave<'d, T> { | |||
| 133 | future::poll_fn(|cx| { | 165 | future::poll_fn(|cx| { |
| 134 | let r = f(self); | 166 | let r = f(self); |
| 135 | 167 | ||
| 136 | trace!("intr p: {:013b}", T::regs().ic_raw_intr_stat().read().0); | ||
| 137 | |||
| 138 | if r.is_pending() { | 168 | if r.is_pending() { |
| 139 | T::waker().register(cx.waker()); | 169 | T::waker().register(cx.waker()); |
| 140 | g(self); | 170 | g(self); |
| @@ -146,14 +176,36 @@ impl<'d, T: Instance> I2cSlave<'d, T> { | |||
| 146 | } | 176 | } |
| 147 | 177 | ||
| 148 | #[inline(always)] | 178 | #[inline(always)] |
| 149 | fn drain_fifo(&mut self, buffer: &mut [u8], offset: usize) -> usize { | 179 | fn drain_fifo(&mut self, buffer: &mut [u8], offset: &mut usize) { |
| 150 | let p = T::regs(); | 180 | let p = T::regs(); |
| 151 | let len = p.ic_rxflr().read().rxflr() as usize; | 181 | |
| 152 | let end = offset + len; | 182 | for b in &mut buffer[*offset..] { |
| 153 | for i in offset..end { | 183 | if let Some(pending) = self.pending_byte.take() { |
| 154 | buffer[i] = p.ic_data_cmd().read().dat(); | 184 | *b = pending; |
| 185 | *offset += 1; | ||
| 186 | continue; | ||
| 187 | } | ||
| 188 | |||
| 189 | let status = p.ic_status().read(); | ||
| 190 | if !status.rfne() { | ||
| 191 | break; | ||
| 192 | } | ||
| 193 | |||
| 194 | let dat = p.ic_data_cmd().read(); | ||
| 195 | if *offset != 0 && dat.first_data_byte() { | ||
| 196 | // The RP2040 state machine will keep placing bytes into the | ||
| 197 | // FIFO, even if they are part of a subsequent write transaction. | ||
| 198 | // | ||
| 199 | // Unfortunately merely reading ic_data_cmd will consume that | ||
| 200 | // byte, the first byte of the next transaction, so we need | ||
| 201 | // to store it elsewhere | ||
| 202 | self.pending_byte = Some(dat.dat()); | ||
| 203 | break; | ||
| 204 | } | ||
| 205 | |||
| 206 | *b = dat.dat(); | ||
| 207 | *offset += 1; | ||
| 155 | } | 208 | } |
| 156 | end | ||
| 157 | } | 209 | } |
| 158 | 210 | ||
| 159 | #[inline(always)] | 211 | #[inline(always)] |
| @@ -165,52 +217,62 @@ impl<'d, T: Instance> I2cSlave<'d, T> { | |||
| 165 | } | 217 | } |
| 166 | 218 | ||
| 167 | /// Wait asynchronously for commands from an I2C master. | 219 | /// Wait asynchronously for commands from an I2C master. |
| 168 | /// `buffer` is provided in case master does a 'write' and is unused for 'read'. | 220 | /// `buffer` is provided in case master does a 'write', 'write read', or 'general call' and is unused for 'read'. |
| 169 | pub async fn listen(&mut self, buffer: &mut [u8]) -> Result<Command, Error> { | 221 | pub async fn listen(&mut self, buffer: &mut [u8]) -> Result<Command, Error> { |
| 170 | let p = T::regs(); | 222 | let p = T::regs(); |
| 171 | 223 | ||
| 172 | p.ic_clr_intr().read(); | ||
| 173 | // set rx fifo watermark to 1 byte | 224 | // set rx fifo watermark to 1 byte |
| 174 | p.ic_rx_tl().write(|w| w.set_rx_tl(0)); | 225 | p.ic_rx_tl().write(|w| w.set_rx_tl(0)); |
| 175 | 226 | ||
| 176 | let mut len = 0; | 227 | let mut len = 0; |
| 177 | let ret = self | 228 | self.wait_on( |
| 178 | .wait_on( | 229 | |me| { |
| 179 | |me| { | 230 | let stat = p.ic_raw_intr_stat().read(); |
| 180 | let stat = p.ic_raw_intr_stat().read(); | 231 | |
| 181 | if p.ic_rxflr().read().rxflr() > 0 { | 232 | if p.ic_rxflr().read().rxflr() > 0 { |
| 182 | len = me.drain_fifo(buffer, len); | 233 | me.drain_fifo(buffer, &mut len); |
| 183 | // we're recieving data, set rx fifo watermark to 12 bytes to reduce interrupt noise | 234 | // we're recieving data, set rx fifo watermark to 12 bytes (3/4 full) to reduce interrupt noise |
| 184 | p.ic_rx_tl().write(|w| w.set_rx_tl(11)); | 235 | p.ic_rx_tl().write(|w| w.set_rx_tl(11)); |
| 185 | } | 236 | } |
| 186 | 237 | ||
| 187 | if stat.restart_det() && stat.rd_req() { | 238 | if buffer.len() == len { |
| 188 | Poll::Ready(Ok(Command::WriteRead(len))) | 239 | if stat.gen_call() { |
| 189 | } else if stat.gen_call() && stat.stop_det() && len > 0 { | 240 | return Poll::Ready(Err(Error::PartialGeneralCall(buffer.len()))); |
| 190 | Poll::Ready(Ok(Command::GeneralCall(len))) | ||
| 191 | } else if stat.stop_det() { | ||
| 192 | Poll::Ready(Ok(Command::Write(len))) | ||
| 193 | } else if stat.rd_req() { | ||
| 194 | Poll::Ready(Ok(Command::Read)) | ||
| 195 | } else { | 241 | } else { |
| 196 | Poll::Pending | 242 | return Poll::Ready(Err(Error::PartialWrite(buffer.len()))); |
| 197 | } | 243 | } |
| 198 | }, | 244 | } |
| 199 | |_me| { | 245 | |
| 200 | p.ic_intr_mask().modify(|w| { | 246 | if stat.restart_det() && stat.rd_req() { |
| 201 | w.set_m_stop_det(true); | 247 | p.ic_clr_restart_det().read(); |
| 202 | w.set_m_restart_det(true); | 248 | Poll::Ready(Ok(Command::WriteRead(len))) |
| 203 | w.set_m_gen_call(true); | 249 | } else if stat.gen_call() && stat.stop_det() && len > 0 { |
| 204 | w.set_m_rd_req(true); | 250 | p.ic_clr_gen_call().read(); |
| 205 | w.set_m_rx_full(true); | 251 | p.ic_clr_stop_det().read(); |
| 206 | }); | 252 | Poll::Ready(Ok(Command::GeneralCall(len))) |
| 207 | }, | 253 | } else if stat.stop_det() && len > 0 { |
| 208 | ) | 254 | p.ic_clr_stop_det().read(); |
| 209 | .await; | 255 | Poll::Ready(Ok(Command::Write(len))) |
| 210 | 256 | } else if stat.rd_req() { | |
| 211 | p.ic_clr_intr().read(); | 257 | p.ic_clr_stop_det().read(); |
| 212 | 258 | p.ic_clr_restart_det().read(); | |
| 213 | ret | 259 | p.ic_clr_gen_call().read(); |
| 260 | Poll::Ready(Ok(Command::Read)) | ||
| 261 | } else { | ||
| 262 | Poll::Pending | ||
| 263 | } | ||
| 264 | }, | ||
| 265 | |_me| { | ||
| 266 | p.ic_intr_mask().modify(|w| { | ||
| 267 | w.set_m_stop_det(true); | ||
| 268 | w.set_m_restart_det(true); | ||
| 269 | w.set_m_gen_call(true); | ||
| 270 | w.set_m_rd_req(true); | ||
| 271 | w.set_m_rx_full(true); | ||
| 272 | }); | ||
| 273 | }, | ||
| 274 | ) | ||
| 275 | .await | ||
| 214 | } | 276 | } |
| 215 | 277 | ||
| 216 | /// Respond to an I2C master READ command, asynchronously. | 278 | /// Respond to an I2C master READ command, asynchronously. |
| @@ -223,47 +285,47 @@ impl<'d, T: Instance> I2cSlave<'d, T> { | |||
| 223 | 285 | ||
| 224 | let mut chunks = buffer.chunks(FIFO_SIZE as usize); | 286 | let mut chunks = buffer.chunks(FIFO_SIZE as usize); |
| 225 | 287 | ||
| 226 | let ret = self | 288 | self.wait_on( |
| 227 | .wait_on( | 289 | |me| { |
| 228 | |me| { | 290 | if let Err(abort_reason) = me.read_and_clear_abort_reason() { |
| 229 | if let Err(abort_reason) = me.read_and_clear_abort_reason() { | 291 | if let Error::Abort(AbortReason::TxNotEmpty(bytes)) = abort_reason { |
| 230 | if let Error::Abort(AbortReason::TxNotEmpty(bytes)) = abort_reason { | 292 | p.ic_clr_intr().read(); |
| 231 | return Poll::Ready(Ok(ReadStatus::LeftoverBytes(bytes))); | 293 | return Poll::Ready(Ok(ReadStatus::LeftoverBytes(bytes))); |
| 232 | } else { | 294 | } else { |
| 233 | return Poll::Ready(Err(abort_reason)); | 295 | return Poll::Ready(Err(abort_reason)); |
| 234 | } | ||
| 235 | } | 296 | } |
| 297 | } | ||
| 236 | 298 | ||
| 237 | if let Some(chunk) = chunks.next() { | 299 | if let Some(chunk) = chunks.next() { |
| 238 | me.write_to_fifo(chunk); | 300 | me.write_to_fifo(chunk); |
| 239 | 301 | ||
| 240 | Poll::Pending | 302 | p.ic_clr_rd_req().read(); |
| 241 | } else { | ||
| 242 | let stat = p.ic_raw_intr_stat().read(); | ||
| 243 | |||
| 244 | if stat.rx_done() && stat.stop_det() { | ||
| 245 | Poll::Ready(Ok(ReadStatus::Done)) | ||
| 246 | } else if stat.rd_req() { | ||
| 247 | Poll::Ready(Ok(ReadStatus::NeedMoreBytes)) | ||
| 248 | } else { | ||
| 249 | Poll::Pending | ||
| 250 | } | ||
| 251 | } | ||
| 252 | }, | ||
| 253 | |_me| { | ||
| 254 | p.ic_intr_mask().modify(|w| { | ||
| 255 | w.set_m_stop_det(true); | ||
| 256 | w.set_m_rx_done(true); | ||
| 257 | w.set_m_tx_empty(true); | ||
| 258 | w.set_m_tx_abrt(true); | ||
| 259 | }) | ||
| 260 | }, | ||
| 261 | ) | ||
| 262 | .await; | ||
| 263 | 303 | ||
| 264 | p.ic_clr_intr().read(); | 304 | Poll::Pending |
| 305 | } else { | ||
| 306 | let stat = p.ic_raw_intr_stat().read(); | ||
| 265 | 307 | ||
| 266 | ret | 308 | if stat.rx_done() && stat.stop_det() { |
| 309 | p.ic_clr_rx_done().read(); | ||
| 310 | p.ic_clr_stop_det().read(); | ||
| 311 | Poll::Ready(Ok(ReadStatus::Done)) | ||
| 312 | } else if stat.rd_req() && stat.tx_empty() { | ||
| 313 | Poll::Ready(Ok(ReadStatus::NeedMoreBytes)) | ||
| 314 | } else { | ||
| 315 | Poll::Pending | ||
| 316 | } | ||
| 317 | } | ||
| 318 | }, | ||
| 319 | |_me| { | ||
| 320 | p.ic_intr_mask().modify(|w| { | ||
| 321 | w.set_m_stop_det(true); | ||
| 322 | w.set_m_rx_done(true); | ||
| 323 | w.set_m_tx_empty(true); | ||
| 324 | w.set_m_tx_abrt(true); | ||
| 325 | }) | ||
| 326 | }, | ||
| 327 | ) | ||
| 328 | .await | ||
| 267 | } | 329 | } |
| 268 | 330 | ||
| 269 | /// Respond to reads with the fill byte until the controller stops asking | 331 | /// Respond to reads with the fill byte until the controller stops asking |
| @@ -294,10 +356,6 @@ impl<'d, T: Instance> I2cSlave<'d, T> { | |||
| 294 | let p = T::regs(); | 356 | let p = T::regs(); |
| 295 | let mut abort_reason = p.ic_tx_abrt_source().read(); | 357 | let mut abort_reason = p.ic_tx_abrt_source().read(); |
| 296 | 358 | ||
| 297 | // Mask off fifo flush count | ||
| 298 | let tx_flush_cnt = abort_reason.tx_flush_cnt(); | ||
| 299 | abort_reason.set_tx_flush_cnt(0); | ||
| 300 | |||
| 301 | // Mask off master_dis | 359 | // Mask off master_dis |
| 302 | abort_reason.set_abrt_master_dis(false); | 360 | abort_reason.set_abrt_master_dis(false); |
| 303 | 361 | ||
| @@ -314,8 +372,8 @@ impl<'d, T: Instance> I2cSlave<'d, T> { | |||
| 314 | AbortReason::NoAcknowledge | 372 | AbortReason::NoAcknowledge |
| 315 | } else if abort_reason.arb_lost() { | 373 | } else if abort_reason.arb_lost() { |
| 316 | AbortReason::ArbitrationLoss | 374 | AbortReason::ArbitrationLoss |
| 317 | } else if abort_reason.abrt_slvflush_txfifo() { | 375 | } else if abort_reason.tx_flush_cnt() > 0 { |
| 318 | AbortReason::TxNotEmpty(tx_flush_cnt) | 376 | AbortReason::TxNotEmpty(abort_reason.tx_flush_cnt()) |
| 319 | } else { | 377 | } else { |
| 320 | AbortReason::Other(abort_reason.0) | 378 | AbortReason::Other(abort_reason.0) |
| 321 | }; | 379 | }; |
