/*
 * This work is part of the White Rabbit project
 *
 * Copyright (C) 2011d CERN (www.cern.ch)
 * 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 AD9516, SPI is opencores
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>

#include "ad9516.h"
#include "fpga_io.h"

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

/*
 * 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 GPIO_PLL_RESET_N 1
#define GPIO_SYS_CLK_SEL 0
#define GPIO_PERIPH_RESET_N 3

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

#define SECONDARY_DIVIDER 0x100

#define BASE_SPI 0x10200
#define BASE_GPIO 0x10300
#define GPIO_COR  0x0
#define GPIO_SOR  0x4

struct ad9516_reg {
	uint16_t reg;
	uint8_t val;
};

/* Configuration for the SCB version greater than or equal 3.4: Base + 6, 7, 8, 9 outputs*/
const struct ad9516_reg ad9516_base_config_34[] = {
{0x0000, 0x99},
{0x0001, 0x00},
{0x0002, 0x10},
{0x0003, 0xC3},
{0x0004, 0x00},
{0x0010, 0x7C},
{0x0011, 0x05},
{0x0012, 0x00},
{0x0013, 0x0C},
{0x0014, 0x12},
{0x0015, 0x00},
{0x0016, 0x05},
{0x0017, 0xB8},//default 0x88 = ref2, now holdover active for sync pin test
{0x0018, 0x07},
{0x0019, 0x00},
{0x001A, 0x2C},//default 0x00 = DLD, now selected ref for ref_sel pin test
{0x001B, 0x00},
{0x001C, 0xA6},//default 0x02, now enable ref selection by ref_sel pin for test
{0x001D, 0x07},//default 0x00, now enable holdover for sync pin test
{0x001E, 0x00},
{0x001F, 0x0E},
{0x00A0, 0x01},
{0x00A1, 0x00},
{0x00A2, 0x00},
{0x00A3, 0x01},
{0x00A4, 0x00},
{0x00A5, 0x00},
{0x00A6, 0x01},
{0x00A7, 0x00},
{0x00A8, 0x00},
{0x00A9, 0x01},
{0x00AA, 0x00},
{0x00AB, 0x00},
{0x00F0, 0x0A},
{0x00F1, 0x0A},
{0x00F2, 0x0A},
{0x00F3, 0x08},//default 0x0A, now enable output
{0x00F4, 0x08},
{0x00F5, 0x08},
 // The following registers configure the PLL outputs from 6 to 9.
{0x0140, 0x42},
{0x0141, 0x42},
{0x0142, 0x43},
{0x0143, 0x4E},
{0x0190, 0x55},
{0x0191, 0x00},
{0x0192, 0x00},
{0x0193, 0x11},
{0x0194, 0x00},
{0x0195, 0x00},
{0x0196, 0x10},
{0x0197, 0x00},
{0x0198, 0x00},
{0x0199, 0x33},
{0x019A, 0x00},
{0x019B, 0x11},
{0x019C, 0x20},
{0x019D, 0x00},
{0x019E, 0x33},
{0x019F, 0x00},
{0x01A0, 0x11},
{0x01A1, 0x20},
{0x01A2, 0x00},
{0x01A3, 0x00},
//
{0x01E0, 0x04},
{0x01E1, 0x02},
{0x0230, 0x00},
{0x0231, 0x00},
};

/* Config for 25 MHz VCTCXO reference (RDiv = 5, use REF1) */
const struct ad9516_reg ad9516_ref_tcxo[] = {
{0x0011, 0x05},
{0x0012, 0x00}, /* RDiv = 5 */
{0x001C, 0xA6}  //default0x06, now enable ref selection by ref_sel pin for test /* Use REF1 */
};

/* Config for 10 MHz external reference (RDiv = 2, use REF2) */
const struct ad9516_reg ad9516_ref_ext[] = {
{0x0011, 0x02},
{0x0012, 0x00}, /* RDiv = 2 */
{0x001C, 0x46}  /* Use REF1 */
};

