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

// falls dies (vor allen includes) definiert ist UND das aktuelle log level (siehe /proc/sys/kernel/printk) > 7 ist, sind auch pr_debug und dev_dbg ausgaben sichtbar!
//#define DEBUG

#ifndef KM_CV_FPGA_SERIAL_H_
#define KM_CV_FPGA_SERIAL_H_

/*
 * Beispiel: https://www.oreilly.com/library/view/linux-device-drivers/0596005903/ch18.html
 *
 * https://github.com/jesstess/ldd3-examples/blob/master/examples/tty/tiny_tty.c
 *
 * Vergleiche auch:
 * drivers/char/ttyprintk.c
 * drivers/tty/serial/8250/8250_dma.c
 *
 * Allgemein zu Modi vergleiche auch: https://www.gnu.org/software/libc/manual/html_node/Input-Modes.html
 *
 * Beachte: wenn ixon eingestellt ist, wird bei Aufruf von tcflow(fd, TCIOFF) bzw. tcflow(fd, TCION) implizit
 * vom line driver ein stop bzw. start byte geschrieben. Im FPGA muss also dafuer beim Senden nichts implementiert werden!
 *
 * ======= FOLGENDES PRUEFEN: =============
 * Beachte:
 * tty_ldisc_flush im KM aufzurufen, nachdem im FPGA ein break empfangen wurde
 * (ausser wenn dies bei pushen eines characters mit flag
 * ========================================
 */

/*
 * KM_CvFpgaSerial.h
 *
 * preitinger
 */

/*. IMPORT ================================================================*/
//#include <linux/config.h>
#include "uart_test_dev.h"
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
//#include <asm/uaccess.h>


//#include <linux/module.h>
//#include <linux/tty_driver.h>
//#include <linux/tty.h>

#include "dmaPayloadKM.h"
#include "dma_tty_write.h"
#include "dma_tty_read.h"
#include "fpgaMem.h"
#include "fpgaCommands.h"
#include "uartParams.h"

#ifdef TEST_DATA_LINE_XON_XOFF
#include "../test/test_data_line_xon_xoff.h"
#endif // ifdef TEST_DATA_LINE_XON_XOFF

/*. ENDIMPORT =============================================================*/

/*. LOCAL =================================================================*/

/*-------------------------------- Makros ---------------------------------*/

// Bereich 240-254 empfohlen fuer eigene Treiber in ${LINUX_SOURCE}/Documentation/admin-guide/devices.txt
#define CV_TTY_MAJOR 240

#define OPEN_CLOSE_IN_INIT


static int cv_tty_open(struct tty_struct *tty, struct file *file);

static void cv_tty_close(struct tty_struct *tty, struct file *file);
static void cv_tty_shutdown(struct tty_struct * tty);
static void cv_tty_cleanup(struct tty_struct * tty);

static int cv_tty_write(struct tty_struct *tty,
              const unsigned char *buffer, int count);
static int cv_tty_write_room(struct tty_struct *tty);
static void cv_tty_set_termios(struct tty_struct *tty, struct ktermios *old_termios);
static void cv_tty_throttle(struct tty_struct * tty);
static void cv_tty_unthrottle(struct tty_struct * tty);
static int cv_tty_chars_in_buffer(struct tty_struct *tty);
static void cv_tty_stop(struct tty_struct *tty);
static void cv_tty_start(struct tty_struct *tty);
static void cv_tty_wait_until_sent(struct tty_struct *tty, int timeout);

static struct tty_driver* cv_tty_driver;


/*
 * Auszug aus dem Buch Device Drivers:
 *
 *
Other Buffering Functions

The chars_in_buffer function in the tty_driver structure is not required in order to have a working tty driver, but it is
recommended. This function is called when the tty core wants to know how many characters are still remaining in the tty
driver’s write buffer to be sent out. If the driver can store characters before it sends them out to the hardware, it should
implement this function in order for the tty core to be able to determine if all of the data in the driver has drained out.

Three functions callbacks in the tty_driver structure can be used to flush any remaining data that the driver is holding on
to. These are not required to be implemented, but are recommended if the tty driver can buffer data before it sends it to
the hardware. The first two function callbacks are called flush_chars and wait_until_sent. These functions are called when
the tty core has sent a number of characters to the tty driver using the put_char function callback. The flush_chars
function callback is called when the tty core wants the tty driver to start sending these characters out to the hardware, if
it hasn’t already started. This function is allowed to return before all of the data is sent out to the hardware. The
wait_until_sent function callback works much the same way; but it must wait until all of the characters are sent before
returning to the tty core or until the passed in timeout value has expired, whichever occurrence happens first. The tty
driver is allowed to sleep within this function in order to complete it. If the timeout value passed to the wait_until_sent
function callback is set to 0, the function should wait until it is finished with the operation.

The remaining data flushing function callback is flush_buffer. It is called by the tty core when the tty driver is to flush
all of the data still in its write buffers out of memory. Any data remaining in the buffer is lost and not sent to the device.
 *
 *
 */


// TODO analysieren, ob wirklich genau diese (alle) notwendig oder andere


