/*******************************************************************************
* Module name: IRIS DMA Task - MibSPI1 multi-buffer mode RX DMA test
* Target: TMS570LC4357 (Cortex-R5F), FreeRTOS
*
* PROBLEM SUMMARY:
*   Trying to get RXDMAENA (DMACTRL[0] bit 15) to generate an automatic
*   hardware-triggered DMA request when MibSPI1 receives data, so the
*   TMS570 DMA controller copies received data from MibSPI1 RXRAM into
*   application RAM (s_rx_buf) without CPU intervention.
*
*   CONFIRMED WORKING:
*     - TX->RX digital loopback at the MibSPI sequencer level is correct.
*     - TG0 (32 x uint16 words = 64 bytes) completes reliably every time;
*       TG-complete interrupt (TGITENST/TGITLVST, VIM channel 26 - the
*       MibSPI1 HIGH level interrupt, not channel 12 which is LOW level)
*       fires correctly every iteration when the last buffer (index 31)
*       uses BUFMODE=5 (suspend single-transfer, wait for TXFULL only).
*     - The DMA control packet itself (source/dest addresses, port
*       assignment, element count/offsets) is correct and verified: a
*       manual software trigger (dmaSetChEnable(DMA_CH_RX, DMA_SW)) issued
*       right after a real mibspiTransfer() completes successfully moves
*       the correct received data from RXRAM into s_rx_buf every time.
*     - RXDMAENA (bit 15) is confirmed set to 1 immediately before
*       mibspiTransfer() is called each iteration.
*
*   NOT WORKING:
*     - The automatic hardware request pulse on the physical DMA request
*       line never arrives at the TMS570 DMA controller. The channel
*       stays armed (HWChnEnaSet stays set, never auto-clears) and no
*       FTC/LFS/HBC/BTC interrupt ever fires from a genuine hardware
*       trigger - only from the manual SW trigger above.
*     - Per the TRM, RXDMAENA's first request pulse requires the
*       referenced buffer (BUFIDx) to be configured as "skip" or "suspend"
*       until RXEMPTY is set, to synchronize the DMA controller with the
*       sequencer. Setting BUFMODE=6 (suspend until RXEMPTY) or BUFMODE=7
*       (suspend until TXFULL+RXEMPTY) on the BUFID buffer (31) causes the
*       sequencer to stall indefinitely at that buffer - RXEMPTY never
*       becomes true because nothing ever reads RXDATA to clear it, since
*       that read is exactly what the (non-firing) RXDMA request pulse
*       was supposed to do. This looks like a circular dependency: the
*       very first RXDMA pulse needs RXEMPTY satisfied, but RXEMPTY can
*       only become satisfied by something performing the read that the
*       first pulse itself is meant to trigger.
*
* Current configuration:
*   - DMACTRL[0]: ONESHOT=1, BUFID=31, RXDMA_MAP=1, TXDMA_MAP=0,
*     RXDMAENA set fresh each iteration (not left on from init),
*     TXDMAENA=0 (TX loaded via CPU, see below), ICOUNT=31.
*   - Buffers 0-30: BUFMODE=5, CSHOLD=1, CSNR=CS0.
*   - Buffer 31 (BUFID target): BUFMODE=5, CSHOLD=0 (releases CS).
*   - AUTOINIT_OFF on the RX DMA control packet (TX DMA is not used at
*     all - mibspiSetData() loads TXRAM directly via CPU copy, since an
*     earlier attempt to also DMA the TX side caused TXRAM corruption).
*   - RX source address required a +2 byte offset from the struct's
*     declared field start to correctly read RXDATA rather than the
*     status/flags half-word, for unknown reasons (a little-endian
*     {uint16 data; uint16 flags} struct per TI's HL_reg_mibspi.h did not
*     match observed hardware behavior at 16-bit-wide reads of RXRAM).
*
* Transfer flow per iteration:
*   1. Fill s_tx_buf[0..31] with test pattern
*   2. Load s_tx_buf into TG0 TXRAM via mibspiSetData() (CPU copy)
*   3. Arm RX DMA (CH4): MibSPI1 RXRAM -> s_rx_buf, 32 elements
*   4. Set RXDMAENA bit fresh, then mibspiTransfer(TG0) - sequencer
*      clocks all 32 words out/in
*   5. TG0 complete -> custom ISR -> semaphore posted
*   6. Task unblocks; s_rx_buf should mirror s_tx_buf via the automatic
*      RX DMA, but does not - it stays at its previous/zero value unless
*      the DMA is triggered manually via software as described above.
*
* Port assignments (TMS570LC4357):
*   Port A = SRAM (memory bus)
*   Port B = peripheral bus (MibSPI RAM)
*   RX (MibSPI RXRAM -> SRAM): PORTB_READ_PORTA_WRITE
*
*******************************************************************************/

