/*
 * This work is part of the White Rabbit project
 *
 * Copyright (C) 2020 CERN/Nikhef (www.cern.ch, www.nikhef.nl)
 * Author: Peter Jansweijer <peterj@nikhef.nl>, based on work from
 * Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
 * Author: Alessandro Rubini <rubini@gnudd.com>
 *
 * Released according to the GNU GPL, version 2 or any later version.
 */

/*
 * Trivial pll programmer using an spi controller.
 * PLL is LTC6950, SPI is opencores
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <wrc.h>

#include "board.h"
#include "syscon.h"

#ifdef CONFIG_WR_NODE
#else /* CONFIG_WR_SWITCH */
#include "gpio-wrs.h"
#endif

#include "rt_ipc.h"

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
#endif

static inline void writel(uint32_t data, void *where)
{
	* (volatile uint32_t *)where = data;
}

static inline uint32_t readl(void *where)
{
	return * (volatile uint32_t *)where;
}

struct ltc6950_reg {
	uint16_t reg;
	uint8_t val;
};

#include "ltc6950_config.h"

/*
 * SPI stuff, used by later code
 */

#define SPI_REG_RX0	0
#define SPI_REG_TX0	0
#define SPI_REG_RX1	4
#define SPI_REG_TX1	4
#define SPI_REG_RX2	8
#define SPI_REG_TX2	8
#define SPI_REG_RX3	12
#define SPI_REG_TX3	12

#define SPI_REG_CTRL	16
#define SPI_REG_DIVIDER	20
#define SPI_REG_SS	24

#define SPI_CTRL_ASS		(1<<13)
#define SPI_CTRL_IE		(1<<12)
#define SPI_CTRL_LSB		(1<<11)
#define SPI_CTRL_TXNEG		(1<<10)
#define SPI_CTRL_RXNEG		(1<<9)
#define SPI_CTRL_GO_BSY		(1<<8)
#define SPI_CTRL_CHAR_LEN(x)	((x) & 0x7f)

#define CS_PLL	0 /* LTC6950 on SPI CS0 */

static int oc_spi_init(void *base_addr)
{
	writel(100, base_addr + SPI_REG_DIVIDER);
	return 0;
}

static int oc_spi_txrx(void *base, int ss, int nbits, uint32_t in, uint32_t *out)
{
	uint32_t rval;

	if (!out)
		out = &rval;

	writel(SPI_CTRL_ASS | SPI_CTRL_CHAR_LEN(nbits)
		     | SPI_CTRL_TXNEG,
		     base + SPI_REG_CTRL);

	writel(in, base + SPI_REG_TX0);
	writel((1 << ss), base + SPI_REG_SS);
	writel(SPI_CTRL_ASS | SPI_CTRL_CHAR_LEN(nbits)
		     | SPI_CTRL_TXNEG | SPI_CTRL_GO_BSY,
		     base + SPI_REG_CTRL);

	while(readl(base + SPI_REG_CTRL) & SPI_CTRL_GO_BSY)
		;
	*out = readl(base + SPI_REG_RX0);
	return 0;
}

/*
 * LTC6950 stuff, using SPI, used by later code.
 * "reg" is 7 bits, "val" is 8 bits, but both are better used as int
 * Serial port sequence:
 * A6|A5|A4|A3|A2|A1|A0|RW|D7|D6|D5|D4|D3|D2|D1|D0
 */

static void ltc6950_write_reg(void *base, int reg, int val)
{
	oc_spi_txrx(base, CS_PLL, 16, (reg << 9) | val, NULL);
}

static int ltc6950_read_reg(void *base, int reg)
{
	uint32_t rval;
	oc_spi_txrx(base, CS_PLL, 16, (reg << 9) | (1 << 8), &rval);
	return rval & 0xff;
}

static void ltc6950_load_regset(void *base, const struct ltc6950_reg *regs, int n_regs, int commit)
{
	int i;
	for(i=0; i<n_regs; i++)
		ltc6950_write_reg(base, regs[i].reg, regs[i].val);
}

static void ltc6950_read_regset(void *base, int n_regs)
{
	int i, v;
	for(i=0; i<n_regs; i++) {
		v = ltc6950_read_reg(base, i);
		pp_printf("Reg: 0x%02x = 0x%02x\n", i, v);
	}
}

static void ltc6950_wait_lock(void *base)
{
	while ((ltc6950_read_reg(base,  0x00) & 4) == 0);
}

static void pll_wr_mode_init(int mode)
{
	gpio_out(GPIO_PLL_WR_MODE0, mode & 0x01); 
	pp_printf("GPIO_PLL_WR_MODE0 %x\n", mode & 0x01);
	gpio_out(GPIO_PLL_WR_MODE1, mode & 0x02); 
	pp_printf("GPIO_PLL_WR_MODE1 %x\n", mode & 0x02);
}
	
#ifdef CONFIG_WR_NODE


int spec7_ltc6950_init(int pll_wr_mode)
{
	pp_printf("Initializing SPEC7 LTC6950 PLL...\n");

	oc_spi_init((void *)BASE_SPI);
	
	void *spi_base = (void *)BASE_SPI;

	/* reset the PLL (RES6950 clears itself)*/
	ltc6950_write_reg(spi_base, 0x03, 4);
	timer_delay(10);
	
	/* Check the presence of the chip */
	if (ltc6950_read_reg(spi_base, 0x16) != 0x65) {
		pp_printf("Error: ltc6950 PLL not responding.\n");
		return -1;
	}

    pll_wr_mode_init(pll_wr_mode);
    
    if (pll_wr_mode == PLL_WR_MODE_MASTER | pll_wr_mode == PLL_WR_MODE_GM ) {
      /* Configuration for the SPEC7:
	     External 10 MHZ In (Bulls-Eye B03/B04) => 125 MHz (PLL_WR_MODE_GM) OR
		 10 MHZ from TCXO (PLL_WR_MODE_MASTER) on outputs 0, 1, 2
      */
      ltc6950_load_regset(spi_base, ltc6950_10mhz_base_config_spec7, ARRAY_SIZE(ltc6950_10mhz_base_config_spec7), 0);
      ltc6950_wait_lock(spi_base);
      pp_printf("ltc6950 locked.\n");
    } else {
      /* Configuration for the SPEC7: Forward 125 MHz VCXO_REFCLK at CLK input to outputs 0, 1, 2 */
      ltc6950_load_regset(spi_base, ltc6950_base_config_spec7, ARRAY_SIZE(ltc6950_base_config_spec7), 0);
      timer_delay(10);
    }
    
    pp_printf("Switch clk_sys source from free running clk_dmtd to ltc6950 output.\n");
  	 /* ltc6950 now initialized so switch clk_sys from free running clk_dmtd to ltc6950 output */
	  gpio_out(GPIO_PLL_CLK_SEL, 1);
	  timer_delay(1000);
    pp_printf("now running on ref clock.\n");
    //ltc6950_read_regset(spi_base, 0x17);

}

#endif
