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

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



#ifndef DMAFRAMEWORK_DMA_TTY_WRITE_H_
#define DMAFRAMEWORK_DMA_TTY_WRITE_H_

#include "../dmaFramework/dmaFramework.h"
#include "tx_size_fifo.h"
#include "fpgaCommands.h"
#include <linux/spinlock.h>
#include <linux/atomic.h>
#include <linux/completion.h>

#define INIT_RESP_COUNT_DOWN            4
#define SRC_TX_NUM                      (2 * INIT_RESP_COUNT_DOWN)
#define TX_DST_CAPACITY                 512

typedef struct {
    struct dma_chan* chan;
    int copyPs;
} TWriteDest;

enum ECharsInBufferState {
    EC_RESET,
    EC_DRAINING
};

typedef struct {
    int32_t srcEndOffset;
    TWriteDest tty_writeDest[2];
    int srcTxIdx;
    atomic_t atomSrcTxFree;
    TTxSizeFifo txSizeFifo;
    spinlock_t sl;
//    struct mutex m;

    int lenSickExtraBuf;
    char sickExtraBuf[TX_DST_CAPACITY];
    bool waitingUntilSent;

    int dbgCountSum;
    int dbgFifoSum;
    struct tty_struct* tty;
    struct completion waitUntilSentCompletion;

    enum ECharsInBufferState cState;
    int cLastCharsInBuffer;

    bool stopped;
    int openCount;

    atomic_t dbgOrderCount;
    int orderStop, orderStart;
    int lenOrderWrites;
    int orderWrites[8];
    int lastCharsInBuffer;
    int orderCharsInBuffer;
    int lastWriteRoom;
    int orderWriteRoom;
} TTtyWrite;

static TTtyWrite ttyWrite;

static void initParamsets(TWriteDest* d, int32_t src, uint32_t dst) {
    int start1ChanNum;
    int chanNum = edma_channel_num(d->chan);
    int chanPs = paramSetOfChannel(chanNum);

    start1ChanNum = edma_channel_num(start1Chan);
    {
        struct edmacc_param ps = {
                .opt = SYNCDIM // AB-synchronisiert
                | ITCCHEN | TCCHEN
                | EDMA_TCC(start1ChanNum),
                .src = dmaBufEndAddr(dmaAddressesEnv.mappedDma, dmaAddressesEnv.mappedSize, src),
                .a_b_cnt = COMBINE_LO_HI(2, TX_DST_CAPACITY / 2),
                .dst = dst,
                .src_dst_bidx = COMBINE_LO_HI(2, 2),
                .link_bcntrld = COMBINE_LO_HI(d->copyPs << 5, TX_DST_CAPACITY / 2),
                .src_dst_cidx = COMBINE_LO_HI(TX_DST_CAPACITY * 2, 0),
                .ccnt = SRC_TX_NUM / 2
        };

        ASSERT((TX_DST_CAPACITY & 1) == 0); // muss gerade sein
//        pr_info("chanPs=%d\n", chanPs);
//        pr_info("copyPs=%d\n", d->copyPs);
        my_edma_write_slot(edmaMappedIo1._a, d->copyPs, &ps);
        my_edma_write_slot(edmaMappedIo1._a, chanPs, &ps);
    }
}



