/**
 * @file wrtd-tdc.c
 *
 * Copyright (c) 2018-2019 CERN (home.cern)
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#include "mockturtle-rt.h"

#include "hw/fmctdc-direct.h"
#include "hw/tdc_regs.h"
#include "hw/acam_gpx.h"

#define BASE_DP_TDC_REGS    0x2000
#define BASE_DP_TDC_DIRECT  0x8000

#define TDC_NUM_CHANNELS 5

#define NB_ACAM_REGS 11

#define TDC_VCXO_DEFAULT_TUNE 32000

#define DEFAULT_DEAD_TIME (80000/16)

struct wrtd_tdc_dev {
        uint32_t io_addr;
        /* FIXME: base channel (to create the event id).  */
};

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

static inline uint32_t tdc_readl(const struct wrtd_tdc_dev *dev, uint32_t reg)
{
        return dp_readl(dev->io_addr + reg);
}

static const struct {
        uint32_t reg;
        uint32_t value;
} acam_config[NB_ACAM_REGS] = {
        {
                0, AR0_ROsc | AR0_HQSel | AR0_TRiseEn(0) |
                AR0_TRiseEn(1) | AR0_TRiseEn(2) |
                AR0_TRiseEn(3) | AR0_TRiseEn(4) |
                AR0_TRiseEn(5) |
                AR0_TFallEn(1) | AR0_TFallEn(2) |
                AR0_TFallEn(3) | AR0_TFallEn(4) | AR0_TFallEn(5)}, {
                1, 0}, {
                2, AR2_IMode | AR2_Disable(6) | AR2_Disable(7) | AR2_Disable(8)}, {
                3, 0}, {
                4, AR4_StartTimer(15) | AR4_EFlagHiZN}, {
                5, AR5_StartOff1(2000)}, {
                6, AR6_Fill(0xfc)}, {
                7, AR7_RefClkDiv(7) | AR7_HSDiv(234) | AR7_NegPhase | AR7_ResAdj}, {
                11, AR11_HFifoErrU(0) | AR11_HFifoErrU(1) |
                AR11_HFifoErrU(2) | AR11_HFifoErrU(3) |
                AR11_HFifoErrU(4) | AR11_HFifoErrU(5) |
                AR11_HFifoErrU(6) | AR11_HFifoErrU(7)}, {
                12, AR12_StartNU | AR12_HFifoE}, {
                14, 0}
};

static inline int acam_is_pll_locked(struct wrtd_tdc_dev *dev)
{
        uint32_t status;

        tdc_writel(dev, TDC_CTRL_READ_ACAM_CFG,
                   BASE_DP_TDC_REGS + TDC_REG_CTRL);
        udelay(100);

        status = tdc_readl(dev, BASE_DP_TDC_REGS + TDC_REG_ACAM_READBACK(12));

        return !(status & AR12_NotLocked);
}

int tdc_acam_init(struct wrtd_tdc_dev *dev)
{
        int i;

        pr_debug("%s: initializing ACAM TDC...\n", __func__);

        tdc_writel(dev, TDC_CTRL_RESET_ACAM, BASE_DP_TDC_REGS + TDC_REG_CTRL);
        udelay(100);

        for (i = 0; i < NB_ACAM_REGS; i++) {
                tdc_writel(dev, acam_config[i].value,
                           BASE_DP_TDC_REGS + TDC_REG_ACAM_CONFIG(acam_config[i].reg));
        }

        /* commit ACAM config regs */
        tdc_writel(dev, TDC_CTRL_LOAD_ACAM_CFG, BASE_DP_TDC_REGS + TDC_REG_CTRL);
        udelay(100);

        /* and reset the chip (keeps configuration) */
        tdc_writel(dev, TDC_CTRL_RESET_ACAM, BASE_DP_TDC_REGS + TDC_REG_CTRL);
        udelay(100);

        /* wait for the ACAM's PLL to lock (2 seconds) */
        mdelay(2000);
        if (acam_is_pll_locked(dev)) {
                pr_debug("%s: ACAM initialization OK.\n",__func__);
                return 0;
        }

        pr_error("%s: ACAM PLL doesn't lock\n", __func__);
        return -EIO;
}

static int tdc_time_init(struct wrtd_tdc_dev *dev)
{
        /* program the VCXO DAC to the default calibration value */
        tdc_writel(dev, TDC_VCXO_DEFAULT_TUNE, BASE_DP_TDC_REGS + TDC_REG_DAC_TUNE);
        tdc_writel(dev, TDC_CTRL_CONFIG_DAC, BASE_DP_TDC_REGS + TDC_REG_CTRL);

        /* set TAI time to zero */
        tdc_writel(dev, 0, BASE_DP_TDC_REGS + TDC_REG_START_UTC);
        tdc_writel(dev, TDC_CTRL_LOAD_UTC, BASE_DP_TDC_REGS + TDC_REG_CTRL);

        return 0;
}

static int tdc_enable_termination(struct wrtd_tdc_dev *tdc,
                                  int channel, int enable)
{
        uint32_t ien;

        ien = tdc_readl(tdc, BASE_DP_TDC_REGS + TDC_REG_INPUT_ENABLE);

        if (enable)
                ien |= (1 << channel);
        else
                ien &= ~(1 << channel);

        tdc_writel(tdc, ien, BASE_DP_TDC_REGS + TDC_REG_INPUT_ENABLE);

        return 0;
}

static int tdc_channels_init(struct wrtd_tdc_dev *tdc)
{
        int i;

        for (i = 0; i < TDC_NUM_CHANNELS; i++) {
                /* termination is off by default */
                tdc_enable_termination(tdc, i, 0);
        }

        return 0;
}

