#include "rpi_dma.h"

#include <stdio.h>

#include "rpi_videocore.h"

// DMA channels and data requests
#define DMA_SMI_DREQ 4
#define DMA_PWM_DREQ 5
#define DMA_SPI_TX_DREQ 6
#define DMA_SPI_RX_DREQ 7
#define DMA_BASE (PHYS_REG_BASE + 0x007000)
// DMA register addresses offset by 0x100 * chan_num
#define DMA_CS 0x00
#define DMA_CONBLK_AD 0x04
#define DMA_TI 0x08
#define DMA_SRCE_AD 0x0c
#define DMA_DEST_AD 0x10
#define DMA_TXFR_LEN 0x14
#define DMA_STRIDE 0x18
#define DMA_NEXTCONBK 0x1c
#define DMA_DEBUG 0x20
#define DMA_REG(ch, r) ((r) == DMA_ENABLE ? DMA_ENABLE : (ch)*0x100 + (r))
#define DMA_ENABLE 0xff0
// DMA register values
#define DMA_WAIT_RESP (1 << 3)
#define DMA_CB_DEST_INC (1 << 4)
#define DMA_DEST_DREQ (1 << 6)
#define DMA_CB_SRCE_INC (1 << 8)
#define DMA_SRCE_DREQ (1 << 10)
#define DMA_PRIORITY(n) ((n) << 16)

// Virtual memory pointers to acceess GPIO, DMA and PWM from user space
MEM_MAP dma_regs;

char *dma_regstrs[] = {"DMA CS",  "CB_AD",  "TI",      "SRCE_AD", "DEST_AD",
                       "TFR_LEN", "STRIDE", "NEXT_CB", "DEBUG",   ""};

void dma_setup(MEM_MAP *mp, int chan, int nsamp, uint8_t **txdata, int offset, uint32_t dest_ad) {
  map_periph(&dma_regs, (void *)DMA_BASE, PAGE_SIZE);

  DMA_CB *cbs = mp->virt;

  *txdata = (uint8_t *)(cbs + offset);
  enable_dma(chan);
  cbs[0].ti = DMA_DEST_DREQ | (DMA_SMI_DREQ << 16) | DMA_CB_SRCE_INC | DMA_WAIT_RESP;
  cbs[0].tfr_len = nsamp;
  cbs[0].srce_ad = MEM_BUS_ADDR(mp, *txdata);
  cbs[0].dest_ad = dest_ad;
}

void dma_close() { unmap_periph_mem(&dma_regs); }

// Enable and reset DMA
void enable_dma(int chan) {
  *REG32(dma_regs, DMA_ENABLE) |= (1 << chan);
  *REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31;
}

// Start DMA, given first control block
void start_dma(MEM_MAP *mp, int chan, DMA_CB *cbp, uint32_t csval) {
  *REG32(dma_regs, DMA_REG(chan, DMA_CONBLK_AD)) = MEM_BUS_ADDR(mp, cbp);
  *REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 2;         // Clear 'end' flag
  *REG32(dma_regs, DMA_REG(chan, DMA_DEBUG)) = 7;      // Clear error bits
  *REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 | csval; // Start DMA
}

// Return remaining transfer length
uint32_t dma_transfer_len(int chan) { return (*REG32(dma_regs, DMA_REG(chan, DMA_TXFR_LEN))); }

// Check if DMA is active
uint32_t dma_active(int chan) { return ((*REG32(dma_regs, DMA_REG(chan, DMA_CS))) & 1); }

// Halt current DMA operation by resetting controller
void stop_dma(int chan) {
  if (dma_regs.virt)
    *REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31;
}

// Display DMA registers
void disp_dma(int chan) {
  volatile uint32_t *p = REG32(dma_regs, DMA_REG(chan, DMA_CS));
  int i = 0;

  while (dma_regstrs[i][0]) {
    printf("%-7s %08X ", dma_regstrs[i++], *p++);
    if (i % 5 == 0 || dma_regstrs[i][0] == 0)
      printf("\n");
  }
}