/**
 * @file wrtd-adcin.c
 *
 * ADCout: trigger ADC sampling.  Output from the WRTD pov.
 *
 * Copyright (c) 2018-2019 CERN (home.cern)
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#include <errno.h>
#include <string.h>

#include "mockturtle-rt.h"
#include <mockturtle-framework.h>
#include "wrtd-common.h"
#include "fmc_adc_aux_trigin.h"

#define OUT_QUEUE_MAXTIME 10
#define OUT_QUEUE_PREFIX adcout_
#define OUT_QUEUE_SIZE 4

#include "out_queue.h"

struct wrtd_adcout_dev {
        uint32_t io_addr;
        int idle;
        struct adcout_out_queue queue;
};

static inline void adcout_writel(struct wrtd_adcout_dev *dev, uint32_t value,
                                 uint32_t reg)
{
        dp_writel(value, dev->io_addr + reg);
}

static inline uint32_t adcout_readl (struct wrtd_adcout_dev *dev, uint32_t reg)
{
        return dp_readl(dev->io_addr + reg);
}



/**
 * Drop the given enqueued trigger
 */
static void adcout_drop_trigger(struct wrtd_adcout_dev *dev,
                                struct wrtd_event *ev, unsigned reason,
                                struct wrtd_tstamp *now)
{
        struct adcout_out_queue *q = &dev->queue;
        dev->idle = 1;

        if (adcout_out_queue_empty(q)) {
                /* Should never happen.  */
                return;
        }

        /* Drop the pulse */
        adcout_out_queue_pop(q);

        /* Disarm the ADC output */
        adcout_writel(dev, 0, AUX_TRIGIN_CTRL);

        wrtd_log(WRTD_LOG_MSG_EV_DISCARDED, reason, ev, now);
}


/**
 * Output driving function. Reads pulses from the output queue,
 * programs the output and updates the output statistics.
 */
static void adcout_output (struct wrtd_adcout_dev *dev)
{
        struct adcout_out_queue *q = &dev->queue;
        struct wrtd_event *ev = adcout_out_queue_front(q);
        uint32_t ctrl = adcout_readl(dev, AUX_TRIGIN_CTRL);
        struct wrtd_tstamp ts;

        /* Check if the output has triggered */
        if (!dev->idle) {
                if (ctrl & AUX_TRIGIN_CTRL_ENABLE) {
                        /* Armed but still waiting for trigger */
                        struct wrtd_tstamp now;
                        ts_now(&now);
                        if (adcout_out_queue_check_timeout (q, &now)) {
                                /* Will never trigger.  Missed.  */
                                adcout_drop_trigger(dev, ev, WRTD_LOG_DISCARD_TIMEOUT, &now);
                        }
                } else {
                        /* Has been triggered.  */
                        wrtd_log(WRTD_LOG_MSG_EV_CONSUMED,
                                 WRTD_LOG_CONSUMED_DONE, ev, NULL);

                        adcout_out_queue_pop(q);
                        dev->idle = 1;
                }
                return;
        }

        /* Output is idle: check if there's something in the queue to execute */
        if (adcout_out_queue_empty(q))
                return;

        if (!wr_is_timing_ok()) {
                adcout_drop_trigger(dev, ev, WRTD_LOG_DISCARD_NO_SYNC, NULL);
                return;
        }

        /* Program the output start time */
        adcout_writel(dev, 0, AUX_TRIGIN_SECONDS + 0);
        adcout_writel(dev, ev->ts.seconds, AUX_TRIGIN_SECONDS + 4);
        adcout_writel(dev, ev->ts.ns / 8, AUX_TRIGIN_CYCLES);
        adcout_writel(dev, AUX_TRIGIN_CTRL_ENABLE, AUX_TRIGIN_CTRL);

        wrtd_log(WRTD_LOG_MSG_EV_CONSUMED, WRTD_LOG_CONSUMED_START, ev, NULL);

        ts_add3_ns (&ts, &ev->ts, 8000);

        /*
         * Store the last programmed timestamp (+ some margin) and mark
         * the output as busy
         */
        q->last_programmed_sec = ts.seconds;
        q->last_programmed_ns = ts.ns;
        dev->idle = 0;
}

static int adcout_local_output(struct wrtd_adcout_dev *dev,
                               struct wrtd_event *ev, unsigned ch)
{
        struct wrtd_event *pq_ev;

        pq_ev = adcout_out_queue_push(&dev->queue);
        if (!pq_ev) {
                return -EOVERFLOW;
        }

        copy_event(pq_ev, ev);

        return 0;
}

static void adcout_init(struct wrtd_adcout_dev *dev)
{
        adcout_out_queue_init(&dev->queue);
        dev->idle = 1;

        pr_debug("adcout initialization complete\n\r");
}