static int tty_writeDest_init(TWriteDest* d, int32_t src, uint32_t dst) {
    dma_cap_mask_t mask;
    int res = 0;
//    int start1ChanNum;

    dma_cap_zero(mask);
    d->chan = dma_request_chan_by_mask(&mask);

    if (unlikely(IS_ERR_OR_NULL(d->chan))) {
        return PTR_ERR(d->chan);
    }

    d->copyPs = my_edma_alloc_slot(-1);

    if (unlikely(d->copyPs < 0)) {
        res = d->copyPs;
        goto err_ps;
    }

    initParamsets(d, src, dst);

//    start1ChanNum = edma_channel_num(start1Chan);
//
//    {
//        int chanNum = edma_channel_num(d->chan);
//        int chanPs = paramSetOfChannel(chanNum);
//        struct edmacc_param ps = {
//                .opt = SYNCDIM // AB-synchronisiert
//                | ITCCHEN | TCCHEN
//                | EDMA_TCC(start1ChanNum),
//                .src = dmaBufEndAddr(dmaAddressesEnv.mappedDma, dmaAddressesEnv.mappedSize, src),
//                .a_b_cnt = COMBINE_LO_HI(2, TX_DST_CAPACITY / 2),
//                .dst = dst,
//                .src_dst_bidx = COMBINE_LO_HI(2, 2),
//                .link_bcntrld = COMBINE_LO_HI(d->copyPs << 5, TX_DST_CAPACITY / 2),
//                .src_dst_cidx = COMBINE_LO_HI(TX_DST_CAPACITY * 2, 0),
//                .ccnt = SRC_TX_NUM
//        };
//
//        ASSERT((TX_DST_CAPACITY & 1) == 0); // muss gerade sein
//        pr_info("chanPs=%d\n", chanPs);
//        pr_info("copyPs=%d\n", d->copyPs);
//        my_edma_write_slot(edmaMappedIo1._a, d->copyPs, &ps);
//        my_edma_write_slot(edmaMappedIo1._a, chanPs, &ps);
//    }

    return 0;

    err_ps:
    dma_release_channel(d->chan);

    return res;

}

static void tty_writeDest_exit(TWriteDest* d) {
    my_edma_free_slot(d->copyPs);
    dma_release_channel(d->chan);
}

static int dma_tty_write_init(int32_t* dmaOffEnd) {
    TTtyWrite* p = &ttyWrite;
    int res = 0, i, j;

    dmaengine_get();

    p->srcEndOffset = *dmaOffEnd - (SRC_TX_NUM * TX_DST_CAPACITY);

    for (i = 0; i < 2; ++i) {
        res = tty_writeDest_init(&p->tty_writeDest[i], p->srcEndOffset + i * TX_DST_CAPACITY, dmaWriteAddrFpgaRamKm(i == 0 ? FRKM_TX_DST_BEGIN_0 : FRKM_TX_DST_BEGIN_1));

        if (unlikely(res)) {
            for (j = i - 1; j >= 0; --j) {
                tty_writeDest_exit(&p->tty_writeDest[j]);
            }

            dmaengine_put();
            return res;
        }
    }


    *dmaOffEnd = p->srcEndOffset;
    p->srcTxIdx = 0;
    atomic_set(&p->atomSrcTxFree, SRC_TX_NUM);
    for (i = 0; i < 2; ++i) {
        initParamsets(&p->tty_writeDest[i], p->srcEndOffset + i * TX_DST_CAPACITY, dmaWriteAddrFpgaRamKm(i == 0 ? FRKM_TX_DST_BEGIN_0 : FRKM_TX_DST_BEGIN_1));
    }
    tx_size_fifo_init(&p->txSizeFifo, FRKM_TX_SIZE_FIFO_BEGIN, FRKM_TX_SIZE_FIFO_BEGIN + SRC_TX_NUM);
//    mutex_init(&p->m);
    spin_lock_init(&p->sl);

    p->lenSickExtraBuf = 0;
    p->tty = NULL;
    p->stopped = false;
    p->openCount = 0;

    return 0;

}

// darf nur intern mit gelocktem ttyWrite.sl aufgerufen werden:
static bool twInternStart(void) {
    TTtyWrite* p = &ttyWrite;
    bool changed = false;

    if (p->stopped) {
        // send command "stop_start_cmd"
        uint16_t cmd = create_stop_start_cmd();
        DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM, cmd);
        p->stopped = false;
        pr_info("tty_write: new state unstopped\n");
        changed = true;
    }

    return changed;
}

