1
\$\begingroup\$

SPIMSS DMA examples are conspicuously missing from the MAX32660 SDK version that I have. Is there any documentation that explains whatever operating conditions are relevant to an actual SPIMSS DMA transfer? (I read and re-read the entire SPIMSS and DMA chapters and didn't find anything suggesting that the setup parameters were as rigid as I've found. The manual as written gives the impression that you can just set this up however you want and get going).

I got master mode to sometimes work by hacking stuff until I found just the right magic values (tx burst = 8 bytes, rx burst = 2 bytes, tx thresh = 6-7, rx thresh = 1). The fact that any other values seem to fail does not give me much confidence. Also even the mostly-working values are not working reliably (the rx dma just likes to hang sometimes. Other times I get transmit overrun flagged, which makes no sense - why would the DMA write to a full FIFO? The whole point of DMA is the CPU shouldn't have to babysit it).

Mostly the failures seemed to happen on the Rx side. But the parameter rigidity seems to be on both sides, to get a successful loopback transmission.

This is for SPIMSS operating at 12Mbps (which for some reason is specified as its top speed). Which ought to be a piece of cake (in a memory-to-memory DMA, I measured 1340Mbps). No interrupts; just the DMA engine using its direct channel to move data between SPIMSS and memory. Is there anything tricky about this?

In case it helps, here's my loopback example that sometimes works:

#include "dma_regs.h"
#define DMA_SRC(i) *(void const* volatile*)(&MXC_DMA->ch[i].src)
#define DMA_DST(i) *(void      * volatile*)(&MXC_DMA->ch[i].dst)
#define DMA_CNT(i) MXC_DMA->ch[i].cnt
#define DMA_CFG(i) MXC_DMA->ch[i].cfg
#define DMA_WAIT(i) do {} while (MXC_DMA->ch[0].cfg & MXC_F_DMA_CFG_CHEN)

#define N 1024
  uint8_t rx_buf[N] = {};
  uint8_t tx_buf[N];
  int len = N;
  for (int i=0; i<N; i++)
    tx_buf[i] = i + (i >> 8);


  SPIMSS->dma = MXC_F_SPIMSS_DMA_RX_DMA_EN | MXC_F_SPIMSS_DMA_TX_DMA_EN
    | (1 << MXC_F_SPIMSS_DMA_RX_FIFO_LEVEL_POS) // only works with 1
    | (7 << MXC_F_SPIMSS_DMA_TX_FIFO_LEVEL_POS); // only works with 6/7
  // A2 erratum #2 says we need TX level > 0

  SPIMSS_BEGIN(8);

#define RX_DMA
#ifdef RX_DMA
  DEBUG("spimms status = 0x%08x, rxc=%d, txc=%d",
    SPIMSS->status, SPIMSS_RX_COUNT, SPIMSS_TX_COUNT);
  DEBUG("RX DMA BEGIN");
  DMA_DST(1) = rx_buf;
  DMA_CNT(1) = len;
  DMA_CFG(1) = MXC_F_DMA_CFG_CHEN
    | MXC_S_DMA_CFG_SRCWD_BYTE
    | MXC_S_DMA_CFG_DSTWD_BYTE
    | MXC_S_DMA_CFG_REQSEL_SPI1RX
    | MXC_F_DMA_CFG_DSTINC
    | MXC_S_DMA_CFG_PRI_HIGH
    | ((2-1) << MXC_F_DMA_CFG_BRST_POS); // only works with burst size 2
#endif

  DEBUG("TX DMA BEGIN");
  DMA_SRC(0) = tx_buf;
  DMA_CNT(0) = len;
  DMA_CFG(0) = MXC_F_DMA_CFG_CHEN
    | MXC_S_DMA_CFG_SRCWD_BYTE
    | MXC_S_DMA_CFG_DSTWD_BYTE
    | MXC_S_DMA_CFG_REQSEL_SPI1TX
    | MXC_F_DMA_CFG_SRCINC
    | MXC_S_DMA_CFG_PRI_MEDHIGH
    | ((8-1) << MXC_F_DMA_CFG_BRST_POS); // only works with burst size 8

#ifndef RX_DMA
  for (int i=0; i<N; i++)
    rx_buf[i] = SPIMSS_RX8();
#endif

  DEBUG("spimms status = 0x%08x, rxc=%d, txc=%d",
    SPIMSS->status, SPIMSS_RX_COUNT, SPIMSS_TX_COUNT);
  // status=0

  while (DMA_CNT(0))
    DEBUG("tx_dma_count = %d, rx_dma_count=%d", DMA_CNT(0), DMA_CNT(1));

  DEBUG("spimms status = 0x%08x, rxc=%d, txc=%d",
    SPIMSS->status, SPIMSS_RX_COUNT, SPIMSS_TX_COUNT);
  // status=0x88 (rx overrun, interrupt)

  Tick_Delay_US(100);
  DEBUG("spimms status = 0x%08x", SPIMSS->status);
  DEBUG("tx dma status = 0x%08x", MXC_DMA->ch[0].st); // 4=done
  DEBUG("rx dma status = 0x%08x", MXC_DMA->ch[1].st);

  DEBUG_HEX("rx_buf", rx_buf, N);
  DMA_WAIT(0);
  DEBUG("DMA END");
  for (int i=0; i<N; i++)
    if (rx_buf[i] != tx_buf[i])
      ERROR("difference at buffer position 0x%04x", i);

Where is the documentation that I need to get SPIMSS DMA to actually work reliably on MAX32660?

Update

Still didn't find documentation (as of 7/21 version of user guide), BUT it looks like tx_burst=2, tx_level=1 fixes the TX overruns. I have no idea why the level has to be set so low. (Fortunately this is such a slow peripheral that it doesn't underrun.) Rx still requires copious CPU babysitting (slightly better than not having DMA at all).