static struct tty_operations serial_ops = {
    .open = cv_tty_open,
    .close = cv_tty_close,
    .shutdown = cv_tty_shutdown,
    .cleanup = cv_tty_cleanup,
    .write = cv_tty_write,
    .write_room = cv_tty_write_room,
    .set_termios = cv_tty_set_termios,
    .throttle = cv_tty_throttle,
    .unthrottle = cv_tty_unthrottle,
    // Erledigt: throttle und unthrottle implementieren fuer Flusskontrolle beim Lesen!
    // dann bei throttle dem fpga mitteilen dass es dem sender mitteilen soll, dass er nichts mehr schreibt
    // bei unthrottle dies wieder rueckgaengig machen.
    // Die tty line discipline uebernimmt das Schreiben von xoff bzw. xon, wenn ixoff eingestellt ist.
    // klar rts muss im fpga auf jeden fall gesteuert werden, falls hw-flusskontrolle eingestellt ist.
    .chars_in_buffer = cv_tty_chars_in_buffer,
    .stop = cv_tty_stop,
    .start = cv_tty_start,
//    .flush_buffer = cv_tty_flush_buffer,
    .wait_until_sent = cv_tty_wait_until_sent
};

/*
Am besten wohl dies verwenden:
tty_insert_flip_string_fixed_flag
Damit koennte dann von dma-faehigem puffer nach dma-transfer von fpga zu KM die daten im dma-puffer in den puffer im core bzw. in der line geschrieben werden.
Nicht wie im beispiel erklaert jedes char einzeln mit tty_insert_flip_char schreiben!
 */

static void onInitDone(void* dummy) {
    pr_info("init done response received.\n");


#ifdef OPEN_CLOSE_IN_INIT
//        trOnOpen(0);

        // command uart_open ist ohne Antwort, also nicht fpgaSend...() verwendet:
#if 1 // TODO wieder #if 0 damit uart wieder geoeffnet wird
    pr_warn("uartOpen command commented out for testing of the flash problem\n");
#else
    DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM, create_uartOpen());
#endif
#endif

#ifdef MODULE_GET_DURING_INIT
        module_put(THIS_MODULE); // zugehoeriges try_module_get vor senden des init commands
#endif
}

#if 0

static int
KM_CvFpgaSerial_init(void* mappedCpu, dma_addr_t mappedDma, uint32_t mappedSize) {
    int res = 0;
    int i;

    pr_info("KM_CvFpgaSerial - init - ;-)\n");

    res = dmaPayloadKM_init(mappedCpu, mappedDma, mappedSize);

    if (unlikely(res)) {
        goto err_dmaPayloadKM_init;
    }

    res = dma_tty_read_init();

    if (unlikely(res)) {
        goto err_dma_tty_read_init;
    }

    cv_tty_driver = tty_alloc_driver(CV_TTY_MINORS, /*TTY_DRIVER_RESET_TERMIOS | */ TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_REAL_RAW); // aufraeumen mit: put_tty_driver
//    cv_tty_driver = alloc_tty_driver(CV_TTY_MINORS); // aufraeumen mit: put_tty_driver

    if (IS_ERR(cv_tty_driver)) {
        pr_err("tty_alloc_driver failed\n");
        res = PTR_ERR(cv_tty_driver);
        goto err_alloc_driver;
    }

    pr_info("tty_alloc_driver done\n");

    {
        struct tty_driver* d = cv_tty_driver;

        d->owner = THIS_MODULE;
        d->driver_name = "cvtty";
        d->name = "cvtty";
//        d->devfs_name = "tts/cvtty%d";
        d->major = CV_TTY_MAJOR;
        d->minor_start = 0;
        pr_info("default d->ports: %p\n", d->ports);

        for (i = 0; i < CV_TTY_MINORS + 1; ++i) {
            pr_info("port[%d]= %p\n", i, d->ports[i]);
        }

        // von tiny_tty:
        d->type = TTY_DRIVER_TYPE_SERIAL;
        d->subtype = SERIAL_TYPE_NORMAL;

        // von ttyprintk.c:
//        d->type = TTY_DRIVER_TYPE_CONSOLE;

//        d->flags = TTY_DRIVER_REAL_RAW,
        d->init_termios = tty_std_termios;
        // testweise fuer ttyprintk-einstellungen weg:
//        d->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
        tty_set_operations(cv_tty_driver, &serial_ops);

        for (i = 0; i < CV_TTY_MINORS; ++i) {
            tty_port_link_device(&cv_port[i].port, cv_tty_driver, i);
        }

        res = tty_register_driver(cv_tty_driver);

        if (res) {
            pr_err("failed to register cv tty driver\n");
            goto err_register;
        }

        pr_info("cv tty driver registered\n");
    }

    for (i = 0; i < CV_TTY_MINORS; ++i) {
        if (IS_ERR_OR_NULL(tty_register_device(cv_tty_driver, i, NULL))) {
            int j;
            for (j = i - 1; j >= 0; --j) {
                tty_unregister_device(cv_tty_driver, j);
            }

            res = -1;
            goto err_register_dev;
        }

        pr_info("cv tty device %d registered\n", i);
    }

    res = dma_tty_write_init();

    if (res) {
        goto err_dma_tty_write;
    }

    pr_info("cv_tty erfolgreich initialisiert.\n");
    pr_info("danach ein testinfo ohne linefeed");
    pr_info("und noch eines ohne linefeed");

    res = fpgaMem_init();

    if (res) {
        goto err_fpgaMem_init;
    }

    DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM_RESET, 0);
    msleep(1);
    // TODO am besten hier im reset-zustand des fpgas den pinmux fuer den dma-interrupt vom fpga setzen
    // im prtsoft:
//    ka.setPinMux(0x1b4, 0, false, false, true);
    // setPinMux(int off, int mode, bool pullEnabled, bool upSelected, bool receiverEnabled)
    {
        DmaChan_ControlModulAccess controlModul;
        res = DmaChan_ControlModulAccess_create(&controlModul);

        if (res) {
            goto err_ctrlModul_pinmux;
        }

        DmaChan_ControlModulAccess_setPinMux(&controlModul, 0x1b4, 0, false, false, true);
        DmaChan_ControlModulAccess_delete(&controlModul);
    }

    DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM_RESET, 1);
    msleep(1);

#ifndef TEST_DMA_PAYLOAD_KM
    // hierfuer kein undo
    DmaChan_MappedIo_set16(&fpgaMappedIo, fpgaWriteAddrOfKmRamAddr(FRKM_INIT_JUST_RESP_TYPE_AND_ISR_PS), (pkmJustRespTypeAndIsrPs << 5));
    pr_info("nach beschreiben von FRKM_INIT_JUST_RESP_TYPE_AND_ISR_PS - und zwar 0x%x\n", (pkmJustRespTypeAndIsrPs << 5));
    {
        uint16_t testVal = DmaChan_MappedIo_get16(&fpgaMappedIo, fpgaReadAddrOfKmRamAddr(FRKM_INIT_JUST_RESP_TYPE_AND_ISR_PS));
        pr_info("testVal: 0x%x\n", testVal);
    }
    fpgaSendCmdWithResp(create_Init(), onInitDone, NULL);
    pr_info("nach senden von init\n");
#endif // ifndef TEST_DMA_PAYLOAD_KM

    goto success;

//    dma_tty_write_exit();

    err_ctrlModul_pinmux:
    // fpga soll in reset-zustand bleiben
    fpgaMem_exit();

    err_fpgaMem_init:
    dma_tty_write_exit();


    err_dma_tty_write:
    {
        int i;
        for (i = CV_TTY_MINORS - 1; i >= 0; --i) {
            tty_unregister_device(cv_tty_driver, i);
        }
    }

    err_register_dev:
    tty_unregister_driver(cv_tty_driver);

    err_register:
    put_tty_driver(cv_tty_driver);

    err_alloc_driver:
    dma_tty_read_exit();

    err_dma_tty_read_init:
    dmaPayloadKM_exit();

    err_dmaPayloadKM_init:

    success:
    return res;
}

#else

static int
KM_CvFpgaSerial_init(void* mappedCpu, dma_addr_t mappedDma, uint32_t mappedSize) {
    int res = 0;
    int i;
    int32_t dmaOffEnd = DBE_END;

//    pr_info("KM_CvFpgaSerial - init - ;-)\n");

    res = dmaPayloadKM_init(mappedCpu, mappedDma, mappedSize, dma_tty_write_onRespTxCount, dma_tty_read_onRespRxCount, dma_tty_write_onDrain);

    if (unlikely(res)) {
        goto err_dmaPayloadKM_init;
    }

    res = dma_tty_write_init(&dmaOffEnd);

    if (unlikely(res)) {
        goto err_dma_tty_write_init;
    }

    {
//        pr_info("vorher: dmaOffEnd=%d\n", dmaOffEnd);
        res = dma_tty_read_init(&dmaOffEnd);
        // in dmaOffEnd waere nun das neue Ende des noch verfuegbaren DMA-Bereichs.
//        pr_info("nachher: dmaOffEnd=%d\n", dmaOffEnd);

    }

    if (unlikely(res)) {
        goto err_dma_tty_read_init;
    }

#if 0
    cv_tty_driver = tty_alloc_driver(CV_TTY_MINORS, /*TTY_DRIVER_RESET_TERMIOS | */ TTY_DRIVER_DYNAMIC_DEV /*| TTY_DRIVER_REAL_RAW*/); // aufraeumen mit: put_tty_driver
#else
    cv_tty_driver = tty_alloc_driver(CV_TTY_MINORS, /*TTY_DRIVER_RESET_TERMIOS | */ TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_REAL_RAW); // aufraeumen mit: put_tty_driver
#endif
//    cv_tty_driver = alloc_tty_driver(CV_TTY_MINORS); // aufraeumen mit: put_tty_driver

    if (IS_ERR(cv_tty_driver)) {
        pr_err("tty_alloc_driver failed\n");
        res = PTR_ERR(cv_tty_driver);
        goto err_alloc_driver;
    }

//    pr_info("tty_alloc_driver done\n");

    {
        struct tty_driver* d = cv_tty_driver;

        d->owner = THIS_MODULE;
        d->driver_name = "cvtty";
        d->name = "ttyFPGA";
//        d->devfs_name = "tts/cvtty%d";
        d->major = CV_TTY_MAJOR;
        d->minor_start = 0;
//        pr_info("default d->ports: %p\n", d->ports);
//
//        for (i = 0; i < CV_TTY_MINORS + 1; ++i) {
//            pr_info("port[%d]= %p\n", i, d->ports[i]);
//        }

        // von tiny_tty:
        d->type = TTY_DRIVER_TYPE_SERIAL;
        d->subtype = SERIAL_TYPE_NORMAL;

        // von ttyprintk.c:
//        d->type = TTY_DRIVER_TYPE_CONSOLE;

//        d->flags = TTY_DRIVER_REAL_RAW,
        d->init_termios = tty_std_termios;
        // testweise fuer ttyprintk-einstellungen weg:
        d->init_termios.c_cflag = B57600 | CS8;
        d->init_termios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
                        | INLCR | IGNCR | ICRNL | IXON);
        d->init_termios.c_oflag &= ~OPOST;
        d->init_termios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
        d->init_termios.c_cflag &= ~(CSIZE | PARENB);
        d->init_termios.c_cflag |= CS8;
        tty_set_operations(cv_tty_driver, &serial_ops);

        res = tty_register_driver(cv_tty_driver);

        if (res) {
            pr_err("failed to register cv tty driver\n");
            goto err_register;
        }

//        pr_info("cv tty driver registered\n");

//        for (i = 0; i < CV_TTY_MINORS; ++i) {
//            tty_port_link_device(&cv_port[i].port, cv_tty_driver, i);
//        }
    }

    for (i = 0; i < CV_TTY_MINORS; ++i) {
        if (IS_ERR_OR_NULL(tty_port_register_device(&cv_port[i].port, cv_tty_driver, i, NULL))) {
            int j;
            for (j = i - 1; j >= 0; --j) {
                pr_warn("tty_unregister_device wegen fehler\n");
                tty_unregister_device(cv_tty_driver, j);
            }

            res = -1;
            goto err_register_dev;
        }

//        pr_info("cv tty device %d registered\n", i);
    }