static void twOnOpen(struct tty_struct* tty) {
    TTtyWrite* p = &ttyWrite;
    int i;
    unsigned long spinFlags;

    //    mutex_lock(&p->m);
    // jetzt in init:
#if 0
    spin_lock_irqsave(&p->sl, spinFlags);

    p->srcTxIdx = 0;
    p->srcTxFree = SRC_TX_NUM;
    for (i = 0; i < 2; ++i) {
        initParamsets(&p->tty_writeDest[i], p->srcEndOffset + i * TX_DST_CAPACITY, dmaWriteAddrFpgaRamKm(i == 0 ? FRKM_TX_DST_BEGIN_0 : FRKM_TX_DST_BEGIN_1));
    }
    tx_size_fifo_init(&p->txSizeFifo, FRKM_TX_SIZE_FIFO_BEGIN, FRKM_TX_SIZE_FIFO_BEGIN + SRC_TX_NUM);
    //    ASSERT(mutex_is_locked(&p->m));
    //    mutex_unlock(&p->m);
    spin_unlock_irqrestore(&p->sl, spinFlags);
#endif

    spin_lock_irqsave(&p->sl, spinFlags);

    if (p->openCount++ == 0) {
        p->tty = tty;

        p->waitingUntilSent = false;
        p->cState = EC_RESET;
        p->cLastCharsInBuffer = -1;

        atomic_set(&p->dbgOrderCount, 0);
        p->dbgCountSum = 0;
        p->dbgFifoSum = 0;
        p->orderStop = p->orderStart = -1;

        p->lenOrderWrites = 0;

        for (i = 0; i < 4; ++i) {
            p->orderWrites[i] = 0;
        }

        p->lastCharsInBuffer = -1;
        p->orderCharsInBuffer = -1;
        p->lastWriteRoom = -1;
        p->orderWriteRoom = -1;
    }

    twInternStart();

    spin_unlock_irqrestore(&p->sl, spinFlags);
}

/**
 * @return   true iff was last close, that means open_count has become == 0
 */
static bool twOnClose(void) {
    TTtyWrite* p = &ttyWrite;
//    int i;
    unsigned long spinFlags;
    bool wasLast = false;


//    mutex_lock(&p->m);
    // jetzt in init:
#if 0
    spin_lock_irqsave(&p->sl, spinFlags);

    p->srcTxIdx = 0;
    p->srcTxFree = SRC_TX_NUM;
    for (i = 0; i < 2; ++i) {
        initParamsets(&p->tty_writeDest[i], p->srcEndOffset + i * TX_DST_CAPACITY, dmaWriteAddrFpgaRamKm(i == 0 ? FRKM_TX_DST_BEGIN_0 : FRKM_TX_DST_BEGIN_1));
    }
    tx_size_fifo_init(&p->txSizeFifo, FRKM_TX_SIZE_FIFO_BEGIN, FRKM_TX_SIZE_FIFO_BEGIN + SRC_TX_NUM);
//    ASSERT(mutex_is_locked(&p->m));
//    mutex_unlock(&p->m);
    spin_unlock_irqrestore(&p->sl, spinFlags);
#endif

    spin_lock_irqsave(&p->sl, spinFlags);

    if (--p->openCount == 0) {

        p->tty = NULL;
        wasLast = true;
        //    pr_info("Zustand in close:\n");
        //    pr_info("write: dbgCountSum=%d\n", (int) p->dbgCountSum);
        //    pr_info("dbgFifoSum=%d\n", (int) p->dbgFifoSum);
        //    pr_info("p->lenSickExtraBuf=%d\n", (int) p->lenSickExtraBuf);
        //    pr_info("orderStop=%d, orderStart=%d\n", p->orderStop, p->orderStart);
        //
        //    for (i = 0; i < p->lenOrderWrites; ++i) {
        //        pr_info("orderWrites[%d] = %d\n", i, p->orderWrites[i]);
        //    }
        //
        //    pr_info("lastCharsInBuffer = %d\n", p->lastCharsInBuffer);
        //    pr_info("orderCharsInBuffer = %d\n", p->orderCharsInBuffer);
        //    pr_info("lastWriteRoom = %d\n", p->lastWriteRoom);
        //    pr_info("orderWriteRoom = %d\n", p->orderWriteRoom);
    }

    spin_unlock_irqrestore(&p->sl, spinFlags);

    return wasLast;
}