\$\endgroup\$
2
  • \$\begingroup\$ I do not know about documentation. User Manuals for Maxim MCU are always very minimalistic. Source codes of official driver for SPIMSS on MAX32660 (which Maxim internally refer under codename ME11) are available here: github.com/Analog-Devices-MSDK/msdk/blob/main/Libraries/… and github.com/Analog-Devices-MSDK/msdk/blob/main/Libraries/…. I used it without DMA. Because it has internal FIFO I did not need DMA, but I still needed to patch/hack provided driver a lot. \$\endgroup\$
    – Misaz
    Commented Sep 18, 2023 at 18:18
  • \$\begingroup\$ @Misaz Yeah, that looks like a newer version of the SDK that I have. Still no DMA though. (Also BTW the code that overwrites the FIFO count looks really suspicious. How is that not a race condition?) Anyways, now at least I got something working (see my answer). \$\endgroup\$ Commented Sep 19, 2023 at 4:34

1 Answer 1

1
\$\begingroup\$

In case we don't find existing documentation for SPIMSS DMA... After much experimentation, I got hands-free Rx/Tx working @ 11.3Mbps with the following code:

#include "spimss_regs.h"
#define SPIMSS MXC_SPIMSS

#define SPIMSS_TX_COUNT             \
  ((SPIMSS->dma & MXC_F_SPIMSS_DMA_TX_FIFO_CNT) \
   >> MXC_F_SPIMSS_DMA_TX_FIFO_CNT_POS)

#define SPIMSS_RX_COUNT \
  ((SPIMSS->dma & MXC_F_SPIMSS_DMA_RX_FIFO_CNT) \
   >> MXC_F_SPIMSS_DMA_RX_FIFO_CNT_POS)

#define SPIMSS_BEGIN(w)                         \
  SPIMSS->mod = MXC_F_SPIMSS_MOD_SSIO                   \
    | (((w) & 15) << MXC_F_SPIMSS_MOD_NUMBITS_POS) | MXC_F_SPIMSS_MOD_TX_LJ

#define SPIMSS_END() \
  SPIMSS->mod = MXC_F_SPIMSS_MOD_SSIO | MXC_F_SPIMSS_MOD_SSV

#define SPIMSS_WAIT_RX_AVAIL_POW2(x) \
  do {} while (!(SPIMSS->dma &       \
             (MXC_F_SPIMSS_DMA_RX_FIFO_CNT \
                  & ((-(x)) << MXC_F_SPIMSS_DMA_RX_FIFO_CNT_POS))))

#define SPIMSS_WAIT_TX_AVAIL(x)                     \
  do {} while                               \
      ((SPIMSS->dma & MXC_F_SPIMSS_DMA_TX_FIFO_CNT)         \
    > ((MXC_SPIMSS_FIFO_DEPTH - x) << MXC_F_SPIMSS_DMA_TX_FIFO_CNT_POS))

#define SPIMSS_FLUSH()                  \
  while (SPIMSS_TX_COUNT); Tick_Delay(3);       \
  SPIMSS->dma    |= MXC_F_SPIMSS_DMA_RX_FIFO_CLEAR; \
  SPIMSS->status |= 0
// Must flush SPIMSS in the following cases:
//   - write-only non-DMA to DMA
//   - write-only to read
//
// Flushing can be avoided by explicitly reading the data register
// immediately after it is written.