static void tdc_enable_acquisition(struct wrtd_tdc_dev *tdc, int enable)
{
        uint32_t ien;

        ien = tdc_readl(tdc, BASE_DP_TDC_REGS + TDC_REG_INPUT_ENABLE);
        if (enable) {
                /* Enable TDC acquisition */
                tdc_writel(tdc, ien | TDC_INPUT_ENABLE_CH_ALL | TDC_INPUT_ENABLE_FLAG,
                           BASE_DP_TDC_REGS + TDC_REG_INPUT_ENABLE);
                /* Enable ACAM acquisition */
                tdc_writel(tdc, TDC_CTRL_EN_ACQ, BASE_DP_TDC_REGS + TDC_REG_CTRL);
        } else {
                /* Disable ACAM acquisition */
                tdc_writel(tdc, TDC_CTRL_DIS_ACQ, BASE_DP_TDC_REGS + TDC_REG_CTRL);
                /* Disable TDC acquisition */
                tdc_writel(tdc, ien & ~(TDC_INPUT_ENABLE_CH_ALL | TDC_INPUT_ENABLE_FLAG),
                           BASE_DP_TDC_REGS + TDC_REG_INPUT_ENABLE);
        }
}

/* FIXME missing all calibration */
static int tdc_init(struct wrtd_tdc_dev *tdc)
{
        int err;

        pr_debug("%s: Initializing the TDC...\n\r", __func__);

        /* Initialize the TDC FIFO (channels disabled, default dead time) */
        tdc_writel(tdc, 0x0, BASE_DP_TDC_DIRECT + DR_REG_CHAN_ENABLE);
        tdc_writel(tdc, DEFAULT_DEAD_TIME, BASE_DP_TDC_DIRECT + DR_REG_DEAD_TIME);

#ifndef SIMULATION
        tdc_enable_acquisition(tdc, 0);

        err = tdc_acam_init(tdc);
        if (err)
                return err;

        err = tdc_time_init(tdc);
        if (err)
                return err;

        err = tdc_channels_init(tdc);
        if (err)
                return err;

        tdc_enable_acquisition(tdc, 1);
#endif

        pr_debug("%s: TDC initialization complete\n\r", __func__);

        return 0;
}

static inline int tdc_wr_link_up(struct wrtd_tdc_dev *tdc)
{
        return tdc_readl(tdc, BASE_DP_TDC_REGS + TDC_REG_WR_STAT) & TDC_WR_STAT_LINK;
}

static inline int tdc_wr_time_locked(struct wrtd_tdc_dev *tdc)
{
        return tdc_readl(tdc, BASE_DP_TDC_REGS + TDC_REG_WR_STAT) & TDC_WR_STAT_AUX_LOCKED;
}

static void tdc_wr_enable_lock(struct wrtd_tdc_dev *tdc, int enable)
{
        tdc_writel(tdc, TDC_CTRL_DIS_ACQ, BASE_DP_TDC_REGS + TDC_REG_CTRL);
        tdc_writel(tdc, enable ? TDC_WR_CTRL_ENABLE : 0, BASE_DP_TDC_REGS + TDC_REG_WR_CTRL);
        tdc_writel(tdc, TDC_CTRL_EN_ACQ, BASE_DP_TDC_REGS + TDC_REG_CTRL);
}

static inline int tdc_wr_time_ready(struct wrtd_tdc_dev *tdc)
{
        return tdc_readl(tdc, BASE_DP_TDC_REGS + TDC_REG_WR_STAT) & TDC_WR_STAT_TIME_VALID;
}

static inline int tdc_wr_sync_timeout(void)
{
        /* Wait 3 seconds until the tdc is ready (+ 1 for truncation).  */
        return 4;
}

/**
 * Handles input timestamps from all TDC channels.
 *
 * TODO: tdc config: base address
 *                   first channel number (for sid)
 *                   time offset
 */
static void tdc_input(struct wrtd_tdc_dev *tdc)
{
        uint32_t fifo_sr = tdc_readl(tdc, BASE_DP_TDC_DIRECT + DR_REG_FIFO_CSR);
        struct wrtd_event ev;
        int meta;

        /* Poll the FIFO and read the timestamp */
        if(fifo_sr & DR_FIFO_CSR_EMPTY)
                return;

        ev.ts.seconds = tdc_readl(tdc, BASE_DP_TDC_DIRECT + DR_REG_FIFO_R0);
        ev.ts.ns = tdc_readl(tdc, BASE_DP_TDC_DIRECT + DR_REG_FIFO_R1) * 8;
        meta = tdc_readl(tdc, BASE_DP_TDC_DIRECT + DR_REG_FIFO_R2);

        /* Conversion from ACAM TDC bins to WR already done in gateware
           (including overflow checks), result is in 2**-12 8ns ticks */
        uint32_t frac = meta & 0x00fff;

        ev.ts.ns += frac >> 9;

        /* Change frac resolution from 2**-9 ns to 2**-32 ns */
        ev.ts.frac = frac << (32 - 9);

        int channel = (meta >> 19) & 0x7;

        memset(ev.id, 0, WRTD_ID_LEN);
        ev.id[0] = 'L';
        ev.id[1] = 'C';
        ev.id[2] = '-';
        ev.id[3] = 'I';
        ev.id[4] = '1' + channel;  /* channel starts from 0. */
        ev.flags = 0;

        wrtd_log(WRTD_LOG_MSG_EV_GENERATED,
                 WRTD_LOG_GENERATED_DEVICE + (channel * 8),
                 NULL, &ev, NULL);

        /* Pass to wrtd.  */
        wrtd_route_in(&ev);
}