//    pr_info("cv_tty erfolgreich initialisiert.\n");
//    pr_info("danach ein testinfo ohne linefeed");
//    pr_info("und noch eines ohne linefeed");

    res = fpgaMem_init();

    if (res) {
        goto err_fpgaMem_init;
    }

    DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM_RESET, 0);
    msleep(1);
    // TODO am besten hier im reset-zustand des fpgas den pinmux fuer den dma-interrupt vom fpga setzen
    // im prtsoft:
//    ka.setPinMux(0x1b4, 0, false, false, true);
    // setPinMux(int off, int mode, bool pullEnabled, bool upSelected, bool receiverEnabled)
    {
        DmaChan_ControlModulAccess controlModul;
        res = DmaChan_ControlModulAccess_create(&controlModul);

        if (res) {
            goto err_ctrlModul_pinmux;
        }

        DmaChan_ControlModulAccess_setPinMux(&controlModul, 0x1b4, 0, false, false, true);
        DmaChan_ControlModulAccess_delete(&controlModul);
    }

    DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM_RESET, 1);
    msleep(1);

#ifndef TEST_DMA_PAYLOAD_KM
    // hierfuer kein undo
    ramKmWrite16(FRKM_INIT_JUST_RESP_TYPE_AND_ISR_LINK, (pkmJustRespTypeAndIsrPs << 5));
//    DmaChan_MappedIo_set16(&fpgaMappedIo, fpgaWriteAddrOfRamKmAddr(FRKM_INIT_JUST_RESP_TYPE_AND_ISR_PS), (pkmJustRespTypeAndIsrPs << 5));
//    pr_info("nach beschreiben von FRKM_INIT_JUST_RESP_TYPE_AND_ISR_LINK - und zwar 0x%x\n", (pkmJustRespTypeAndIsrPs << 5));
//    {
//        uint16_t testVal = DmaChan_MappedIo_get16(&fpgaMappedIo, fpgaReadAddrOfRamKmAddr(FRKM_INIT_JUST_RESP_TYPE_AND_ISR_LINK));
//        pr_info("testVal: 0x%x\n", testVal);
//    }
    dma_tty_write_prepare_ram_km();
    dma_tty_read_prepare_ram_km();
    pr_info("vor init command\n");
#ifdef MODULE_GET_DURING_INIT
    ASSERT(try_module_get(THIS_MODULE)); // zugehoeriges module_put in onInitDone; bewirkt dass das KM vor Aufruf von initDone nicht entfernt werden kann, was zu einem Absturz fuehren koennte.
#endif
    fpgaSendCmdWithResp(create_Init(), onInitDone, NULL);
//    pr_info("nach senden von init\n");
#endif // ifndef TEST_DMA_PAYLOAD_KM

#ifdef TEST_DATA_LINE_XON_XOFF
    res = test_data_line_xon_xoff_init();

    if (res) {
        goto err_test_data_line_xon_xoff;
    }
#endif

    pr_info("vor utd_init\n");
    res = utd_init(&cv_port[0].port);

    if (res) {
        goto err_utd_init;
    }

    goto success;

    err_utd_init:

//    dma_tty_write_exit();

#ifdef TEST_DATA_LINE_XON_XOFF
    test_data_line_xon_xoff_exit();

    err_test_data_line_xon_xoff:
#endif // ifdef TEST_DATA_LINE_XON_XOFF

    DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM_RESET, 0);


    err_ctrlModul_pinmux:
    // fpga soll in reset-zustand bleiben
    fpgaMem_exit();

    err_fpgaMem_init:
    {
        int i;
        for (i = CV_TTY_MINORS - 1; i >= 0; --i) {
            tty_unregister_device(cv_tty_driver, i);
        }
    }

    err_register_dev:
    tty_unregister_driver(cv_tty_driver);

    err_register:
    put_tty_driver(cv_tty_driver);

    err_alloc_driver:
    dma_tty_read_exit();

    err_dma_tty_read_init:
    dma_tty_write_exit();


    err_dma_tty_write_init:
    dmaPayloadKM_exit();

    err_dmaPayloadKM_init:

    success:
    return res;
}