static int oc_spi_init(void *base_addr)
{
	//oc_spi_base = base_addr;

	_fpga_writel(BASE_SPI + SPI_REG_DIVIDER, 100);
	return 0;
}

static int oc_spi_txrx(int ss, int nbits, uint32_t in, uint32_t *out)
{
	uint32_t rval, tmp=0;

	if (!out)
		out = &rval;

	_fpga_writel(SPI_CTRL_ASS | SPI_CTRL_CHAR_LEN(nbits)
		     | SPI_CTRL_TXNEG,
		     BASE_SPI + SPI_REG_CTRL);

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

	while((_fpga_readl(BASE_SPI + SPI_REG_CTRL) & SPI_CTRL_GO_BSY) && (tmp++ < 0xFFFFFFFF))
		;
	*out = _fpga_readl(BASE_SPI + SPI_REG_RX0);
	return 0;
}

/*
 * AD9516 stuff, using SPI, used by later code.
 * "reg" is 12 bits, "val" is 8 bits, but both are better used as int
 */

static void ad9516_write_reg(int reg, int val)
{
	oc_spi_txrx(CS_PLL, 24, (reg << 8) | val, NULL);
}

int ad9516_read_reg(int reg)
{
	uint32_t rval;
	oc_spi_txrx(CS_PLL, 24, (reg << 8) | (1 << 23), &rval);
	return rval & 0xff;
}

static void ad9516_load_regset(const struct ad9516_reg *regs, int n_regs, int commit)
{
	int i;
	for(i=0; i<n_regs; i++)
		ad9516_write_reg(regs[i].reg, regs[i].val);
		
	if(commit)
		ad9516_write_reg(0x232, 1);
}

static void ad9516_wait_lock(void)
{
	while ((ad9516_read_reg(0x1f) & 1) == 0);
}

static int ad9516_set_vco_divider(int ratio) /* Sets the VCO divider (2..6) or 0 to enable static output */
{
	if(ratio == 0)
		ad9516_write_reg(0x1e0, 0x5); /* static mode */
	else
		ad9516_write_reg(0x1e0, (ratio-2));
	ad9516_write_reg(0x232, 0x1);
	return 0;
}

static void ad9516_sync_outputs(void)
{
	/* VCO divider: static mode */
	ad9516_write_reg(0x1E0, 0x7);
	ad9516_write_reg(0x232, 0x1);

	/* Sync the outputs when they're inactive to avoid +-1 cycle uncertainity */
	ad9516_write_reg(0x230, 1);
	ad9516_write_reg(0x232, 1);
	ad9516_write_reg(0x230, 0);
	ad9516_write_reg(0x232, 1);
}

static void gpio_out(int pin, int val)
{
    int temp;

    if(val)
    {
        temp = _fpga_readl(BASE_GPIO+4);
        temp |= (1<<pin);
    }
    else
        temp = (1<<pin);
    
    _fpga_writel(BASE_GPIO + (val ? GPIO_SOR : GPIO_COR), temp);
}