/* Scheduler includes */
#include "FreeRTOS.h"
#include "os_task.h"
#include "os_semphr.h"

/* HAL */
#include "mibspi.h"
#include "HL_sys_dma.h"
#include "vimcontrol.h"     /* VIM channel 26 = MibSPI1 HIGH level interrupt */
#include "HL_spi.h"         /* spiREG5, SPI_PIN_CLK                         */

/* Project */
#include "dma.h"            /* vConfigureDmaInterrupt - diagnostic RX BTC   */

/* Project */
#include "TaskLib.h"
#include "threadconfig.h"

/* This module */
#include "iris_dma_task.h"

/*******************************************************************************
* Memory section macros (matches project convention, e.g. canx_mem.h)
* IN_MEM_INT_UNCACHED_RAM_ALIGNED places a buffer in .uncachediram, which is
* already defined in the linker file (UNCACHEDIRAM region, 32KB at end of
* IRAM). DMA-visible buffers must live here, not in external SRAM/.bss,
* otherwise the CPU's cached view and the DMA's actual writes diverge.       */
#define CANX_HW_TMS570
#if defined( CANX_HW_TMS570 )
 #define IN_MEM_ALIGNED                    __attribute__(( aligned(4) ))
 #define IN_MEM_INT_UNCACHED_RAM           __attribute__(( section(".uncachediram") ))
 #define IN_MEM_INT_UNCACHED_RAM_ALIGNED   IN_MEM_INT_UNCACHED_RAM IN_MEM_ALIGNED
#else
 #define IN_MEM_INT_UNCACHED_RAM_ALIGNED
#endif

/*******************************************************************************
* Configuration
*******************************************************************************/
#define TEST_WORDS          (32U)   /* 32 x uint16 = 64 bytes                */
#define SPI_TG              (0U)    /* Transfer Group 0                      */

/* DMA channels: CH0-CH3 are reserved by the UART driver (serial.c):
 *   UART1 RX = CH0, UART1 dummy = CH2
 *   UART2 RX = CH1, UART2 dummy = CH3
 * Using CH4/CH5 to avoid conflicts.                                        */
#define DMA_CH_RX           DMA_CH4 /* MibSPI1 RX -> SRAM                   */
#define DMA_CH_TX           DMA_CH5 /* SRAM -> MibSPI1 TX                   */
#define DMA_REQ_RX          DMA_REQ1    /* MIBSPI1[0] = RX = DMAREQ[1]     */
#define DMA_REQ_TX          DMA_REQ0    /* MIBSPI1[1] = TX = DMAREQ[0]     */
#define MIBSPI_RAM_STRIDE   (8U)    /* bytes between MibSPI RAM entries      */
#define IRIS_DMA_QUEUE_LEN  (4U)

/*******************************************************************************
* Task infrastructure
*******************************************************************************/
static StackType_t    xIrisDmaStack[mainDEFAULT_STACK_SIZE];
static UINT8          ucIrisDmaQueueStorage[sizeof(xStackEvent_t) * IRIS_DMA_QUEUE_LEN];

