#ifndef DMA_H
#define DMA_H

#include <stdint.h>
#include <stdbool.h>

#include "reg.h"

#define EDMA3_BASE 0x02740000

#define CCCFG REG(EDMA3_BASE + 0x0004) // Channel Ctrl Configuration (Read only)

/* Event Registers */
#define EMCRL REG(EDMA3_BASE + 0x0308) // Event Missed Clear Low
#define ERL   REG(EDMA3_BASE + 0x1000) // Event Low
#define ECRL  REG(EDMA3_BASE + 0x1008) // Event Clear Low
#define ESRL  REG(EDMA3_BASE + 0x1010) // Event Set Low
#define CERL  REG(EDMA3_BASE + 0x1018) // Event Chained Low
#define EEERL REG(EDMA3_BASE + 0x1020) // Event Enable Low
#define EECRL REG(EDMA3_BASE + 0x1028) // Event Enable Clear Low
#define EESRL REG(EDMA3_BASE + 0x1030) // Event Enable Set Low
#define SERL  REG(EDMA3_BASE + 0x1038) // Secondary Event Low
#define SECRL REG(EDMA3_BASE + 0x1040) // Secondary Event Clear Low

#define EMCRH REG(EDMA3_BASE + 0x030C) // Event Missed Clear High
#define ERH   REG(EDMA3_BASE + 0x1004) // Event High
#define ECRH  REG(EDMA3_BASE + 0x100C) // Event Clear High
#define ESRH  REG(EDMA3_BASE + 0x1014) // Event Set High
#define CERH  REG(EDMA3_BASE + 0x101C) // Event Chained High
#define EEERH REG(EDMA3_BASE + 0x1024) // Event Enable High
#define EECRH REG(EDMA3_BASE + 0x102C) // Event Enable Clear High
#define EESRH REG(EDMA3_BASE + 0x1034) // Event Enable Set High
#define SERH  REG(EDMA3_BASE + 0x103C) // Secondary Event High
#define SECRH REG(EDMA3_BASE + 0x1044) // Secondary Event Clear High

#define QER   REG(EDMA3_BASE + 0x1080) // QDMA Event
#define QEEER REG(EDMA3_BASE + 0x1084) // QDMA Event Enable
#define QEECR REG(EDMA3_BASE + 0x1088) // QDMA Event Enable Clear
#define QEESR REG(EDMA3_BASE + 0x108C) // QDMA Event Enable Set
#define QSER  REG(EDMA3_BASE + 0x1090) // Secondary QDMA Event
#define QSECR REG(EDMA3_BASE + 0x1094) // Secondary QDMA Event Clear

/* Interrupt Registers */
#define CIERL  REG(EDMA3_BASE + 0x1050) // Channel Interrupt Enable Low
#define CIECRL REG(EDMA3_BASE + 0x1058) // Channel interrupt Enable Clear Low
#define CIESRL REG(EDMA3_BASE + 0x1060) // Channel interrupt Enable Clear Low
#define CIPRL  REG(EDMA3_BASE + 0x1068) // Channel Interrupt Pending Low
#define CICRL  REG(EDMA3_BASE + 0x1070) // Channel interrupt Pending Clear Low

#define CIERH  REG(EDMA3_BASE + 0x1054) // Channel Interrupt Enable High
#define CIECRH REG(EDMA3_BASE + 0x105C) // Channel Interrupt Enable Clear High
#define CIESRH REG(EDMA3_BASE + 0x1064) // Channel Interrupt Enable Clear High
#define CIPRH  REG(EDMA3_BASE + 0x106C) // Channel Interrupt Pending High
#define CICRH  REG(EDMA3_BASE + 0x1074) // Channel Interrupt Pending Clear High

#define IEVAL  REG(EDMA3_BASE + 0x1078) // Channel Interrupt Evaluate, not used

/* Channel Interrupt Pending Register Pointers (Low, High) */
#define CIPRL_REG ((volatile uint32_t *)(EDMA3_BASE + 0x1068)
#define CIPRH_REG ((volatile uint32_t *)(EDMA3_BASE + 0x106C)

