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

/*
 * dma_tty_write.h
 *
 *  Created on: 23.03.2022
 *      Author: preitinger
 */


/*
 * Aktiviert als Payload durch den Mechanismus in dmaFramework.h
 *
 * Ein oder mehrere Teiluebertragungen ohne zwischenzeitliche Neukonfigurierung.
 *
 * Feste Fifogroesse im FPGA: TTY_WRITE_FIFO_SIZE
 * Feste Almost-Empty-Schranke fuer den Fifo im FPGA: TTY_WRITE_FIFO_ALMOST_EMPTY
 *
 * Ziel: jeder Einzeltransfer so, dass der Inhalt garantiert in den tty write fifo
 * im FPGA passt, wenn dieser "almost empty" ist, d.h. hoechstens TTY_WRITE_FIFO_ALMOST_EMPTY
 * Woerter enthaelt.
 * Ein Einzeltransfer besteht also immer aus bis zu TTY_WRITE_FIFO_SIZE - TTY_WRITE_FIFO_ALMOST_EMPTY Woertern.
 *
 * Offene Frage: Wie ungerade Byteanzahlen uebertragen. In jedem 16-Bit-Wort nur 1 Byte uebertragen,
 * kommt nicht in Frage.
 * Spontane Idee: 1. Byte ueber Extra-Register uebertragen.
 * Problem dabei: Wie weiss das FPGA, wie viele im FIFO noch vor diesem spezialwort drankommen?
 * Andere Idee: Schluesselwort (z.B. 0x0000) vor einem wort mit nur einem zu uebertragenden byte.
 * Ausserdem "Escape-Schluesselwort" (z.B. 0xffff), das bewirkt, dass naechstes Wort
 * (ggf. auch 0xffff oder 0x0000) woertlich uebertragen wird und kein Schluesselwort ist.
 * Aber wieder kompliziert... :-(
 * Noch andere Idee: fifo mit count und Kommando fuer "Einzelbyte".
 * Bei diesem Kommando koennte sich dann das FPGA den aktuellen fifo-count merken.
 * Aber Problem dabei: write/read-synchronisierung des counts. Faellt also weg :(
 *
 * Bisher scheint die "Escape-Loesung am besten". Aber grosser Nachteil:
 * kein schnelles memcpy moeglich, sondern jedes wort muss einzeln geprueft und ggf.
 * escaped geschrieben werden.
 *
 * Neue Idee:
 * Vor jedem Block die Anzahl der Bytes uebertragen.
 * Dann im FPGA bei jedem 16-Bit-Wort erst pruefen ob restzahl = 1, dann nur das low-byte
 * nehmen, sonst beide nehmen und restzahl um 2 verringern, bis restzahl = 0. Dann von vorn.
 *
 * Letztes "Anzahl-Konzept" wird implementiert.
 */

#ifndef DMAFRAMEWORK_DMA_TTY_WRITE_H_
#define DMAFRAMEWORK_DMA_TTY_WRITE_H_

#include "../dmaFramework/dmaFramework.h"

// "x geteilt durch y, ganzzahlig aufgerundet"
// cool, gibts schon im kernel fast wortwoertlich gleich:
//#define DIV_ROUND_UP(x, y) (((x) + (y) - 1) / (y))

