#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of_device.h>
#include <linux/slab.h> // kmalloc

#include <linux/uaccess.h> // copy_from_user

#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>

#include <linux/fs.h> // file_operations
#include <linux/poll.h>

#include <linux/kthread.h>
#include <linux/wait.h>
#include <linux/delay.h> // msleep

#include <linux/gpio.h>
#include <linux/interrupt.h>

#include "O/svn.h"



#define RK_BIT8         BIT(8)


#define SAM             BIT(0)
#define DAM		BIT(1)
#define SYNCDIM		BIT(2)
#define STATIC		BIT(3)
#define EDMA_FWID	(0x07 << 8)
#define TCCMODE		BIT(11)
#define EDMA_TCC(t)	((t) << 12)
#define TCINTEN		BIT(20)
#define ITCINTEN	BIT(21)
#define TCCHEN		BIT(22)
#define ITCCHEN		BIT(23)

struct edmacc_param {
	u32 opt;
	u32 src;
	u32 a_b_cnt;
	u32 dst;
	u32 src_dst_bidx;
	u32 link_bcntrld;
	u32 src_dst_cidx;
	u32 ccnt;
} __packed;

struct dma_async_tx_descriptor *edma_prep_dma_fpga2(
    struct dma_chan *chan, struct edmacc_param *param,
	enum dma_transfer_direction dir, size_t residue, size_t len, bool debug);

int dma_edit_akt_slot_fpga(struct dma_chan *chan, int ccnt_new, bool debug);

// #define KITI_FPGASIM

#define DEVICE_NAME "cvfpga"
#define CLASS_NAME  "kiti"

/*=========================================================================*/

size_t prtmemlen = 64*1024*1024;	//In CMA Allocierter Speicher muss gros genug sein!
char* src;
static dma_addr_t  busSrc;

#ifdef KITI_FPGASIM
char* dest;
static dma_addr_t busDest;
#endif

unsigned int uiNoOfLines;
unsigned int uiLineLenBytes;
unsigned int uiDataStartAddrOffset;

static bool doLineCpy;
static struct dma_chan *dma_ch;
static struct completion dma_complet;

char* pcImage;

static struct edmacc_param param;

static int kiti_remove(struct platform_device *pdev);

static int major;
static struct class*  class;
static struct device*  device;

#define THREAD_NAME "cvfpgasim"
static struct task_struct *fpgasim_thread;

DECLARE_WAIT_QUEUE_HEAD(waitq_fpgasim);
DECLARE_COMPLETION(waitq_fpgasim_complete);
int waitq_fpgasim_flag = 0;
#define WAITQ_FPGASIM_FLAG_START 1
#define WAITQ_FPGASIM_FLAG_EXIT  2

DECLARE_WAIT_QUEUE_HEAD(waitq_poll);
static bool poll_can_write = false;
static bool poll_can_read  = false;
static bool bWaitForComplete  = false;

#define KITI_FPGA_BASE 0x02000000
#define EDMA_BASE_CC 0x49000000
//#define KITI_FPGA_DEST_ADR 0x02000080
// Keine Ahnung warum, aber Adresse erscheint auf LogicAnalyser um ein Bit verschoben
// Also 0x80 -> 0x40
#define KITI_FPGA_DEST_ADR 0x02000100

void __iomem *fpga_map;
volatile unsigned short* fpga_acc;


#define FPGA_DMA_DO_REQ (volatile unsigned short*)(fpga_acc+0x12)
#define FPGA_DMA_PORT_SELECT (volatile unsigned short*)(fpga_acc+0x13)
#define FPGA_DMA_REQ_PERIOD (volatile unsigned short*)(fpga_acc+0x10)
#define FPGA_DMA_REQ_SIG_ACTIVE (volatile unsigned short*)(fpga_acc+0x11)

unsigned int dmaEvIrqNumber = 0;

/*=========================================================================*/
static void dma_callback(void *data)
{
	pr_info("dma_callback\n");
	complete(&dma_complet);
}

/*=========================================================================*/
/**
 * Thread zur auswertung der Rueckgabe des DMA und Ausgabe der Debug Informationen
 * @param arg
 * @return
 */