static TaskConfiguration_t xIrisDmaTaskParams =
{
    .xStaticQueue        = {0},
    .pucQueueStorageArea = ucIrisDmaQueueStorage,
    .uxQueueLength       = IRIS_DMA_QUEUE_LEN,
    .xTaskBuffer         = {0},
    .pxStack             = xIrisDmaStack,
    .uxStackLength       = mainDEFAULT_STACK_SIZE,
    .xQueueParam         = {0}
};

/*******************************************************************************
* Transfer buffers - placed in uncached internal IRAM (.uncachediram), not
* the default .bss section. The default .bss lands in external SRAM
* (0x60c00000 region per linker file), which the TMS570 DMA writes to fine
* via Port A, but the CPU's view of that memory was found stale/zero after
* DMA writes during HW-triggered transfers - consistent with a cache or bus
* visibility issue on that path. Internal uncached IRAM avoids this.
* Must NOT be stack variables; the DMA writes to them after transfer starts.
*******************************************************************************/
static uint16 s_tx_buf[TEST_WORDS] IN_MEM_INT_UNCACHED_RAM_ALIGNED;
static uint16 s_rx_buf[TEST_WORDS] IN_MEM_INT_UNCACHED_RAM_ALIGNED;

/*******************************************************************************
* Completion semaphore
* Given by vIrisMibSpi1IsrHandler (ISR), taken by task.
*******************************************************************************/
static SemaphoreHandle_t s_done_sem;
static StaticSemaphore_t s_done_sem_buf;

/*******************************************************************************
* RX DMA FTC semaphore (DIAGNOSTIC)
* Given specifically by vIrisDmaRxAnyHandler when type == FTC fires on
* DMA_CH_RX. Lets the task block deterministically on "did a genuine FTC
* happen" rather than just sampling s_rx_ftc_count at an arbitrary point.
*******************************************************************************/
static SemaphoreHandle_t s_rx_ftc_sem;
static StaticSemaphore_t s_rx_ftc_sem_buf;

/*******************************************************************************
* Diagnostic: RX DMA interrupt hit counters - one per interrupt type.
* Registers all four available types (FTC/LFS/HBC/BTC) on DMA_CH_RX to see
* which, if any, ever fires. Add all four *_count variables to the
* expressions window. Purely diagnostic - does not affect the semaphore/
* ISR flow or any data movement.
*******************************************************************************/
static volatile uint32 s_rx_ftc_count = 0U;
static volatile uint32 s_rx_lfs_count = 0U;
static volatile uint32 s_rx_hbc_count = 0U;
static volatile uint32 s_rx_btc_count = 0U;

/*******************************************************************************
* Local function prototypes
*******************************************************************************/
static portTASK_FUNCTION_PROTO(vIrisDmaTask, pvParameters);
static void vIrisMibSpi1IsrHandler(void);
static portBASE_TYPE vIrisDmaRxAnyHandler(dmaInterrupt_t type, dmaChannel_t channel);  /* DIAGNOSTIC */
static void prvArmDma(void);

/*******************************************************************************
* vStartIrisDmaTask
*******************************************************************************/
void vStartIrisDmaTask(UBaseType_t uxPriority)
{
    const TickType_t xBlockTimeReceive = pdMS_TO_TICKS((TickType_t)1000);
    const TickType_t xBlockTimeSend    = pdMS_TO_TICKS((TickType_t)10);

    s_done_sem    = xSemaphoreCreateBinaryStatic(&s_done_sem_buf);
    s_rx_ftc_sem  = xSemaphoreCreateBinaryStatic(&s_rx_ftc_sem_buf);  /* DIAGNOSTIC */

    if (uConfigureStaticTask(&xIrisDmaTaskParams,
                              IRIS_DMA_TASK,
                              xBlockTimeReceive,
                              xBlockTimeSend) == pdTRUE)
    {
        uStartStaticTask(&xIrisDmaTaskParams,
                          vIrisDmaTask,
                          uxPriority,
                          "IRIS_DMA");
    }
}