/*
 * Anforderung: 1MBit/s, d.h. 1000000/8 Byte/s, d.h. 125 Byte / ms, d.h. fuer 32 Byte ist 1ms * 32/125 = 256us Zeit
 * Also reicht im FPGA Fifogroesse 64 Byte fuer den write-Fifo, wenn das FPGA selbst bei erreichen von almost_empty den
 * vom KM schon vorbereiteten Transfer triggert.
 * Allerdings muss das FPGA selbst mitzaehlen, wie oft es fuer den aktuell aktiven Transfer triggern darf, was
 * dem ccnt des Transfers entspricht.
 * Danach muss das FPGA pruefen, ob das KM einen zweiten Transfer schon vorbereitet hat und kann dann
 * mit diesem direkt fortfahren, oder es muss die Uebertragung stoppen bis das KM wieder einen Transfer
 * konfiguriert und beim FPGA angemeldet hat (incl. ccnt zum Mitzaehlen im FPGA).
 * Das KM reagiert jeweils auf die von ihm ja selbst konfigurierten edma-completion-interrupts um dann, wenn
 * Daten vorhanden sind, einen neuen Transfer zu konfigurieren und beim FPGA anzumelden, oder um in den idle-Zustand zu gehen.
 * Im Idle-Zustand wird dann beim naechsten write ans KM direkt ein Transfer konfiguriert und beim FPGA angemeldet,
 * ohne erst auf eine dma completion zu warten.
 * Es koennte dann die Zustaende idle1 und idle2 geben: bei idle1 ist noch ein DMA-Transfer aktiv, aber noch kein Folge-Transfer
 * angemeldet. Bei idle2 ist gar kein DMA-Transfer aktiv. Oder statt idle1 und idle2 eine Variable readyTransfers
 * mit Werten 0 fuer "idle2", 1 fuer "idle1" und 2 fuer "busy".
 * Anfangszustand waere dann readyTransfers=0 oder "idle2".
 */

#define TTY_WRITE_FIFO_SIZE 1024
#define TTY_WRITE_FIFO_ALMOST_EMPTY 512

//#define DMA_TTY_WRITE_TEST

static struct dma_chan* twChan = NULL;
static int twLastChunkPs, twCleanupPs;

static int dma_tty_write_init(void) {
    dma_cap_mask_t mask;
    int res = 0;

    dmaengine_get();


    dma_cap_zero(mask);
    twChan = dma_request_chan_by_mask(&mask);

    if (unlikely(IS_ERR_OR_NULL(twChan))) {
        res = (int) PTR_ERR(twChan);
        goto err_chan;
    }

    twLastChunkPs = my_edma_alloc_slot(-1);

    if (unlikely(twLastChunkPs < 0)) {
        res = -1;
        goto err_twLastChunkPs;
    }

    twCleanupPs = my_edma_alloc_slot(-1);

    if (unlikely(twCleanupPs < 0)) {
        res = -1;
        goto err_twCleanupPs;
    }

    return 0;

    err_twCleanupPs:
    my_edma_free_slot(twLastChunkPs);

    err_twLastChunkPs:
    dma_release_channel(twChan);
    twChan = NULL;

    err_chan:
    return res;
}

static void dma_tty_write_exit(void) {
    my_edma_free_slot(twCleanupPs);
    my_edma_free_slot(twLastChunkPs);
    dma_release_channel(twChan);
    twChan = NULL;
    dmaengine_put();
}

static void dma_tty_writeNotLastChunkPs(int psNum, int start1ChanNum, int chunkIn16Bit, int numChunks, int src, int dst, int numChunkBytes, int twLastChunkPs) {
    struct edmacc_param ps = {
            .opt = SYNCDIM // AB-synchronisiert
            | ITCCHEN | TCCHEN
            | EDMA_TCC(start1ChanNum),
            .src = src,
            .a_b_cnt = COMBINE_LO_HI(2, chunkIn16Bit),
            .dst = dst,
            .src_dst_bidx = COMBINE_LO_HI(2, 0),
            .link_bcntrld = COMBINE_LO_HI(twLastChunkPs << 5,
                    chunkIn16Bit),
            .src_dst_cidx = COMBINE_LO_HI(numChunkBytes, 0),
            .ccnt = numChunks - 1
    };
    ASSERT(numChunks > 1); // sonst waere es ja der letzte Chunk :-)
    my_edma_write_slot(edmaMappedIo1._a, psNum, &ps);
}