static int kiti_fpgasim_thread(void* arg)
{
#ifdef KITI_FPGASIM
	unsigned int ui;
	unsigned int uiDebgC;
	char* pc;
#endif
	int flag;

	while(1) {
		pr_notice("********* thread wait 4 ev\n");
//		wait_event_interruptible(waitq_fpgasim, waitq_fpgasim_flag != 0 );
		wait_for_completion_interruptible(&waitq_fpgasim_complete);
		//init_completion(&waitq_fpgasim_complete);								//darf nur ein mal ausgefuehrt werden- jetzt in kiti_probe
		flag = waitq_fpgasim_flag;
		waitq_fpgasim_flag = 0;
		pr_notice("********* thread ev\n");
		switch (flag)
		{
			case WAITQ_FPGASIM_FLAG_START:
				pr_notice("********* thread ev start\n");
                poll_can_read = false;
				poll_can_write = false;
				*FPGA_DMA_PORT_SELECT = 1;

				doLineCpy = true;
#ifdef KITI_FPGASIM
				*FPGA_DMA_DO_REQ = 0;
				pc = pcImage;

				*FPGA_DMA_DO_REQ = 0x30;
				pr_notice("start fpga sim\n");
				uiDebgC = 0;

				for (ui=0; ui<((uiLineLenBytes/2)*uiNoOfLines); ui++) {
					*FPGA_DMA_DO_REQ = 0x10;
					usleep_range(10, 10);
					*FPGA_DMA_DO_REQ = 0x30;
					usleep_range(10, 10);
					memcpy(pc, dest, 2);
					pc += 2;
					if (ui-uiDebgC > 10000) {
						pr_notice("%u bytes of %u\n", ui*2, uiLineLenBytes*uiNoOfLines);
						uiDebgC = ui;
					}
				}
         		*FPGA_DMA_DO_REQ = 0x30;
#endif
         		pr_notice("********* wait4complete\n");
                bWaitForComplete = true;
				wait_for_completion(&dma_complet);
                bWaitForComplete = false;

                pr_notice("********* DMA DONE !!!!!!!!\n");
                // Fuer LogicAnalyser
                *FPGA_DMA_PORT_SELECT = 2;

				if (doLineCpy) {
					poll_can_read = true;
					poll_can_write = true;
				}
				wake_up(&waitq_poll);
				break;

			case WAITQ_FPGASIM_FLAG_EXIT:
			    pr_notice("********* thread ev exit\n");
				return 0;
				break;

			default:
				pr_notice("********* thread ev unknown\n");
				return 0;
		}
	}
    do_exit(0);
    return 0;
}

/*=========================================================================*/
static ssize_t kiti_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
#define P4HEAD_LEN 20
	char pcP4Header[P4HEAD_LEN];
	int iBytesWritten;


	pr_info("Data Read : %u, %u!\n", len, (unsigned int)*off);

	iBytesWritten = scnprintf(pcP4Header, P4HEAD_LEN, "PrtMem:%x\n",prtmemlen );
	if (copy_to_user(buf, pcP4Header, iBytesWritten))
	{
		pr_err("Data Read1 : Err!\n");
		return -EFAULT;
	}
	return iBytesWritten;
}

/*=========================================================================*/
/**Funktion zur auswertung der ueber das Filesystem gesendeten Befehle
 *
 * Ruft die eigentlichen DMA Funktionen auf und nimmt die Parameter vom aufrufenden Programm entgegen.
 */