#endif
static void
KM_CvFpgaSerial_exit(void)
{
//    pr_info("KM_CvFpgaSerial - exit\n");

    utd_exit();

#ifdef OPEN_CLOSE_IN_INIT

//    trOnClose(0);

#if 1
    pr_warn("uartClose auskommentiert fuer Flashproblem\n");
#else
    // command uart_close ist ohne Antwort, also nicht fpgaSend...() verwendet:
    DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM, create_uartClose());
#endif

#endif // ifdef OPEN_CLOSE_IN_INIT


    DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM_RESET, 0);
    fpgaMem_exit();
    dma_tty_write_exit();


    {
        int i;
        for (i = CV_TTY_MINORS - 1; i >= 0; --i) {
            tty_unregister_device(cv_tty_driver, i); // Gegenstueck zu tty_register_device
        }

//        pr_info("before tty_unregister_driver");
        tty_unregister_driver(cv_tty_driver); // Gegenstueck zu tty_register_driver

//        pr_info("before put_tty_driver\n");
        put_tty_driver(cv_tty_driver); // Gegenstueck zu tty_alloc_driver
//        pr_info("after put_tty_driver\n");

        dma_tty_read_exit();
        dmaPayloadKM_exit();
    }
}

static int cv_tty_open(struct tty_struct *tty, struct file *file) {
    int res;
    bool firstOpenOf0 = false;

    // Der Aufrufer garantiert, dass das tty_lock gehalten wird, was wiederum von tty_port_open vorausgesetzt wird.
    // Siehe kernel doku zu tty_port_open und struct tty_operations

    pr_info("cv_tty_open: index=%d\n", tty->index);
    pr_info("cv_tty_open: driver_data vorher: %p\n", tty->driver_data);

#if 0
    {
        int res;
        const char* testData = "Bla";
        res = tty_insert_flip_string(&cv_port->port, testData, 3);
        pr_info("res of insert_flip_string(\"Bla\"): %d\n", res);
        tty_flip_buffer_push(&cv_port->port);
    }
#endif


    firstOpenOf0 = (tty->index == 0 && !tty->driver_data);
    tty->driver_data = cv_port + tty->index;
//    pr_info("vorher: tty->port: %p\n", tty->port);
//    pr_info("cv_tty_open: driver_data nachher: %p\n", tty->driver_data);
    res = tty_port_open(tty->port, tty, file);
//    pr_info("nach tty_port_open: res=%d\n", res);

    if (!res) {
#ifndef OPEN_CLOSE_IN_INIT
//        trOnOpen(tty->index);
#endif
//        pr_info("nach tty_port_open: tty->port: %p\n", tty->port);

        twOnOpen(tty);

#ifndef OPEN_CLOSE_IN_INIT
            // command uart_open ist ohne Antwort, also nicht fpgaSend...() verwendet:
            DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM, create_uartOpen());
#endif


            // nun testweise am ende von init:
#ifdef TEST_DMA_TTY_READ
            trStartTestThread();
#endif

#ifdef TEST_DATA_LINE_XON_XOFF
            test_data_line_xon_xoff_onOpen();
#endif


        res = 0;
    }


    return res;
}

static void cv_tty_close(struct tty_struct *tty, struct file *file) {
    bool allClosed = false;

    pr_info("cv_tty_close: index=%d\n", tty->index);

#ifndef OPEN_CLOSE_IN_INIT
//    trOnClose(tty->index);
#endif

#ifdef TEST_DMA_TTY_READ
    if (cv_port[tty->index].open_count == 0) {

//        pr_info("vor trStopTestThread\n");
        trStopTestThread();

    }
#endif

    pr_info("before twOnClose\n");
    allClosed = twOnClose();
    pr_info("after twOnClose\n");

#ifndef OPEN_CLOSE_IN_INIT
    // command uart_close ist ohne Antwort, also nicht fpgaSend...() verwendet:
    DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM, create_uartClose());
#endif

    if (allClosed) {
        pr_info("vor tty_port_close\n");
        tty_port_close(tty->port, tty, file);

        pr_info("cv_tty_close done\n");
    }
}

static void cv_tty_shutdown(struct tty_struct * tty) {
    twOnShutdown();
    trOnShutdown(tty->index);
    pr_info("cv_tty_shutdown\n");
}

static void cv_tty_cleanup(struct tty_struct * tty) {
    twOnCleanup();
    pr_info("cv_tty_cleanup\n");
}

#if 0
DEFINE_SPINLOCK(example_lock);

static void example_for_spinlock(void) {

    unsigned long flags; // wird in Makro spin_lock_irqsave() beschrieben und in spin_unlock_irqrestore gelesen.

    spin_lock_irqsave(&example_lock, flags);
    // ...
    spin_unlock_irqrestore(&example_lock, flags);
}
#endif // if 0

