/**
\brief Sets the DC offset for the selected path (I or Q) in the selected AD9963s
\param baseaddr Base memory address of w3_ad_controller pcore
\param csMask OR'd combination of RFA_AD_CS and RFB_AD_CS
\param iqSel Select I or Q path; must be AD_CHAN_I or AD_CHAN_Q
\param dco DC offset to apply, in [0,1024]
*/
int ad_set_TxDCO(u32 baseaddr, u32 csMask, u8 iqSel, u16 dco) {

	//Sanity check inputs
	if( ((csMask & (AD_CTRL_ALL_RF_CS)) == 0) || (dco>1023))
		return -1;
	
	if(iqSel == AD_CHAN_I) {
		//AUXIO2=DAC10A=I DAC TxDCO
		ad_spi_write(baseaddr, csMask, 0x49, ((dco & 0x3FF) >> 2)); //DAC10A data[9:2] - 8 MSB
		ad_spi_write(baseaddr, csMask, 0x4A, (dco & 0x3)); //DAC10A {6'b0, data[1:0]} - 2 LSB
	}
static void config_adxl362(spi_device dev)
{
        uint8_t cmd[3];

        /* reset ADXL362 */
        cmd[0] = ADXL362_CMD_WRITE_REG;
        cmd[1] = ADXL362_REG_SOFT_RESET;
        cmd[2] = ADXL362_RESET_CODE;
        ad_spi_write(dev, cmd, sizeof(cmd));
        OS_DELAY_MS(1);

        /*
         * configure filter control register
         * range: 4g
         * halved bandwidth: true
         * external sampling trigger: false
         * output data rate: 25 Hz
         */
        cmd[0] = ADXL362_CMD_WRITE_REG;
        cmd[1] = ADXL362_REG_FILTER_CTL;
        cmd[2] = ADXL362_FILTER_CTL_CODE;
        ad_spi_write(dev, cmd, sizeof(cmd));

        /*
         * configure power control register
         * external clock: false
         * low noise: ultralow noise mode
         * wake-up mode: false
         * autosleep: false
         * measure mode: measure
         */
        cmd[0] = ADXL362_CMD_WRITE_REG;
        cmd[1] = ADXL362_REG_POWER_CTL;
        cmd[2] = ADXL362_POWER_CTL_CODE;
        ad_spi_write(dev, cmd, sizeof(cmd));

        /* wait for first measurement's value */
        OS_DELAY_MS(1000 / 25 + 1);
}
/**
\brief Initializes the AD controller. This function must be called once at boot before any AD or RF operations will work
\param baseaddr Base memory address of w3_ad_controller pcore
\param clkdiv Clock divider for SPI serial clock (set to 3 for 160MHz bus)
*/
int ad_init(u32 baseaddr, u32 adSel, u8 clkdiv)
{
	u32 rstMask, reg5c, reg72, reg5c_check, reg72_check;
	
	if((adSel & AD_CTRL_ALL_RF_CS) == 0) {
		print("ad_init: Invalid adSel parameter!\n");
		return -1;
	}

	rstMask = 0;
	reg5c_check = 0;
	reg72_check = 0;
	if(adSel & RFA_AD_CS) {rstMask |= ADCTRL_REG_CONFIG_MASK_RFA_AD_RESET; reg5c_check |= 0x00000008; reg72_check |= 0x00000001;}
	if(adSel & RFB_AD_CS) {rstMask |= ADCTRL_REG_CONFIG_MASK_RFB_AD_RESET; reg5c_check |= 0x00000800; reg72_check |= 0x00000100;}
	if(adSel & RFC_AD_CS) {rstMask |= ADCTRL_REG_CONFIG_MASK_RFC_AD_RESET; reg5c_check |= 0x00080000; reg72_check |= 0x00010000;}
	if(adSel & RFD_AD_CS) {rstMask |= ADCTRL_REG_CONFIG_MASK_RFD_AD_RESET; reg5c_check |= 0x08000000; reg72_check |= 0x01000000;}
	
	//Toggle AD resets (active low), Set SPI clock divider
	Xil_Out32(baseaddr + ADCTRL_REG_CONFIG, 0);
	Xil_Out32(baseaddr + ADCTRL_REG_CONFIG, (clkdiv & ADCTRL_REG_CONFIG_MASK_CLKDIV) | rstMask);

	//Toggle soft reset, set SDIO pin to bidirectional (only way to do SPI reads)
	ad_spi_write(baseaddr, (adSel), 0x00, 0xBD); //SDIO=1, LSB_first=0, reset=1
	ad_spi_write(baseaddr, (adSel), 0x00, 0x99); //SDIO=1, LSB_first=0, reset=0

	//Confirm the SPI ports are working
	//AD9963 reg5C should be 0x08 always, reg72 is 0x1 on boot
	reg5c = (ad_spi_read(baseaddr,  (adSel), 0x5C))&reg5c_check;
	reg72 = (ad_spi_read(baseaddr,  (adSel), 0x72))&reg72_check;
	if((reg5c != reg5c_check) || (reg72 != reg72_check)) {
		xil_printf("First AD SPI read was wrong: addr[5C]=0x%08x (should be 0x%08x), addr[72]=0x%08x (should be 0x%08x)\n", reg5c, reg5c_check, reg72, reg72_check);
		print("Asserting AD9963 resets\n");
		Xil_Out32(baseaddr + ADCTRL_REG_CONFIG, 0);

		return -1;
	}
	
	/* Default AD9963 configuration:
		-External ref resistor (NOTE: apparent datasheet bug!)
		-Full-duplex mode (Tx data on TXD port, Rx data on TRXD port)
		-Power up everything except:
			-DAC12A, DAC12B, AUXADC (all unconnected on PCB)
			-DLL
		-Clocking:
			-DLL disabled
			-ADC clock = DAC clock = ext clock (nominally 80MHz)
		-Tx path:
			-Data in 2's complement (NOTE: datasheet bug!)
			-TXCLK is input at TXD sample rate
			-TXD is DDR, I/Q interleaved, I first
			-Tx interpolation filters bypassed
			-Tx gains:
				-Linear gain set to 100%
				-Linear-in-dB gain set to -3dB
				-DAC RSET set to 100%
			-Tx DCO DACs:
				-Enabled, configured for [0,+2]v range
				-Set to mid-scale output (approx equal to common mode voltage of I/Q diff pairs)
		-Rx path:
			-Data in 2's complement (NOTE: datasheet bug!)
			-TRXCLK is output at TRXD sample rate
			-TRXD is DDR, I/Q interleaved, I first
			-Decimation filter bypassed
			-RXCML output enabled (used by ADC driver diff amp)
			-ADC common mode buffer off (required for DC coupled inputs)
			-Rx I path negated digitally (to match swap of p/n traces on PCB)
	*/
		
	//Power on/off blocks
	ad_spi_write(baseaddr, (adSel), 0x40, 0x00); //DAC12A, DAC12B off
	ad_spi_write(baseaddr, (adSel), 0x60, 0x00); //DLL off, everything else on
	ad_spi_write(baseaddr, (adSel), 0x61, 0x03); //LDOs on, AUXADC off
	//xil_printf("AD TEST: reg61=0x%08x\n", ad_spi_read(baseaddr,  (adSel), 0x61));
	
	//Clocking setup
	// [7]=0: ADCCLK=ext clk
	// [6]=0: DACCLK=ext clk
	// [4]=0: disable DLL input ref
	// [3:0]: DLL divide ratio (only 1, 2, 3, 4, 5, 6, 8 valid)
	ad_spi_write(baseaddr, (adSel), 0x71, 0x01); //DLL ref input off, ADCCLK=extCLK, DACCLK=extCLK, DLL_DIV=1

	//Reference resistor selection
	// Datasheet says reg62[0]=0 for internal resistor, reg62[0]=1 for external resistor
	// But experimentally DAC currents are much more stable with temperature when reg62[0]=0
	// I'm guessing this bit is flipped in the datasheet
	ad_spi_write(baseaddr, (adSel), 0x62, 0x00);

	//Clock disables and DCS
	// [7:3]=0: Enable internal clocks to ADCs/DACs
	// [1:0]=0: Set ADCCLK=ext clock (no division)
	// [2]=0: Enable ADC duty cycle stabilizer (recommended for ADC rates > 75MHz)
	ad_spi_write(baseaddr, (adSel), 0x66, 0x00); //Enable internal clocks, enable DCS

	//Aux DACs (Tx DC offset adjustment)
	// DAC10B=Q DCO, DAC10A=I DCO
	// DAC outputs update after LSB write (configured by reg40[0])
	ad_spi_write(baseaddr, (adSel), 0x45, 0x88); //DAC10B on, full scale = [0,+2]v
	ad_spi_write(baseaddr, (adSel), 0x46, 0x80); //DAC10B data[9:2]
	ad_spi_write(baseaddr, (adSel), 0x47, 0x00); //DAC10B {6'b0, data[1:0]}

	ad_spi_write(baseaddr, (adSel), 0x48, 0x88); //DAC10A on, full scale = [0,+2]v
	ad_spi_write(baseaddr, (adSel), 0x49, 0x80); //DAC10A data[9:2]
	ad_spi_write(baseaddr, (adSel), 0x50, 0x00); //DAC10A {6'b0, data[1:0]}

	//ADC common mode buffer: disabled for DC coupled inputs
	ad_spi_write(baseaddr, (adSel), 0x7E, 0x01);

	//Spectral inversion
	// Invert RxI (reg3D[2]=1) to match PCB
	// TxI, TxQ also swapped on PCB, but ignored here since -1*(a+jb) is just one of many phase shifts the Tx signal sees
	ad_spi_write(baseaddr, (adSel), 0x3D, 0x04); //Invert RxI to match PCB (board swaps +/- for better routing)

	//Rx clock and data order/format
	// [7:1]=[0 x 1 0 1 0 1] to match design of ad_bridge input registers (TRXD DDR relative to TRXCLK, I data first)
	// [0]=1 for 2's compliment (Datasheet says reg32[0]=0 for 2's compliment, but experiments show 0 is really straight-binary)
	ad_spi_write(baseaddr, (adSel), 0x32, 0x2B);

	//Full-duplex mode (DACs/TXD and ADCs/TRXD both active all the time)
	ad_spi_write(baseaddr, (adSel), 0x3F, 0x01); //FD mode

	//Tx data format (datasheet bug! reg[31][0] is flipped; 1=2's complement, 0=straight binary)
	//0x17 worked great with latest ad_bridge (where TXCLK is ODDR(D1=1,D0=0,C=ad_ref_clk_90) and TXD are ODDR (D1=I,D2=Q,C=ad_ref_clk_0))
	ad_spi_write(baseaddr, (adSel), 0x31, 0x17); //Txdata=DDR two's complement,

	//Tx/Rx data paths
	ad_spi_write(baseaddr, (adSel), 0x30, 0x3F); //Bypass all rate change filters, enable Tx/Rx clocks
//	ad_spi_write(baseaddr, (adSel), 0x30, 0x37); //INT0 on, enable Tx/Rx clocks
//	ad_spi_write(baseaddr, (adSel), 0x30, 0x27); //INT0+INT1 on, enable Tx/Rx clocks
//	ad_spi_write(baseaddr, (adSel), 0x30, 0x23); //INT0+INT1+SRCC on, enable Tx/Rx clocks

	//ADC RXCML output buffer requires special register process (see AD9963 datasheet pg. 21 "sub serial interface communications")
	ad_spi_write(baseaddr, (adSel), 0x05, 0x03); //Address both ADCs
	ad_spi_write(baseaddr, (adSel), 0x0F, 0x02); //Enable RXCML output
	ad_spi_write(baseaddr, (adSel), 0x10, 0x00); //Set I/Q offset to 0
	ad_spi_write(baseaddr, (adSel), 0xFF, 0x01); //Trigger ADC param update
	ad_spi_write(baseaddr, (adSel), 0x05, 0x00); //De-Address both ADCs

	//REFIO adjustment: set to default of 0.8v
	ad_spi_write(baseaddr, (adSel), 0x6E, 0x40);

	//Tx gains (it seems these registers default to non-zero, and maybe non-I/Q-matched values; safest to set them explicitly after reset)
	//I/Q GAIN1[5:0]: Fix5_0 value, Linear-in-dB, 0.25dB per bit
	//  0-25=>0dB-+6dB, 25-32:+6dB, 33-41:-6dB, 41-63=>-6dB-0dB
	ad_spi_write(baseaddr, (adSel), 0x68, 0); //IGAIN1
	ad_spi_write(baseaddr, (adSel), 0x6B, 0); //QGAIN1

	//I/Q GAIN2[5:0]: Fix5_0 value, Linear +/-2.5%, 0.08% per bit
	// 0:+0, 31:+max, 32:-max, 63:-0
	ad_spi_write(baseaddr, (adSel), 0x69, 0); //IGAIN2
	ad_spi_write(baseaddr, (adSel), 0x6C, 0); //QGAIN2

	//I/Q RSET[5:0]: Fix5_0, Linear +/-20%, 0.625% per bit
	// 0:-0, 31:-max, 32:+max, 63:+0
	ad_spi_write(baseaddr, (adSel), 0x6A, 0); //IRSET
	ad_spi_write(baseaddr, (adSel), 0x6D, 0); //QRSET

	//Digital output drive strengths: all 8mA
	ad_spi_write(baseaddr, (adSel), 0x63, 0x55); //2 bits each: TRXD TRXIQ TRXCLK TXCLK
	
	//Disable Tx and Rx BIST modes
	ad_spi_write(baseaddr, (adSel), 0x50, 0x00); //Tx BIST control
	ad_spi_write(baseaddr, (adSel), 0x51, 0x00); //Rx BIST control
	
	//Finally enable TX CLK output from ad_bridge
	// This is a register bit in the ad_controller, connected to the ad_bridge in the XPS project
	//  that controls the OBUFT for the TXCLK output, to avoid drive fights pre-init
	Xil_Out32((baseaddr + ADCTRL_REG_CONFIG), (Xil_In32((baseaddr + ADCTRL_REG_CONFIG)) | ADCTRL_REG_CONFIG_TXCLK_OUT_EN));

	return 0;
}