static ssize_t kiti_write(struct file *filp, const char  *buf, size_t len, loff_t *off)
{
#define CMD_BUF_LEN 30
	char pcBuffer[CMD_BUF_LEN+1];
	struct dma_async_tx_descriptor *dma_m2m_desc;
	unsigned int localSourceAddr = 0;
	dma_cookie_t cookie;
	if (len > CMD_BUF_LEN)
    	return -ENOMEM;

    if (copy_from_user(pcBuffer, buf, len))
    {
    	pr_err("Data Write : Err!\n");
    }
    pcBuffer[len] = '\0';
    pr_notice("kiti_write Write (%u): %s\n", len, pcBuffer);
    if (len < 2) {
    	pr_err("kiti_write Write: Len should be >=2\n");
    	return len;
    }

    uiNoOfLines = 0;
    uiLineLenBytes = 0;

    if (pcBuffer[0]=='T' && pcBuffer[1]=='S' && len > 2) {
   /* 	if (pcBuffer[2] == '1')
    		*FPGA_DMA_DO_REQ = 1;
    	if (pcBuffer[2] == '0')
    		*FPGA_DMA_DO_REQ = 0;
    	if (pcBuffer[2] == '2')
    		*FPGA_DMA_DO_REQ = 0x10;
    	if (pcBuffer[2] == '3')
    		*FPGA_DMA_DO_REQ = 0x30;*/
    }

    if (pcBuffer[0]=='T' && pcBuffer[1]=='P' && len > 3) {
    	/*unsigned int p, j;
    	p = (pcBuffer[2]-'0')*10 + (pcBuffer[3]-'0');
    	pr_notice("do %u pulse\n", p);
		for (j=0; j<p; j++) {
			*FPGA_DMA_DO_REQ = 0x10;
			usleep_range(10, 10);
			*FPGA_DMA_DO_REQ = 0x30;
			usleep_range(10, 10);
		}*/
    }
    //DMA Pausieren
    if (pcBuffer[0]=='P' && pcBuffer[1]=='S') {
    	/*pr_notice("PS: Pause\n");
    	dma_ch->device->device_pause(dma_ch);*/
    }
    //DMA Pause beenden
    if (pcBuffer[0]=='R' && pcBuffer[1]=='S') {
    	/*pr_notice("RS: Resume\n");
    	dma_ch->device->device_resume(dma_ch);*/
    }
    //alle anstehenden DMA Uebertragungen beenden
    if (pcBuffer[0]=='C' && pcBuffer[1]=='C') {
    	pr_notice("CC: Cancel\n");
    	doLineCpy = false;
    	dma_ch->device->device_terminate_all(dma_ch);
    	if(bWaitForComplete)
    	    complete(&dma_complet);
    }
    if (pcBuffer[0]=='L' && pcBuffer[1]=='S' && len > 3) {
        	sscanf(pcBuffer, "LS;%u", &uiNoOfLines);
        	pr_notice("LS: Set new Line number\n");
        	if (uiNoOfLines>0 ) {
        		if (dma_ch==NULL || dma_ch->device==NULL) {
					pr_err("no dma channel\n");
					return 0;
				}
        		dma_edit_akt_slot_fpga(dma_ch,uiNoOfLines,1);
        	}
    }

    //Neue DMA Uebertragung initialisierun und starten
    if (pcBuffer[0]=='S' && pcBuffer[1]=='T' && len > 5) {
    	sscanf(pcBuffer, "ST;%u;%u;%u", &uiNoOfLines, &uiLineLenBytes,&uiDataStartAddrOffset);
    	pr_notice("ST: Start\n");
    	if (uiNoOfLines>0 && uiLineLenBytes>0) {
   			localSourceAddr = busSrc + uiDataStartAddrOffset;	//Speicheradress um Offset anpassen

   			pr_notice("src Addr:%x",localSourceAddr);

    		pr_notice("set ev start\n");

#ifdef KITI_FPGASIM
    		memset(pcImage, 0, prtmemlen);
#endif

    		if (dma_ch==NULL || dma_ch->device==NULL) {
    			pr_err("no dma channel\n");
    			return 0;
    		}
    		if (dma_ch->device->device_prep_dma_memcpy == NULL) {
    			pr_err("no memcpy dma channel\n");
    			return 0;
    		}

 //    		pr_notice("start param seting \n");

    	    memset(&param, 0, sizeof(struct edmacc_param));

    	    param.opt |= TCINTEN; 							// Transfer Complete Int Enable
            param.opt |= SYNCDIM; 							// ABSYNC = 1
            param.opt |= RK_BIT8; 							// FIFO Size 2 Byte (16 bit)
    	    param.src = localSourceAddr;								//Startadresse der Daten
    	    //                   bcnt                  acnt
    	    param.a_b_cnt = ((uiLineLenBytes/2)<<16) | (2);	//bcnt- Anzhal an 16 Bit uebertragungen fuer die aktuelle Zeilenlaenge ;acnt- 16 Bit (2 Byte) pro Uebertragung
    	    param.dst = KITI_FPGA_DEST_ADR;					//Ziel Adresse im FPGA
            param.src_dst_bidx = 2;							//16 Bit (2 Byte) pro Uebertragung
            param.src_dst_cidx = uiLineLenBytes;			//
    	    param.ccnt = uiNoOfLines;						//Zu uebertragende Zeilen
    	    param.link_bcntrld = 0xFFFF;

//    	    pr_notice("device_prep_write_1_dma start\n");
    	    dma_m2m_desc = edma_prep_dma_fpga2(dma_ch, &param, DMA_MEM_TO_DEV,
    	        	    		uiLineLenBytes, uiLineLenBytes*uiNoOfLines, false);
    	    pr_notice("device_prep_write_1_dma done\n");
    	    waitq_fpgasim_flag = WAITQ_FPGASIM_FLAG_START;
			complete(&waitq_fpgasim_complete);
    	    if (dma_m2m_desc == NULL)
    	    	 return len;

    	    dma_m2m_desc->callback = dma_callback;
    	    pr_notice("device_prep_dma done\n");

    	    cookie = dmaengine_submit(dma_m2m_desc);
    	    if (dma_submit_error(cookie)){
    	    	pr_err("failed to do DMA tx_submit");
    	        return 0;
    	    }
    	    dma_async_issue_pending(dma_ch);
    	    pr_notice("dmaengine_submit done\n");

    	    //waitq_fpgasim_flag = WAITQ_FPGASIM_FLAG_START;
            // wake_up_interruptible(&waitq_fpgasim);
            //complete(&waitq_fpgasim_complete);
    	}
    }

    pr_notice("uiNoOfLines: %u\n", uiNoOfLines);
    pr_notice("uiLineLenBytes: %u\n", uiLineLenBytes);

    return len;
}

