#include "common.h"

/************************   DEFINE ******************************* */
#define CDMA_ALLOC_SIZE (1024*1024)

#define CDMA_STATU_IDLE_FLAG     0x02
#define CDMA_STATU_INT_ERR_FLAG  0x10

#define CDMA_SOFT_RESET_FLAG 0x04
/* **********************   OFFSET ******************************* */
#define CDMA_PC_REG_FLAG 0x80000000
#define CDMA_REG_BASE_OFFSET      0x8000

#define CDMA_REG_CONTROL_OFFSET    (CDMA_REG_BASE_OFFSET + 0x00)
#define CDMA_REG_STATUS_OFFSET     (CDMA_REG_BASE_OFFSET + 0x04)
#define CDMA_REG_SRC_ADDR_OFFSET_L (CDMA_REG_BASE_OFFSET + 0x18)
#define CDMA_REG_SRC_ADDR_OFFSET_H (CDMA_REG_BASE_OFFSET + 0x1C)
#define CDMA_REG_DST_ADDR_OFFSET_L (CDMA_REG_BASE_OFFSET + 0x20)
#define CDMA_REG_DST_ADDR_OFFSET_H (CDMA_REG_BASE_OFFSET + 0x24)
#define CDMA_REG_SIZE_OFFSET       (CDMA_REG_BASE_OFFSET + 0x28)

#define AXI_BASE_ADDR_TRANSLATION_CFG_REG_BAR0_U 0x208
#define AXI_BASE_ADDR_TRANSLATION_CFG_REG_BAR0_L 0x20C
#define AXI_BASE_ADDR_TRANSLATION_CFG_REG_BAR1_U 0x210
#define AXI_BASE_ADDR_TRANSLATION_CFG_REG_BAR1_L 0x214
#define AXI_BASE_ADDR_TRANSLATION_CFG_REG_BAR2_U 0x218
#define AXI_BASE_ADDR_TRANSLATION_CFG_REG_BAR2_L 0x21C
/* **************************************************************** */

static __inline __u64 _get_ns(void);

static __inline __s32 _read_reg(void* offset);
static __inline void _write_reg(const zpcidev_device *d, __u32 value, void* offset);

static long zpcidev_cdma_setReg(const zpcidev_device *d, __u64 srcAddr, __u64 desAddr, __u32 size);
static __inline __s32 zpcidev_cdma_wait_over(const zpcidev_device *d, __u64 timeout_ms);
static __inline void zpcidev_cdma_reset(const zpcidev_device *d);

static __inline long _zpcidev_cdma_release(zpcidev_device *d);
static __inline long _zpcidev_cdma_alloc(zpcidev_device *d);

static __inline long zpcidev_cdma_alloc(struct file *file);
static __inline long zpcidev_cdma_stop(struct file *file);

// static void _create_test_data(void* ptr, __u32 size);
static __inline void _print_user_data(void* ptr, __u32 size);

static __inline int _zpcidev_cdma_pc_read_from_device(zpcidev_device *d, const PCIe_CDMA_MSG *cdmaMsg);
static __inline int _zpcidev_cdma_pc_write_to_device(zpcidev_device *d, const PCIe_CDMA_MSG *cdmaMsg);
static __inline int _zpcidev_cdma_device_copy_to_device(zpcidev_device *d, const PCIe_CDMA_MSG *cdmaMsg);

/*********************************************/


static __inline void _write_reg(const zpcidev_device *d, __u32 value, void* offset){
    _DBG_("write reg [0x%lx]:0x%0x", (uintptr_t)offset, value);
    writel(value, offset);
}

static long zpcidev_cdma_setReg(const zpcidev_device *d, __u64 srcAddr, __u64 desAddr, __u32 size){
    _write_reg(d, srcAddr&0xFFFFFFFF, d->bars[1].base + CDMA_REG_SRC_ADDR_OFFSET_L);
    // _write_reg(d, srcAddr>>32, d->bars[1].base + CDMA_REG_SRC_ADDR_OFFSET_H);
    _write_reg(d, desAddr&0xFFFFFFFF, d->bars[1].base + CDMA_REG_DST_ADDR_OFFSET_L);
    // _write_reg(d, desAddr>>32, d->bars[1].base + CDMA_REG_DST_ADDR_OFFSET_H);
    _write_reg(d, size, d->bars[1].base + CDMA_REG_SIZE_OFFSET);
    return 0;
} 

static __inline __u64 _get_ns(void){
    return ktime_to_ns(ktime_get());
}

static __inline __s32 _read_reg(void* offset){
    __s32 val = readl(offset);
    // _MSG_("read reg [0x%0x]:0x%0x", offset, val);
    return val;
}

