SatCat5
sam_usart.cc
1 // Copyright 2024-2025 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include "sam_usart.h"
7 
8 // ASF3 includes
9 extern "C"{
10 #include <core_cm7.h>
11 #include <ioport.h>
12 #include <pmc.h>
13 #include <wdt.h>
14 }
15 
21 
22 // Configure to reset watchdog timer when receiving/sending bytes
23 #ifdef SATCAT5_SAMV71_UART_WRST
24  #define SATCAT5_SAMV71_UART_WRST=1
25 #endif
26 
27 // SAMV71 cache is 32-bytes per line, invalidate on the appropriate boundary.
28 static const u32 CACHE_LINESIZE = 32;
29 static const u32 CACHE_ADDRMASK = 0xFFFFFFE0;
30 
31 // Struct holding relevant parameters for each peripheral.
32 struct usart_conf {
33  u8 clk_id;
34  IRQn irq;
35  bool supports_fc;
36  u8 tx_dma_ch;
37  u8 rx_dma_ch;
38  u8 tx_perid;
39  u8 rx_perid;
40 };
41 
42 // XDMAC needs to be reset exactly once before configuration. This flag will be
43 // set true when the first called constructor resets the peripheral.
44 static bool XDMAC_RESET_DONE = false;
45 
46 // Lookup relevant peripheral information from USART instance.
47 // DMA channel assignments are static since this is assumed to be the only
48 // active DMA controller on the device. Datasheet and ASF3 disagree on number of
49 // available channels (7 vs. 24), seems to be 24 in hardware.
50 static usart_conf get_conf(const Usart* usart) {
51  if (usart == USART0) {
52  return { ID_USART0, USART0_IRQn, true, 0, 1,
53  XDMAC_CHANNEL_HWID_USART0_TX, XDMAC_CHANNEL_HWID_USART0_RX };
54  } else if (usart == USART1) {
55  return { ID_USART1, USART1_IRQn, true, 2, 3,
56  XDMAC_CHANNEL_HWID_USART1_TX, XDMAC_CHANNEL_HWID_USART1_RX };
57  } else if (usart == USART2) {
58  return { ID_USART2, USART2_IRQn, true, 4, 5,
59  XDMAC_CHANNEL_HWID_USART2_TX, XDMAC_CHANNEL_HWID_USART2_RX };
60  } else if (usart == (Usart*) UART0) {
61  return { ID_UART0, UART0_IRQn, false, 6, 7,
62  XDMAC_CHANNEL_HWID_UART0_TX, XDMAC_CHANNEL_HWID_UART0_RX };
63  } else if (usart == (Usart*) UART1) {
64  return { ID_UART1, UART1_IRQn, false, 8, 9,
65  XDMAC_CHANNEL_HWID_UART1_TX, XDMAC_CHANNEL_HWID_UART1_RX };
66  } else if (usart == (Usart*) UART2) {
67  return { ID_UART2, UART2_IRQn, false, 10, 11,
68  XDMAC_CHANNEL_HWID_UART2_TX, XDMAC_CHANNEL_HWID_UART2_RX };
69  } else if (usart == (Usart*) UART3) {
70  return { ID_UART3, UART3_IRQn, false, 12, 13,
71  XDMAC_CHANNEL_HWID_UART3_TX, XDMAC_CHANNEL_HWID_UART3_RX };
72  } else if (usart == (Usart*) UART4) {
73  return { ID_UART4, UART4_IRQn, false, 14, 15,
74  XDMAC_CHANNEL_HWID_UART4_TX, XDMAC_CHANNEL_HWID_UART4_RX };
75  } else {
76  return { ID_PERIPH_COUNT, PERIPH_COUNT_IRQn, false, 0, 0, 0, 0 };
77  }
78 }
79 
80 // Constructor calls out to setup functions.
81 UsartSamv71::UsartSamv71(Usart* usart, unsigned baud_hz, unsigned poll_ms,
82  u8* txbuff, unsigned tx_nbytes, u8* rxbuff, unsigned rx_nbytes,
83  u8* rxdma0, u8* rxdma1, unsigned rxdma_nbytes, bool fc_on)
84  : BufferedIO(txbuff, tx_nbytes, 0, rxbuff, rx_nbytes, 0)
85  , Always(false) // Do not auto-register.
86  , HandlerSAMV71("UsartSamv71", XDMAC_IRQn)
87  , m_usart(usart)
88  , m_txdma_nbytes(0)
89  , m_rxdma0(rxdma0)
90  , m_rxdma1(rxdma1)
91  , m_rxdma_nbytes(rxdma_nbytes)
92  , m_rxdma_buffidx(0)
93 {
94  // Lookup the passed in USART and set self in a dead state on failure.
95  usart_conf ids = get_conf(m_usart);
96  if (ids.clk_id == ID_PERIPH_COUNT) {
97  m_usart = nullptr;
98  return;
99  }
100  m_tx_dma_ch = ids.tx_dma_ch;
101  m_rx_dma_ch = ids.rx_dma_ch;
102 
103  // If the XDMAC needs a reset, execute exactly once.
104  if (!XDMAC_RESET_DONE) {
105  XDMAC->XDMAC_GD = 0xFFFFFFFF; // Disable all channels
106  XDMAC->XDMAC_GID = 0xFFFFFFFF; // Disable all interrupts
107  XDMAC->XDMAC_GSWR = 1; // Software reset
108  XDMAC_RESET_DONE = true;
109  }
110 
111  // Set up clocking, DMA controller, and USART peripheral.
112  sysclk_enable_peripheral_clock(ids.clk_id);
113  pmc_enable_periph_clk(ID_XDMAC);
114  configure_xdmac(ids.tx_perid, ids.rx_perid);
115  configure(baud_hz, fc_on, fc_on);
116 
117  // Start polling at the specified rate, 0 = use poll::Always instead.
118  if (poll_ms == 0) {
119  poll_register();
120  } else {
121  timer_every(poll_ms);
122  }
123 }
124 
125 // Configures the USART peripheral baud rate and flow control.
126 void UsartSamv71::configure(unsigned baud_hz, bool rts_en, bool cts_en) {
127 
128  // Sanity check: driver is correctly configured.
129  if (!m_usart) { return; }
130 
131  // Always set to 8 bit length, no parity, 1 stop bit.
132  sam_usart_opt_t opt =
133  {
134  .baudrate = baud_hz,
135  .char_length = US_MR_CHRL_8_BIT,
136  .parity_type = US_MR_PAR_NO,
137  .stop_bits = US_MR_NBSTOP_1_BIT,
138  .channel_mode = US_MR_USART_MODE_NORMAL
139  };
140 
141  // Separate init functions with RTS/CTS ("handshaking") and without.
142  m_rts_en = get_conf(m_usart).supports_fc && rts_en;
143  m_cts_en = get_conf(m_usart).supports_fc && cts_en;
144  if (m_cts_en) {
145  usart_init_hw_handshaking(m_usart, &opt, sysclk_get_peripheral_hz());
146  } else {
147  usart_init_rs232(m_usart, &opt, sysclk_get_peripheral_hz());
148  }
149  if (m_rts_en && !((XDMAC->XDMAC_GS >> m_rx_dma_ch) & 0x1)) {
150  rts_high(); // Block sender until DMA on.
151  } else {
152  rts_low();
153  }
154  usart_enable_tx(m_usart);
155  usart_enable_rx(m_usart);
156 }
157 
158 // Check for end-of-block (BIS) IRQs from RX/TX DMAs and service, clears IRQs.
160  if (XDMAC->XDMAC_CHID[m_rx_dma_ch].XDMAC_CIS & XDMAC_CIS_BIS) {
161  rts_high(); // RX full: block sender and service immediately.
162  poll_rx_dma();
163  }
164  if (XDMAC->XDMAC_CHID[m_tx_dma_ch].XDMAC_CIS & XDMAC_CIS_BIS) {
165  poll_tx_dma(); // TX empty: check for unsent bytes.
166  }
167 }
168 
169 // If the TX DMA engine has free buffer space, copy any bytes from the
170 // transmit-side PacketBuffer to its address space.
172 
173  // Sanity check: driver is correctly configured.
174  if (!m_usart) { return; }
175 
176  // Return immediately if the DMA channel is busy.
177  if ((XDMAC->XDMAC_GS >> m_tx_dma_ch) & 0x1) { return; }
178 
179  // If we just finished a transaction, consume the PacketBuffer bytes.
180  if (m_txdma_nbytes > 0) { m_tx.read_consume(m_txdma_nbytes); }
181 
182  // Check if we have any data waiting to send.
183  m_txdma_nbytes = m_tx.get_peek_ready();
184  if (!m_txdma_nbytes) { return; }
185 
186 #ifdef SATCAT5_SAMV71_UART_WRST
187  // Reset Watchdog
188  wdt_restart(WDT);
189 #endif
190 
191 #if SATCAT5_SAMV71_UART_DCACHE
192  // Flush the data cache for any 32-byte lines the DMA will read from.
193  u32* cache_addr = (u32*) ((u32) m_tx.peek(m_txdma_nbytes) & CACHE_ADDRMASK);
194  SCB_CleanDCache_by_Addr(cache_addr, m_txdma_nbytes + CACHE_LINESIZE-1);
195 #endif
196 
197  // Configure DMA address and length and start the transfer.
198  XDMAC->XDMAC_CHID[m_tx_dma_ch].XDMAC_CSA = (u32) m_tx.peek(m_txdma_nbytes);
199  XDMAC->XDMAC_CHID[m_tx_dma_ch].XDMAC_CUBC = (u32) m_txdma_nbytes;
200  XDMAC->XDMAC_GE = (1 << m_tx_dma_ch);
201 }
202 
203 // If the RX DMA engine has bytes available, copy them into the receive-side
204 // PacketBuffer. Maintain a pair of ping-pong buffers in the DMA to ensure bytes
205 // are not lost while copying.
207 
208  // Sanity check: driver is correctly configured.
209  if (!m_usart) { return; }
210 
211  // Skip if the DMA engine is enabled but has received no bytes.
212  // NOTE: If moving to multiple microblocks, ensure to follow the procedure
213  // outlined in Section 35.8 of the datasheet.
214  if (((XDMAC->XDMAC_GS >> m_rx_dma_ch) & 0x1) &&
215  XDMAC->XDMAC_CHID[m_rx_dma_ch].XDMAC_CUBC == m_rxdma_nbytes) { return; }
216 
217  // Data available - disable DMA, swap buffers, re-enable DMA.
218  // TODO: Unclear if this disables peripheral linkage and has the potential
219  // to drop data. Consider moving to flush+suspend and/or hardware
220  // linked-list support. See xdmac_example.c for more.
221  AtomicLock lock("UsartSamv71::poll_rx_dma()");
222  const u8* read_buff = get_rxdma_buff(); // Save used buffer
223  m_rxdma_buffidx = 1 - m_rxdma_buffidx; // Swap read/write buffers
224  rts_high(); // Drive RTS high while servicing DMA
225  XDMAC->XDMAC_GD = (1 << m_rx_dma_ch); // Disable channel
226  while ((XDMAC->XDMAC_GS >> m_rx_dma_ch) & 0x1) {} // Wait for flush, ~1us
227  unsigned nbytes_wr = m_rxdma_nbytes -
228  XDMAC->XDMAC_CHID[m_rx_dma_ch].XDMAC_CUBC;
229  XDMAC->XDMAC_CHID[m_rx_dma_ch].XDMAC_CDA = (u32) get_rxdma_buff();
230  XDMAC->XDMAC_CHID[m_rx_dma_ch].XDMAC_CUBC = m_rxdma_nbytes;
231  XDMAC->XDMAC_GE = (1 << m_rx_dma_ch); // Re-enable channel
232  rts_low(); // Enabled, drive RTS low
233  lock.release();
234 
235 #ifdef SATCAT5_SAMV71_UART_WRST
236  // Reset Watchdog
237  wdt_restart(WDT);
238 #endif
239 
240  // Invalidate cache for any relevant lines then copy to the PacketBuffer.
241  if (!nbytes_wr) { return; }
242 #if SATCAT5_SAMV71_UART_DCACHE
243  u32* cache_addr = (u32*) ((u32) read_buff & CACHE_ADDRMASK);
244  SCB_InvalidateDCache_by_Addr(cache_addr, nbytes_wr + CACHE_LINESIZE-1);
245 #endif
246  m_rx.write_bytes(nbytes_wr, read_buff);
248 }
249 
250 // Configure TX/RX DMA controllers.
251 void UsartSamv71::configure_xdmac(u32 tx_perid, u32 rx_perid) {
252 
253  // TX DMA-to-USART transfer is configured as a single block+microblock.
254  // Source: Memory, address and length are set via poll_tx_dma().
255  // Destination: Peripheral, ID is passed in as an argument.
256  xdmac_channel_config_t tx_dma_conf =
257  {
258  .mbr_ubc = 0,
259  .mbr_sa = 0,
260  .mbr_da = (u32)&(m_usart->US_THR),
261  .mbr_cfg = XDMAC_CC_TYPE_PER_TRAN |
262  XDMAC_CC_MBSIZE_SINGLE |
263  XDMAC_CC_DSYNC_MEM2PER |
264  XDMAC_CC_CSIZE_CHK_1 |
265  XDMAC_CC_DWIDTH_BYTE |
266  XDMAC_CC_SIF_AHB_IF0 |
267  XDMAC_CC_DIF_AHB_IF1 |
268  XDMAC_CC_SAM_INCREMENTED_AM |
269  XDMAC_CC_DAM_FIXED_AM |
270  XDMAC_CC_PERID(tx_perid),
271  .mbr_bc = 0,
272  .mbr_ds = 0,
273  .mbr_sus = 0,
274  .mbr_dus = 0
275  };
276 
277  // RX USART-to-DMA transfer is configured as a single block+microblock.
278  // Source: Peripheral, ID is passed in as an argument.
279  // Destination: Memory, double-buffered with fixed length.
280  xdmac_channel_config_t rx_dma_conf =
281  {
282  .mbr_ubc = m_rxdma_nbytes,
283  .mbr_sa = (u32) &(m_usart->US_RHR),
284  .mbr_da = (u32) get_rxdma_buff(),
285  .mbr_cfg = XDMAC_CC_TYPE_PER_TRAN |
286  XDMAC_CC_MBSIZE_SINGLE |
287  XDMAC_CC_DSYNC_PER2MEM |
288  XDMAC_CC_CSIZE_CHK_1 |
289  XDMAC_CC_DWIDTH_BYTE |
290  XDMAC_CC_SIF_AHB_IF1 |
291  XDMAC_CC_DIF_AHB_IF0 |
292  XDMAC_CC_SAM_FIXED_AM |
293  XDMAC_CC_DAM_INCREMENTED_AM |
294  XDMAC_CC_PERID(rx_perid),
295  .mbr_bc = 0,
296  .mbr_ds = 0,
297  .mbr_sus = 0,
298  .mbr_dus = 0
299  };
300 
301  // Disable channels if necessary then (re-)configure with interrupts.
302  xdmac_channel_disable(XDMAC, m_tx_dma_ch);
303  xdmac_channel_disable(XDMAC, m_rx_dma_ch);
304  xdmac_configure_transfer(XDMAC, m_tx_dma_ch, &tx_dma_conf);
305  xdmac_configure_transfer(XDMAC, m_rx_dma_ch, &rx_dma_conf);
306  xdmac_enable_interrupt(XDMAC, m_rx_dma_ch);
307  xdmac_channel_enable_interrupt(XDMAC, m_rx_dma_ch, XDMAC_CIE_BIE);
308  xdmac_enable_interrupt(XDMAC, m_tx_dma_ch);
309  xdmac_channel_enable_interrupt(XDMAC, m_tx_dma_ch, XDMAC_CIE_BIE);
310  // DMA engine enable is performed on first poll_rx_dma() call.
311 }
312 
313 // In handshaking mode (RTS/CTS), the RTS pin is driven High when RTSEN is set.
314 // In other modes, the RTS pin is driven High when RTSDIS is set.
315 // This is ignored (unused US_CR bit) in 2-wire UART mode.
317  if (!m_rts_en) { return; } // Ignore (leave low) if RTS disabled.
318  if (m_cts_en) {
319  m_usart->US_CR = US_CR_RTSEN;
320  } else {
321  m_usart->US_CR = US_CR_RTSDIS;
322  }
323 }
324 
325 // In handshaking mode (RTS/CTS), the RTS pin is driven Low when RTSDIS is set.
326 // In other modes, the RTS pin is driven Low when RTSEN is set.
327 // This is ignored (unused US_CR bit) in 2-wire UART mode.
329  if (m_cts_en) {
330  m_usart->US_CR = US_CR_RTSDIS;
331  } else {
332  m_usart->US_CR = US_CR_RTSEN;
333  }
334 }
Extensible transmit and receive buffer.
Definition: io_buffer.h:42
satcat5::io::PacketBuffer m_tx
Transmit data (user writes, child reads)
Definition: io_buffer.h:57
satcat5::io::PacketBuffer m_rx
Receive data (user reads, child writes)
Definition: io_buffer.h:60
const u8 * peek(unsigned nbytes) const
Peek nbytes into the circular buffer.
Definition: pkt_buffer.cc:248
unsigned get_peek_ready() const
Find the longest available contiguous segment that can be requested by peek().
Definition: pkt_buffer.cc:242
bool write_finalize() override
Mark end of frame and release temporary working data.
Definition: pkt_buffer.cc:105
void write_bytes(unsigned nbytes, const void *src) override
Write 0 or more bytes from a buffer.
Definition: pkt_buffer.cc:73
bool read_consume(unsigned nbytes) override
Read and discard 0 or more bytes.
Definition: pkt_buffer.cc:256
Automatic lock or mutex.
void release()
Optionally release this lock before the destructor is called.
An "Always" object is polled whenever service() is called.
Definition: polling.h:96
void poll_register()
Register this pollable object, called by the constructor.
Definition: polling.cc:183
void timer_every(unsigned msec)
Configure a repeating notification every X milliseconds.
Definition: polling.cc:321
Interrupt handler interface for the Microchip SAM V71.
io::BufferedIO interface for the SAMV71 USART and UART peripherals.
Definition: sam_usart.h:95
void rts_low()
Drive the RTS signal high/low if flow-control is on.
Definition: sam_usart.cc:328
void rts_high()
Drive the RTS signal high/low if flow-control is on.
Definition: sam_usart.cc:316
void configure(unsigned baud_hz, bool rts_en, bool cts_en)
Set baud rate and RTS/CTS enable.
Definition: sam_usart.cc:126
void configure_xdmac(u32 tx_dma_ch, u32 rx_dma_ch)
Initial setup of the TX/RX DMA controllers.
Definition: sam_usart.cc:251
void poll_tx_dma()
Poll the RX DMA engine for new data received.
Definition: sam_usart.cc:171
void poll_rx_dma()
Poll the TX DMA engine for unsent data to transmit.
Definition: sam_usart.cc:206
u8 * get_rxdma_buff()
Get the RX buffer the DMA is currently writing to.
Definition: sam_usart.h:146
void irq_event() override
IRQ indicates the RX DMA is full or the TX DMA is empty.
Definition: sam_usart.cc:159
SAMV71 UART/USART serial interface driver.