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

/*
 * throttling.h
 *
 *  Created on: 27.07.2022
 *      Author: preitinger
 *
 * Warum auch immer, aber der High-Level TTY Treiber in Linux ruft bei
 * dichter Datenuebertragung und -empfang regelmaessig throttle und dann jedoch
 * nach <300us gleich wieder unthrottle auf, auch wenn der line discipline buffer noch
 * so gut wie leer ist.
 * Wenn das FPGA jedoch bei jedem throttle ungefiltert der Gegenseite
 * eine Sendepause aufnoetigt, sinkt der Durchsatz unnoetigerweise extrem.
 * Daher wird hier ein throttle vom Kernel erst nach 300us ans FPGA weiter geleitet, wenn der Kernel nicht zwischenzeitlich wieder
 * ein unthrottle signalisiert hat.
 *
 */

#ifndef FPGASERIAL_THROTTLING_H_
#define FPGASERIAL_THROTTLING_H_

#include "fpgaCommands.h"
#include "fpgaMem.h"
#include "../KM_DmaChan_MappedIo.h"
#include "../KM_FpgaAddresses.h"

#include <linux/spinlock.h>
#include <linux/timer.h>
#include <linux/kernel.h>

// statemachine diagramm: CV_Gen2.Softwarearchitektur.Zusammenarbeit Userspace-Kernel-FPGA.COM im FPGA.cv throttling (im KM)
enum CvThrottledState {
    CV_UNTHROTTLED,
    CV_THROTTLE_TIMER,
    CV_THROTTLED
};

typedef struct {
    enum CvThrottledState st;
    struct timer_list timer;
    spinlock_t sl;

} CvThrottled_t;

static void intern_cv_throttling_timerCallback(struct timer_list * t) {
    unsigned long spinFlags;
    CvThrottled_t* p = container_of(t, CvThrottled_t, timer);

    spin_lock_irqsave(&p->sl, spinFlags);

    switch (p->st) {

        case CV_THROTTLE_TIMER: {
            uint16_t cmd = create_throttle_unthrottle_cmd();
            DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM, cmd);
            p->st = CV_THROTTLED;
            break;
        }

        default:
            // ignore
            break;
    }

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

static void cv_throttling_init(CvThrottled_t* p) {
    p->st = CV_UNTHROTTLED;
    timer_setup(&p->timer, intern_cv_throttling_timerCallback, 0);
    spin_lock_init(&p->sl);
}

//static void intern_cv_throttling_throttle(CvThrottled_t* p);

//
// Ereignisse
//

static void cv_throttling_kernel_throttle(CvThrottled_t* p) {
    unsigned long spinFlags;

    spin_lock_irqsave(&p->sl, spinFlags);


    switch (p->st) {
        case CV_UNTHROTTLED:
            mod_timer(&p->timer, jiffies + usecs_to_jiffies(300));
            p->st = CV_THROTTLE_TIMER;
            break;

        case CV_THROTTLE_TIMER:
            pr_err("kernel throttled in state CV_THROTTLE_TIMER\n");
            break;

        case CV_THROTTLED:
            pr_err("kernel throttled in state CV_THROTTLE_TIMER\n");
            break;
    }

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

static void cv_throttling_kernel_unthrottle(CvThrottled_t* p) {
    unsigned long spinFlags;

    spin_lock_irqsave(&p->sl, spinFlags);

    switch (p->st) {
        case CV_UNTHROTTLED:
            pr_err("kernel unthrottled in state \n");
            break;

        case CV_THROTTLE_TIMER:
            del_timer(&p->timer);
            p->st = CV_UNTHROTTLED;
            break;

        case CV_THROTTLED: {
            uint16_t cmd = create_throttle_unthrottle_cmd();
            DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM, cmd);
            p->st = CV_UNTHROTTLED;
            break;
        }
    }

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

static void cv_throttling_shutdown(CvThrottled_t* p) {
    unsigned long spinFlags;

    spin_lock_irqsave(&p->sl, spinFlags);

    pr_info("cv_throttling_shutdown in state %d\n", p->st);

    switch (p->st) {
        case CV_UNTHROTTLED:
            // ignore
            break;

        case CV_THROTTLE_TIMER:
            del_timer(&p->timer);
            p->st = CV_UNTHROTTLED;
            break;

        case CV_THROTTLED: {
            uint16_t cmd = create_throttle_unthrottle_cmd();
            DmaChan_MappedIo_set16(&fpgaMappedIo, ADDR_OTH_KM, cmd);
            p->st = CV_UNTHROTTLED;
            break;
        }
    }

    spin_unlock_irqrestore(&p->sl, spinFlags);

}


#endif /* FPGASERIAL_THROTTLING_H_ */
