/***************************************************************************/
/*                             Druckersoftware                             */
/*                    Copyright (C) Carl Valentin GmbH                     */
/***************************************************************************/

/*
 * dmaPayloadKM.h
 *
 * DMA-Transfers, die sich als Payload eingliedern in das in ../dmaFramework/dmaFramework.h implementierte Schema.
 * Es gibt (aktuell) 2 Payload-Varianten: eine fuer die JobApi im prtsoft, die andere fuer dieses Kernelmodul.
 * Dieser Payload fuer das KM wird durch den entsprechenden tcc-Wert zu TCC_KM (siehe generic_dma2.vhd) durch DMA-Channel-Verkettung
 * (chaining) ausgefuehrt.
 *
 *  Created on: 31.03.2022
 *      Author: preitinger
 */

#ifndef FPGASERIAL_DMAPAYLOADKM_H_
#define FPGASERIAL_DMAPAYLOADKM_H_

#include "../dmaFramework/dmaFramework.h"
#include "../KM_FpgaAddresses.h"
#include "fpgaRamKmAddresses.h"
#include "dmaCompletionThread.h"
#include "fpgaResponses.h"
#include "fpgaCommands.h"
#include "fpgaMem.h"

// Offsets in EDMA-Parametersatz (siehe SPRUH73K, Kap. 11.3.3.1)
#define EDMA_OFF_LINK        0x14

static struct dma_chan* payloadKMCh = NULL;
static int pkmDelayPs, pkmLinkPs, payloadKMCopyPs, pkmJustRespTypeAndIsrPs, pkmChainBackPs;

// Gut 830KB am Ende des grossen DMA-faehigenen Speicherblocks werden aktuell von der JobApi nicht verwendet,
// koennen also hier verwendet werden.

// Verwendete Offsets (relativ zum Ende des DMA-faehigen Puffers)
// DBE = DMA_BUF_END
#define DBE_RESPONSE_TYPE               (-2)
#define DBE_RX_COUNT                    (-4)

// TODO auch anpassen, wenn oben neue DBE_... - Konstanten eingefuegt werden
#define DBE_END DBE_RX_COUNT

typedef void (*TCmdCallback)(void* arg);


struct CmdCallbackEntry {
    TCmdCallback func;
    void* arg;

};

DEFINE_KFIFO(cmdCallbackFifo, struct CmdCallbackEntry, (1<<2));

static void* cpuBufEndAddr(void* mappedCpu, uint32_t mappedSize, off_t offEnd) {
    return  ((mappedCpu + mappedSize) + offEnd);
}

static uint32_t dmaBufEndAddr(dma_addr_t mappedDma, uint32_t mappedSize, int32_t offEnd) {
    return (uint32_t) ((mappedDma + mappedSize) + offEnd);
}

// zumindest im alten EDMA-Treiber wurde der dma completion handler
// direkt in einer ISR aufgerufen. Evtl. ist das nicht mehr so, aber
// zur Sicherheit so implementiert als ob es so waere, d.h.:
// hier nur nie-blockierende Aktionen um die eigentlichen
// Aktivitaeten im zugehoerigen kthread zu triggern.

// TODO DEFINE_KFIFO, kfifo_put, kfifo_get
// TODO DECLARE_WAITQUEUE_HEAD (eigentlich define ;-));
// im kthread: wait_event
// hier in ISR (oder auch schon thread, ist unbekannt...): wake_up
// herunterfahren:
// 1. sicherstellen dass dp_dmaCompletion nicht mehr aufgerufen werden kann, z.b. indem man den pinmux fuer fpga-interrupts wieder auf gpio umstellt und
//    dann den dma-kanal schliesst.
// 2. kfifo_put mit hinweis, dass kthread sich beenden soll
// 3. wake_up
// 4. kthread beendet sich darauf hin und feuert eine completion auf die in "exit" gewartet wird

static void
dp_dmaCompletion(unsigned channel, enum dma_status status, void* data) {
//    pr_info("dp_dmaCompletion: channel=%d, status=%d\n", (int) channel, (int) status);
    dmaCompletionFireCompletion(channel, status);

}

