/**
 * @file fd_init.c
 *
 * Author: Federico Vaga <federico.vaga@cern.ch>
 * Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
 * Author: Dimitris Lampridis <dimitris.lampridis@cern.ch>
 *
 * 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 "hw/fd_channel_regs.h"
#include "hw/fd_main_regs.h"

#include "wrtd-fd.h"

#ifdef SIMULATION
#define UDELAY(X)
#define MDELAY(X)
#else
#define UDELAY(X) udelay(X)
#define MDELAY(X) mdelay(X)
#endif

/**
 * fd_do_reset
 * The reset function (by Tomasz)
 *
 * This function can reset the entire mezzanine (FMC) or just
 * the fine-delay core (CORE).
 * In the reset register 0 means reset, 1 means normal operation.
 */
static void fd_do_reset(struct wrtd_fd_dev *fd, int hw_reset)
{
        if (hw_reset) {
                /* clear RSTS_RST_FMC bit, set  RSTS_RST_CORE bit*/
                fd_writel(fd, FD_RSTR_LOCK_W(0xdead) | FD_RSTR_RST_CORE_MASK,
                          FD_REG_RSTR);
                UDELAY(10000);
                fd_writel(fd, FD_RSTR_LOCK_W(0xdead) | FD_RSTR_RST_CORE_MASK
                          | FD_RSTR_RST_FMC_MASK, FD_REG_RSTR);
                /* TPS3307 supervisor needs time to de-assert master reset */
                MDELAY(600);
                return;
        }

        /* clear RSTS_RST_CORE bit, set RSTS_RST_FMC bit */
        fd_writel(fd, FD_RSTR_LOCK_W(0xdead) | FD_RSTR_RST_FMC_MASK,
                  FD_REG_RSTR);
        UDELAY(1000);
        fd_writel(fd, FD_RSTR_LOCK_W(0xdead) | FD_RSTR_RST_FMC_MASK
                  | FD_RSTR_RST_CORE_MASK, FD_REG_RSTR);
        UDELAY(1000);
}

/* Some init procedures to be intermixed with subsystems */
int fd_gpio_defaults(struct wrtd_fd_dev *fd)
{
        fd_gpio_dir(fd, FD_GPIO_TRIG_INTERNAL, FD_GPIO_OUT);
        fd_gpio_set(fd, FD_GPIO_TRIG_INTERNAL);

        fd_gpio_set(fd, FD_GPIO_OUTPUT_MASK);
        fd_gpio_dir(fd, FD_GPIO_OUTPUT_MASK, FD_GPIO_OUT);

        fd_gpio_dir(fd, FD_GPIO_TERM_EN, FD_GPIO_OUT);
        fd_gpio_clr(fd, FD_GPIO_TERM_EN);
        return 0;
}

int fd_reset_again(struct wrtd_fd_dev *fd)
{
        /* Reset the FD core once we have proper reference/TDC clocks */
        fd_do_reset(fd, 0 /* not hw */);

        MDELAY(10);

        if (! (fd_readl(fd, FD_REG_GCR) & FD_GCR_DDR_LOCKED) )
        {
                pr_error("timeout waiting for GCR lock bit\n");
                return -EIO;
        }

        fd_do_reset(fd, 0 /* not hw */);
        return 0;
}

/* FIXME missing all calibration */
int fd_init(struct wrtd_fd_dev *fd)
{
        int err, ch;

        pr_debug("Initializing the Fine Delay board...\n");

        fd_do_reset(fd, 1);

        err = fd_gpio_init(fd);
        if (err)
                return err;

        err = fd_pll_init(fd);
        if (err)
                return err;

        fd_gpio_defaults(fd);

        err = fd_reset_again(fd);
        if (err)
                return err;

        err = fd_acam_init(fd);
        if (err)
                return err;

        for (ch = 1; ch <= FD_NUM_CHANNELS; ch++)
                fd_gpio_set(fd, FD_GPIO_OUTPUT_EN(ch));

        // todo: read offsets from the EEPROM. I2C should be working OK.

        return 0;
}

#ifdef SIMULATION
int fd_sim_init(struct wrtd_fd_dev *fd)
{
        int err;

        pr_debug("Initializing the Fine Delay board...\n");

        fd_do_reset(fd, 1);

        return 0;
}
#endif