static void twOnShutdown(void) {
//    TTtyWrite* p = &ttyWrite;
//
//    pr_info("Zustand in shutdown:\n");
//
//    pr_info("lastCharsInBuffer = %d\n", p->lastCharsInBuffer);
//    pr_info("orderCharsInBuffer = %d\n", p->orderCharsInBuffer);
//    pr_info("lastWriteRoom = %d\n", p->lastWriteRoom);
//    pr_info("orderWriteRoom = %d\n", p->orderWriteRoom);

}

static void twOnCleanup(void) {
//    TTtyWrite* p = &ttyWrite;
//
//    pr_info("Zustand in cleanup:\n");
//
//    pr_info("lastCharsInBuffer = %d\n", p->lastCharsInBuffer);
//    pr_info("orderCharsInBuffer = %d\n", p->orderCharsInBuffer);
//    pr_info("lastWriteRoom = %d\n", p->lastWriteRoom);
//    pr_info("orderWriteRoom = %d\n", p->orderWriteRoom);

}

static void dma_tty_write_prepare_ram_km(void) {
    TTtyWrite* p = &ttyWrite;
    unsigned long spinFlags;

//    mutex_lock(&p->m);
    spin_lock_irqsave(&p->sl, spinFlags);
    ramKmWrite16(FRKM_INIT_TX_TCC_0, (uint16_t) edma_channel_num(p->tty_writeDest[0].chan));
    ramKmWrite16(FRKM_INIT_TX_TCC_1, (uint16_t) edma_channel_num(p->tty_writeDest[1].chan));
//    ASSERT(mutex_is_locked(&p->m));
//    mutex_unlock(&p->m);
    spin_unlock_irqrestore(&p->sl, spinFlags);
}

/*
Aus https://docs.huihoo.com/doxygen/linux/kernel/3.7/include_2linux_2tty_8h.html#a1620b355605f95c29691205f487e21bb

Return the number of bytes that can be queued to this device at the present time.
The result should be treated as a guarantee and the driver cannot offer a value it later shrinks by
more than the number of bytes written. If no method is provided 2K is always returned and data may be
lost as there will be no flow control.
 */
static int dma_tty_write_room(void) {
    TTtyWrite* p = &ttyWrite;
//    unsigned long spinFlags;
    int res;

    res = atomic_read(&p->atomSrcTxFree);
    p->lastWriteRoom = res * TX_DST_CAPACITY;
    p->orderWriteRoom = atomic_inc_return(&p->dbgOrderCount);

//    pr_info("dma_tty_write_room returning %d\n", res * TX_DST_CAPACITY);
    return res * TX_DST_CAPACITY;
}

static void twOnStop(void) {
    TTtyWrite* p = &ttyWrite;
    unsigned long spinFlags;
    bool changed = false;

    p->orderStop = atomic_inc_return(&p->dbgOrderCount);

    spin_lock_irqsave(&p->sl, spinFlags);

    if (!p->stopped) {
        // send command "stop_start_cmd"
        uint16_t cmd = create_stop_start_cmd();
        DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM, cmd);
        p->stopped = true;
        pr_info("tty_write: new state stopped\n");
        changed = true;
    }

    spin_unlock_irqrestore(&p->sl, spinFlags);

    if (changed) {
        pr_info("tx in FPGA stopped.\n");
    }
}

static void twOnStart(void) {
    TTtyWrite* p = &ttyWrite;
    unsigned long spinFlags;
    bool changed = false;

    p->orderStart = atomic_inc_return(&p->dbgOrderCount);

    spin_lock_irqsave(&p->sl, spinFlags);

    changed = twInternStart();

    spin_unlock_irqrestore(&p->sl, spinFlags);

    if (changed) {
        pr_info("tx in FPGA re-started.\n");
    }
}

/**
 * @return Anzahl tatsaechlich geschriebener Bytes ab *data
 */
