// SPDX-License-Identifier: MIT
/*
 * LPDDR4 DMA example for the Microchip PolarFire SoC
 *
 * Copyright (c) 2021 Microchip Technology Inc. All rights reserved.
 */

#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/types.h>

#include <dirent.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <poll.h>
#include <stdlib.h>
#include <assert.h>

#define UNCACHED_DDR_BASE   0xC8000000U
#define UNCACHED_DDR_BASE_2 0xC8002000U
#define SYSFS_PATH_LEN         (128)
#define ID_STR_LEN             (32)
#define UIO_DEVICE_PATH_LEN    (32)
#define NUM_UIO_DEVICES        (32)

#define FPROTOCONV_START   0x00000001U
#define FPROTOCONV_INCR    0x00000002U  // burst_type[1:0] = 01 (INCR)
#define FPROTOCONV_INTRENB 0x00000001U
#define FPROTOCONV_INTRSRC 0x00000001U
#define FSTREAM_SIZE            0x1000U

#define S2MM_VERSN (0x0 / 4)
#define MM2S_VERSN (0x400 / 4)

#define S2MM_CTRL (0x10 / 4)
#define S2MM_STS (0x14 / 4)
#define S2MM_LEN (0x18 / 4)
#define S2MM_ADDR0 (0x1C / 4)
#define S2MM_INTRENB (0x24 / 4) 
#define S2MM_INTRSRC (0x28 / 4)
#define MM2S_CTRL (0x410 / 4)
#define MM2S_STS (0x414 / 4)
#define MM2S_LEN (0x418 / 4)
#define MM2S_ADDR0 (0x41C / 4)
#define MM2S_INTRENB (0x424 / 4)
#define MM2S_INTRSRC (0x428 / 4)

#define DBG(fmt, ...) do { fprintf(stderr, "[DBG] " fmt "\n", ##__VA_ARGS__); } while(0)


static char uio_id_str_fprotoconv0[] = "protoconv0";
static char uio_id_str_fprotoconv1[] = "protoconv1";

static char sysfs_template[] = "/sys/class/uio/uio%d/%s";

static uint32_t get_memory_size(char *sysfs_path, char *uio_device)
{
        FILE *fp;
        uint32_t sz;

        /*
         * open the file the describes the memory range size.
         * this is set up by the reg property of the node in the device tree
         */
        fp = fopen(sysfs_path, "r");
        if (fp == NULL) {
                fprintf(stderr, "unable to determine size for %s\n", uio_device);
                exit(0);
        }
        fscanf(fp, "0x%016X", &sz);
        fclose(fp);
        return sz;
}

static int get_uio_device(char * id)
{
        FILE *fp;
        int i;
        size_t len;
        char file_id[ID_STR_LEN];
        char sysfs_path[SYSFS_PATH_LEN];

        for (i = 0; i < NUM_UIO_DEVICES; i++) {
                snprintf(sysfs_path, SYSFS_PATH_LEN, sysfs_template, i, "/name");
                fp = fopen(sysfs_path, "r");
                if (fp == NULL)
                        break;
                fscanf(fp, "%32s", file_id);
                len = strlen(id);
                if (len > ID_STR_LEN-1)
                        len = ID_STR_LEN-1;
                if (strncmp(file_id, id, len) == 0) {
                        return i;
                }
        }
        return -1;
}

static uint32_t get_bit(uint32_t r, size_t b)
{
        assert(b < 32);
        return (r >> b) & 1u;
}

static uint32_t get_bits(uint32_t r, size_t b, size_t s)
{
    assert(b < 32);
    assert(s > 0 && s <= 32);
    assert(b + s <= 32);
    
    return (r >> b) & ((1u << s) - 1);
}