static void dma_tty_writeLastChunkPs(int psNum, int twChanNum, int chunkIn16Bit, int numChunks, int src, int dst, int numChunkBytes, int numBytes) {
    struct edmacc_param ps = {
            .opt = SYNCDIM // AB-synchronisiert
            | TCCHEN
            | TCINTEN
            | EDMA_TCC(twChanNum),
            .src = src + (numChunks - 1) * numChunkBytes,
            .a_b_cnt = COMBINE_LO_HI(2, (numBytes - (numChunks - 1) * numChunkBytes) >> 1),
            .dst = dst,
            .src_dst_bidx = COMBINE_LO_HI(2, 0),
            .link_bcntrld = COMBINE_LO_HI(twCleanupPs << 5,
                    0),
            .src_dst_cidx = COMBINE_LO_HI(0, 0), // kommt nicht zum einsatz, da ccnt = 1
            .ccnt = 1
    };
    ASSERT(numChunks >= 1);
    my_edma_write_slot(edmaMappedIo1._a, psNum, &ps);

}

#ifdef DMA_TTY_WRITE_TEST

struct TwTestData {
    void* mappedCpu;
    uint16_t expectedDstVal;
};
static struct TwTestData twTestData;

#endif // DMA_TTY_WRITE_TEST

static void twDmaCompletion(unsigned channel, enum dma_status status, void* data) {
#ifdef DMA_TTY_WRITE_TEST
    {
        struct TwTestData* d = &twTestData;
        uint16_t* dst = (uint16_t*) ((char*) d->mappedCpu + 1024);

        pr_info("twDmaCompletion channel=%u, status=%d\n", channel, (int) status);

        pr_info("*dst: 0x%x\n", (int) *dst);
        pr_info("*(uint8_t*)dst: 0x%x\n", (int) (*(uint8_t*)dst));
        ASSERT(d->expectedDstVal == *dst);
    }

#endif // DMA_TTY_WRITE_TEST
}


/**
 * chunkIn16Bit: Anzahl von 16-Bit-Worten, fuer die Platz im Fifo tty_write_fifo im FPGA
 * garantiert wird (weil das FPGA erst bei Almostempty='1' den DMA-Transfer triggert).
 */