static int dma_tty_write_onWrite(const unsigned char *data, int size) {
    TTtyWrite* p = &ttyWrite;
    unsigned int addCount = 0;
    uint8_t* cpuBuf;
    int res = 0;
    unsigned long spinFlags;
    int tmpLen;

    ASSERT(size >= 0);
//    mutex_lock(&p->m);
    spin_lock_irqsave(&p->sl, spinFlags);

    if (p->lenOrderWrites < 8) {
        p->orderWrites[p->lenOrderWrites++] = atomic_inc_return(&p->dbgOrderCount);
    }


#if 0 // Pseudo-Code aus Activity Diagram "COM TX Ablauf detailliert":
    memcpy(srcBuf + srcTxIdx * TX_DST_CAPACITY, data, min(size, TX_DST_CAPACITY));
    srcTxIdx = (srcTxIdx + 1) %  srcTxNum;
    --srcTxFree;
    data += TX_DST_CAPACITY;
    size -= min(size, TX_DST_CAPACITY);
#endif

//    pr_info("dma_tty_write_onWrite:\nsrcTxFree=%d\n, srcTxIdx=%d\n, size=%d\n", atomic_read(&p->atomSrcTxFree), p->srcTxIdx, size);

    ASSERT(size == 0 || data);
    ASSERT(size >= 0);
    ASSERT(atomic_read(&p->atomSrcTxFree) >= 0);
    cpuBuf = cpuBufEndAddr(dmaAddressesEnv.mappedCpu, dmaAddressesEnv.mappedSize, p->srcEndOffset);



#if 1
    while ((p->lenSickExtraBuf > 0 || size > 0) && atomic_read(&p->atomSrcTxFree) > 0) {
#else
        while ((p->lenSickExtraBuf > 0 || size > 0)) {
            spin_unlock_irqrestore(&p->sl, spinFlags);
            while (atomic_read(&p->atomSrcTxFree) == 0) {
                msleep(2);
            }
            spin_lock_irqsave(&p->sl, spinFlags);
#endif

        if (p->lenSickExtraBuf > 0) {
            pr_info("Reiche %d Bytes aus dem sickExtraBuf nach...\n", p->lenSickExtraBuf);
            tmpLen = p->lenSickExtraBuf;
            ASSERT(tmpLen <= TX_DST_CAPACITY);
            memcpy(cpuBuf + p->srcTxIdx * TX_DST_CAPACITY, p->sickExtraBuf, tmpLen);
            p->lenSickExtraBuf = 0;
            // nun ggf. noch auffuellen mit Inhalt von data

            if (size > 0 && tmpLen < TX_DST_CAPACITY) {
                int rest = min(size, TX_DST_CAPACITY - tmpLen);
                pr_info("Haenge %d regulaere Bytes dran...\n", rest);
                memcpy(cpuBuf + p->srcTxIdx * TX_DST_CAPACITY + tmpLen, data, rest);
                data += rest;
                size -= rest;
                res += rest;
                tmpLen += rest;
            }
        } else {
            ASSERT(size > 0);
            tmpLen = min(size, TX_DST_CAPACITY);


            memcpy(cpuBuf + p->srcTxIdx * TX_DST_CAPACITY, data, tmpLen);
            data += tmpLen;
            size -= tmpLen;
            res += tmpLen;
        }

        ++p->srcTxIdx;
        p->srcTxIdx %= SRC_TX_NUM;
        {
            int srcTxFree = atomic_sub_return(1, &p->atomSrcTxFree);
            ASSERT(srcTxFree >= 0);
            ASSERT(size >= 0);
//            pr_info("tmpLen=%d\n", (int) tmpLen);
            tx_size_fifo_push(&p->txSizeFifo, tmpLen);
            ++addCount;
//            pr_info("while: size=%d, srcTxFree=%d, res=%d, addCount=%d\n", size, srcTxFree, res, addCount);
        }

    }

    ASSERT(addCount >= 0);

    if (addCount > 0) {
        spin_unlock_irqrestore(&p->sl, spinFlags);

        {
            uint16_t cmd = create_txBuffer(addCount);
            //        pr_info("tx buffer cmd: 0x%x\n", cmd);
            DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM, cmd);
        }
    } else if (res == 0) {
        if (size > 0) {
#if 1
            if (size > 1) {
                // gehen davon aus, dass fuer size > 1 rueckgabewert 0 schon erlaubt ist, nur fuer manche faelle mit size == 1 (wenn es statt put_char) aufgerufen wird.

//                if (!in_interrupt()) {
//                    pr_info("return 0 in write (atomic_read(&p->atomSrcTxFree) = %d)\n", atomic_read(&p->atomSrcTxFree));
//                }
//                pr_info("res = 0 in write\n");
                res = 0;
            } else {
                // Warum auch immer, aber die Schicht im tty core, die diese Funktion aufruft, kommt nicht mit einem Rueckgabewert 0 zurecht
                // bzw. erwartet wohl dass immer mindestens das erste Byte geschrieben wird.
                // Komme also wohl nicht umhin , hier eine Kruecke einzubauen, die fuer den Fall, dass gerade srcTxFree == 0 ist,
                // das Schreiben von einem Byte vorgetaeuscht wird, indem dies noch mal in einem extra fifo gepuffert wird und dann der inhalt dieses fifos
                // ggf. vorneweg geschrieben wird, sobald srcTxFree wieder frei ist... :-/
                //            pr_warn("Es waere zu res = 0 gekommen, benutze sickExtraBuf...\n");
                ASSERT(p->lenSickExtraBuf < TX_DST_CAPACITY); // wenn der auch noch voll laeuft ist eh alles im A...
                p->sickExtraBuf[p->lenSickExtraBuf++] = data[0];
                res = 1;
            }
#elif 0
            // Warum auch immer, aber die Schicht im tty core, die diese Funktion aufruft, kommt nicht mit einem Rueckgabewert 0 zurecht
            // bzw. erwartet wohl dass immer mindestens das erste Byte geschrieben wird.
            // Komme also wohl nicht umhin , hier eine Kruecke einzubauen, die fuer den Fall, dass gerade srcTxFree == 0 ist,
            // das Schreiben von einem Byte vorgetaeuscht wird, indem dies noch mal in einem extra fifo gepuffert wird und dann der inhalt dieses fifos
            // ggf. vorneweg geschrieben wird, sobald srcTxFree wieder frei ist... :-/
//            pr_warn("Es waere zu res = 0 gekommen, benutze sickExtraBuf...\n");
            ASSERT(p->lenSickExtraBuf < TX_DST_CAPACITY); // wenn der auch noch voll laeuft ist eh alles im A...
            p->sickExtraBuf[p->lenSickExtraBuf++] = data[0];
            res = 1;
#else
            res = 0;
#endif
        }

//        pr_warn("addCount=0; res=%d, p->srcTxFree=%d\n", res, p->srcTxFree);

        spin_unlock_irqrestore(&p->sl, spinFlags);
    }

//    pr_info("dma_tty_write_onWrite returning %d\n", res);
//    ASSERT(mutex_is_locked(&p->m));
//    mutex_unlock(&p->m);

//    pr_info("write returning %d\n", res);
#if 1
    ASSERT(size != 1 || res > 0); // Der tty core braucht es so, dass nie 0 zurueckgegeben wird wenn size == 1 :-/ - deshalb oben die kruecke mit dem "sickExtraBuf"
#else
    if (res == 0) {
        msleep(1);
    }
#endif
    p->dbgCountSum += res;
    return res;
}