// ACHTUNG! cv_tty_write kann auch im interrupt-Kontext aufgerufen werden. (siehe https://www.oreilly.com/library/view/linux-device-drivers/0596005903/ch18.html - Flow of Data)
// D.h. es duerfen keine blockierenden Funktionen verwendet werden, wie z.B. sleep
// Eigentlich nicht einmal printk bzw. Abstraktionen davon... :-/ wird aber selbst im beispiel trotzdem gemacht.
// also auch kein mutex oder down etc.
// hier synchronisieren mit:
// - spin_lock_irqsave(), spin_unlock_irqrestore()
// kein mutex!
// DMA-Kanaele und -Parametersaetze hier auch nicht allokieren/freigeben, da dort evtl. mutex verwendet wird!
// Feste Kanaele und Parametersaetze vorab allokieren und hier nur manipulieren und/oder triggern.

// Aber wie ich nun gesehen habe, halten sich auch nicht alle im Kernel integrierten tty-Treiber daran. Viele verwenden z.B. einen Mutex... :-/
// Aber manche verwenden auch konsequent nur interrupt-taugliche spinlocks... :-/ Linux ist halt auch nur ein gewachsener Mist ohne durchgaengiges Konzept.

static int cv_tty_write(struct tty_struct *tty,
                         const unsigned char *buffer, int count) {
    int res;
    struct tty_port* port = &cv_port[tty->index].port;

//    if (count >= 1) {
//        pr_info("buffer[0]: %02x\n", buffer[0]);
//    }

//    msleep(300);

//    if (!in_interrupt()) {
//        pr_info("cv_tty_write called not in interrupt mit count=%d\n", count);
//    }
    ASSERT(!in_interrupt()); // TODO nur zum testen
    ASSERT(port == tty->port);
    ASSERT(tty->driver_data == cv_port + tty->index);

//    pr_info("write: count=%d\n", count);
    ASSERT(count >= 0);

//    pr_info("nach assert zu driver_data\n");

//    // nun der naechste port
//    port = &cv_port[(tty->index + 1) % CV_TTY_MINORS].port;
//    pr_info("available space in port: %d\n", tty_buffer_space_avail(port));
//    res = tty_buffer_request_room(port, count);
//    pr_info("tty_buffer_request_room returned %d\n", res);
//
//    if (count > 1024) {
//        pr_info("count limitiert von %d auf 1024\n", count);
//        count = 1024; // limitieren um zu sehen, was zwischenschicht aus grossem write macht
//    }
//
//    for (i = 0; i < count; ++i) {
////        pr_info("insert byte %02x\n", buffer[i]);
//        res = tty_insert_flip_char(port, buffer[i], TTY_NORMAL);
////        pr_info("tty_insert_flip_char returned %d\n", res);
//    }
//
////    // aus der reihe ein 'x' mit paritaetsfehler
////    tty_insert_flip_char(port, 'x', TTY_PARITY);
////    tty_insert_flip_char(port, 'x', TTY_NORMAL);
////    tty_insert_flip_char(port, 0x80, TTY_NORMAL);
////    tty_insert_flip_char(port, 0x88, TTY_NORMAL);
////    // dann ein gueltiges byte 0377 um zu sehen ob dies als 2 0377 hintereinander codiert wird.
////    pr_info("normales 0177\n");
////    tty_insert_flip_char(port, 0177, TTY_NORMAL);
////    // dann noch ein byte 0377 mit TTY_PARITY
////    tty_insert_flip_char(port, 0377, TTY_PARITY);
//
//    tty_flip_buffer_push(port);

#ifdef TEST_DATA_LINE_XON_XOFF
    test_data_line_xon_xoff_write(buffer, count);
#endif

    res = dma_tty_write_onWrite(buffer, count);

#if 0 // loest das obigen bug (count=-1) aus?
    if (!in_interrupt()) {
        if (res < count) {
//            pr_info("Schlafe 1s da res = %d < %d = count\n", res, count);
//            msleep(1000); // schlafen damit fpga nach kommt
            msleep(10);
//            msleep(1);
        }
    }
#endif

    if (res > count) {
        pr_err("res=%d, count=%d\n", (int) res, (int) count);
    } else {
//        pr_info("res=%d\n", res);
    }

    ASSERT(res <= count);
//    if (!in_interrupt()) {
//        pr_info("cv_tty_write called not in interrupt, returns %d\n", res);
//    }
    return res;
}

static int cv_tty_write_room(struct tty_struct *tty) {
//    pr_info("cv_tty_write_room\n");
    return dma_tty_write_room();
}

//
// TODO besser folgendes Makros in linux/tty.h verwenden:
// z.B. I_IXON(tty), I_IXOFF(tty), C_BAUD(tty)

#define CHECK_IFLAG(f) do { if (x->c_iflag & f) { printk(KERN_INFO " " #f "\n"); } else printk(KERN_INFO " -" #f "\n"); } while (false)
#define CHECK_CFLAG(f) do { if (x->c_cflag & f) { printk(KERN_INFO " " #f "\n"); } else printk(KERN_INFO " -" #f "\n"); } while (false)