static __s32 zpcidev_cdma_wait_over(const zpcidev_device *d, __u64 timeout_ms){
    __s32 rult = -1, val = 0;
    __u64 deadlineTick = _get_ns() + timeout_ms * 1000000;
    do {
        val = _read_reg(d->bars[1].base + CDMA_REG_STATUS_OFFSET);
        if (val & CDMA_STATU_IDLE_FLAG) {
            if (val & CDMA_STATU_INT_ERR_FLAG){
                _ERR_("cdma status err: 0x%0X.", val);
                _MSG_("cdma soft reset.");
                zpcidev_cdma_reset(d);
            }
            _DBG_("cdma status: 0x%0X.", val);
            return 0;
        }
    }while (deadlineTick > _get_ns());
    _ERR_("timeout. cdma status: 0x%0X.", val);
    return rult;
}

static __inline void zpcidev_cdma_reset(const zpcidev_device *d){
    _write_reg(d, CDMA_SOFT_RESET_FLAG, d->bars[1].base + CDMA_REG_CONTROL_OFFSET);
}

static long _zpcidev_cdma_release(zpcidev_device *d){
    if (d->dmaHandle.dma_va){
        dma_free_coherent(&d->pcidev->dev, d->dmaHandle.dma_size, d->dmaHandle.dma_va, d->dmaHandle.dma_pa);
        d->dmaHandle.dma_va = NULL;
    }
    return 0;
}

static long _zpcidev_cdma_alloc(zpcidev_device *d){
    long ret = 0;
    __u32 paL, paH;
    struct device* pdev;
    _zpcidev_cdma_release(d);

    pdev = &d->pcidev->dev;
    ret = dma_set_coherent_mask(&d->pcidev->dev, DMA_BIT_MASK(64));
    _DBG_("dma_set_coherent_mask ret: %ld", ret);
    _DBG_("dma_alloc_coherent size: 0x%lX", d->dmaHandle.dma_size);
    d->dmaHandle.dma_va = dma_alloc_coherent(pdev, d->dmaHandle.dma_size, &d->dmaHandle.dma_pa, GFP_KERNEL);
    if (d->dmaHandle.dma_va == NULL){
        _ERR_("dma_alloc_coherent failed.");
        return -ENOMEM;
    }
    paH = d->dmaHandle.dma_pa >> 32;
    paL = d->dmaHandle.dma_pa & 0xFFFFFFFF;
    
    if (paL & d->dmaHandle.dma_pa_mask){
        d->dmaHandle.dma_pa_ofs = d->dmaHandle.dma_pa_mask + 1 - (paL & d->dmaHandle.dma_pa_mask);
        paL = (paL & ~(d->dmaHandle.dma_pa_mask)) + 1;
    }
    _write_reg(d, paH, d->bars[0].base + AXI_BASE_ADDR_TRANSLATION_CFG_REG_BAR0_U);
    _write_reg(d, paL, d->bars[0].base + AXI_BASE_ADDR_TRANSLATION_CFG_REG_BAR0_L);
    if (d->dmaHandle.dma_pa_ofs){
        _MSG_("cdma va:0x%lx, pa:0x%llx", (uintptr_t)d->dmaHandle.dma_va, d->dmaHandle.dma_pa);
        _MSG_("cdma mask:0x%x, offset:0x%x", d->dmaHandle.dma_pa_mask, d->dmaHandle.dma_pa_ofs);
    }else{
        _MSG_("cdma pa:0x%llx", d->dmaHandle.dma_pa);
    }
    return 0;
}

static long zpcidev_cdma_alloc(struct file *file){
    long ret;
    zpcidev_device *d = file_to_dev(file);
    _MSG_("+++ %s +++", d->miscDevName);
    ret = _zpcidev_cdma_alloc(d);
    _MSG_("---");
    return ret;
}

static long zpcidev_cdma_stop(struct file *file){
    long ret;
    zpcidev_device *d = file_to_dev(file);
    _MSG_("+++ %s +++", d->miscDevName);
    ret = _zpcidev_cdma_release(d);
    _MSG_("---");
    return ret;
}

// static void _create_test_data(void* ptr, __u32 size){
//     __u32 index = 0;
//     for (; index < size; index++){
//         ((__u8*)ptr)[index] = index;
//     }
// }

static void _print_user_data(void* ptr, __u32 size){
    __u32 index = 0;
    _MSG_("data show:");
    for (; index < size; index++){
        printk("0x%08X:0x%02X ", index, ((__u8*)ptr)[index]);
    }
}

