#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/fs.h>

#include <linux/dma-mapping.h>

#include "drexdma.h"



// Initial DMA buffers function
int init_dma_buffers(struct drexpcie_device_priv *drexpcie_priv)
{
   int buffer_index;

   // allocate an array for the buffer pointers (kernel space)
   drexpcie_priv->bufPtr = kzalloc(drexpcie_priv->num_bufs*sizeof(void *),GFP_KERNEL);
   if (drexpcie_priv->bufPtr == NULL) {
      printk("drexpcie[%d]: error, failed to alloc bufPtr memory\n", drexpcie_priv->chnum);
      return -ENOMEM;
   }

   // allocate an array for the dma pointers (physical address)
   drexpcie_priv->dmaPtr = kzalloc(drexpcie_priv->num_bufs*sizeof(dma_addr_t),GFP_KERNEL);
   if (drexpcie_priv->dmaPtr == NULL) {
      printk("drexpcie[%d]: error, failed to alloc dmaPtr memory\n", drexpcie_priv->chnum);
      return -ENOMEM;
   }

   // loop through the total number of desired buffer and attempt to allocate;
   // This will populate both the bufPtr and dmaPtr
   for (buffer_index = 0; buffer_index < drexpcie_priv->num_bufs; buffer_index++) {
      drexpcie_priv->bufPtr[buffer_index] = dma_alloc_coherent(&(drexpcie_priv->driv_priv->pdev->dev), drexpcie_priv->num_bytes, &drexpcie_priv->dmaPtr[buffer_index], GFP_KERNEL);
      printk("Allocating Memory: %p, %llX\n",drexpcie_priv->bufPtr[buffer_index],drexpcie_priv->dmaPtr[buffer_index]);
            
      if (drexpcie_priv->bufPtr[buffer_index] == NULL) {
         printk("drexpcie[%d]: warning, DMA mem allocation failed at %d buffers\n", drexpcie_priv->chnum, buffer_index);
         break;
      }
   } 

   drexpcie_priv->valid_bufs = buffer_index;  

   return 0;
}

// Release (free) DMA buffers function
void release_dma_buffers(struct drexpcie_device_priv *drexpcie_priv)
{
   int buffer_index;

   if (drexpcie_priv->bufPtr) {
      for (buffer_index=0; buffer_index<drexpcie_priv->valid_bufs; buffer_index++) {
         if (drexpcie_priv->bufPtr[buffer_index]) {
            printk("Freeing Memory: %p, %llX\n",drexpcie_priv->bufPtr[buffer_index],drexpcie_priv->dmaPtr[buffer_index]);
            dma_free_coherent(&(drexpcie_priv->driv_priv->pdev->dev), drexpcie_priv->num_bytes , drexpcie_priv->bufPtr[buffer_index], drexpcie_priv->dmaPtr[buffer_index]);
            
            // This is unnecessary, but cleaner
            drexpcie_priv->bufPtr[buffer_index] = NULL;
            drexpcie_priv->dmaPtr[buffer_index] = (dma_addr_t) NULL;
         }
      }
      kfree(drexpcie_priv->bufPtr);
      drexpcie_priv->bufPtr = NULL;
   }

   if (drexpcie_priv->dmaPtr) {
      kfree(drexpcie_priv->dmaPtr);
      drexpcie_priv->dmaPtr = NULL;
   }

   drexpcie_priv->valid_bufs = 0;

}

// Start the DMA function
int dma_init(struct drexpcie_device_priv *drexpcie_priv)
{
   int i;
   int ret;
   uint32_t temp[3];
   uint64_t temp64;

   // set reset bits
   iowrite32(0x00000007, drexpcie_priv->base_mem+DREXDMA_OFFSET_CTRL);
                      
   // clears queues if dma_start is called multiple times.
   release_dma_buffers(drexpcie_priv);

   ret = init_dma_buffers(drexpcie_priv);

   if (ret) {
      release_dma_buffers(drexpcie_priv);
      return ret;
   }
   
   // clear reset bits
   iowrite32(0x00000000, drexpcie_priv->base_mem+DREXDMA_OFFSET_CTRL);
       
   // writes buffer to the available FIFO
   printk("drexpcie[%d]: writing %d buffers to FIFO\n", drexpcie_priv->chnum, drexpcie_priv->valid_bufs );
   for (i = 0; i < drexpcie_priv->valid_bufs; i++) {
      temp64 = drexpcie_priv->dmaPtr[i];
      temp[0] = (((drexpcie_priv->num_bytes) & 0x7FFFFF) << 9);
      temp[0] |= (((i) & 0x000001FF));
      temp[1] = temp64 & 0xFFFFFFFF;
      temp[2] = (temp64 >> 32) & 0x000000FF;

      printk("drexpcie[%d]: writing %x %x %x to FIFO\n", drexpcie_priv->chnum, temp[2],temp[1],temp[0]);
      iowrite32(temp[0], drexpcie_priv->base_mem+DREXDMA_OFFSET_FIFO);
      iowrite32(temp[1], drexpcie_priv->base_mem+DREXDMA_OFFSET_FIFO+4);
      iowrite32(temp[2], drexpcie_priv->base_mem+DREXDMA_OFFSET_FIFO+8);            
   }
   
   // Enable Loop Mode
   printk("drexpcie[%d]: enabling loop mode\n", drexpcie_priv->chnum);
   iowrite32(1, drexpcie_priv->base_mem+DREXDMA_OFFSET_CTRL+4);

   // Enable DMA Engine
   // iowrite32(0x00000100, drexpcie_privDREXDMA_OFFSET_CTRL);

   return 0;
}

int dma_clear(struct drexpcie_device_priv *drexpcie_priv)
{

   // set reset bits
   iowrite32(0x00000007, drexpcie_priv->base_mem+DREXDMA_OFFSET_CTRL);
      
   // Disable Loop Mode
   iowrite32(0, drexpcie_priv->base_mem+DREXDMA_OFFSET_CTRL+4);

   release_dma_buffers(drexpcie_priv);
 
   return 0;
}

int dma_start(struct drexpcie_device_priv *drexpcie_priv)
{
   
   // Enable DMA Engine
   iowrite32(0x00000100, drexpcie_priv->base_mem+DREXDMA_OFFSET_CTRL);
 
   return 0;
}

int dma_stop(struct drexpcie_device_priv *drexpcie_priv)
{
   
   // Disable DMA Engine
   iowrite32(0x00000000, drexpcie_priv->base_mem+DREXDMA_OFFSET_CTRL);

   return 0;
}