/* Masks for maximum EDMA3 Channel, QDMA Channel and PaRAM index */
#define _mC(chan) ((chan) & 0x3F)
#define _mQ(chan) ((chan) & 0x07)
#define _mP(param) ((param) & 0x1FF)

#define EDMA_MAPR (EDMA3_BASE + 0x0100)
#define QDMA_MAPR (EDMA3_BASE + 0x0200)
#define EVQ_MAPR  (EDMA3_BASE + 0x0240)

/* Channel Mapping Channel [0..63], PaRAM [0..511] */
#define EDMA_MAP(Channel, PaRAM)           \
    REG(EDMA_MAPR + (_mC(Channel) << 2)) = \
    _mP(PaRAM) << 5;

#define GET_EDMA_MAP(Channel) \
    ((REG(EDMA_MAPR + (_mC(Channel) << 2))) >> 5)

#define QDMA_MAP(Channel, PaRAM, Trigger)  \
    REG(QDMA_MAPR + (_mQ(Channel) << 2)) = \
    ((_mP(PaRAM) << 5) | ((Trigger) << 2));

#define GET_QDMA_MAP(Channel) \
    ((REG(QDMA_MAPR + (_mQ(Channel) << 2))) >> 5)

#define GET_QDMA_TRG(Channel) \
    ((REG(QDMA_MAPR + (_mQ(Channel) << 2))) & 0x1F >> 2)

#define EVENT_QUEUE_MAP(Event, Nr) {                                        \
    uint32_t Shift = (_mC(Event) - ((_mC(Event) >> 3) << 3)) << 2;          \
    REG(EVQ_MAPR + (_mC(Event) >> 1)) &= ~(0x00000007 << Shift);            \
    REG(EVQ_MAPR + (_mC(Event) >> 1)) |= (((uint32_t)(Nr) & 0x3) << Shift); \
}

/* EDMA3 PaRAM definitions, maps onto PaRAM space directly */
typedef struct {
    uint32_t OPT;
    uint32_t SRC;
    uint32_t BACNT;   // uint16_t BCNT; uint16_t ACNT;
    uint32_t DST;
    uint32_t BIDX;    // unit16_t DSTBIDX; uint16_t SRCBIDX;
    uint32_t RLDLNK;  // uint16_t BCNTRLD; uint16_t LINK
    uint32_t CIDX;    // unit16_t DSTCIDX; uint16_t SRCCIDX;
    uint32_t CCNT;    // uint16_t Reserved; uint16_t CCNT
} EDMA3_PARAMS;

typedef EDMA3_PARAMS* pEDMA;

/* first 64 channels are default EDMA channels */
#define EDMA_CHANNELS ((pEDMA)(EDMA3_BASE + 0x4000))

/* next (512-64) channels are LINK channels */
#define LINK_CHANNELS ((pEDMA)(EDMA3_BASE + 0x4800))

/* offset_of with uint32_t granularity, as the old constants were made manually,
 * used to configure the trigger for QDMA */
#define EDMA3_SRC (((uint32_t)&((pEDMA)0)->SRC)/sizeof(uint32_t))

/* EDMA3 Value */
#define _vC(event) \
    (1UL << ((event) & 0x1F))

/* QDMA3 Value */
#define _vQ(event) \
    (1UL << ((event) & 0x07))

#define EDMA3_LINK(event) \
    (_mP(event) * sizeof(EDMA3_PARAMS))

#define EDMA3_TCC(event) \
    (((event) << EDMA3_TCC_SHIFT) & EDMA3_TCC_MASK)

#define EDMA3_PARAM_BACNT(bcnt, acnt, asize) \
    (((uint16_t)(bcnt) << 16) | (((uint16_t)(acnt) * (asize)) & 0xFFFF))

#define EDMA3_LINK_CHANNEL(event) \
    (_mP((event) + 64) * sizeof(EDMA3_PARAMS))