/*******************************************************************************
* vIrisDmaTask
*******************************************************************************/
static portTASK_FUNCTION(vIrisDmaTask, pvParameters)
{
    uint32 buf;
    uint32 i = 0;
    uint32 last = 0;
    (void)pvParameters;

    /* Step 1: Initialise MibSPI1 - sets SPIEN, data format, baud rate,
     * pin mux. NOTE: mibspiInit() only configures 8 words for TG0 and
     * sets BUFMODE=4 (continuous). Both are overridden below.             */
    taskENTER_CRITICAL();
    mibspiInit();
    taskEXIT_CRITICAL();

    /* Step 2: Enable MibSPI1 bus switch (SN74CBTLV3126 OE) via spiREG5.
     * Without this the level shifter holds all lines low and nothing
     * reaches the connector. Matches xSpiEnable(TXSEL_SPI1) in spitask.c */
    spiREG5->GCR0 = 1U;
    spiREG5->PC1 |= (1U << (uint32)SPI_PIN_CLK);
    spiREG5->PC3 |= (1U << (uint32)SPI_PIN_CLK);

    /* Step 3: Enable internal loopback - remove when connecting real HW   */
    mibspiEnableLoopback(mibspiREG1, Digital_Lbk);

    /* Step 4: Fix TG0 word count to 32 words.
     * mibspiInit() sets TG1 PSTART=8 giving TG0 only 8 entries. We need
     * 32 entries for 64 bytes. TGCTRL[1] PSTART=32, LTGPEND last=31.
     * NOTE: The permanent fix is to change MIBSPI1_TGCTRL1_CONFIGVALUE
     * in mibspi.h from 8U to 32U and rebuild.                            */
    mibspiREG1->TGCTRL[1U] = (mibspiREG1->TGCTRL[1U] & 0xFFFF00FFU)
                            | (32U << 8U);
    mibspiREG1->LTGPEND    = (mibspiREG1->LTGPEND & 0xFFFF00FFU)
                            | ((TEST_WORDS - 1U) << 8U);

    /* Step 5: Set TXRAM control words for all 32 TG0 buffers.
     * mibspiInit() sets BUFMODE=4 (continuous mode) which retransmits
     * stale data without waiting for DMA to fill the buffer. We need
     * BUFMODE=5 (suspend single-transfer) so the sequencer suspends
     * until DMA writes new TXDATA before each clock cycle.
     * Control word (uint16) fields:
     *   15:13 BUFMODE = 5 (suspend single-transfer, wait for TXFULL)
     *   12    CSHOLD  = 1 keep CS asserted between words (0 on last)
     *   9:8   DFSEL   = 0 use DATA_FORMAT0
     *   7:0   CSNR    = CS_0 = 0xFE (CS0 active, active-low bitmask)    */
    for (buf = 0U; buf < (TEST_WORDS - 1U); buf++)
    {
        mibspiRAM1->tx[buf].control =
              (uint16)((uint16)5U << 13U)   /* BUFMODE=5 suspend          */
            | (uint16)((uint16)1U << 12U)   /* CSHOLD=1 keep CS low       */
            | (uint16)((uint16)0U << 8U)    /* DFSEL=0 use FMT0           */
            | (uint16)CS_0;                 /* CSNR=0xFE CS0 active       */
    }
    /* Last buffer (index 31, matches DMACTRL[0] BUFID=31): CSHOLD=0 to
     * release CS after the final word.
     * NOTE: Tried BUFMODE=7 (suspend until TXFULL+RXEMPTY) AND BUFMODE=6
     * (suspend until RXEMPTY only) per TRM's RXDMAENA description - BOTH
     * deadlock the sequencer, stopping TG-complete entirely. Reverted to
     * BUFMODE=5 (no RX-side wait condition) to keep TG-complete working.
     * RXDMAENA's request pulse not firing remains unsolved; the TRM's
     * prescribed suspend modes are not viable in this configuration for
     * reasons not yet identified.                                        */
    mibspiRAM1->tx[TEST_WORDS - 1U].control =
          (uint16)((uint16)5U << 13U)       /* BUFMODE=5 suspend (reverted)*/
        | (uint16)((uint16)0U << 12U)       /* CSHOLD=0 release CS        */
        | (uint16)((uint16)0U << 8U)        /* DFSEL=0 use FMT0           */
        | (uint16)CS_0;                     /* CSNR=0xFE CS0 active       */

    /* Step 6: Enable DMA controller                                        */
    dmaEnable();

    /* Step 7: Wire MibSPI1 high level interrupt to VIM and enable it.
     * MibSPI1 TG high level interrupt = VIM channel 26.
     * SPI1_HIGH_LEVEL_INT (ch 12) is level 0, TG interrupts use level 1.
     * mibspi1HighLevelInterrupt is not defined in this project so we
     * provide our own ISR and map it directly to VIM channel 26.            */
    vVimChannelMap((VIM_INDEX)26U, &vIrisMibSpi1IsrHandler);  /* MibSPI1 high level = VIM ch 26 */
    vEnableInterrupt((VIM_INDEX)26U);

    /* Step 8: Enable TG0 completion interrupt via mibspiEnableGroupNotification.
     * This sets TGITENST and TGITLVST with the correct bit shift (group<<16).
     * level=1 = high level IRQ, matches SPI1_HIGH_LEVEL_INT.             */
    mibspiEnableGroupNotification(mibspiREG1, SPI_TG, 1U);

    /* Step 9: Assign DMA request lines to channels.
     * TRM Table 6-41: DMAREQ[0]=MIBSPI1 TX, DMAREQ[1]=MIBSPI1 RX.
     * Done here inside the task so UART dmaReqAssign calls (which run
     * at task startup) cannot overwrite our assignments.                  */
    dmaReqAssign(DMA_CH_RX, DMA_REQ_RX);   /* CH4 <- REQ1 (MibSPI1 RX)  */
    /* TX DMA removed - mibspiSetData() handles TX via CPU copy           */

    /* Step 9b (DIAGNOSTIC): Register all four interrupt types on RX channel.
     * Purely for debugging - independent of the semaphore/TG-complete
     * flow. Increments the matching counter (s_rx_ftc/lfs/hbc/btc_count)
     * for whichever type genuinely fires, so we can see if CH4 ever
     * responds to a MibSPI-generated hardware request pulse at all, and
     * if so, via which completion signal.                                */
    vConfigureDmaInterrupt(DMA_CH_RX, FTC, DMA_INTA, vIrisDmaRxAnyHandler);
    vConfigureDmaInterrupt(DMA_CH_RX, LFS, DMA_INTA, vIrisDmaRxAnyHandler);
    vConfigureDmaInterrupt(DMA_CH_RX, HBC, DMA_INTA, vIrisDmaRxAnyHandler);
    vConfigureDmaInterrupt(DMA_CH_RX, BTC, DMA_INTA, vIrisDmaRxAnyHandler);

    /* Step 10: Arm DMA channels for the first transfer.
     * Sets source/dest addresses and element counts, enables HW trigger.
     * Must be done before DMACTRL enables DMA requests from MibSPI.      */
    prvArmDma();

    /* Step 11: Configure MibSPI internal DMA channel 0 - static fields only.
     * RXDMAENA is intentionally left at 0 here. Per TRM and the working
     * reference (element14 "SPI Master with DMA" blog, TMS570LC43): setting
     * the ENA bit IS the trigger event for the first DMA request pulse, so
     * it is set fresh inside the loop right before each transfer, not left
     * on permanently from init.
     *
     * ONESHOT=1  : auto-disable after ICOUNT+1 transfers
     * BUFID=31   : last buffer of TG0 - RXDMA fires on last word received
     * RXDMA_MAP=1: RX path -> physical DMA REQ1
     * TXDMA_MAP=0: TX path -> physical DMA REQ0 (unused, TX via CPU)
     * RXDMAENA=0 : set per-transfer below, not here
     * TXDMAENA=0 : TX DMA disabled - TX via CPU mibspiSetData()
     * ICOUNT=31  : 32 transfers (ICOUNT+1)
     * NOTE: left unchanged for the single-element DMA test below - the
     * MibSPI sequencer still clocks out all 32 words and the RXDMA
     * request still fires once after buffer 31 completes, same trigger
     * point as before. Only the TMS570 DMA side (prvArmDma RX packet)
     * is reduced to move one element when that single request fires.    */
    mibspiREG1->DMACTRL[0] = (1U << 31U)       /* ONESHOT                */
                            | (31U << 24U)      /* BUFID = 31 (last buf)  */
                            | (1U << 20U)       /* RXDMA_MAP = REQ1       */
                            | (0U << 16U)       /* TXDMA_MAP = REQ0       */
                            | (0U << 15U)       /* RXDMAENA - set per-xfer*/
                            | (0U << 14U)       /* TXDMAENA disabled      */
                            | (31U << 8U);      /* ICOUNT = 31 (32 words) */

    /* Step 12: DMAREQEN (INT0 bit 16) is NOT set.
     * Per TRM: in multi-buffer mode this bit is ignored - DMA requests
     * are generated by the MibSPI sequencer via DMAxCTRL.                */
    /* mibspiREG1->INT0 |= (1U << 16U); -- ignored in multi-buffer mode   */


    for (;;)
    {
        /* Fill TX buffer: 0xAA00, 0xAA01 ... 0xAA1F                        */
        for (i = 0; i < TEST_WORDS; i++)
        {
            s_tx_buf[i] = (uint16)(0xAA00U | (i+last));
        }
        last = (i+last);

        /* Clear RX buffer so stale data is not mistaken for new data       */
        for (i = 0U; i < TEST_WORDS; i++)
        {
            s_rx_buf[i] = 0x0000U;
        }

        /* Load TX words into TG0 TXRAM (CPU copy)                          */
        mibspiSetData(mibspiREG1, SPI_TG, s_tx_buf);

        /* Re-arm DMA channels for this transfer.
         * Sets the control packets (source/dest buffer addresses and sizes)
         * and re-enables hardware triggering on both channels.
         * Required because AUTOINIT_OFF is set - the DMA disables the
         * channel after each transfer completes. To remove this call,
         * switch to AUTOINIT_ON in prvArmDma.                             */
        taskENTER_CRITICAL();
        prvArmDma();

        /* Enable RXDMAENA fresh for this transfer. Per TRM and the blog
         * reference: setting this bit IS the trigger that arms the first
         * DMA request pulse - ONESHOT auto-clears it back to 0 after
         * ICOUNT+1 (32) transfers, so it must be re-set every iteration,
         * not left enabled permanently from init.                        */
        mibspiREG1->DMACTRL[0] |= (1U << 15U);

        mibspiTransfer(mibspiREG1, SPI_TG);
        taskEXIT_CRITICAL();

        /* Block until TG0 complete (vIrisMibSpi1IsrHandler posts semaphore)
         * Timeout 100ms - at 1MHz, 32x16-bit words = ~512us so well within */
        if (xSemaphoreTake(s_done_sem, pdMS_TO_TICKS(100U)) != pdTRUE)
        {
            /* Timeout - check in debugger:
             *   mibspiREG1->TGINTFLG  - did TG0 complete?
             *   mibspiREG1->DMACTRL[0] - RXDMAENA bit 15 set per-transfer?
             *   dmaREG->ReqAssg1       - CH4=REQ1, CH5=REQ0?
             *   mibspiRAM1->tx[0].control - BUFMODE=5?                   */
            vTaskDelay(pdMS_TO_TICKS(1000U));
            continue;
        }

        /* ── DIAGNOSTIC: manual SW trigger of RX DMA ────────────────────────
         * At this point the TG-complete interrupt has already fired, so
         * mibspiTransfer() genuinely finished and RXRAM holds real loopback
         * data (not a manually-poked test value). Forcing the RX channel
         * via software here, independent of whatever RXDMAENA/BUFMODE did
         * or didn't do automatically, tells us if the control packet/
         * addressing/data path is correct in isolation. If s_rx_buf
         * populates correctly after this line, the data path is fine and
         * the remaining problem is purely the HW trigger condition.
         * Remove once s_rx_ftc/lfs/hbc/btc_count confirm the HW trigger
         * fires on its own.                                               */
        dmaSetChEnable(DMA_CH_RX, DMA_SW);

        /* DIAGNOSTIC: block briefly waiting specifically for FTC to fire
         * from this SW-triggered run. xResult tells us deterministically
         * whether FTC happened (pdTRUE) or timed out (pdFALSE), rather
         * than just sampling s_rx_ftc_count at an arbitrary breakpoint.   */
        {
            BaseType_t xFtcResult = xSemaphoreTake(s_rx_ftc_sem, pdMS_TO_TICKS(50U));
            (void)xFtcResult;  /* inspect in debugger: pdTRUE = FTC fired */
        }

        /* ── SET BREAKPOINT HERE ────────────────────────────────────────────
         * s_tx_buf : AA00 AA01 ... AA1F  (sent)
         * s_rx_buf : AA00 AA01 ... AA1F  (loopback - should match tx)
         * xFtcResult above: pdTRUE if FTC fired within 50ms of SW trigger
         * ──────────────────────────────────────────────────────────────── */

        vTaskDelay(pdMS_TO_TICKS(1000U));
    }
}