#include "dma_regs.h"

#define DMA_SRC(i) *(void const* volatile*)(&MXC_DMA->ch[i].src)
#define DMA_DST(i) *(void      * volatile*)(&MXC_DMA->ch[i].dst)
#define DMA_CNT(i) MXC_DMA->ch[i].cnt
#define DMA_CFG(i) MXC_DMA->ch[i].cfg
#define DMA_WAIT(i) do {} while (MXC_DMA->ch[0].cfg & MXC_F_DMA_CFG_CHEN)

#define DMA_SPI1TX_DUMMY \
  (MXC_F_DMA_CFG_CHEN | MXC_S_DMA_CFG_SRCWD_WORD \
   | MXC_S_DMA_CFG_REQSEL_SPI1TX | (1 << MXC_F_DMA_CFG_BRST_POS))

#define DMA_SPI1TX (DMA_SPI1TX_DUMMY | MXC_F_DMA_CFG_SRCINC)

#define DMA_SPI1RX \
  (MXC_F_DMA_CFG_CHEN    \
   | MXC_S_DMA_CFG_DSTWD_WORD | MXC_S_DMA_CFG_REQSEL_SPI1RX     \
   | MXC_F_DMA_CFG_DSTINC     | (1 << MXC_F_DMA_CFG_BRST_POS))

uint32_t dummy = 0;

static inline void DMA_Init()
{
  MXC_GCR->perckcn0 &= ~MXC_F_GCR_PERCKCN0_DMAD;
  
  SPIMSS->dma = MXC_F_SPIMSS_DMA_RX_DMA_EN | MXC_F_SPIMSS_DMA_TX_DMA_EN
    | (1 << MXC_F_SPIMSS_DMA_RX_FIFO_LEVEL_POS)  // only works with 1
    | (1 << MXC_F_SPIMSS_DMA_TX_FIFO_LEVEL_POS); // only works with 1
  DMA_SRC(1) = &dummy;
}


void Read(void* buf, int len)
{
  SPIMSS_BEGIN(8);
  DMA_DST(0) = buf;
  DMA_CNT(0) = DMA_CNT(1) = len & -2;
  DMA_CFG(0) = DMA_SPI1RX;
  DMA_CFG(1) = DMA_SPI1TX_DUMMY;
  
  DMA_WAIT(0);
  if (len & 1)
    {
      SPIMSS_TX8_NO_WAIT(0);
      ((uint8_t*)buf)[len-1] = SPIMSS_RX8();
    }
  SPIMSS_END();
  DEBUG_HEX("Read", buf, len);
}

void Write(void const* buf, int len)
{
  DEBUG_HEX("Write", buf, len);
  uint8_t const* p = buf;

  SPIMSS_BEGIN(8);
  DMA_SRC(0) = p;
  DMA_CNT(0) = len;
  DMA_CFG(0) = DMA_SPI1TX;
  DMA_WAIT(0);
  Tick_Delay(3);
  SPIMSS_FLUSH();
  SPIMSS_END();
}

Some of the tricks here:

  • SPIMSS transfers are BYTE
  • rx/tx thresholds are ONE
  • burst size is 2 (maybe 1 works sometimes)
  • total read size is a multiple of the burst size
  • don't pause a transfer that's in progress
  • don't chain buffers (the second buffer gets loaded... but doesn't start.)
  • avoid zero-length transfers (a couple out of every million will fail)
  • some delay is needed between starting a transfer and inspecting the DMA state (update: possibly only if doing zero-length transfers, which you shouldn't do as per point above if you care about reliability. Just pay for the branch instruction already.)
\$\endgroup\$
2
  • \$\begingroup\$ I've gotten SPIMSS DMA to work reliably on my MAX32660 using word transfers for everything, 8-byte burst size, RX/TX thresholds of value "7" for each FIFO (i.e. 8 entries in RX FIFO, 8 entries free in TX FIFO). I just round down the RX DMA request length to a multiple of 8 bytes and read the last 0..7 bytes from the FIFO manually, seemed to work very well for me, no delays or flushing needed. That said I am not doing any pausing or DMA chaining, but my flash driver is working perfectly with these settings! I can experiment with some other values too if that'd be interesting? \$\endgroup\$
    – Thomas
    Commented Sep 25, 2023 at 22:54
  • \$\begingroup\$ edit: apologies, the DMA transfer size is indeed byte for both (due to how the FIFO register works) - the rest is accurate though \$\endgroup\$
    – Thomas
    Commented Sep 25, 2023 at 23:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.