/**
 * @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_alt_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 int adcout_wr_present(struct wrtd_adcout_dev *fd)
{
        return 1;
}

static inline int adcout_wr_link_up(struct wrtd_adcout_dev *fd)
{
        return 1;
}

static inline int adcout_wr_time_locked(struct wrtd_adcout_dev *fd)
{
        return 1;
}

static inline int adcout_wr_time_ready(struct wrtd_adcout_dev *fd)
{
        return 1;
}

static inline int adcout_wr_sync_timeout(void)
{
        return 0;
}

static void adcout_wr_enable_lock(struct wrtd_adcout_dev *fd, int enable)
{
        return;
}


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, ALT_TRIGIN_CTRL);

        wrtd_log(WRTD_LOG_MSG_EV_DISCARDED, reason, NULL, 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, ALT_TRIGIN_CTRL);
        struct wrtd_tstamp ts;

        /* Check if the output has triggered */
        if (!dev->idle) {
#if 0
                if (!wr_is_timing_ok()) {
                        /* Timing has been lost.  */
                        drop_trigger(out, ev, q, WRTD_LOG_DISCARD_NO_SYNC);
                        return;
                }
#endif
                if (ctrl & ALT_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,
                                 NULL, 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, ALT_TRIGIN_SECONDS + 0);
        adcout_writel(dev, ev->ts.seconds, ALT_TRIGIN_SECONDS + 4);
        adcout_writel(dev, ev->ts.ns / 8, ALT_TRIGIN_CYCLES);
        adcout_writel(dev, ALT_TRIGIN_CTRL_ENABLE, ALT_TRIGIN_CTRL);

        wrtd_log(WRTD_LOG_MSG_EV_CONSUMED, WRTD_LOG_CONSUMED_START,
                 NULL, 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;
        }

        *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");
}
