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

#ifndef __FINE_DELAY_WRAPPER_H
#define __FINE_DELAY_WRAPPER_H

#include "wrtd-common.h"

/* Channels are called 1..4 in all docs. Internally it's 0..3 */
#define FD_CH_1         0
#define FD_CH_LAST      3
#define FD_NUM_CHANNELS 4
#define FD_CH_EXT(i)    ((i) + 1)

#define FD_NUM_TAPS     1024    /* This is an hardware feature of SY89295U */
#define FD_CAL_STEPS    1024    /* This is a parameter: must be power of 2 */

#define FD_MAGIC_FPGA 0xf19ede1a /* FD_REG_IDR content */

/* Values for the configuration of the acam PLL. Can be changed */
#define ACAM_DESIRED_BIN    80.9553
#define ACAM_CLOCK_FREQ_KHZ 31250

/* ACAM TDC operation modes */
enum fd_acam_modes {
        ACAM_RMODE,
        ACAM_IMODE,
        ACAM_GMODE
};

#define FD_GPIO_TERM_EN         0x0001          /* Input terminator enable */
#define FD_GPIO_OUTPUT_EN(x)                            \
        (1 << (6-(x)))  /* Output driver enable */
#define FD_GPIO_OUTPUT_MASK     0x003c          /* Output driver enable */
#define FD_GPIO_TRIG_INTERNAL   0x0040          /* TDC trig (1=in, 1=fpga) */
#define FD_GPIO_CAL_DISABLE     0x0080          /* 0 enables calibration */


/*
 * You can change the following value to have a pll with smaller divisor,
 * at the cost of potentially less precision in the desired bin value.
 */
#define ACAM_MAX_REFDIV 7

#define ACAM_MASK        ((1<<29) - 1) /* 28 bits */

/* SPI Bus chip selects */
#define FD_CS_DAC       0       /* DAC for VCXO */
#define FD_CS_PLL       1       /* AD9516 PLL */
#define FD_CS_GPIO      2       /* MCP23S17 GPIO */

/* MCP23S17 register addresses (only ones which are used by the lib) */
#define FD_MCP_IODIR    0x00
#define FD_MCP_IPOL     0x01
#define FD_MCP_IOCON    0x0a
#define FD_MCP_GPIO     0x12
#define FD_MCP_OLAT     0x14

#define FD_MAX_QUEUE_PULSES 4

/* Pulse FIFO for a single Fine Delay output */
struct lrt_pulse_queue {
        struct wrtd_event events[FD_MAX_QUEUE_PULSES];
        int head, tail, count;
};

struct wrtd_fd_channel {
        uint32_t channel_addr;
        struct lrt_pulse_queue queue;

        uint8_t idle;

        uint32_t width_ns;

        /* Last timestamp value written to output config.  */
        uint32_t last_programmed_sec;
        uint32_t last_programmed_ns;
};

struct wrtd_fd_dev {
        uint32_t io_addr;
        int fd_acam_addr;
        int fd_bin_size;
        uint32_t fd_mcp_iodir;
        uint32_t fd_mcp_olat;

        struct wrtd_fd_channel channels[FD_NUM_CHANNELS];
};

int fd_spi_xfer(struct wrtd_fd_dev *fd, int ss, int num_bits,
                uint32_t in, uint32_t *out);
int fd_spi_init(struct wrtd_fd_dev *fd);
int fd_pll_init(struct wrtd_fd_dev *fd);

int fd_acam_init(struct wrtd_fd_dev *fd);
uint32_t acam_readl(struct wrtd_fd_dev *fd, int reg);
void acam_writel(struct wrtd_fd_dev *fd, int val, int reg);

/* Functions exported by calibrate.c, called within acam.c */
int fd_calibrate_outputs(struct wrtd_fd_dev *fd);

/* Functions exported by gpio.c */
int fd_gpio_init(struct wrtd_fd_dev *fd);
void fd_gpio_exit(struct wrtd_fd_dev *fd);
void fd_gpio_dir(struct wrtd_fd_dev *fd, int pin, int dir);
void fd_gpio_val(struct wrtd_fd_dev *fd, int pin, int val);
void fd_gpio_set_clr(struct wrtd_fd_dev *fd, int pin, int set);

int fd_init(struct wrtd_fd_dev *fd);
int fd_sim_init(struct wrtd_fd_dev *fd);

#define fd_gpio_set(fd, pin) fd_gpio_set_clr(fd, (pin), 1)
#define fd_gpio_clr(fd, pin) fd_gpio_set_clr(fd, (pin), 0)

#define FD_GPIO_IN      0
#define FD_GPIO_OUT     1

static inline void fd_writel(struct wrtd_fd_dev *fd, uint32_t val, uint32_t reg)
{
        dp_writel(val, fd->io_addr + reg);
}

static inline uint32_t fd_readl(struct wrtd_fd_dev *fd, uint32_t reg)
{
        return dp_readl(fd->io_addr + reg);
}

static inline uint32_t fd_drv_ch_readl(struct wrtd_fd_dev *fd, int ch,
                                       unsigned long reg)
{
        return fd_readl(fd, 0x100 + ch * 0x100 + reg);
}

static inline void fd_drv_ch_writel(struct wrtd_fd_dev *fd, int ch,
                                    uint32_t v, unsigned long reg)
{
        fd_writel(fd, v, 0x100 + ch * 0x100 + reg);
}

static inline uint64_t div_u64(uint64_t dividend, uint32_t divisor)
{
        return dividend / divisor;
}


#endif