static void (*onRespRxCount)(void) = NULL;
static void (*onRespTxCount)(void) = NULL;
static void (*onDrain)(void) = NULL;


// diese Funktion wird in dmaCompletionThread.h registriert, welches eine Ebene darunter ist und von diesem Header hier nichts wissen soll
static void dmaPayloadKM_processDmaCompletion(unsigned channel, enum dma_status status) {
//    pr_info("dmaPayloadKM_processDmaCompletion ch=%d, status=%d\n", (int) channel, (int) status);
    switch (status) {
        case DMA_COMPLETE: {
            uint16_t* p;
            struct CmdCallbackEntry cb;
            int n;


            if (channel == edma_channel_num(payloadKMCh)) {
                // response type am Ende des DMA-Puffers checken und entsprechend Fallunterscheidung
                p = cpuBufEndAddr(dmaAddressesEnv.mappedCpu, dmaAddressesEnv.mappedSize, DBE_RESPONSE_TYPE);

//                pr_info("erhaltener response type: 0x%x\n", *p);

                switch (*p) {
                    case CV_FPGA_RESP_DONE:

                        n = kfifo_get(&cmdCallbackFifo, &cb);
                        if (likely(n == 1)) {
                            if (cb.func) {
                                (*cb.func)(cb.arg);
                            }
                        } else {
                            pr_warn("no callback found for event CV_FPGA_RESP_DONE\n");
                        }
                        break;

                    case CV_FPGA_RESP_RX_COUNT:
//                        pr_info("rx count: dmaPayloadKM_processDmaCompletion ch=%d, status=%d\n", (int) channel, (int) status);
                        onRespRxCount();
                        break;

                    case CV_FPGA_RESP_TX_COUNT:
//                        pr_info("tx count: dmaPayloadKM_processDmaCompletion ch=%d, status=%d\n", (int) channel, (int) status);
                        onRespTxCount();
                        break;

                    case CV_FPGA_RESP_DRAIN:
//                        pr_info("vor onDrain\n");
                        onDrain();
//                        pr_info("nach onDrain\n");
                        break;

                    default:
                        pr_err("Unerwarteter response type: %d\n", (int) *p);
                        break;

                }

                { // nun fuer jeden response type ein resp ack command. Erst wenn dies im fpga eingegangen ist,
                    // wird dort naechster dma mit einem response type zugelassen. Ohne kam es zur Auslesung veralteter response type werte :-(
#if 0 // alt
                    uint16_t cmd = create_resp_ack();
                    //        pr_info("tx buffer cmd: 0x%x\n", cmd);
                    DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM, cmd);
#else // neu
# ifndef TEST_UART_OFF_2
                    DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM_RESP_ACK, 1); // wert wird ignoriert; nur schreiben auf diese adresse ist relevant fuer triggerung von "resp_trig" im fpga-modul km_access
# endif
#endif
                }

#               ifdef TEST_DMA_PAYLOAD_KM
                {
                    uint16_t* p = cpuBufEndAddr(dmaAddressesEnv.mappedCpu, dmaAddressesEnv.mappedSize, DBE_RESPONSE_TYPE);
                    pr_info("erhaltener response type: 0x%x\n", *p);
                    ASSERT(*p == 0x1234);
                }
#               endif
            }
            break;
        }
        default:
            pr_err("Unused dma status for channel %d: %d\n", channel, status);
            ASSERT(false);
    }
}