#define EDMA3_INDEX(dst, src) \
    (((dst) << 16 ) | ((src) & 0xFFFF))

enum EDMA3_CONST {
    EDMA3_OPT_ITCCHEN  = 1L << 23, // intermediate transfer chaining bit
    EDMA3_OPT_TCCHEN   = 1L << 22, // transfer completion chaining bit
    EDMA3_OPT_IINTCHEN = 1L << 21, // intermediate interrupt enable bit
    EDMA3_OPT_INTCHEN  = 1L << 20, // transfer completion interrupt enable bit
    EDMA3_TCC_MASK     = BIT_MASK(17, 12), // tcc mask bits, 0x3F000
    EDMA3_TCC_SHIFT    = 12,       // tcc mask shift
    EDMA3_OPT_TCC_MODE = 1L << 11, //  early tcc, if set
    EDMA3_FWID_MASK    = BIT_MASK(10, 8), // FIFO-Width, for constant addr
    EDMA3_OPT_STATIC   = 1L << 3, // PaRAM is static (final or isolated TR),
                                  // if set, otherwise updatable
    EDMA3_OPT_SYNC_AB  = 1L << 2, // sync to AB if set, otherwise sync to A
    EDMA3_OPT_DAM      = 1L << 1, // constant destination address mode, if set
    EDMA3_OPT_SAM      = 1L << 0  // constant source address mode, if set,
                                  // must be 256 Byte aligned
};


#define CIPR_REG(event)           \
    ((event) & 0x20 ? CIPRH_REG : \
                      CIPRL_REG)

/* Waits for completion notification */
#define EDMA_WAIT(event)                              \
    if ((event) & 0x20) while(!(CIPRH & _vC(event))); \
                   else while(!(CIPRL & _vC(event)));

#define EDMA_COMPLETED(event)                  \
    (((event) & 0x20) ? (CIPRH & _vC(event)) : \
                        (CIPRL & _vC(event)))

/* Clears the completion notification */
#define EDMA_ACK(event)                     \
    if ((event) & 0x20) CICRH = _vC(event); \
                   else CICRL = _vC(event);

#define EDMA_ENABLE(event)                  \
    if ((event) & 0x20) EESRH = _vC(event); \
                   else EESRL = _vC(event);

#define EDMA_DISABLE(event)                 \
    if ((event) & 0x20) EECRH = _vC(event); \
                   else EECRL = _vC(event);

/* Triggers an event, thereby starting a DMA transfer */
#define EDMA_START(event)                  \
    if ((event) & 0x20) ESRH = _vC(event); \
                   else ESRL = _vC(event);

/* Clears an event, thereby stopping a DMA request */
#define EDMA_STOP(event)                   \
    if ((event) & 0x20) ECRH = _vC(event); \
                   else ECRL = _vC(event);

#define EDMA_STARTED(event)              \
    ((event) & 0x20 ? ERH & _vC(event) : \
                      ERL & _vC(event))

#define QDMA_ENABLE(event) QEESR = _vQ(event);

#define QDMA_DISABLE(event) QEECR = _vQ(event);

#pragma pack(push, 1)
typedef struct {
    uint32_t channels;   /* Type pEDMA */
    uint8_t channel;
    bool static_params;
    bool chain_on_complete;
    bool chain_intermediate;
    bool interrupt_on_complete;
    bool sync_AB; /* true = SYNC AB, false = SYNC A */
    uint8_t completion_code;
    uint16_t link;
    uint32_t src;
    uint32_t dst;
    uint16_t a_count;
    uint16_t b_count;
    uint16_t b_count_reload;
    uint32_t c_count;
    int16_t bidx_dst;
    int16_t bidx_src;
    int16_t cidx_dst;
    int16_t cidx_src;
} dma_params_t;
#pragma pack(pop)


#define EDMA3_NO_LINK (uint16_t)-1


void dma_events_disable_all();
void dma_completions_clear_all();
void dma_reset();
void dma_channel_init(const dma_params_t *params);

#endif