static void dump_termios(const char* header, struct ktermios* x) {
    printk(KERN_INFO "%s\niflag:", header);
    CHECK_IFLAG(IGNBRK);
    CHECK_IFLAG(BRKINT);
    CHECK_IFLAG(IGNPAR);
    CHECK_IFLAG(PARMRK);
    CHECK_IFLAG(INPCK);
    CHECK_IFLAG(ISTRIP);
    CHECK_IFLAG(INLCR);
    CHECK_IFLAG(IGNCR);
    CHECK_IFLAG(ICRNL);
    CHECK_IFLAG(IUCLC);
    CHECK_IFLAG(IXON);
    CHECK_IFLAG(IXANY);
    CHECK_IFLAG(IXOFF);
    CHECK_IFLAG(IMAXBEL);
    CHECK_IFLAG(IUTF8);
    pr_info("cflag:\n");
    CHECK_CFLAG(CRTSCTS);
    pr_info("ispeed: %d\n", (int) x->c_ispeed);
    pr_info("ospeed: %d\n", (int) x->c_ospeed);
}



// Spicken bei: https://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/062/6226/6226l1.html
// Aber offenbar Bug in RELEVANT_IFLAG. Hier muesste IXON und IXOFF angegeben sein, weil das in der Funktion abgefragt wird!
#if 0
#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))

static void
tiny_set_termios (struct tty_struct *tty,
                  struct termios *old_termios)
{
   unsigned int cflag;

   cflag = tty->termios->c_cflag;

   /* check that they really want us to change
    * something */
   if (old_termios) {
       if ((cflag == old_termios->c_cflag) &&
          (RELEVANT_IFLAG(tty->termios->c_iflag) ==
           RELEVANT_IFLAG(old_termios->c_iflag))) {
               printk (KERN_DEBUG
               " - nothing to change...\n");
               return;
       }
   }

   /* get the byte size */
   switch (cflag & CSIZE) {
       case CS5:
           printk (KERN_DEBUG " - data bits = 5\n");
           break;
       case CS6:
           printk (KERN_DEBUG " - data bits = 6\n");
           break;
       case CS7:
           printk (KERN_DEBUG " - data bits = 7\n");
           break;
       default:
       case CS8:
           printk (KERN_DEBUG " - data bits = 8\n");
           break;
   }

   /* determine the parity */
   if (cflag & PARENB)
       if (cflag & PARODD)
           printk (KERN_DEBUG " - parity = odd\n");
       else
           printk (KERN_DEBUG " - parity = even\n");
   else
       printk (KERN_DEBUG " - parity = none\n");

   /* figure out the stop bits requested */
   if (cflag & CSTOPB)
       printk (KERN_DEBUG " - stop bits = 2\n");
   else
       printk (KERN_DEBUG " - stop bits = 1\n");

   /* figure out the flow control settings */
   if (cflag & CRTSCTS)
       printk (KERN_DEBUG
               " - RTS/CTS is enabled\n");
   else
       printk (KERN_DEBUG
               " - RTS/CTS is disabled\n");

   /* determine software flow control */
   /* if we are implementing XON/XOFF,
    * set the start and stop character in the
    * device */
   if (I_IXOFF(tty) || I_IXON(tty)) {
       unsigned char stop_char  = STOP_CHAR(tty);
       unsigned char start_char = START_CHAR(tty);

       /* if we are implementing
        * INBOUND XON/XOFF */
       if (I_IXOFF(tty))
           printk (KERN_DEBUG
                   " - INBOUND XON/XOFF "
                   "is enabled, "
                   "XON = %2x, XOFF = %2x",
                   start_char, stop_char);
       else
           printk (KERN_DEBUG
                   " - INBOUND XON/XOFF "
                   "is disabled");

       /* if we are implementing
        * OUTBOUND XON/XOFF */
       if (I_IXON(tty))
           printk (KERN_DEBUG
                   " - OUTBOUND XON/XOFF "
                   " is enabled, "
                   "XON = %2x, XOFF = %2x",
                   start_char, stop_char);
       else
           printk (KERN_DEBUG
                   " - OUTBOUND XON/XOFF "
                   " is disabled");
   }

   /* get the baud rate wanted */
   printk (KERN_DEBUG
           " - baud rate = %d",
           tty_get_baud_rate(tty));

   return;
}

#endif


#define CV_RELEVANT_IFLAG(iflag) (iflag & (IXON|IXOFF))