int main(void)
{
        volatile uint32_t i = 0;
        uint32_t pending = 0;
        int dma_uio0;
        int dma_uio1;
        int mem_uio;
        int ret = 0;
        volatile uint32_t* ddr_mm2s = 0;
        volatile uint32_t* ddr_s2mm = 0;
        volatile uint32_t* proto_dev0 = 0;
        volatile uint32_t* proto_dev1 = 0;
        char uio_device0[UIO_DEVICE_PATH_LEN] = {'\0'};
        char uio_device1[UIO_DEVICE_PATH_LEN] = {'\0'};
        char sysfs_path[SYSFS_PATH_LEN] = {'\0'};
        uint32_t protoconv_mmap_size0;
        uint32_t protoconv_mmap_size1;
        int index0;
        int index1;

        printf("locating device for %s\n", uio_id_str_fprotoconv0);
        index0 = get_uio_device(uio_id_str_fprotoconv0);
        if (index0 < 0) {
                fprintf(stderr, "can't locate uio device for %s\n",
                                uio_id_str_fprotoconv0);
                return -1;
        }
	assert(index0 == 0);
        
	printf("locating device for %s\n", uio_id_str_fprotoconv1);
        index1 = get_uio_device(uio_id_str_fprotoconv1);
        if (index1 < 0) {
                fprintf(stderr, "can't locate uio device for %s\n",
                                uio_id_str_fprotoconv1);
                return -1;
        }
	assert(index1 == 1);

        snprintf(uio_device0, UIO_DEVICE_PATH_LEN, "/dev/uio%d", index0);
        printf("located %s\n", uio_device0);

        snprintf(uio_device1, UIO_DEVICE_PATH_LEN, "/dev/uio%d", index1);
        printf("located %s\n", uio_device1);

        dma_uio0 = open(uio_device0, O_RDWR);
        dma_uio1 = open(uio_device1, O_RDWR);
        if (dma_uio0 < 0) {
                fprintf(stderr, "cannot open %s: %s\n",
                                uio_device0, strerror(errno));
                return -1;
        } else {
                printf("opened %s (r,w)\n", uio_device0);
        }
        if (dma_uio1 < 0) {
                fprintf(stderr, "cannot open %s: %s\n",
                                uio_device1, strerror(errno));
                return -1;
        } else {
                printf("opened %s (r,w)\n", uio_device1);
        }
	

        snprintf(sysfs_path, SYSFS_PATH_LEN, sysfs_template, index0,
                        "maps/map0/size");
        protoconv_mmap_size0 = get_memory_size(sysfs_path, uio_device0);

        snprintf(sysfs_path, SYSFS_PATH_LEN, sysfs_template, index1,
                        "maps/map0/size");
        protoconv_mmap_size1 = get_memory_size(sysfs_path, uio_device1);
        if (protoconv_mmap_size0 == 0) {
                fprintf(stderr, "bad memory size for %s\n", uio_device0);
                return -1;
        }
        if (protoconv_mmap_size1 == 0) {
                fprintf(stderr, "bad memory size for %s\n", uio_device1);
                return -1;
        }
        printf("Protoconv0 memory size 0x%x\n", protoconv_mmap_size0);
        printf("Protoconv1 memory size 0x%x\n", protoconv_mmap_size1);
        proto_dev0 = mmap(NULL, protoconv_mmap_size0,
                        PROT_READ|PROT_WRITE, MAP_SHARED, dma_uio0, 0);
        if (proto_dev0 == MAP_FAILED) {
                fprintf(stderr, "cannot mmap %s: %s\n", uio_device0, strerror(errno));
                return -1;
        } else {
                printf("mapped 0x%x bytes for %s\n", protoconv_mmap_size0, uio_device0);
        }
        proto_dev1 = mmap(NULL, protoconv_mmap_size1,
                        PROT_READ|PROT_WRITE, MAP_SHARED, dma_uio1, 0);
        if (proto_dev1 == MAP_FAILED) {
                fprintf(stderr, "cannot mmap %s: %s\n", uio_device1, strerror(errno));
                return -1;
        } else {
                printf("mapped 0x%x bytes for %s\n", protoconv_mmap_size1, uio_device1);
        }
        printf("MM2S version %d.%d\n",
                        get_bits(proto_dev0[MM2S_VERSN], 24, 8),
                        get_bits(proto_dev0[MM2S_VERSN], 16, 8));
        printf("MM2S version %d.%d\n",
                        get_bits(proto_dev1[MM2S_VERSN], 24, 8),
                        get_bits(proto_dev1[MM2S_VERSN], 16, 8));

        printf("S2MM version %d.%d\n",
                        get_bits(proto_dev0[S2MM_VERSN], 24, 8),
                        get_bits(proto_dev0[S2MM_VERSN], 16, 8));
        printf("S2MM_STS = 0x%x\n", proto_dev0[S2MM_STS]);
        printf("MM2S_STS = 0x%x\n", proto_dev0[MM2S_STS]);
        printf("MM2S_STS = 0x%x\n", proto_dev1[MM2S_STS]);
/*
        assert((proto_dev[MM2S_STS] & 0x1) == 1); 
        assert((proto_dev[S2MM_STS] & 0x1) == 1); 

        proto_dev[MM2S_CTRL] = 0;
        proto_dev[S2MM_CTRL] = 0;
        proto_dev[MM2S_LEN]  = 0;
        proto_dev[S2MM_LEN]  = 0;
        proto_dev[MM2S_ADDR0] = 0;
        proto_dev[S2MM_ADDR0] = 0;
        proto_dev[S2MM_INTRENB] = 0;

        mem_uio = open("/dev/mem", O_RDWR);
        if (mem_uio == -1) {
                perror("Error opening /dev/mem"); 
                return -1;
        }
        // MM2S
        ddr_mm2s = mmap(NULL, FSTREAM_SIZE, PROT_READ | PROT_WRITE,
                        MAP_SHARED, mem_uio, UNCACHED_DDR_BASE);
        if (ddr_mm2s == MAP_FAILED) {
                fprintf(stderr, "Cannot mmap: %s\n", strerror(errno));
                close(mem_uio);
                return -1;
        } else {
                printf("mmap at %x successful\n", UNCACHED_DDR_BASE);
        }
        //S2MM
        ddr_s2mm = mmap(NULL, FSTREAM_SIZE, PROT_READ | PROT_WRITE,
                        MAP_SHARED, mem_uio, UNCACHED_DDR_BASE_2);
        if (ddr_s2mm == MAP_FAILED) {
                fprintf(stderr, "Cannot mmap: %s\n", strerror(errno));
                close(mem_uio);
                exit(0);
        } else {
                printf("mmap at %x successful\n", UNCACHED_DDR_BASE_2);
        }

*/
         /* --- Clear any pending interrupt sources from previous runs --- */
	/*
         DBG("clearing S2MM_INTRSRC");
         proto_dev[S2MM_INTRSRC] = 0xFF;
         DBG("clearing MM2S_INTRSRC");
         proto_dev[MM2S_INTRSRC] = 0xFF;

         DBG("reading S2MM_STS before setup: 0x%x", proto_dev[S2MM_STS]);
         DBG("reading MM2S_STS before setup: 0x%x", proto_dev[MM2S_STS]);

         DBG("writing ddr_mm2s/ddr_s2mm (16 words)");
         for (i = 0; i < 16; ++i) {
                 ddr_mm2s[i] = i;
                 ddr_s2mm[i] = 0xDEADBEEF;
         }
         DBG("DDR writes done");

         DBG("setting MM2S_ADDR0 = 0x%x", UNCACHED_DDR_BASE);
         proto_dev[MM2S_ADDR0] = UNCACHED_DDR_BASE;
         DBG("setting MM2S_LEN = %d", 16 * 4);
         proto_dev[MM2S_LEN] = 16 * 4;

         DBG("setting S2MM_ADDR0 = 0x%x", UNCACHED_DDR_BASE_2);
         proto_dev[S2MM_ADDR0] = UNCACHED_DDR_BASE_2;
         DBG("setting S2MM_LEN = %d", 16 * 4);
         proto_dev[S2MM_LEN] = 16 * 4;
         proto_dev[S2MM_INTRENB] = proto_dev[S2MM_INTRENB] | FPROTOCONV_INTRENB; 

         DBG("disabling S2MM interrupts");
         proto_dev[S2MM_INTRENB] = 0x0;
         DBG("disabling MM2S interrupts");
         proto_dev[MM2S_INTRENB] = 0x0;
*/
         /* Readback to verify register writes */
/*
         DBG("readback: MM2S_ADDR0=0x%x MM2S_LEN=0x%x", proto_dev[MM2S_ADDR0], proto_dev[MM2S_LEN]);
         DBG("readback: S2MM_ADDR0=0x%x S2MM_LEN=0x%x", proto_dev[S2MM_ADDR0], proto_dev[S2MM_LEN]);
         DBG("readback: S2MM_INTRENB=0x%x MM2S_INTRENB=0x%x", proto_dev[S2MM_INTRENB], proto_dev[MM2S_INTRENB]);

         DBG("starting S2MM (INCR | START)");
         proto_dev[S2MM_CTRL] = FPROTOCONV_INCR | FPROTOCONV_START;
         DBG("starting MM2S (INCR | START)");
         proto_dev[MM2S_CTRL] = FPROTOCONV_INCR | FPROTOCONV_START;
*/
         /*
          * Poll for completion.
          * IMPORTANT: status register is single-read-valid —
          * read ONCE per iteration, cache the result.
          */
/*  
       {
                 uint32_t mm2s_done = 0, s2mm_done = 0;
                 for (i = 0; i < 100 && (!mm2s_done || !s2mm_done); i++) {
                         uint32_t mm2s_sts = proto_dev[MM2S_STS];
                         uint32_t s2mm_sts = proto_dev[S2MM_STS];
                         if (mm2s_sts & 0x1) mm2s_done = 1;
                         if (s2mm_sts & 0x1) s2mm_done = 1;
                         DBG("  [%d] MM2S_STS=0x%08x(%s) S2MM_STS=0x%08x(%s)",
                             i, mm2s_sts, mm2s_done ? "done" : "busy",
                             s2mm_sts, s2mm_done ? "done" : "busy");
                         if (mm2s_done && s2mm_done)
                                 break;
                         usleep(1000);
                 }
                 if (!mm2s_done || !s2mm_done) {
                         fprintf(stderr, "ERROR: transfer timeout! mm2s=%s s2mm=%s\n",
                                 mm2s_done ? "done" : "STUCK",
                                 s2mm_done ? "done" : "STUCK");
                 } else {
                         printf("MM2S + S2MM transfers done\n");
                 }
         }

         for (i = 0; i < 16; ++i) {
                 if (ddr_mm2s[i] != ddr_s2mm[i]) {
                         printf("PROTCONV transfer failed [%d]: sent 0x%x, got 0x%x\n",
                                 i, ddr_mm2s[i], ddr_s2mm[i]);
                 }
         }
         ret = munmap((void*)ddr_mm2s, FSTREAM_SIZE);
         if(ret < 0) {
                 printf("unable to unmap the ddr_mm2s\n");
         } else {
                 printf("unmap the ddr_mm2s\n");
         }
         ret = munmap((void*)ddr_s2mm, FSTREAM_SIZE);
         if(ret < 0) {
                 printf("unable to unmap the ddr_s2mm\n");
         } else {
                 printf("unmap the ddr_s2mm\n");
         }
         close(mem_uio);
*/
         ret = munmap((void*)proto_dev0, protoconv_mmap_size0);
         if(ret < 0) {
                 printf("unable to unmap the fprotoconv0\n");
         } else {
                 printf("unmap the fprotoconv0\n");
         }
         
	 ret = munmap((void*)proto_dev1, protoconv_mmap_size1);

         if(ret < 0) {
                 printf("unable to unmap the fprotoconv1\n");
         } else {
                 printf("unmap the fprotoconv1\n");
         }

         close(dma_uio0);
         close(dma_uio1);
         return 0;
}