static __inline void _zpcidev_cdma_init(cdma_ctrl_t* cdma){
    cdma->dma_size = CDMA_ALLOC_SIZE;
    cdma->dma_pa_mask = CDMA_ALLOC_SIZE - 1;
    cdma->dma_pa_ofs = 0;
    mutex_init(&cdma->dma_mtx);
}

static __inline int _zpcidev_cdma_pc_read_from_device(zpcidev_device *d, const PCIe_CDMA_MSG *cdmaMsg){
    int ret = 0;
    mutex_lock(&d->dmaHandle.dma_mtx);
    do{
        _DBG_("read device:0x%lX to local:0x%lX, size:0x%X", cdmaMsg->srcAddr, cdmaMsg->desAddr, cdmaMsg->dataSize);
        ret = zpcidev_cdma_setReg(d, cdmaMsg->srcAddr, CDMA_PC_REG_FLAG, cdmaMsg->dataSize);
        if (ret) {
            _ERR_("set reg failed.");
            break;
        }
        ret = zpcidev_cdma_wait_over(d, cdmaMsg->timeout_ms);
        if (ret) {
            _ERR_("read device:0x%lX to local:0x%lX, size:0x%X", cdmaMsg->srcAddr, cdmaMsg->desAddr, cdmaMsg->dataSize);
            _ERR_("wait over failed.");
            break;
        }
        // _print_user_data((__u8*)d->dmaHandle.dma_va + d->dmaHandle.dma_pa_ofs, cdmaMsg->dataSize);
        _DBG_("copy 2 user startup");
        ret = copy_to_user((void*)cdmaMsg->desAddr, (__u8*)d->dmaHandle.dma_va + d->dmaHandle.dma_pa_ofs, cdmaMsg->dataSize);
        if (ret) {
            _ERR_("copy to user failed, ret=%d", ret);
            break;
        }
        _DBG_("copy end.");
    }while (0);
    mutex_unlock(&d->dmaHandle.dma_mtx);
    return ret;
}

static __inline int _zpcidev_cdma_pc_write_to_device(zpcidev_device *d, const PCIe_CDMA_MSG *cdmaMsg){
    int ret = 0;
    mutex_lock(&d->dmaHandle.dma_mtx);
    do{
        _DBG_("copy from user startup");
        ret = copy_from_user((__u8*)d->dmaHandle.dma_va + d->dmaHandle.dma_pa_ofs, (void*)cdmaMsg->srcAddr, cdmaMsg->dataSize);
        if (ret) {
            _ERR_("copy from user failed, ret=%d", ret);   
            break;
        }
        _DBG_("copy end.");
        // _print_user_data((__u8*)d->dmaHandle.dma_va + d->dmaHandle.dma_pa_ofs, cdmaMsg->dataSize);
        _DBG_("write device:0x%lX from local:0x%lX, size:0x%X", cdmaMsg->desAddr, cdmaMsg->srcAddr, cdmaMsg->dataSize);
        ret = zpcidev_cdma_setReg(d, CDMA_PC_REG_FLAG, cdmaMsg->desAddr, cdmaMsg->dataSize);
        if (ret) {
            _ERR_("set flag error.");   
            break;
        }
        ret = zpcidev_cdma_wait_over(d, cdmaMsg->timeout_ms);
        if (ret) {
            _ERR_("write device:0x%lX from local:0x%lX, size:0x%X", cdmaMsg->desAddr, cdmaMsg->srcAddr, cdmaMsg->dataSize);
            _ERR_("wait over error.");   
            break;
        }
    }while (0);
    mutex_unlock(&d->dmaHandle.dma_mtx);
    return ret;
}

static __inline int _zpcidev_cdma_device_copy_to_device(zpcidev_device *d, const PCIe_CDMA_MSG *cdmaMsg){
    int ret = 0;
    mutex_lock(&d->dmaHandle.dma_mtx);
    do{
        _DBG_("copy device:0x%lX to device:0x%lX, size:0x%X", cdmaMsg->srcAddr, cdmaMsg->desAddr, cdmaMsg->dataSize);
        ret = zpcidev_cdma_setReg(d, cdmaMsg->srcAddr, cdmaMsg->desAddr, cdmaMsg->dataSize);
        if (ret) {
            _ERR_("zpcidev_cdma_setReg failed");
            break;
        }
        ret = zpcidev_cdma_wait_over(d, cdmaMsg->timeout_ms);
        if (ret) {
            _ERR_("copy device:0x%lX to device:0x%lX, size:0x%X", cdmaMsg->srcAddr, cdmaMsg->desAddr, cdmaMsg->dataSize);
            _ERR_("zpcidev_cdma_wait_over failed");   
            break;
        }
    }while (0);
    mutex_unlock(&d->dmaHandle.dma_mtx);
    return ret;
}

/********************************************************* */