static int dmaPayloadKM_init(void* mappedCpu, dma_addr_t mappedDma, uint32_t mappedSize, void (*onRespTxCount1)(void), void (*onRespRxCount1)(void), void (*onDrain1)(void)) {
    int res = 0;
    dma_cap_mask_t mask;
    struct CvHandler h = {
            dp_dmaCompletion,
            NULL
    };
    int payloadChanNum = -1;
    int payloadPs = -1;
    int start1ChanNum = -1;

    onRespRxCount = onRespRxCount1;
    onRespTxCount = onRespTxCount1;
    onDrain = onDrain1;

    res = dmaCompletionThread_init(&dmaPayloadKM_processDmaCompletion);

    if (res) {
        return res;
    }


    dmaengine_get();
    dma_cap_zero(mask);
    payloadKMCh = dma_request_chan_by_mask(&mask);

    if (IS_ERR_OR_NULL(payloadKMCh)) {
        res = -1;
        goto err_request_chan;
    }

    pkmDelayPs = my_edma_alloc_slot(-1);

    if (unlikely(pkmDelayPs < 0)) {
        res = -1;
        goto err_delayPs;
    }

    pkmLinkPs = my_edma_alloc_slot(-1);

    if (unlikely(pkmLinkPs < 0)) {
        res = -1;
        goto err_linkPs;
    }

    payloadKMCopyPs = my_edma_alloc_slot(-1);

    if (unlikely(payloadKMCopyPs < 0)) {
        res = -1;
        goto err_payloadKMCopy;
    }

    pkmJustRespTypeAndIsrPs = my_edma_alloc_slot(-1);

    if (unlikely(pkmJustRespTypeAndIsrPs < 0)) {
        res = -1;
        goto err_justIsr;
    }

    pkmChainBackPs = my_edma_alloc_slot(-1);

    if (unlikely(pkmChainBackPs < 0)) {
        res = -1;
        goto err_chainBackPs;
    }

    edma_setCvHandler(payloadKMCh, h);
    payloadChanNum = edma_channel_num(payloadKMCh);
    payloadPs = paramSetOfChannel(payloadChanNum);
    start1ChanNum = edma_channel_num(start1Chan);

//    dumpPsNum("payloadPs (zu payloadKMCh)", payloadPs);
//    dumpPsNum("pkmDelayPs", pkmDelayPs);
//    dumpPsNum("pkmLinkPs", pkmLinkPs);
//    dumpPsNum("payloadKMCopyPs", payloadKMCopyPs);
//    dumpPsNum("pkmJustRespTypeAndIsrPs", pkmJustRespTypeAndIsrPs);
//    dumpPsNum("pkmChainBackPs", pkmChainBackPs);


    {
        struct edmacc_param ps = {
                .opt = SYNCDIM | EDMA_TCC(payloadChanNum) | TCCHEN,
                .src = dmaReadAddrFpgaRamKm(FRKM_DMA_LINK_ADDR),
                .a_b_cnt = COMBINE_LO_HI(2, 1),
                .dst = dmaAddrEdmaParamset(pkmLinkPs) | EDMA_OFF_LINK,
                .src_dst_bidx = 0,
                .link_bcntrld = COMBINE_LO_HI(pkmDelayPs << 5, 0),
                .src_dst_cidx = 0,
                .ccnt = 1
        };

        my_edma_write_slot(edmaMappedIo1._a, payloadPs, &ps);
        my_edma_write_slot(edmaMappedIo1._a, payloadKMCopyPs, &ps);
    }

    {
        struct edmacc_param ps = {
                .opt = SYNCDIM | EDMA_TCC(payloadChanNum) | TCCHEN,
                .src = 0,
                .a_b_cnt = COMBINE_LO_HI(0, 0),
                .dst = 0,
                .src_dst_bidx = 0,
                .link_bcntrld = COMBINE_LO_HI(pkmLinkPs << 5, 0),
                .src_dst_cidx = 0,
                .ccnt = 1
        };

        my_edma_write_slot(edmaMappedIo1._a, pkmDelayPs, &ps);
    }

    {
        struct edmacc_param ps = {
                .opt = SYNCDIM | EDMA_TCC(payloadChanNum) | TCCHEN,
                .src = 0,
                .a_b_cnt = COMBINE_LO_HI(0, 0),
                .dst = 0,
                .src_dst_bidx = 0,
                .link_bcntrld = 0, // link wird dynamisch von payloadChanPs beschrieben, z.B. mit (pkmLinkPs << 5)
                .src_dst_cidx = 0,
                .ccnt = 1
        };

        my_edma_write_slot(edmaMappedIo1._a, pkmLinkPs, &ps);
    }

    { // nur resp_type aus km_ram im FPGA kopieren und ISR bzw. dma callback ausloesen mit payloadChanNum als TCC, dann weiter mit pkmChainBackPs
        struct edmacc_param ps = {
                .opt = SYNCDIM | EDMA_TCC(payloadChanNum) | TCCHEN | TCINTEN,
                .src = dmaReadAddrFpgaRamKm(FRKM_RESPONSE_TYPE),
                .a_b_cnt = COMBINE_LO_HI(2, 1),
                .dst = dmaBufEndAddr(mappedDma, mappedSize, DBE_RESPONSE_TYPE),
                .src_dst_bidx = 0,
                .link_bcntrld = COMBINE_LO_HI(pkmChainBackPs << 5, 0),
                .src_dst_cidx = 0,
                .ccnt = 1
        };

        my_edma_write_slot(edmaMappedIo1._a, pkmJustRespTypeAndIsrPs, &ps);
    }

    {
        struct edmacc_param ps = {
                .opt = SYNCDIM | EDMA_TCC(start1ChanNum)
#ifndef TEST_DMA_PAYLOAD_KM
                | TCCHEN
#endif
                ,
                .src = 0,
                .a_b_cnt = COMBINE_LO_HI(0, 0),
                .dst = 0,
                .src_dst_bidx = 0,
                .link_bcntrld = COMBINE_LO_HI(payloadKMCopyPs << 5, 0),
                .src_dst_cidx = 0,
                .ccnt = 1
        };

        my_edma_write_slot(edmaMappedIo1._a, pkmChainBackPs, &ps);
    }

//    pr_info("dmaPayloadKM_init successful\n");
    return 0;

    err_chainBackPs:

    my_edma_free_slot(pkmJustRespTypeAndIsrPs);

    err_justIsr:

    my_edma_free_slot(payloadKMCopyPs);

    err_payloadKMCopy:

    my_edma_free_slot(pkmLinkPs);

    err_linkPs:

    my_edma_free_slot(pkmDelayPs);

    err_delayPs:

    dma_release_channel(payloadKMCh);

    err_request_chan:

    dmaengine_put();
    dmaCompletionThread_exit();

    return res;
}