/*=========================================================================*/
static unsigned int kiti_poll(struct file *filp, struct poll_table_struct *wait)
{
	__poll_t mask = 0;

	poll_wait(filp, &waitq_poll, wait);

	pr_notice("kiti_poll\n");

	if (poll_can_read)
	{
		poll_can_read = false;
		mask |= ( POLLIN | POLLRDNORM );
	}

	if (poll_can_write)
	{
		poll_can_write = false;
		mask |= ( POLLOUT | POLLWRNORM );
	}

    return mask;
}

/*=========================================================================*/
static int kiti_mmap(struct file* file, struct vm_area_struct* vma)
{
	int ret = 0;
	unsigned long size = (unsigned long)(vma->vm_end - vma->vm_start);

	if (size > prtmemlen)
		return -ENOMEM;
	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
    ret = remap_pfn_range(vma, vma->vm_start, busSrc >> PAGE_SHIFT,
    					  size, vma->vm_page_prot);
    if (ret != 0) {
        return -EAGAIN;
    }

    pr_info("kiti_mmap: %lu\n", size);

	return 0;
}

/*=========================================================================*/
static struct file_operations fops =
{
    .owner      = THIS_MODULE,
	.write      = kiti_write,
	.read       = kiti_read,
	.poll       = kiti_poll,
    .mmap       = kiti_mmap,
};

/*=========================================================================*/
static int kiti_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct dma_slave_config dma_cfg;

    fpga_map = ioremap(KITI_FPGA_BASE, 4096);
    if (!fpga_map) {
    	pr_err("Failed to ioremap FPGA\n");
        return -EAGAIN;
    }
    fpga_acc = (volatile unsigned short*)fpga_map;

    init_completion(&dma_complet);
    init_completion(&waitq_fpgasim_complete);

#ifdef KITI_FPGASIM
    pcImage = kmalloc(prtmemlen, GFP_KERNEL);

    dest = dma_alloc_coherent(&pdev->dev, prtmemlen, &busDest, GFP_KERNEL);
    if (dest == NULL) {
    	pr_err("no memory!");
    	ret = -ENOMEM;
    	goto p_err;
    }