static void cv_tty_set_termios(struct tty_struct *tty, struct ktermios *old_termios) {
    unsigned int cflag;

    pr_info("cv_tty_set_termios");
    dump_termios("new", &tty->termios);
    if (!old_termios) {
        pr_info("old_termios is null\n");
    } else {
        dump_termios("old", old_termios);
    }


//    tty->termios->c_ispeed;
    cflag = tty->termios.c_cflag;

    /* check that they really want us to change
     * something */
    if (old_termios) {
        if ((cflag == old_termios->c_cflag) &&
           (CV_RELEVANT_IFLAG(tty->termios.c_iflag) ==
            CV_RELEVANT_IFLAG(old_termios->c_iflag) &&
            tty->termios.c_ispeed == old_termios->c_ispeed &&
            tty->termios.c_ospeed == old_termios->c_ospeed)) {
            pr_info(" - nothing to change...\n");
            return;
        }
    }

    // TODO fuellen von ram_km[FRKM_UART_PARAMS_<i>] und
    // anschliessendes uartParams_cmd
    // hierzu Hilfsfunktionen in uartParams.h verwenden

    {
        int txBaudrate = (int) tty->termios.c_ospeed;
        int rxBaudrate = (int) tty->termios.c_ispeed;
        int txPeriodCount = up_uartPeriodCount(txBaudrate);
        int rxPeriodCount = up_uartPeriodCount(rxBaudrate);
        int rawDataBits, dataBits, parity, stopBits, rtsCts;
        bool inboundXon, outboundXon;
        uint8_t startChar, stopChar;
        uint16_t word0, word1, word2, word3;
        uint16_t cmd;

        switch (cflag & CSIZE) {
            case CS5:
                rawDataBits = 5;
                break;
            case CS6:
                rawDataBits = 6;
                break;
            case CS7:
                rawDataBits = 7;
                break;
            default:
                rawDataBits = 8;
                break;
        }

        ASSERT(rawDataBits >= 5 && rawDataBits <= 8);
        dataBits = up_uartDataBits(rawDataBits);
        ASSERT(dataBits >= 3 && dataBits <= 6);

        if (cflag & PARENB) {
            if (cflag & PARODD) {
                parity = 1; // odd parity
            } else {
                parity = 2; // even parity
            }

        } else {
            parity = 0; // no parity
        }

        ASSERT(parity >= 0 && parity <= 2);

        if (cflag & CSTOPB) {
            stopBits = 1; // bedeutet 2 stop bits
        } else {
            stopBits = 0; // bedeutet 1 stop bit
        }

        if (cflag & CRTSCTS) {
            rtsCts = 1; // rts/cts enabled
        } else {
            rtsCts = 0; // rts/cts disabled
        }

        inboundXon = I_IXOFF(tty);
        outboundXon = I_IXON(tty);
        stopChar = STOP_CHAR(tty);
        startChar = START_CHAR(tty);

        word0 = up_word0(txPeriodCount);
        word1 = up_word1(txPeriodCount, rxPeriodCount);
        word2 = up_word2(rxPeriodCount, dataBits, parity, stopBits);
        word3 = up_word3(startChar, stopChar);
//        pr_info("word0=0x%x\n", word0);
//        pr_info("word1=0x%x\n", word1);
//        pr_info("word2=0x%x\n", word2);
//        pr_info("word3=0x%x\n", word3);

        ramKmWrite16(FRKM_UART_PARAMS_0, word0);
        ramKmWrite16(FRKM_UART_PARAMS_1, word1);
        ramKmWrite16(FRKM_UART_PARAMS_2, word2);
        ramKmWrite16(FRKM_UART_PARAMS_3, word3);

        msleep(1);

        cmd = create_uartParams_cmd(rtsCts, inboundXon, outboundXon);
        pr_info("cmd=0x%x\n", cmd);
        DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM, cmd);

    }



}

/* (kopiert aus include/linux/tty_driver.h)
 *      This routine notifies the tty driver that input buffers for
 *      the line discipline are close to full, and it should somehow
 *      signal that no more characters should be sent to the tty.
 *
 *      Optional: Always invoke via tty_throttle(), called under the
 *      termios lock.
 */
static void cv_tty_throttle(struct tty_struct * tty) {
//    pr_info("cv_tty_throttle for index %d\n", tty->index);

#ifdef TEST_DMA_TTY_READ
    mutex_lock(&trTestData.m);
    ++trTestData.throttleCount;
    mutex_unlock(&trTestData.m);
#endif // TEST_DMA_TTY_READ

    trOnThrottle(tty->index);
}

/* (kopiert aus include/linux/tty_driver.h)
 *      This routine notifies the tty drivers that it should signals
 *      that characters can now be sent to the tty without fear of
 *      overrunning the input buffers of the line disciplines.
 *
 *      Optional: Always invoke via tty_unthrottle(), called under the
 *      termios lock.
 */
static void cv_tty_unthrottle(struct tty_struct * tty) {
//    pr_info("cv_tty_unthrottle for index %d\n", tty->index);
#ifdef TEST_DMA_TTY_READ
    mutex_lock(&trTestData.m);
    ++trTestData.unthrottleCount;
    mutex_unlock(&trTestData.m);
#endif // TEST_DMA_TTY_READ

    trOnUnthrottle(tty->index);
}

/*
 * Auszug aus "Linux device drivers":
 *
The chars_in_buffer function in the tty_driver structure is not required in order to have a working tty driver, but it is
recommended. This function is called when the tty core wants to know how many characters are still remaining in the tty
driver’s write buffer to be sent out. If the driver can store characters before it sends them out to the hardware, it should
implement this function in order for the tty core to be able to determine if all of the data in the driver has drained out.
 *
 */
static int cv_tty_chars_in_buffer(struct tty_struct *tty) {
    int res;

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

static void cv_tty_stop(struct tty_struct *tty) {
    pr_info("cv_tty_stop\n");

    // uart_stop command ans FPGA; dort transmit pausieren bis zum command uart_start

    twOnStop();
}
static void cv_tty_start(struct tty_struct *tty) {
    pr_info("cv_tty_start\n");

    // TODO uart_start command ans FPGA; dort ggf. pausierendes transmit wieder fortsetzen

    twOnStart();
}

static void cv_tty_flush_buffer(struct tty_struct *tty) __attribute((unused));

static void cv_tty_flush_buffer(struct tty_struct *tty) {
    pr_info("flush_buffer\n");
}

static void cv_tty_wait_until_sent(struct tty_struct *tty, int timeout) {
    pr_info("wait_until_sent: timeout=%d\n", timeout);
    twOnWaitUntilAllSent(timeout);
}



#endif // define KM_CV_FPGA_SERIAL_H_