static void dma_tty_write_onRespTxCount(void) {
    TTtyWrite* p = &ttyWrite;
    unsigned long spinFlags;
    int newSrcTxFree;

    spin_lock_irqsave(&p->sl, spinFlags);

//    {
//        int oldSrcTxFree = atomic_read(&p->atomSrcTxFree);
//        pr_info("dma_tty_write_onRespTxCount - vorher srcTxFree=%d\n", oldSrcTxFree);
//        ASSERT(oldSrcTxFree >= 0);
//    }

    newSrcTxFree = atomic_add_return(INIT_RESP_COUNT_DOWN, &p->atomSrcTxFree);

    if (p->lenSickExtraBuf > 0) {
#if 0

        int len = p->lenSickExtraBuf;
        int res;
        p->lenSickExtraBuf = 0;
        res = dma_tty_write_onWrite(p->sickExtraBuf, len);
        ASSERT(len == res);
#else
        int res;

        res = dma_tty_write_onWrite(0, 0);
        ASSERT(res == 0);
#endif
    }


    if (newSrcTxFree == INIT_RESP_COUNT_DOWN) {
        // war also vorher 0
//        pr_info("wakeup tty\n");

        if (p->tty) {
            /*
             * zu wakeup:
                tty_wakeup - request more data : terminal

                Internal and external helper for wakeups of tty. This function informs the line discipline if present that
                the driver is ready to receive more output data.
             */
            tty_wakeup(p->tty);
//            pr_info("after tty_wakeup\n");
        } else {
            pr_warn("skipped wakeup because p->tty NULL\n");
        }
    }

//    {
//        int newSrcTxFree = atomic_read(&p->atomSrcTxFree);
//        if (newSrcTxFree > SRC_TX_NUM) {
//            pr_err("newSrcTxFree=%d\n", (int) newSrcTxFree);
//        }
//        ASSERT(newSrcTxFree <= SRC_TX_NUM);
//    }

    spin_unlock_irqrestore(&p->sl, spinFlags);

}