/*******************************************************************************
* prvArmDma
*   Configures and enables both DMA channels for one 32-word transfer.
*   Must be called with interrupts disabled (inside taskENTER_CRITICAL).
*
*   Port assignments:
*     TX: SRAM (Port A) -> MibSPI TXRAM (Port B) = PORTA_READ_PORTB_WRITE
*     RX: MibSPI RXRAM (Port B) -> SRAM (Port A) = PORTB_READ_PORTA_WRITE
*   Note: the TI SPI DMA example uses PORTA_READ_PORTA_WRITE because it
*   uses regular SPI DAT1/BUF registers (on Port A). MibSPI RAM is on
*   Port B so our assignments differ and are correct for this peripheral.
*******************************************************************************/
static void prvArmDma(void)
{
    g_dmaCTRL pkt;
    uint32    tg_pstart;
    uint32    rxram_addr;

    tg_pstart  = (mibspiREG1->TGCTRL[SPI_TG] >> 8U) & 0xFFU;
    /* +2 byte offset: confirmed via debugger that a 16-bit read at the
     * struct's base address (+0) returns the flags/status half-word, and
     * +2 returns the actual received data - opposite of what the
     * mibspiRamBase struct's declared {data;flags} little-endian field
     * order would suggest. Hardware/bus behavior overrides the struct
     * layout for raw 16-bit-width accesses at this address.             */
    rxram_addr = (uint32)&mibspiRAM1->rx[tg_pstart] + 2U;

    /* TX: s_tx_buf (SRAM, Port A) -> MibSPI TXRAM (Port B)                 */
    /* TX: mibspiSetData() loads TXRAM via CPU before transfer starts.
     * TX DMA removed - it was overwriting TXRAM and corrupting data.
     * TX DMA would only be needed for streaming new data mid-transfer.   */

    /* RX: MibSPI RXRAM (Port B) -> s_rx_buf (SRAM, Port A)
     * FULL TRANSFER: ELCNT=TEST_WORDS (32), moving the entire received
     * message in one frame. ELSOFFSET=4: each rx[] entry is a 4-byte
     * {uint16 data; uint16 flags} pair (confirmed via raw memory dump -
     * consecutive entries sit 4 bytes apart, e.g. 0xFF0E0200, 0xFF0E0204,
     * 0xFF0E0208...), so stepping by 4 lands exactly on each entry's
     * .data field. ELDOFFSET=2: s_rx_buf is a contiguous uint16 array.
     * Currently triggered via SW (dmaSetChEnable DMA_SW in the task loop)
     * since this path is confirmed working - HW trigger via RXDMAENA
     * still does not fire on its own (separate, still-open problem).     */
    pkt.SADD      = rxram_addr;
    pkt.DADD      = (uint32)&s_rx_buf[0];
    pkt.CHCTRL    = 0U;
    pkt.FRCNT     = 1U;
    pkt.ELCNT     = TEST_WORDS;
    pkt.ELSOFFSET = 4U;   /* 4 bytes between consecutive rx[] entries     */
    pkt.ELDOFFSET = 2U;   /* 2 bytes between consecutive s_rx_buf words   */
    pkt.FRSOFFSET = 0U;
    pkt.FRDOFFSET = 0U;
    pkt.PORTASGN  = (uint32)PORTB_READ_PORTA_WRITE;
    pkt.RDSIZE    = ACCESS_16_BIT;
    pkt.WRSIZE    = ACCESS_16_BIT;
    pkt.TTYPE     = FRAME_TRANSFER;
    pkt.ADDMODERD = ADDR_OFFSET;
    pkt.ADDMODEWR = ADDR_OFFSET;
    pkt.AUTOINIT  = AUTOINIT_OFF;
    dmaSetCtrlPacket(DMA_CH_RX, pkt);
    dmaSetChEnable(DMA_CH_RX, DMA_HW);
}