static void dma_tty_prepare_paramsets(int numPayloadBytes, uint32_t src, uint32_t dst, int chunkIn16Bit) {
    // Per DMA muessen 1 + (numPayloadBytes + 1) / 2 16-Bit-Worte ab
    // DMA-Adresse src gelesen und in den Fifo tty_write_fifo im FPGA (DMA-Adresse dst)
    // geschrieben werden.


    // WIEDER ALT:
    // nicht ganz so moeglich wie urspruenglich geplant da fuer den interrupt
    // der tcc twChanNum gesetzt sein muss!
    // also 2 Faelle: numChunks == 1 oder numChunks > 1 (da numPayloadBytes > 0 vorausgesetzt
    // werden darf).
    // Im 1. Fall (numChunks == 1), wird twChan mit sich selbst (twChanNum) verkettet und der
    // transfer completion interrupt gesetzt
    // und verlinkt mit dem twCleanupPs.
    // Im 2. Fall, wird twChan mit start1ChanNum verkettet und mit twLastChunkPs verlinkt.
    // twLastChunkPs uebertraegt genau die letzten numChunkBytes, loest transfer completion
    // interrupt aus, daher mit twChanNum verkettet
    // und verlinkt mit twCleanupPs.
    // twCleanupPs ist ein unverlinkter dummy-Parametersatz, ohne interrupt verkettet mit
    // start1ChanNum

    // Ja obiges muesste passen.
    // Nein, passt wieder nicht, da bcntrld anders als angenommen bei AB-Synchronisierung
    // nicht verwendet wird. :-(

    // Neu:
    /*
     * 1. Fall: alles passt in den Fifo (numChunks = 1)
     *   twChanPs: completion-verkettet mit twChanNum, completion interrupt, verlinkt mit
     *     twCleanupPs,
     *     acnt = 2, bcnt = (numBytes >> 1), ccnt = 1
     *   twLastChunkPs: nicht verwendet, also Null-Parametersatz
     *
     * 2. Fall: numChunks > 1
     *   twChanPs: verkettet (beide arten) mit start1ChanNum, verlinkt mit twLastChunkPs,
     *   acnt = 2, bcnt = chunkIn16Bit, ccnt = numChunks - 1.
     *   twLastChunkPs: verkettet mit twChanNum, completion interrupt, verlinkt mit twCleanupPs,
     *   acnt = 2, bcnt = ((numBytes - (numChunks - 1) * numChunkBytes) >> 1),
     *   ccnt = 1
     *
     * twCleanupPs ist ein unverlinkter dummy-Parametersatz, ohne interrupt verkettet mit
     * start1ChanNum
     */

    int start1ChanNum, twChanNum;
    int numBytes = 2 + ((numPayloadBytes + 1) & (~1));
    int numChunkBytes = (chunkIn16Bit << 1);
    int numChunks;
    unsigned twChanPs;
    int numRestBytes;

    start1ChanNum = edma_channel_num(start1Chan);
    twChanNum = edma_channel_num(twChan);
    numChunks = DIV_ROUND_UP(numBytes, numChunkBytes);
    twChanPs = paramSetOfChannel(twChanNum);
    numRestBytes = numBytes - numChunkBytes * (numChunks - 1);

    pr_info("numChunks: %d\n", numChunks);
    pr_info("twChanNum=%d\n", twChanNum);

    if (numChunks > 1) {
        dma_tty_writeNotLastChunkPs(twChanPs, start1ChanNum, chunkIn16Bit, numChunks, src, dst, numChunkBytes, twLastChunkPs);
        dma_tty_writeLastChunkPs(twLastChunkPs, twChanNum, chunkIn16Bit, numChunks, src, dst, numChunkBytes, numBytes);
    } else {
        dma_tty_writeLastChunkPs(twChanPs,      twChanNum, chunkIn16Bit, numChunks, src, dst, numChunkBytes, numBytes);
    }

    {
        struct edmacc_param ps = {
                .opt = TCCHEN | EDMA_TCC(start1ChanNum),
                .src = 0,
                .a_b_cnt = COMBINE_LO_HI(0, 0),
                .dst = 0,
                .src_dst_bidx = 0,
                .link_bcntrld = 0xffff,
                .src_dst_cidx = 0,
                .ccnt = 1
        };

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

    {
        struct CvHandler h = {
                twDmaCompletion,
                NULL
        };
        edma_setCvHandler(twChan, h);
    }

    pr_info("twChanNum: %d\ntwChanPs: %d\ntwLastChunkPs: %d\ntwCleanupPs: %d\n", (int) twChanNum, (int) twChanPs, (int) twLastChunkPs, (int) twCleanupPs);


#ifdef ALT
    int numBytes = 2 + ((numPayloadBytes + 1) & (~1));
    int numChunkBytes = (chunkIn16Bit << 1);
    int numChunks;
    int numRestBytes;
    bool linkNeeded;
    unsigned chanPs;
    int start1ChanNum;
    int twChanNum;

    numChunks = DIV_ROUND_UP(numBytes, numChunkBytes);
    ASSERT(numChunks >= 1);
    linkNeeded = (numChunks > 1);
    ASSERT(linkNeeded == (numBytes > numChunkBytes)); // sonst wuerde DIV_ROUND_UP falsch rechnen
    numRestBytes = numBytes - numChunkBytes * (numChunks - 1);
    start1ChanNum = edma_channel_num(start1Chan);
    twChanNum = edma_channel_num(twChan);
    chanPs = paramSetOfChannel(twChanNum);

    {
        struct edmacc_param ps = {
                .opt = SYNCDIM // immer AB-Synchronisation
                // immer mit start1Chan verkettet (egal ob mit tw_rest_ps verlinkt - oder nicht)
                | EDMA_TCC(start1ChanNum)
                // immer transfer completion chaining und intermediate transfer completion chaining
                | TCCHEN | ITCCHEN
                // falls keine Verlinkung benoetigt, transfer completion interrupt, sonst dieser erst im verlinkten tw_rest_ps
                | (linkNeeded ? 0 : TCINTEN),
                .src = src,
                .a_b_cnt = COMBINE_LO_HI(2, numRestBytes >> 1),
                .dst = FPGA_ADDR_BASE | ADDR_OTH_TTY_WRITE,
                .src_dst_bidx = COMBINE_LO_HI(2, 0),
                .link_bcntrld = COMBINE_LO_HI(
                        linkNeeded ? (tw_rest_ps << 5) : 0xffff,
                        chunkIn16Bit),
                .src_dst_cidx = COMBINE_LO_HI(0, 0), // war noch falsch ;-)
                .ccnt = (linkNeeded ? numChunks - 1 : 1)
        };

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

    {
        int TODO = 0;
        struct edmacc_param ps = {
                .opt = (linkNeeded ? (
                        SYNCDIM // AB-Synchronisation
                        // immer mit start1Chan verkettet
                        | EDMA_TCC(start1ChanNum)
                        // immer transfer completion chaining und intermediate transfer completion chaining
                        | TCCHEN | ITCCHEN
                        // transfer completion interrupt
                        | TCINTEN
                ) : 0),
                .src = (linkNeeded ? (src + numBytes - numChunkBytes) : 0),
                .a_b_cnt = (linkNeeded ? TODO : 0),
                .dst = (linkNeeded ? TODO : 0),
                .src_dst_bidx = (linkNeeded ? COMBINE_LO_HI(TODO, TODO) : 0),
                .link_bcntrld = (linkNeeded ? COMBINE_LO_HI(TODO, TODO) : 0),
                .src_dst_cidx = (linkNeeded ? COMBINE_LO_HI(TODO, TODO) : 0),
                .ccnt = (linkNeeded ? TODO : 0)
        };
    }

#endif // ALT
}

#ifdef DMA_TTY_WRITE_TEST
static void dma_tty_write_runTest(void* mappedCpu, dma_addr_t mappedDma) {
    int numPayloadBytes;
    uint32_t src;
    uint32_t dst;
    int chunkIn16Bit;
    uint16_t* len;
    uint16_t* payload;

    pr_info("dma_tty_write_runTest\n");

    numPayloadBytes = 4; // TODO mehr werte testen
    src = mappedDma;
    dst = mappedDma + 1024;
#if 0
    chunkIn16Bit = 8; // TODO eigentlich mehr, erster test...
#else
    chunkIn16Bit = 1; // TODO eigentlich mehr, erster test...
#endif
    dma_tty_prepare_paramsets(numPayloadBytes, src, dst, chunkIn16Bit);

    ASSERT(((ptrdiff_t) mappedCpu & 1) == 0);
    len = (uint16_t*) mappedCpu;
    payload = len + 1;

    *len = numPayloadBytes;
    payload[0] = 0x0001;
    payload[1] = 0x0203;

    twTestData.mappedCpu = mappedCpu;
    twTestData.expectedDstVal = payload[1];


    chan_trigger(edma_channel_num(twChan));
    pr_info("twChan triggered #1\n");
    chan_trigger(edma_channel_num(twChan));
    pr_info("twChan triggered #2\n");
    chan_trigger(edma_channel_num(twChan));
    pr_info("twChan triggered #3\n");

    // TODO  test callback fuer twChan implementieren und pruefen ob das letzte wort 0x0203 (oder 0x0302? ;-)) korrekt in mappedCpu + 1024 steht
    // TODO vergewissern dass chaining in diesem test nix anstellt oder aber sicherstellen dass es nichts anstellt oder chaining testweise deaktivieren

}
#endif // ifdef DMA_TTY_WRITE_TEST

#endif /* DMAFRAMEWORK_DMA_TTY_WRITE_H_ */