static void twOnWaitUntilAllSent(int timeoutMs) {
    TTtyWrite* p = &ttyWrite;
    unsigned long spinFlags;


    // prepare for response "drain"
    spin_lock_irqsave(&p->sl, spinFlags);
    init_completion(&p->waitUntilSentCompletion);
    p->waitingUntilSent = true;
    spin_unlock_irqrestore(&p->sl, spinFlags);

    { // send command "drain"
        uint16_t cmd = create_drain();
        //        pr_info("tx buffer cmd: 0x%x\n", cmd);
        DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM, cmd);
    }


//    pr_info("vor wait_for_completion_timeout in twOnWaitUntilAllSent\n");
    wait_for_completion_timeout(&p->waitUntilSentCompletion, msecs_to_jiffies(timeoutMs));
//    pr_info("nach wait_for_completion_timeout in twOnWaitUntilAllSent\n");


}

static void dma_tty_write_onDrain(void) {
    TTtyWrite* p = &ttyWrite;
    unsigned long spinFlags;
    bool waiting;

    spin_lock_irqsave(&p->sl, spinFlags);
    waiting = p->waitingUntilSent;
    p->waitingUntilSent = false;
    spin_unlock_irqrestore(&p->sl, spinFlags);

    if (waiting) {
        complete(&p->waitUntilSentCompletion);
//        pr_info("has completed in onAllSent\n");
    } else {
        pr_warn("onAllSent when not waiting for it\n");
    }

}


/**
 * ACHTUNG!
 *
 * Diese Implementierung setzt folgendes voraus:
 * Bei Realisierung eines tcdrain-Vorgangs ruft der tty core chars_in_buffer
 * wiederholt auf solange sich der wert reduziert bis 0 erreicht ist.
 * Da das FPGA nur alle INIT_RESP_COUNT_DOWN "Write-Chunks" mitteilt dass so viele
 * abgearbeitet wurden damit nicht zu viele Antworten vom FPGA gesendet werden muessen
 * bei regulaerem Betrieb mit hoher Baudrate.
 * Beim ersten Aufruf von chars_in_buffer wird, falls nicht ohnehin aufgrund eines
 * aktuellen Zustands srcTxFree == 0 feststeht, dass das FPGA "alles gesendet hat",
 * das Kommando "drain" ans FPGA geschickt.
 * Beim zweiten Aufruf von chars_in_buffer wird 0 zurueckgegeben, falls
 * die "drain" Antwort inzwischen einging. Sonst der letzte Wert minus 1.
 */