/*******************************************************************************
* vIrisMibSpi1IsrHandler  (ISR context)
*
* Our own MibSPI1 high level ISR - mapped to VIM channel 12 via vVimChannelMap
* bypassing the HAL's mibspi1HighLevelInterrupt which is not defined in this
* project. Fires when TG0 has finished clocking all words out on the wire.
*
* Reads TGINTFLG to identify which TG completed, clears the flag, then
* posts the semaphore to unblock the task.
*******************************************************************************/
#pragma CODE_STATE(vIrisMibSpi1IsrHandler, 32)
#pragma INTERRUPT(vIrisMibSpi1IsrHandler, IRQ)
static void vIrisMibSpi1IsrHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    uint32     tgflg;

    /* Read and clear TG interrupt flag                                     */
    tgflg = mibspiREG1->TGINTFLG;
    mibspiREG1->TGINTFLG = tgflg;

    /* Check TG0 completed (bit 16 per mibspiEnableGroupNotification shift) */
    if ((tgflg & ((uint32)1U << (SPI_TG + 16U))) != 0U)
    {
        xSemaphoreGiveFromISR(s_done_sem, &xHigherPriorityTaskWoken);
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}

/*******************************************************************************
* vIrisDmaRxAnyHandler  (ISR context, DIAGNOSTIC ONLY)
*
* Shared handler for all four interrupt types (FTC/LFS/HBC/BTC) registered
* on DMA_CH_RX (channel 4). Increments the matching counter for whichever
* type genuinely fires, and additionally gives s_rx_ftc_sem specifically
* on FTC so the task can block deterministically on "did FTC happen" via
* xSemaphoreTake, rather than only sampling counters at a breakpoint.
* Add s_rx_ftc_count, s_rx_lfs_count, s_rx_hbc_count, and s_rx_btc_count
* to the expressions window.
*******************************************************************************/
static portBASE_TYPE vIrisDmaRxAnyHandler(dmaInterrupt_t type, dmaChannel_t channel)
{
    portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;

    (void)channel;

    switch (type)
    {
        case FTC:
            s_rx_ftc_count++;
            xSemaphoreGiveFromISR(s_rx_ftc_sem, &xHigherPriorityTaskWoken);
            break;
        case LFS: s_rx_lfs_count++; break;
        case HBC: s_rx_hbc_count++; break;
        case BTC: s_rx_btc_count++; break;
        default:  break;
    }

    return xHigherPriorityTaskWoken;
}

/*******************************************************************************
* mibspiGroupNotification  (HAL weak callback - not used)
*
* This project wires its own ISR (vIrisMibSpi1IsrHandler) directly to the VIM
* so this callback is never called. It must still be defined to satisfy the
* linker since mibspi.h declares it as non-weak on this toolchain.
*******************************************************************************/
void mibspiGroupNotification(mibspiBASE_t *mibspi, uint32 group)
{
    (void)mibspi;
    (void)group;
}
