xref: /aosp_15_r20/external/pigweed/pw_spi_mcuxpresso/responder.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker // Copyright 2024 The Pigweed Authors
2*61c4878aSAndroid Build Coastguard Worker //
3*61c4878aSAndroid Build Coastguard Worker // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4*61c4878aSAndroid Build Coastguard Worker // use this file except in compliance with the License. You may obtain a copy of
5*61c4878aSAndroid Build Coastguard Worker // the License at
6*61c4878aSAndroid Build Coastguard Worker //
7*61c4878aSAndroid Build Coastguard Worker //     https://www.apache.org/licenses/LICENSE-2.0
8*61c4878aSAndroid Build Coastguard Worker //
9*61c4878aSAndroid Build Coastguard Worker // Unless required by applicable law or agreed to in writing, software
10*61c4878aSAndroid Build Coastguard Worker // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11*61c4878aSAndroid Build Coastguard Worker // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12*61c4878aSAndroid Build Coastguard Worker // License for the specific language governing permissions and limitations under
13*61c4878aSAndroid Build Coastguard Worker // the License.
14*61c4878aSAndroid Build Coastguard Worker #include "pw_spi_mcuxpresso/responder.h"
15*61c4878aSAndroid Build Coastguard Worker 
16*61c4878aSAndroid Build Coastguard Worker #include <cinttypes>
17*61c4878aSAndroid Build Coastguard Worker 
18*61c4878aSAndroid Build Coastguard Worker #include "pw_assert/check.h"
19*61c4878aSAndroid Build Coastguard Worker #include "pw_log/log.h"
20*61c4878aSAndroid Build Coastguard Worker #include "pw_status/try.h"
21*61c4878aSAndroid Build Coastguard Worker 
22*61c4878aSAndroid Build Coastguard Worker // Vendor terminology requires this to be disabled.
23*61c4878aSAndroid Build Coastguard Worker // inclusive-language: disable
24*61c4878aSAndroid Build Coastguard Worker 
25*61c4878aSAndroid Build Coastguard Worker namespace pw::spi {
26*61c4878aSAndroid Build Coastguard Worker namespace {
27*61c4878aSAndroid Build Coastguard Worker 
SpanData(ByteSpan & span)28*61c4878aSAndroid Build Coastguard Worker uint8_t* SpanData(ByteSpan& span) {
29*61c4878aSAndroid Build Coastguard Worker   static_assert(std::is_same_v<uint8_t, unsigned char>);
30*61c4878aSAndroid Build Coastguard Worker   return reinterpret_cast<uint8_t*>(span.data());
31*61c4878aSAndroid Build Coastguard Worker }
32*61c4878aSAndroid Build Coastguard Worker 
SpanDataDiscardConst(ConstByteSpan & span)33*61c4878aSAndroid Build Coastguard Worker uint8_t* SpanDataDiscardConst(ConstByteSpan& span) {
34*61c4878aSAndroid Build Coastguard Worker   static_assert(std::is_same_v<uint8_t, unsigned char>);
35*61c4878aSAndroid Build Coastguard Worker   return const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(span.data()));
36*61c4878aSAndroid Build Coastguard Worker }
37*61c4878aSAndroid Build Coastguard Worker 
ToPwStatus(status_t status)38*61c4878aSAndroid Build Coastguard Worker Status ToPwStatus(status_t status) {
39*61c4878aSAndroid Build Coastguard Worker   switch (status) {
40*61c4878aSAndroid Build Coastguard Worker     // Intentional fall-through
41*61c4878aSAndroid Build Coastguard Worker     case kStatus_Success:
42*61c4878aSAndroid Build Coastguard Worker     case kStatus_SPI_Idle:
43*61c4878aSAndroid Build Coastguard Worker       return OkStatus();
44*61c4878aSAndroid Build Coastguard Worker     case kStatus_ReadOnly:
45*61c4878aSAndroid Build Coastguard Worker       return Status::PermissionDenied();
46*61c4878aSAndroid Build Coastguard Worker     case kStatus_OutOfRange:
47*61c4878aSAndroid Build Coastguard Worker       return Status::OutOfRange();
48*61c4878aSAndroid Build Coastguard Worker     case kStatus_InvalidArgument:
49*61c4878aSAndroid Build Coastguard Worker       return Status::InvalidArgument();
50*61c4878aSAndroid Build Coastguard Worker     case kStatus_Timeout:
51*61c4878aSAndroid Build Coastguard Worker       return Status::DeadlineExceeded();
52*61c4878aSAndroid Build Coastguard Worker     case kStatus_NoTransferInProgress:
53*61c4878aSAndroid Build Coastguard Worker       return Status::FailedPrecondition();
54*61c4878aSAndroid Build Coastguard Worker     // Intentional fall-through
55*61c4878aSAndroid Build Coastguard Worker     case kStatus_Fail:
56*61c4878aSAndroid Build Coastguard Worker     default:
57*61c4878aSAndroid Build Coastguard Worker       PW_LOG_ERROR("Mcuxpresso SPI unknown error code: %d",
58*61c4878aSAndroid Build Coastguard Worker                    static_cast<int>(status));
59*61c4878aSAndroid Build Coastguard Worker       return Status::Unknown();
60*61c4878aSAndroid Build Coastguard Worker   }
61*61c4878aSAndroid Build Coastguard Worker }
62*61c4878aSAndroid Build Coastguard Worker 
SetSdkConfig(const McuxpressoResponder::Config & config,spi_slave_config_t & sdk_config)63*61c4878aSAndroid Build Coastguard Worker Status SetSdkConfig(const McuxpressoResponder::Config& config,
64*61c4878aSAndroid Build Coastguard Worker                     spi_slave_config_t& sdk_config) {
65*61c4878aSAndroid Build Coastguard Worker   switch (config.polarity) {
66*61c4878aSAndroid Build Coastguard Worker     case ClockPolarity::kActiveLow:
67*61c4878aSAndroid Build Coastguard Worker       sdk_config.polarity = kSPI_ClockPolarityActiveLow;
68*61c4878aSAndroid Build Coastguard Worker       break;
69*61c4878aSAndroid Build Coastguard Worker     case ClockPolarity::kActiveHigh:
70*61c4878aSAndroid Build Coastguard Worker       sdk_config.polarity = kSPI_ClockPolarityActiveHigh;
71*61c4878aSAndroid Build Coastguard Worker       break;
72*61c4878aSAndroid Build Coastguard Worker     default:
73*61c4878aSAndroid Build Coastguard Worker       return Status::InvalidArgument();
74*61c4878aSAndroid Build Coastguard Worker   }
75*61c4878aSAndroid Build Coastguard Worker 
76*61c4878aSAndroid Build Coastguard Worker   switch (config.phase) {
77*61c4878aSAndroid Build Coastguard Worker     case ClockPhase::kRisingEdge:
78*61c4878aSAndroid Build Coastguard Worker       sdk_config.phase = kSPI_ClockPhaseFirstEdge;
79*61c4878aSAndroid Build Coastguard Worker       break;
80*61c4878aSAndroid Build Coastguard Worker     case ClockPhase::kFallingEdge:
81*61c4878aSAndroid Build Coastguard Worker       sdk_config.phase = kSPI_ClockPhaseSecondEdge;
82*61c4878aSAndroid Build Coastguard Worker       break;
83*61c4878aSAndroid Build Coastguard Worker     default:
84*61c4878aSAndroid Build Coastguard Worker       return Status::InvalidArgument();
85*61c4878aSAndroid Build Coastguard Worker   }
86*61c4878aSAndroid Build Coastguard Worker 
87*61c4878aSAndroid Build Coastguard Worker   switch (config.bit_order) {
88*61c4878aSAndroid Build Coastguard Worker     case BitOrder::kMsbFirst:
89*61c4878aSAndroid Build Coastguard Worker       sdk_config.direction = kSPI_MsbFirst;
90*61c4878aSAndroid Build Coastguard Worker       break;
91*61c4878aSAndroid Build Coastguard Worker     case BitOrder::kLsbFirst:
92*61c4878aSAndroid Build Coastguard Worker       sdk_config.direction = kSPI_LsbFirst;
93*61c4878aSAndroid Build Coastguard Worker       break;
94*61c4878aSAndroid Build Coastguard Worker     default:
95*61c4878aSAndroid Build Coastguard Worker       return Status::InvalidArgument();
96*61c4878aSAndroid Build Coastguard Worker   }
97*61c4878aSAndroid Build Coastguard Worker 
98*61c4878aSAndroid Build Coastguard Worker   switch (config.bits_per_word()) {
99*61c4878aSAndroid Build Coastguard Worker     case 4:
100*61c4878aSAndroid Build Coastguard Worker       sdk_config.dataWidth = kSPI_Data4Bits;
101*61c4878aSAndroid Build Coastguard Worker       break;
102*61c4878aSAndroid Build Coastguard Worker     case 5:
103*61c4878aSAndroid Build Coastguard Worker       sdk_config.dataWidth = kSPI_Data5Bits;
104*61c4878aSAndroid Build Coastguard Worker       break;
105*61c4878aSAndroid Build Coastguard Worker     case 6:
106*61c4878aSAndroid Build Coastguard Worker       sdk_config.dataWidth = kSPI_Data6Bits;
107*61c4878aSAndroid Build Coastguard Worker       break;
108*61c4878aSAndroid Build Coastguard Worker     case 7:
109*61c4878aSAndroid Build Coastguard Worker       sdk_config.dataWidth = kSPI_Data7Bits;
110*61c4878aSAndroid Build Coastguard Worker       break;
111*61c4878aSAndroid Build Coastguard Worker     case 8:
112*61c4878aSAndroid Build Coastguard Worker       sdk_config.dataWidth = kSPI_Data8Bits;
113*61c4878aSAndroid Build Coastguard Worker       break;
114*61c4878aSAndroid Build Coastguard Worker     case 9:
115*61c4878aSAndroid Build Coastguard Worker       sdk_config.dataWidth = kSPI_Data9Bits;
116*61c4878aSAndroid Build Coastguard Worker       break;
117*61c4878aSAndroid Build Coastguard Worker     case 10:
118*61c4878aSAndroid Build Coastguard Worker       sdk_config.dataWidth = kSPI_Data10Bits;
119*61c4878aSAndroid Build Coastguard Worker       break;
120*61c4878aSAndroid Build Coastguard Worker     case 11:
121*61c4878aSAndroid Build Coastguard Worker       sdk_config.dataWidth = kSPI_Data11Bits;
122*61c4878aSAndroid Build Coastguard Worker       break;
123*61c4878aSAndroid Build Coastguard Worker     case 12:
124*61c4878aSAndroid Build Coastguard Worker       sdk_config.dataWidth = kSPI_Data12Bits;
125*61c4878aSAndroid Build Coastguard Worker       break;
126*61c4878aSAndroid Build Coastguard Worker     case 13:
127*61c4878aSAndroid Build Coastguard Worker       sdk_config.dataWidth = kSPI_Data13Bits;
128*61c4878aSAndroid Build Coastguard Worker       break;
129*61c4878aSAndroid Build Coastguard Worker     case 14:
130*61c4878aSAndroid Build Coastguard Worker       sdk_config.dataWidth = kSPI_Data14Bits;
131*61c4878aSAndroid Build Coastguard Worker       break;
132*61c4878aSAndroid Build Coastguard Worker     case 15:
133*61c4878aSAndroid Build Coastguard Worker       sdk_config.dataWidth = kSPI_Data15Bits;
134*61c4878aSAndroid Build Coastguard Worker       break;
135*61c4878aSAndroid Build Coastguard Worker     case 16:
136*61c4878aSAndroid Build Coastguard Worker       sdk_config.dataWidth = kSPI_Data16Bits;
137*61c4878aSAndroid Build Coastguard Worker       break;
138*61c4878aSAndroid Build Coastguard Worker     default:
139*61c4878aSAndroid Build Coastguard Worker       return Status::InvalidArgument();
140*61c4878aSAndroid Build Coastguard Worker   }
141*61c4878aSAndroid Build Coastguard Worker 
142*61c4878aSAndroid Build Coastguard Worker   return OkStatus();
143*61c4878aSAndroid Build Coastguard Worker }
144*61c4878aSAndroid Build Coastguard Worker 
145*61c4878aSAndroid Build Coastguard Worker //
146*61c4878aSAndroid Build Coastguard Worker // Helpful things missing from the SDK
147*61c4878aSAndroid Build Coastguard Worker //
148*61c4878aSAndroid Build Coastguard Worker const IRQn_Type spi_irq_map[] = SPI_IRQS;
149*61c4878aSAndroid Build Coastguard Worker 
150*61c4878aSAndroid Build Coastguard Worker // Enable interrupt on CS asserted / de-asserted.
SPI_EnableSSInterrupt(SPI_Type * base)151*61c4878aSAndroid Build Coastguard Worker void SPI_EnableSSInterrupt(SPI_Type* base) {
152*61c4878aSAndroid Build Coastguard Worker   base->STAT = SPI_STAT_SSA_MASK | SPI_STAT_SSD_MASK;  // Clear first
153*61c4878aSAndroid Build Coastguard Worker   base->INTENSET = SPI_INTENSET_SSAEN_MASK | SPI_INTENSET_SSDEN_MASK;
154*61c4878aSAndroid Build Coastguard Worker }
155*61c4878aSAndroid Build Coastguard Worker 
156*61c4878aSAndroid Build Coastguard Worker // Disable interrupt on CS asserted / de-asserted.
SPI_DisableSSInterrupt(SPI_Type * base)157*61c4878aSAndroid Build Coastguard Worker void SPI_DisableSSInterrupt(SPI_Type* base) {
158*61c4878aSAndroid Build Coastguard Worker   base->INTENCLR = SPI_INTENSET_SSAEN_MASK | SPI_INTENSET_SSDEN_MASK;
159*61c4878aSAndroid Build Coastguard Worker }
160*61c4878aSAndroid Build Coastguard Worker 
161*61c4878aSAndroid Build Coastguard Worker // Empty the TX and RX FIFOs.
SPI_EmptyFifos(SPI_Type * base)162*61c4878aSAndroid Build Coastguard Worker void SPI_EmptyFifos(SPI_Type* base) {
163*61c4878aSAndroid Build Coastguard Worker   base->FIFOCFG |= SPI_FIFOCFG_EMPTYTX_MASK | SPI_FIFOCFG_EMPTYRX_MASK;
164*61c4878aSAndroid Build Coastguard Worker }
165*61c4878aSAndroid Build Coastguard Worker 
SPI_RxFifoIsEmpty(SPI_Type * base)166*61c4878aSAndroid Build Coastguard Worker bool SPI_RxFifoIsEmpty(SPI_Type* base) {
167*61c4878aSAndroid Build Coastguard Worker   // RXNOTEMPTY: Receive FIFO is Not Empty
168*61c4878aSAndroid Build Coastguard Worker   // 0 - The receive FIFO is empty.
169*61c4878aSAndroid Build Coastguard Worker   // 1 - The receive FIFO is not empty, so data can be read.
170*61c4878aSAndroid Build Coastguard Worker   return !(base->FIFOSTAT & SPI_FIFOSTAT_RXNOTEMPTY_MASK);
171*61c4878aSAndroid Build Coastguard Worker }
172*61c4878aSAndroid Build Coastguard Worker 
SPI_RxError(SPI_Type * base)173*61c4878aSAndroid Build Coastguard Worker bool SPI_RxError(SPI_Type* base) {
174*61c4878aSAndroid Build Coastguard Worker   return (base->FIFOSTAT & SPI_FIFOSTAT_RXERR_MASK);
175*61c4878aSAndroid Build Coastguard Worker }
176*61c4878aSAndroid Build Coastguard Worker 
SPI_TxError(SPI_Type * base)177*61c4878aSAndroid Build Coastguard Worker bool SPI_TxError(SPI_Type* base) {
178*61c4878aSAndroid Build Coastguard Worker   return (base->FIFOSTAT & SPI_FIFOSTAT_TXERR_MASK);
179*61c4878aSAndroid Build Coastguard Worker }
180*61c4878aSAndroid Build Coastguard Worker 
181*61c4878aSAndroid Build Coastguard Worker // Non-FIFO interrupt sources
182*61c4878aSAndroid Build Coastguard Worker enum _spi_interrupt_sources {
183*61c4878aSAndroid Build Coastguard Worker   kSPI_SlaveSelAssertIrq = SPI_INTENSET_SSAEN_MASK,
184*61c4878aSAndroid Build Coastguard Worker   kSPI_SlaveSelDeassertIrq = SPI_INTENSET_SSDEN_MASK,
185*61c4878aSAndroid Build Coastguard Worker   kSPI_MasterIdleIrq = SPI_INTENSET_MSTIDLEEN_MASK,
186*61c4878aSAndroid Build Coastguard Worker };
187*61c4878aSAndroid Build Coastguard Worker 
188*61c4878aSAndroid Build Coastguard Worker // Gets a bitmap of active (pending + enabled) interrupts.
189*61c4878aSAndroid Build Coastguard Worker // Test against _spi_interrupt_sources constants.
SPI_GetActiveInterrupts(SPI_Type * base)190*61c4878aSAndroid Build Coastguard Worker uint32_t SPI_GetActiveInterrupts(SPI_Type* base) {
191*61c4878aSAndroid Build Coastguard Worker   // Verify that the bits in INTSTAT and INTENSET are the same.
192*61c4878aSAndroid Build Coastguard Worker   static_assert(SPI_INTSTAT_SSA_MASK == SPI_INTENSET_SSAEN_MASK);
193*61c4878aSAndroid Build Coastguard Worker   static_assert(SPI_INTSTAT_SSD_MASK == SPI_INTENSET_SSDEN_MASK);
194*61c4878aSAndroid Build Coastguard Worker   static_assert(SPI_INTSTAT_MSTIDLE_MASK == SPI_INTENSET_MSTIDLEEN_MASK);
195*61c4878aSAndroid Build Coastguard Worker   return base->INTSTAT & base->INTENSET;
196*61c4878aSAndroid Build Coastguard Worker }
197*61c4878aSAndroid Build Coastguard Worker 
198*61c4878aSAndroid Build Coastguard Worker // Clears a bitmap of active interrupts.
199*61c4878aSAndroid Build Coastguard Worker // This acknowledges the interrupt; it does not disable it.
200*61c4878aSAndroid Build Coastguard Worker // @irqs is either kSPI_SlaveSelAssertIrq or kSPI_SlaveSelDeassertIrq.
SPI_ClearActiveInterrupts(SPI_Type * base,uint32_t irqs)201*61c4878aSAndroid Build Coastguard Worker void SPI_ClearActiveInterrupts(SPI_Type* base, uint32_t irqs) {
202*61c4878aSAndroid Build Coastguard Worker   // Verify that the bits in STAT match the enum.
203*61c4878aSAndroid Build Coastguard Worker   static_assert(SPI_STAT_SSA_MASK == kSPI_SlaveSelAssertIrq);
204*61c4878aSAndroid Build Coastguard Worker   static_assert(SPI_STAT_SSD_MASK == kSPI_SlaveSelDeassertIrq);
205*61c4878aSAndroid Build Coastguard Worker   PW_CHECK((irqs & ~(kSPI_SlaveSelAssertIrq | kSPI_SlaveSelDeassertIrq)) == 0);
206*61c4878aSAndroid Build Coastguard Worker   base->STAT = irqs;  // write to clear
207*61c4878aSAndroid Build Coastguard Worker }
208*61c4878aSAndroid Build Coastguard Worker 
209*61c4878aSAndroid Build Coastguard Worker }  // namespace
210*61c4878aSAndroid Build Coastguard Worker 
Initialize()211*61c4878aSAndroid Build Coastguard Worker Status McuxpressoResponder::Initialize() {
212*61c4878aSAndroid Build Coastguard Worker   status_t sdk_status;
213*61c4878aSAndroid Build Coastguard Worker   spi_slave_config_t sdk_config;
214*61c4878aSAndroid Build Coastguard Worker   spi_dma_callback_t callback;
215*61c4878aSAndroid Build Coastguard Worker 
216*61c4878aSAndroid Build Coastguard Worker   SPI_SlaveGetDefaultConfig(&sdk_config);
217*61c4878aSAndroid Build Coastguard Worker   PW_TRY(SetSdkConfig(config_, sdk_config));
218*61c4878aSAndroid Build Coastguard Worker 
219*61c4878aSAndroid Build Coastguard Worker   // Hard coded for now, till added to Config
220*61c4878aSAndroid Build Coastguard Worker   sdk_config.sselPol = kSPI_SpolActiveAllLow;
221*61c4878aSAndroid Build Coastguard Worker 
222*61c4878aSAndroid Build Coastguard Worker   sdk_status = SPI_SlaveInit(base_, &sdk_config);
223*61c4878aSAndroid Build Coastguard Worker   if (sdk_status != kStatus_Success) {
224*61c4878aSAndroid Build Coastguard Worker     PW_LOG_ERROR("SPI_SlaveInit failed: %ld", sdk_status);
225*61c4878aSAndroid Build Coastguard Worker     return ToPwStatus(sdk_status);
226*61c4878aSAndroid Build Coastguard Worker   }
227*61c4878aSAndroid Build Coastguard Worker 
228*61c4878aSAndroid Build Coastguard Worker   if (config_.handle_cs) {
229*61c4878aSAndroid Build Coastguard Worker     // Set up the FLEXCOMM IRQ to get CS assertion/deassertion.
230*61c4878aSAndroid Build Coastguard Worker     // See SPI_MasterTransferCreateHandle().
231*61c4878aSAndroid Build Coastguard Worker     // Note that the 'handle' argument can actually be anything.
232*61c4878aSAndroid Build Coastguard Worker     FLEXCOMM_SetIRQHandler(base_, FlexcommSpiIrqHandler, this);
233*61c4878aSAndroid Build Coastguard Worker 
234*61c4878aSAndroid Build Coastguard Worker     // Enable SPI interrupt in NVIC
235*61c4878aSAndroid Build Coastguard Worker     uint32_t instance = SPI_GetInstance(base_);
236*61c4878aSAndroid Build Coastguard Worker     EnableIRQ(spi_irq_map[instance]);
237*61c4878aSAndroid Build Coastguard Worker 
238*61c4878aSAndroid Build Coastguard Worker     // We only use the CS deassertion interrupt to complete transfers.
239*61c4878aSAndroid Build Coastguard Worker     // Don't provide any callback to the SPI driver (to be invoked by DMA IRQ).
240*61c4878aSAndroid Build Coastguard Worker     callback = nullptr;
241*61c4878aSAndroid Build Coastguard Worker 
242*61c4878aSAndroid Build Coastguard Worker     // Disable the DMA channel interrupts.
243*61c4878aSAndroid Build Coastguard Worker     // If we leave them enabled, then the SPI driver could complete a full
244*61c4878aSAndroid Build Coastguard Worker     // transfer, move the state to kSPI_Idle, and prevent
245*61c4878aSAndroid Build Coastguard Worker     // SPI_SlaveTransferGetCountDMA() from working.
246*61c4878aSAndroid Build Coastguard Worker     rx_dma_.DisableInterrupts();
247*61c4878aSAndroid Build Coastguard Worker     tx_dma_.DisableInterrupts();
248*61c4878aSAndroid Build Coastguard Worker   } else {
249*61c4878aSAndroid Build Coastguard Worker     // Without CS deassertion, we use the SPI driver callback (invoked by DMA
250*61c4878aSAndroid Build Coastguard Worker     // IRQ) to complete transfers.
251*61c4878aSAndroid Build Coastguard Worker     callback = McuxpressoResponder::SdkCallback;
252*61c4878aSAndroid Build Coastguard Worker 
253*61c4878aSAndroid Build Coastguard Worker     // Enable the DMA channel interrupts.
254*61c4878aSAndroid Build Coastguard Worker     // These are enabled by default by DMA_CreateHandle(), but re-enable them
255*61c4878aSAndroid Build Coastguard Worker     // anyway in case they were disabled for some reason.
256*61c4878aSAndroid Build Coastguard Worker     rx_dma_.EnableInterrupts();
257*61c4878aSAndroid Build Coastguard Worker     tx_dma_.EnableInterrupts();
258*61c4878aSAndroid Build Coastguard Worker   }
259*61c4878aSAndroid Build Coastguard Worker 
260*61c4878aSAndroid Build Coastguard Worker   sdk_status = SPI_SlaveTransferCreateHandleDMA(
261*61c4878aSAndroid Build Coastguard Worker       base_, &handle_, callback, this, tx_dma_.handle(), rx_dma_.handle());
262*61c4878aSAndroid Build Coastguard Worker   if (sdk_status != kStatus_Success) {
263*61c4878aSAndroid Build Coastguard Worker     PW_LOG_ERROR("SPI_SlaveTransferCreateHandleDMA failed: %ld", sdk_status);
264*61c4878aSAndroid Build Coastguard Worker     return ToPwStatus(sdk_status);
265*61c4878aSAndroid Build Coastguard Worker   }
266*61c4878aSAndroid Build Coastguard Worker 
267*61c4878aSAndroid Build Coastguard Worker   return OkStatus();
268*61c4878aSAndroid Build Coastguard Worker }
269*61c4878aSAndroid Build Coastguard Worker 
TransferComplete(Status status,size_t bytes_transferred)270*61c4878aSAndroid Build Coastguard Worker void McuxpressoResponder::TransferComplete(Status status,
271*61c4878aSAndroid Build Coastguard Worker                                            size_t bytes_transferred) {
272*61c4878aSAndroid Build Coastguard Worker   if (config_.handle_cs) {
273*61c4878aSAndroid Build Coastguard Worker     SPI_DisableSSInterrupt(base_);
274*61c4878aSAndroid Build Coastguard Worker   }
275*61c4878aSAndroid Build Coastguard Worker 
276*61c4878aSAndroid Build Coastguard Worker   // Abort the DMA transfer (if active).
277*61c4878aSAndroid Build Coastguard Worker   SPI_SlaveTransferAbortDMA(base_, &handle_);
278*61c4878aSAndroid Build Coastguard Worker 
279*61c4878aSAndroid Build Coastguard Worker   // Check for TX underflow / RX overflow
280*61c4878aSAndroid Build Coastguard Worker   //
281*61c4878aSAndroid Build Coastguard Worker   // Ideally we want to check for FIFO under/overflow only *while* the transfer
282*61c4878aSAndroid Build Coastguard Worker   // is running. But if the initiator sent more bytes than the DMA was set up
283*61c4878aSAndroid Build Coastguard Worker   // to tx/rx, both of these errors will happen (after the DMA is complete).
284*61c4878aSAndroid Build Coastguard Worker   //
285*61c4878aSAndroid Build Coastguard Worker   // To do this without risk of false positives, we would need to find a way to
286*61c4878aSAndroid Build Coastguard Worker   // capture this status immediately when the DMA is complete, or otherwise
287*61c4878aSAndroid Build Coastguard Worker   // monitor it during the transfer. Perhaps with a custom DMA chain, we could
288*61c4878aSAndroid Build Coastguard Worker   // capture the status registers via DMA, but that might still be too late.
289*61c4878aSAndroid Build Coastguard Worker   if (status.ok() && config_.check_fifo_error != FifoErrorCheck::kNone) {
290*61c4878aSAndroid Build Coastguard Worker     if (SPI_RxError(base_)) {
291*61c4878aSAndroid Build Coastguard Worker       PW_LOG_ERROR("RX FIFO overflow detected!");
292*61c4878aSAndroid Build Coastguard Worker       if (config_.check_fifo_error == FifoErrorCheck::kError) {
293*61c4878aSAndroid Build Coastguard Worker         status = Status::DataLoss();
294*61c4878aSAndroid Build Coastguard Worker       }
295*61c4878aSAndroid Build Coastguard Worker     }
296*61c4878aSAndroid Build Coastguard Worker     if (SPI_TxError(base_)) {
297*61c4878aSAndroid Build Coastguard Worker       PW_LOG_ERROR("TX FIFO underflow detected!");
298*61c4878aSAndroid Build Coastguard Worker       if (config_.check_fifo_error == FifoErrorCheck::kError) {
299*61c4878aSAndroid Build Coastguard Worker         status = Status::DataLoss();
300*61c4878aSAndroid Build Coastguard Worker       }
301*61c4878aSAndroid Build Coastguard Worker     }
302*61c4878aSAndroid Build Coastguard Worker   }
303*61c4878aSAndroid Build Coastguard Worker 
304*61c4878aSAndroid Build Coastguard Worker   // TODO(jrreinhart) Remove these safety checks.
305*61c4878aSAndroid Build Coastguard Worker   if (rx_dma_.IsBusy()) {
306*61c4878aSAndroid Build Coastguard Worker     PW_LOG_WARN("After completion, rx_dma still busy!");
307*61c4878aSAndroid Build Coastguard Worker   }
308*61c4878aSAndroid Build Coastguard Worker   if (rx_dma_.IsActive()) {
309*61c4878aSAndroid Build Coastguard Worker     PW_LOG_WARN("After completion, rx_dma still active!");
310*61c4878aSAndroid Build Coastguard Worker   }
311*61c4878aSAndroid Build Coastguard Worker 
312*61c4878aSAndroid Build Coastguard Worker   // Empty the FIFOs.
313*61c4878aSAndroid Build Coastguard Worker   // If the initiator sent more bytes than the DMA was set up to receive, the
314*61c4878aSAndroid Build Coastguard Worker   // RXFIFO will have the residue. This isn't strictly necessary since they'll
315*61c4878aSAndroid Build Coastguard Worker   // be cleared on the next call to SPI_SlaveTransferDMA(), but we do it anyway
316*61c4878aSAndroid Build Coastguard Worker   // for cleanliness.
317*61c4878aSAndroid Build Coastguard Worker   SPI_EmptyFifos(base_);
318*61c4878aSAndroid Build Coastguard Worker 
319*61c4878aSAndroid Build Coastguard Worker   // Clear the FIFO DMA request signals.
320*61c4878aSAndroid Build Coastguard Worker   //
321*61c4878aSAndroid Build Coastguard Worker   // From IMXRT500RM 53.4.2.1.2 DMA operation:
322*61c4878aSAndroid Build Coastguard Worker   // "A DMA request is provided for each SPI direction, and can be used instead
323*61c4878aSAndroid Build Coastguard Worker   // of interrupts for transferring data... The DMA controller provides an
324*61c4878aSAndroid Build Coastguard Worker   // acknowledgement signal that clears the related request when it (the DMA
325*61c4878aSAndroid Build Coastguard Worker   // controller) completes handling that request."
326*61c4878aSAndroid Build Coastguard Worker   //
327*61c4878aSAndroid Build Coastguard Worker   // If the initiator sent more bytes than the DMA was set up to receive, this
328*61c4878aSAndroid Build Coastguard Worker   // request signal will remain latched on, even after the FIFO is emptied.
329*61c4878aSAndroid Build Coastguard Worker   // This would cause a subsequent transfer to receive one stale residual byte
330*61c4878aSAndroid Build Coastguard Worker   // from this prior transfer.
331*61c4878aSAndroid Build Coastguard Worker   //
332*61c4878aSAndroid Build Coastguard Worker   // We force if off here by disabling the DMA request signal.
333*61c4878aSAndroid Build Coastguard Worker   // It will be re-enabled on the next transfer.
334*61c4878aSAndroid Build Coastguard Worker   SPI_EnableRxDMA(base_, false);
335*61c4878aSAndroid Build Coastguard Worker   SPI_EnableTxDMA(base_, false);
336*61c4878aSAndroid Build Coastguard Worker 
337*61c4878aSAndroid Build Coastguard Worker   // Invoke the callback
338*61c4878aSAndroid Build Coastguard Worker   auto received = current_transaction_.rx_data.subspan(0, bytes_transferred);
339*61c4878aSAndroid Build Coastguard Worker   current_transaction_ = {};
340*61c4878aSAndroid Build Coastguard Worker   completion_callback_(received, status);
341*61c4878aSAndroid Build Coastguard Worker }
342*61c4878aSAndroid Build Coastguard Worker 
SdkCallback(SPI_Type * base,spi_dma_handle_t * handle,status_t sdk_status,void * userData)343*61c4878aSAndroid Build Coastguard Worker void McuxpressoResponder::SdkCallback(SPI_Type* base,
344*61c4878aSAndroid Build Coastguard Worker                                       spi_dma_handle_t* handle,
345*61c4878aSAndroid Build Coastguard Worker                                       status_t sdk_status,
346*61c4878aSAndroid Build Coastguard Worker                                       void* userData) {
347*61c4878aSAndroid Build Coastguard Worker   // WARNING: This is called in IRQ context.
348*61c4878aSAndroid Build Coastguard Worker   auto* responder = static_cast<McuxpressoResponder*>(userData);
349*61c4878aSAndroid Build Coastguard Worker   PW_CHECK_PTR_EQ(base, responder->base_);
350*61c4878aSAndroid Build Coastguard Worker   PW_CHECK_PTR_EQ(handle, &responder->handle_);
351*61c4878aSAndroid Build Coastguard Worker 
352*61c4878aSAndroid Build Coastguard Worker   return responder->DmaComplete(sdk_status);
353*61c4878aSAndroid Build Coastguard Worker }
354*61c4878aSAndroid Build Coastguard Worker 
DmaComplete(status_t sdk_status)355*61c4878aSAndroid Build Coastguard Worker void McuxpressoResponder::DmaComplete(status_t sdk_status) {
356*61c4878aSAndroid Build Coastguard Worker   // WARNING: This is called in IRQ context.
357*61c4878aSAndroid Build Coastguard Worker   PW_CHECK(!config_.handle_cs,
358*61c4878aSAndroid Build Coastguard Worker            "DmaComplete should never be called when handle_cs=true!");
359*61c4878aSAndroid Build Coastguard Worker 
360*61c4878aSAndroid Build Coastguard Worker   // Move to idle state.
361*61c4878aSAndroid Build Coastguard Worker   if (State prev; !TryChangeState(State::kBusy, State::kIdle, &prev)) {
362*61c4878aSAndroid Build Coastguard Worker     // Spurious callback? Or race condition in DoWriteReadAsync()?
363*61c4878aSAndroid Build Coastguard Worker     PW_LOG_WARN("DmaComplete not in busy state, but %u",
364*61c4878aSAndroid Build Coastguard Worker                 static_cast<unsigned int>(prev));
365*61c4878aSAndroid Build Coastguard Worker     return;
366*61c4878aSAndroid Build Coastguard Worker   }
367*61c4878aSAndroid Build Coastguard Worker 
368*61c4878aSAndroid Build Coastguard Worker   // Transfer complete.
369*61c4878aSAndroid Build Coastguard Worker   auto status = ToPwStatus(sdk_status);
370*61c4878aSAndroid Build Coastguard Worker   size_t bytes_transferred =
371*61c4878aSAndroid Build Coastguard Worker       status.ok() ? current_transaction_.rx_data.size() : 0;
372*61c4878aSAndroid Build Coastguard Worker   TransferComplete(status, bytes_transferred);
373*61c4878aSAndroid Build Coastguard Worker }
374*61c4878aSAndroid Build Coastguard Worker 
FlexcommSpiIrqHandler(void * base,void * arg)375*61c4878aSAndroid Build Coastguard Worker void McuxpressoResponder::FlexcommSpiIrqHandler(void* base, void* arg) {
376*61c4878aSAndroid Build Coastguard Worker   // WARNING: This is called in IRQ context.
377*61c4878aSAndroid Build Coastguard Worker 
378*61c4878aSAndroid Build Coastguard Worker   SPI_Type* spi = static_cast<SPI_Type*>(base);
379*61c4878aSAndroid Build Coastguard Worker   auto* responder = static_cast<McuxpressoResponder*>(arg);
380*61c4878aSAndroid Build Coastguard Worker   PW_CHECK_PTR_EQ(spi, responder->base_);
381*61c4878aSAndroid Build Coastguard Worker 
382*61c4878aSAndroid Build Coastguard Worker   // NOTE: It's possible that CS could deassert and INTSTAT.SSD could latch
383*61c4878aSAndroid Build Coastguard Worker   // shortly after the IRQ handler is entered (due to INTSTAT.SSA), re-setting
384*61c4878aSAndroid Build Coastguard Worker   // the IRQ as pending in the NVIC. In this case, we could handle both SSA and
385*61c4878aSAndroid Build Coastguard Worker   // SSD in the same interrupt. When that happens, the IRQ remains pended in
386*61c4878aSAndroid Build Coastguard Worker   // the NVIC, and the handler will file again. We simply ignore the second
387*61c4878aSAndroid Build Coastguard Worker   // interrupt.
388*61c4878aSAndroid Build Coastguard Worker   //
389*61c4878aSAndroid Build Coastguard Worker   // It would wrong to try and handle only one of SSA or SSD per invocation
390*61c4878aSAndroid Build Coastguard Worker   // because if the interrupt was handled late enough, it might only fire once.
391*61c4878aSAndroid Build Coastguard Worker   const auto active_irqs = SPI_GetActiveInterrupts(spi);
392*61c4878aSAndroid Build Coastguard Worker 
393*61c4878aSAndroid Build Coastguard Worker   // CS asserted?
394*61c4878aSAndroid Build Coastguard Worker   if (active_irqs & kSPI_SlaveSelAssertIrq) {
395*61c4878aSAndroid Build Coastguard Worker     SPI_ClearActiveInterrupts(spi, kSPI_SlaveSelAssertIrq);
396*61c4878aSAndroid Build Coastguard Worker     responder->CsAsserted();
397*61c4878aSAndroid Build Coastguard Worker   }
398*61c4878aSAndroid Build Coastguard Worker 
399*61c4878aSAndroid Build Coastguard Worker   // CS de-asserted?
400*61c4878aSAndroid Build Coastguard Worker   if (active_irqs & kSPI_SlaveSelDeassertIrq) {
401*61c4878aSAndroid Build Coastguard Worker     SPI_ClearActiveInterrupts(spi, kSPI_SlaveSelDeassertIrq);
402*61c4878aSAndroid Build Coastguard Worker     responder->CsDeasserted();
403*61c4878aSAndroid Build Coastguard Worker   }
404*61c4878aSAndroid Build Coastguard Worker }
405*61c4878aSAndroid Build Coastguard Worker 
CsAsserted()406*61c4878aSAndroid Build Coastguard Worker void McuxpressoResponder::CsAsserted() {
407*61c4878aSAndroid Build Coastguard Worker   // WARNING: This is called in IRQ context.
408*61c4878aSAndroid Build Coastguard Worker }
409*61c4878aSAndroid Build Coastguard Worker 
WaitForQuiescenceAfterCsDeassertion()410*61c4878aSAndroid Build Coastguard Worker Status McuxpressoResponder::WaitForQuiescenceAfterCsDeassertion() {
411*61c4878aSAndroid Build Coastguard Worker   // When CS is deasserted, the master is indicating that it has finished
412*61c4878aSAndroid Build Coastguard Worker   // clocking out data into our FIFO. That could be more, less, or the same
413*61c4878aSAndroid Build Coastguard Worker   // number of bytes requested by the user (in DoWriteReadAsync).
414*61c4878aSAndroid Build Coastguard Worker   //
415*61c4878aSAndroid Build Coastguard Worker   // Definitions:
416*61c4878aSAndroid Build Coastguard Worker   //   S: The DMA transfer size (as requested by the user).
417*61c4878aSAndroid Build Coastguard Worker   //   M: The number of bytes sent by the master.
418*61c4878aSAndroid Build Coastguard Worker   //
419*61c4878aSAndroid Build Coastguard Worker   // Case | Condition | DMA will complete? | FIFO will empty?
420*61c4878aSAndroid Build Coastguard Worker   // -----|-----------|--------------------|-------------------
421*61c4878aSAndroid Build Coastguard Worker   //    1 |   M < S   | No                 | Yes
422*61c4878aSAndroid Build Coastguard Worker   //    2 |   M = S   | Yes                | Yes
423*61c4878aSAndroid Build Coastguard Worker   //    3 |   M > S   | Yes                | No
424*61c4878aSAndroid Build Coastguard Worker   //
425*61c4878aSAndroid Build Coastguard Worker   // At this point, the RX FIFO might still have data that the DMA has not yet
426*61c4878aSAndroid Build Coastguard Worker   // read.
427*61c4878aSAndroid Build Coastguard Worker   //
428*61c4878aSAndroid Build Coastguard Worker   // We wait for either the DMA channel to become inactive (case 2 or 3) or for
429*61c4878aSAndroid Build Coastguard Worker   // the RX FIFO to become empty (case 1 or 2). When the FIFO empties, we also
430*61c4878aSAndroid Build Coastguard Worker   // need to wait for the DMA channel to be non-busy, indicating that it has
431*61c4878aSAndroid Build Coastguard Worker   // finished moving the data to SRAM.
432*61c4878aSAndroid Build Coastguard Worker   //
433*61c4878aSAndroid Build Coastguard Worker   // It is expected that by the time this function is called, the hardware will
434*61c4878aSAndroid Build Coastguard Worker   // have already quiesced, and we won't actually wait at all. A warning log
435*61c4878aSAndroid Build Coastguard Worker   // will indicate if that assumption does not hold true.
436*61c4878aSAndroid Build Coastguard Worker   constexpr unsigned int kMaxWaitCount = 10000;  // Arbitrary
437*61c4878aSAndroid Build Coastguard Worker 
438*61c4878aSAndroid Build Coastguard Worker   unsigned int wait_count;
439*61c4878aSAndroid Build Coastguard Worker   for (wait_count = 0; wait_count < kMaxWaitCount; ++wait_count) {
440*61c4878aSAndroid Build Coastguard Worker     if (!rx_dma_.IsActive()) {
441*61c4878aSAndroid Build Coastguard Worker       // The DMA has consumed as many bytes from the FIFO as it ever will.
442*61c4878aSAndroid Build Coastguard Worker       break;
443*61c4878aSAndroid Build Coastguard Worker     }
444*61c4878aSAndroid Build Coastguard Worker 
445*61c4878aSAndroid Build Coastguard Worker     if (SPI_RxFifoIsEmpty(base_) && !rx_dma_.IsBusy()) {
446*61c4878aSAndroid Build Coastguard Worker       // The FIFO is empty, and the DMA channel has moved all data to SRAM.
447*61c4878aSAndroid Build Coastguard Worker       break;
448*61c4878aSAndroid Build Coastguard Worker     }
449*61c4878aSAndroid Build Coastguard Worker 
450*61c4878aSAndroid Build Coastguard Worker     // DMA is still active and FIFO is not empty. We need to wait.
451*61c4878aSAndroid Build Coastguard Worker   }
452*61c4878aSAndroid Build Coastguard Worker 
453*61c4878aSAndroid Build Coastguard Worker   if (wait_count == kMaxWaitCount) {
454*61c4878aSAndroid Build Coastguard Worker     PW_LOG_ERROR(
455*61c4878aSAndroid Build Coastguard Worker         "After CS de-assertion, timed out waiting for DMA done or FIFO empty.");
456*61c4878aSAndroid Build Coastguard Worker     return Status::DeadlineExceeded();
457*61c4878aSAndroid Build Coastguard Worker   }
458*61c4878aSAndroid Build Coastguard Worker 
459*61c4878aSAndroid Build Coastguard Worker   if (wait_count != 0) {
460*61c4878aSAndroid Build Coastguard Worker     PW_LOG_WARN(
461*61c4878aSAndroid Build Coastguard Worker         "After CS de-assertion, waited %u times for DMA done or FIFO empty.",
462*61c4878aSAndroid Build Coastguard Worker         wait_count);
463*61c4878aSAndroid Build Coastguard Worker   }
464*61c4878aSAndroid Build Coastguard Worker   return OkStatus();
465*61c4878aSAndroid Build Coastguard Worker }
466*61c4878aSAndroid Build Coastguard Worker 
CsDeasserted()467*61c4878aSAndroid Build Coastguard Worker void McuxpressoResponder::CsDeasserted() {
468*61c4878aSAndroid Build Coastguard Worker   // WARNING: This is called in IRQ context.
469*61c4878aSAndroid Build Coastguard Worker   PW_CHECK(config_.handle_cs,
470*61c4878aSAndroid Build Coastguard Worker            "CsDeasserted should only be called when handle_cs=true!");
471*61c4878aSAndroid Build Coastguard Worker 
472*61c4878aSAndroid Build Coastguard Worker   // Move to idle state.
473*61c4878aSAndroid Build Coastguard Worker   if (State prev; !TryChangeState(State::kBusy, State::kIdle, &prev)) {
474*61c4878aSAndroid Build Coastguard Worker     PW_LOG_WARN("CsDeasserted not in busy state, but %u",
475*61c4878aSAndroid Build Coastguard Worker                 static_cast<unsigned int>(prev));
476*61c4878aSAndroid Build Coastguard Worker     return;
477*61c4878aSAndroid Build Coastguard Worker   }
478*61c4878aSAndroid Build Coastguard Worker 
479*61c4878aSAndroid Build Coastguard Worker   Status wait_status = WaitForQuiescenceAfterCsDeassertion();
480*61c4878aSAndroid Build Coastguard Worker 
481*61c4878aSAndroid Build Coastguard Worker   // Get the number of bytes actually transferred.
482*61c4878aSAndroid Build Coastguard Worker   //
483*61c4878aSAndroid Build Coastguard Worker   // NOTE: SPI_SlaveTransferGetCountDMA() fails if _handle.state != kSPI_Busy.
484*61c4878aSAndroid Build Coastguard Worker   // Thus, it must be called before SPI_SlaveTransferAbortDMA() which changes
485*61c4878aSAndroid Build Coastguard Worker   // the state to kSPI_Idle. Also, the DMA channel interrupts are disabled when
486*61c4878aSAndroid Build Coastguard Worker   // CS is respected, because SPI_RxDMACallback() and SPI_TxDMACallback() also
487*61c4878aSAndroid Build Coastguard Worker   // change the state to kSPI_Idle.
488*61c4878aSAndroid Build Coastguard Worker   size_t bytes_transferred = 0;
489*61c4878aSAndroid Build Coastguard Worker   status_t sdk_status =
490*61c4878aSAndroid Build Coastguard Worker       SPI_SlaveTransferGetCountDMA(base_, &handle_, &bytes_transferred);
491*61c4878aSAndroid Build Coastguard Worker 
492*61c4878aSAndroid Build Coastguard Worker   // Transfer complete.
493*61c4878aSAndroid Build Coastguard Worker   Status xfer_status = OkStatus();
494*61c4878aSAndroid Build Coastguard Worker   if (!wait_status.ok()) {
495*61c4878aSAndroid Build Coastguard Worker     bytes_transferred = 0;
496*61c4878aSAndroid Build Coastguard Worker     xfer_status = wait_status;
497*61c4878aSAndroid Build Coastguard Worker   } else if (sdk_status != kStatus_Success) {
498*61c4878aSAndroid Build Coastguard Worker     PW_LOG_ERROR("SPI_SlaveTransferGetCountDMA() returned %" PRId32,
499*61c4878aSAndroid Build Coastguard Worker                  sdk_status);
500*61c4878aSAndroid Build Coastguard Worker     bytes_transferred = 0;
501*61c4878aSAndroid Build Coastguard Worker     xfer_status = ToPwStatus(sdk_status);
502*61c4878aSAndroid Build Coastguard Worker   }
503*61c4878aSAndroid Build Coastguard Worker   TransferComplete(xfer_status, bytes_transferred);
504*61c4878aSAndroid Build Coastguard Worker }
505*61c4878aSAndroid Build Coastguard Worker 
DoWriteReadAsync(ConstByteSpan tx_data,ByteSpan rx_data)506*61c4878aSAndroid Build Coastguard Worker Status McuxpressoResponder::DoWriteReadAsync(ConstByteSpan tx_data,
507*61c4878aSAndroid Build Coastguard Worker                                              ByteSpan rx_data) {
508*61c4878aSAndroid Build Coastguard Worker   if (!TryChangeState(State::kIdle, State::kBusy)) {
509*61c4878aSAndroid Build Coastguard Worker     PW_LOG_ERROR("Transaction already started");
510*61c4878aSAndroid Build Coastguard Worker     return Status::FailedPrecondition();
511*61c4878aSAndroid Build Coastguard Worker   }
512*61c4878aSAndroid Build Coastguard Worker   PW_CHECK(!current_transaction_);
513*61c4878aSAndroid Build Coastguard Worker 
514*61c4878aSAndroid Build Coastguard Worker   // TODO(jrreinhart): There is a race here. If DoCancel() is called, it will
515*61c4878aSAndroid Build Coastguard Worker   // move to kIdle, and invoke the callback with CANCELLED. But then we will
516*61c4878aSAndroid Build Coastguard Worker   // still go on to perform the transfer anyway. When the transfer completes,
517*61c4878aSAndroid Build Coastguard Worker   // SdkCallback will see kIdle and skip the callback. We avoid this problem
518*61c4878aSAndroid Build Coastguard Worker   // by saying that DoWriteReadAsync() and DoCancel() should not be called from
519*61c4878aSAndroid Build Coastguard Worker   // different threads, thus we only have to worry about DoCancel() racing the
520*61c4878aSAndroid Build Coastguard Worker   // hardware / IRQ.
521*61c4878aSAndroid Build Coastguard Worker 
522*61c4878aSAndroid Build Coastguard Worker   spi_transfer_t transfer = {};
523*61c4878aSAndroid Build Coastguard Worker 
524*61c4878aSAndroid Build Coastguard Worker   if (!tx_data.empty() && !rx_data.empty()) {
525*61c4878aSAndroid Build Coastguard Worker     // spi_transfer_t has only a single dataSize member, so tx_data and
526*61c4878aSAndroid Build Coastguard Worker     // rx_data must be the same size. Separate rx/tx data sizes could
527*61c4878aSAndroid Build Coastguard Worker     // theoretically be handled, but the SDK doesn't support it.
528*61c4878aSAndroid Build Coastguard Worker     //
529*61c4878aSAndroid Build Coastguard Worker     // TODO(jrreinhart) Support separate rx/tx data sizes.
530*61c4878aSAndroid Build Coastguard Worker     // For non-DMA, it's a pretty simple patch.
531*61c4878aSAndroid Build Coastguard Worker     // It should be doable for DMA also, but I haven't looked into it.
532*61c4878aSAndroid Build Coastguard Worker     if (tx_data.size() != rx_data.size()) {
533*61c4878aSAndroid Build Coastguard Worker       return Status::InvalidArgument();
534*61c4878aSAndroid Build Coastguard Worker     }
535*61c4878aSAndroid Build Coastguard Worker 
536*61c4878aSAndroid Build Coastguard Worker     transfer.txData = SpanDataDiscardConst(tx_data);
537*61c4878aSAndroid Build Coastguard Worker     transfer.rxData = SpanData(rx_data);
538*61c4878aSAndroid Build Coastguard Worker     transfer.dataSize = rx_data.size();
539*61c4878aSAndroid Build Coastguard Worker   } else if (!tx_data.empty()) {
540*61c4878aSAndroid Build Coastguard Worker     transfer.txData = SpanDataDiscardConst(tx_data);
541*61c4878aSAndroid Build Coastguard Worker     transfer.dataSize = tx_data.size();
542*61c4878aSAndroid Build Coastguard Worker   } else if (!rx_data.empty()) {
543*61c4878aSAndroid Build Coastguard Worker     transfer.rxData = SpanData(rx_data);
544*61c4878aSAndroid Build Coastguard Worker     transfer.dataSize = rx_data.size();
545*61c4878aSAndroid Build Coastguard Worker   } else {
546*61c4878aSAndroid Build Coastguard Worker     return Status::InvalidArgument();
547*61c4878aSAndroid Build Coastguard Worker   }
548*61c4878aSAndroid Build Coastguard Worker 
549*61c4878aSAndroid Build Coastguard Worker   current_transaction_ = {
550*61c4878aSAndroid Build Coastguard Worker       .rx_data = rx_data,
551*61c4878aSAndroid Build Coastguard Worker   };
552*61c4878aSAndroid Build Coastguard Worker 
553*61c4878aSAndroid Build Coastguard Worker   if (config_.handle_cs) {
554*61c4878aSAndroid Build Coastguard Worker     // Complete the transfer when CS is deasserted.
555*61c4878aSAndroid Build Coastguard Worker     SPI_EnableSSInterrupt(base_);
556*61c4878aSAndroid Build Coastguard Worker   }
557*61c4878aSAndroid Build Coastguard Worker 
558*61c4878aSAndroid Build Coastguard Worker   status_t sdk_status = SPI_SlaveTransferDMA(base_, &handle_, &transfer);
559*61c4878aSAndroid Build Coastguard Worker   if (sdk_status != kStatus_Success) {
560*61c4878aSAndroid Build Coastguard Worker     PW_LOG_ERROR("SPI_SlaveTransferDMA failed: %ld", sdk_status);
561*61c4878aSAndroid Build Coastguard Worker     return ToPwStatus(sdk_status);
562*61c4878aSAndroid Build Coastguard Worker   }
563*61c4878aSAndroid Build Coastguard Worker 
564*61c4878aSAndroid Build Coastguard Worker   return OkStatus();
565*61c4878aSAndroid Build Coastguard Worker }
566*61c4878aSAndroid Build Coastguard Worker 
DoCancel()567*61c4878aSAndroid Build Coastguard Worker void McuxpressoResponder::DoCancel() {
568*61c4878aSAndroid Build Coastguard Worker   if (!TryChangeState(State::kBusy, State::kIdle)) {
569*61c4878aSAndroid Build Coastguard Worker     return;
570*61c4878aSAndroid Build Coastguard Worker   }
571*61c4878aSAndroid Build Coastguard Worker   TransferComplete(Status::Cancelled(), 0);
572*61c4878aSAndroid Build Coastguard Worker }
573*61c4878aSAndroid Build Coastguard Worker 
574*61c4878aSAndroid Build Coastguard Worker }  // namespace pw::spi
575