static void dmaPayloadKM_exit(void) {
    my_edma_free_slot(pkmChainBackPs);
    my_edma_free_slot(pkmJustRespTypeAndIsrPs);
    my_edma_free_slot(payloadKMCopyPs);
    my_edma_free_slot(pkmLinkPs);
    my_edma_free_slot(pkmDelayPs);
    dma_release_channel(payloadKMCh);
    payloadKMCh = NULL;

    dmaengine_put();
    dmaCompletionThread_exit();
}

#ifdef TEST_DMA_PAYLOAD_KM
void dmaPayloadKM_runTest(void) {
    int payloadKMChNum;

    // Simuliere RAM-Wert im FPGA:
    uint16_t* p = testCpuAddrFpgaKmRam(FRKM_DMA_LINK_ADDR);
    *p = (pkmJustRespTypeAndIsrPs << 5);
    p = testCpuAddrFpgaKmRam(FRKM_RESPONSE_TYPE);
    *p = 0x1234;
    p = cpuBufEndAddr(dmaAddressesEnv.mappedCpu, dmaAddressesEnv.mappedSize, DBE_RESPONSE_TYPE);
    *p = 0; // zur Kontrolle, ob Wert danach gesetzt

    pr_info("payloadKMCh=%p\n", payloadKMCh);
    payloadKMChNum = edma_channel_num(payloadKMCh);
    chan_trigger(payloadKMChNum);

}
#endif // ifdef TEST_DMA_PAYLOAD_KM


static void fpgaSendCmdWithResp(uint16_t cmdWord, TCmdCallback cb, void* cbArg) {
    int n;
    struct CmdCallbackEntry e = {
            .func = cb,
            .arg = cbArg
    };

    n = kfifo_put(&cmdCallbackFifo, e);
    ASSERT(n == 1);
    DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM, cmdWord);
}

#endif /* FPGASERIAL_DMAPAYLOADKM_H_ */