#endif

    src = dma_alloc_coherent(&pdev->dev, prtmemlen, &busSrc, GFP_KERNEL);		//Speicher fuer daten des DMA anlegen
    if (src == NULL) {
    	pr_err("no memory!");
    	ret = -ENOMEM;
    	goto p_err;
    }
    /*else
    {
        for(i=0;i<256;i++)
            *(src+i)=i;

        pr_notice("RK Memory write done\n");


    }*/

    dma_ch = dma_request_chan(&pdev->dev, "cvfpga");		//Dma chanel holen
    if (!dma_ch) {
    	pr_err("no dma channel for cvfpga\n");
        ret = -EAGAIN;
        goto p_err;
    }
    pr_notice("got dma_ch = 0x%x\n", (unsigned int)dma_ch);

    memset(&dma_cfg, 0, (sizeof(struct dma_slave_config)));
    dma_cfg.direction = DMA_MEM_TO_DEV;
    dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
    ret = dmaengine_slave_config(dma_ch, &dma_cfg);					//DMA config
    if (ret) {
    	pr_err("dmaengine_slave_config error\n");
    	goto p_err;
    }
    pr_notice("dmaengine_slave_config pass\n");

    major = register_chrdev(0, DEVICE_NAME, &fops);		//Kernelmudul registrieren
    if (major < 0) {
        pr_err("fail to register major number!");
        ret = major;
        goto p_err;
    }

    class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(class)){
        unregister_chrdev(major, DEVICE_NAME);
        pr_err("failed to register device class");
        ret = PTR_ERR(class);
        goto p_err;
    }

    device = device_create(class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
    if (IS_ERR(device)) {
        class_destroy(class);
        unregister_chrdev(major, DEVICE_NAME);
        ret = PTR_ERR(device);
        goto p_err;
    }

    fpgasim_thread = kthread_run(kiti_fpgasim_thread, NULL, THREAD_NAME);	//Abfrage Thread starten

    pr_notice("CV FPGA KiTi " SVN_REV " module loaded\n");

	return 0;

p_err:
	if (dma_ch != NULL) {
		dma_release_channel(dma_ch);
		dma_ch = NULL;
	}
	dma_free_coherent(&pdev->dev, prtmemlen, src, busSrc);
#ifdef KITI_FPGASIM
	dma_free_coherent(&pdev->dev, prtmemlen, dest, busDest);
	kfree(pcImage);
#endif
	iounmap(fpga_map);
	if (dmaEvIrqNumber != 0) {
	    free_irq(dmaEvIrqNumber, NULL);
	}
	return ret;
}

/*=========================================================================*/
static int kiti_remove(struct platform_device *pdev)
{
	pr_notice("kiti_remove\n");

	waitq_fpgasim_flag = WAITQ_FPGASIM_FLAG_EXIT;
//    wake_up_interruptible(&waitq_fpgasim);
    complete(&waitq_fpgasim_complete);

	kthread_stop(fpgasim_thread);

	device_destroy(class, MKDEV(major, 0));
    class_destroy(class);
    unregister_chrdev(major, DEVICE_NAME);

	if (dma_ch != NULL) {
		dma_release_channel(dma_ch);
	}
    dma_free_coherent(&pdev->dev, prtmemlen, src, busSrc);
#ifdef KITI_FPGASIM
    dma_free_coherent(&pdev->dev, prtmemlen, dest, busDest);
    kfree(pcImage);
#endif
    iounmap(fpga_map);
	if (dmaEvIrqNumber != 0) {
	    free_irq(dmaEvIrqNumber, NULL);
	}

    return 0;
}

/*=========================================================================*/
static struct of_device_id cvfpga_driver_ids[] = {
    {
        .compatible = "CvFpgaKiTi",
    }, { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, cvfpga_driver_ids);

static struct platform_driver cvfpga_driver = {
    .probe = kiti_probe,
    .remove = kiti_remove,
    .driver = {
        .name = "cvfpga_kiti_driver",
        .of_match_table = cvfpga_driver_ids,
    },
};

/*=========================================================================*/
int kiti_init(void)
{
    pr_notice("kiti_init\n");

    if (platform_driver_register(&cvfpga_driver)) {
        pr_err("kiti_init - Error! Could not load driver\n");
        return -1;
    }

    pr_debug("kiti_init was successful\n");

    return 0;
}

/*=========================================================================*/
void kiti_exit(void)
{
	pr_notice("kiti_exit\n");

	platform_driver_unregister(&cvfpga_driver);

    pr_info("CV FPGA module removed\n");
}

/*=========================================================================*/
module_init(kiti_init);
module_exit(kiti_exit);

MODULE_DESCRIPTION("Carl Valentin FPGA Driver KiTi");
MODULE_AUTHOR("Benjamin Tisler <btisler@carl-valentin.de>");
MODULE_LICENSE("GPL");