int ad9516_init(void)
{
	printf("Initializing AD9516 PLL...\n");

	oc_spi_init((void *)BASE_SPI);
    
	gpio_out(GPIO_SYS_CLK_SEL, 0); //switch to the standby reference clock, since the PLL is off after reset 

	//reset the PLL
	gpio_out(GPIO_PLL_RESET_N, 0);
	usleep(10); 
	gpio_out(GPIO_PLL_RESET_N, 1);
	usleep(10);
	
	//Use unidirectional SPI mode 
	ad9516_write_reg(0x000, 0x99);

	// Check the presence of the chip
	if (ad9516_read_reg(0x3) != 0xc3) {
		printf("Error: AD9516 PLL not responding.\n");
		return -1;
	}

	ad9516_load_regset(ad9516_base_config_34, ARRAY_SIZE(ad9516_base_config_34), 0);
	ad9516_load_regset(ad9516_ref_tcxo, ARRAY_SIZE(ad9516_ref_tcxo), 1);
	
    ad9516_wait_lock();
	ad9516_sync_outputs();

	ad9516_set_output_divider(2, 32, 0);  	// OUT2. 187.5 MHz. - not anymore-default, now 15mhz
	//ad9516_set_output_divider(3, 4, 0);  	// OUT3. 187.5 MHz. - not anymore
	ad9516_set_output_divider(4, 1, 0);  	// OUT4. 500 MHz.

	//The following PLL outputs have been configured through the ad9516_base_config_34 register,
	//	* so it doesn't need to replicate the configuration:
	//	*
	//	* Output 6 => 62.5 MHz
	//	* Output 7	=> 62.5 MHz
	//	* Output 8	=> 10 MHz
	//	* Output 9	=> 10 MHz
		

	ad9516_sync_outputs();
	ad9516_set_vco_divider(3); 
	
	printf("AD9516 locked.\n");

    //disabled for test, prevent (most) use of pll clocks
	//gpio_out(GPIO_SYS_CLK_SEL, 1); //switch the system clock to the PLL reference 
	//gpio_out(GPIO_PERIPH_RESET_N, 0); //reset all peripherals which use AD9516-provided clocks
	//gpio_out(GPIO_PERIPH_RESET_N, 1);

    
	return 0;
}

int ad9516_set_output_divider(int output, int ratio, int phase_offset)
{
	uint8_t lcycles = (ratio/2) - 1;
	uint8_t hcycles = (ratio - (ratio / 2)) - 1;
	int secondary = (output & SECONDARY_DIVIDER) ? 1 : 0;
	output &= 0xf;

	if(output >= 0 && output < 6) /* LVPECL outputs */
	{
		uint16_t base = (output / 2) * 0x3 + 0x190;

		if(ratio == 1)  /* bypass the divider */
		{
			uint8_t div_ctl = ad9516_read_reg(base + 1);
			ad9516_write_reg(base + 1, div_ctl | (1<<7) | (phase_offset & 0xf)); 
		} else {
			uint8_t div_ctl = ad9516_read_reg(base + 1);
			ad9516_write_reg(base + 1, (div_ctl & (~(1<<7))) | (phase_offset & 0xf));  /* disable bypass bit */
			ad9516_write_reg(base, (lcycles << 4) | hcycles);
		}
	} else { /* LVDS/CMOS outputs */
			
		uint16_t base = ((output - 6) / 2) * 0x5 + 0x199;

		printf("Output [divider %d]: %d ratio: %d base %x lc %d hc %d\n", secondary, output, ratio, base, lcycles ,hcycles);

		if(!secondary)
		{
			if(ratio == 1)  /* bypass the divider 1 */
				ad9516_write_reg(base + 3, ad9516_read_reg(base + 3) | 0x10); 
			else {
				ad9516_write_reg(base, (lcycles << 4) | hcycles); 
				ad9516_write_reg(base + 1, phase_offset & 0xf);
			}
		} else {
			if(ratio == 1)  /* bypass the divider 2 */
				ad9516_write_reg(base + 3, ad9516_read_reg(base + 3) | 0x20); 
			else {
				ad9516_write_reg(base + 2, (lcycles << 4) | hcycles); 
//				ad9516_write_reg(base + 1, phase_offset & 0xf);
				
		}
		}		
	}

	/* update */
	ad9516_write_reg(0x232, 0x0);
	ad9516_write_reg(0x232, 0x1);
	ad9516_write_reg(0x232, 0x0);
	return 0;
}

void ad9516_set_reg(int reg, int val)
{
    ad9516_write_reg(reg, val);
    
    /* apply */
	ad9516_write_reg(0x232, 0x0);
	ad9516_write_reg(0x232, 0x1);
	ad9516_write_reg(0x232, 0x0);
}