static int dma_tty_write_chars_in_buffer(void) {
    TTtyWrite* p = &ttyWrite;
    int res;
    unsigned long spinFlags;

//    mutex_lock(&p->m);
    spin_lock_irqsave(&p->sl, spinFlags);

    if (p->openCount > 0) {
        // Dies bedeutet dass noch ein zweites file handle geoeffnet ist. Daher besteht kein grund das schliessen dieses file handles
        // indirekt durch einen rueckgabewert > 0 zu verzoegern:
        pr_info("dma_tty_write_chars_in_buffer gibt 0 zurueck da noch mind. ein weiteres file handle geoeffnet.\n");
        res = 0;
    } else {

        //    pr_info("chars_in_buffer: cState=%d\n", (int) p->cState);

        switch (p->cState) {
            case EC_RESET:
                res = tx_size_fifo_chars_in_buffer(&p->txSizeFifo, SRC_TX_NUM - atomic_read(&p->atomSrcTxFree));

                if (res > 0) {
                    // prepare for response "drain"
                    init_completion(&p->waitUntilSentCompletion);
                    p->waitingUntilSent = true;

                    { // send command "drain"
                        uint16_t cmd = create_drain();
                        pr_info("drain cmd: 0x%x\n", cmd);
                        DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM, cmd);
                    }

                    p->cState = EC_DRAINING;
                    p->cLastCharsInBuffer = res;
                }
                break;

            case EC_DRAINING:
#if 0
                if (try_wait_for_completion(&p->waitUntilSentCompletion)) {
                    res = 0;
                    p->cState = EC_RESET;
                    p->cLastCharsInBuffer = -1;
                } else {
                    res = --p->cLastCharsInBuffer; // dekrementieren damit tty core fortschritt sieht ;-)
                    if (res == 0) {
                        p->cState = EC_RESET;
                        p->cLastCharsInBuffer = -1;
                    }
                }
#else
                spin_unlock_irqrestore(&p->sl, spinFlags);
                //            pr_info("vor wait_for_completion(waitUntil...)\n");
                wait_for_completion(&p->waitUntilSentCompletion);
                //            pr_info("nach wait_for_completion(waitUntil...)\n");
                res = 0;
                p->cState = EC_RESET;
                p->cLastCharsInBuffer = -1;
                spin_lock_irqsave(&p->sl, spinFlags);
#endif
                break;

            default:
                ASSERT(false);
                break;
        }

        //
        //    pr_info("chars_in_buffer when waitingUntilSent=%d\n", (int) p->waitingUntilSent);
        //
        //    if (p->tty || p->waitingUntilSent) {
        //        // close noch nicht aufgerufen oder aber onAllSent noch nicht aufgerufen
        //        res = tx_size_fifo_chars_in_buffer(&p->txSizeFifo, SRC_TX_NUM - atomic_read(&p->atomSrcTxFree));
        //    } else {
        //        // In diesem Fall vollstaendig also 0 auch wenn "srcTxFree" nicht 0 (wird naemlich evtl. erst nach dem ersten open nachgereicht vom fpga)
        //        res = 0;
        //    }
    }


    p->lastCharsInBuffer = res;
    p->orderCharsInBuffer = atomic_inc_return(&p->dbgOrderCount);

//    mutex_unlock(&p->m);
    spin_unlock_irqrestore(&p->sl, spinFlags);

//    pr_info("chars_in_buffer: res=%d\n", (int) res);
    return res;
}

static void dma_tty_write_exit(void) {
    TTtyWrite* p = &ttyWrite;
    int i;
    unsigned long spinFlags;

//    ASSERT(!mutex_is_locked(&p->m));
//    mutex_lock(&p->m);
    spin_lock_irqsave(&p->sl, spinFlags);

    for (i = 1; i >= 0; --i) {
        tty_writeDest_exit(&p->tty_writeDest[i]);
    }

//    ASSERT(mutex_is_locked(&p->m));
//    mutex_unlock(&p->m);
    spin_unlock_irqrestore(&p->sl, spinFlags);
//    mutex_destroy(&p->m);
    // keine vergleichbare Funktion zu mutex_destroy fuer spin locks bekannt!

    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_ */
