[1927] | 1 | /***************************************************************** |
---|
| 2 | * File: w3_ad_controller.c |
---|
| 3 | * Copyright (c) 2012 Mango Communications, all rights reseved |
---|
| 4 | * Released under the WARP License |
---|
| 5 | * See http://warp.rice.edu/license for details |
---|
| 6 | *****************************************************************/ |
---|
| 7 | |
---|
| 8 | /** \file w3_ad_controller.c |
---|
| 9 | |
---|
| 10 | \mainpage |
---|
| 11 | This is the driver for the w3_ad_controller core, which implements an SPI master for controlling |
---|
| 12 | the AD9963 analog converters for the WARP v3 RF interfaces. |
---|
| 13 | |
---|
| 14 | @version 3.00.b |
---|
| 15 | @author Patrick Murphy |
---|
| 16 | @copyright (c) 2012 Mango Communications, Inc. All rights reserved.<br> |
---|
| 17 | Released under the WARP open source license (see http://warp.rice.edu/license) |
---|
| 18 | |
---|
| 19 | \brief Main source for w3_ad_controller driver |
---|
| 20 | |
---|
| 21 | */ |
---|
| 22 | |
---|
| 23 | #include "w3_ad_controller.h" |
---|
| 24 | |
---|
| 25 | |
---|
| 26 | /** |
---|
| 27 | \defgroup user_functions Functions |
---|
| 28 | \brief Functions to call from user code |
---|
| 29 | \addtogroup user_functions |
---|
| 30 | |
---|
| 31 | Example: |
---|
| 32 | \code{.c} |
---|
| 33 | //Assumes user code sets AD_BASEADDR to base address of w3_ad_controller core, as set in xparameters.h |
---|
| 34 | |
---|
| 35 | //Initialize the AD9963s |
---|
| 36 | ad_init(AD_CONTROLLER, 3); |
---|
| 37 | |
---|
| 38 | \endcode |
---|
| 39 | |
---|
| 40 | @{ |
---|
| 41 | */ |
---|
| 42 | |
---|
| 43 | /** |
---|
| 44 | \brief Initializes the AD controller. This function must be called once at boot before any AD or RF operations will work |
---|
| 45 | \param baseaddr Base memory address of w3_ad_controller pcore |
---|
| 46 | \param clkdiv Clock divider for SPI serial clock (set to 3 for 160MHz bus) |
---|
| 47 | */ |
---|
| 48 | int ad_init(u32 baseaddr, u32 adSel, u8 clkdiv) |
---|
| 49 | { |
---|
| 50 | u32 rstMask, reg5c, reg72, reg5c_check, reg72_check; |
---|
| 51 | |
---|
| 52 | if((adSel & AD_CTRL_ALL_RF_CS) == 0) { |
---|
| 53 | print("ad_init: Invalid adSel parameter!\n"); |
---|
| 54 | return -1; |
---|
| 55 | } |
---|
| 56 | |
---|
| 57 | rstMask = 0; |
---|
| 58 | reg5c_check = 0; |
---|
| 59 | reg72_check = 0; |
---|
| 60 | if(adSel & RFA_AD_CS) {rstMask |= ADCTRL_REG_CONFIG_MASK_RFA_AD_RESET; reg5c_check |= 0x00000008; reg72_check |= 0x00000001;} |
---|
| 61 | if(adSel & RFB_AD_CS) {rstMask |= ADCTRL_REG_CONFIG_MASK_RFB_AD_RESET; reg5c_check |= 0x00000800; reg72_check |= 0x00000100;} |
---|
| 62 | if(adSel & RFC_AD_CS) {rstMask |= ADCTRL_REG_CONFIG_MASK_RFC_AD_RESET; reg5c_check |= 0x00080000; reg72_check |= 0x00010000;} |
---|
| 63 | if(adSel & RFD_AD_CS) {rstMask |= ADCTRL_REG_CONFIG_MASK_RFD_AD_RESET; reg5c_check |= 0x08000000; reg72_check |= 0x01000000;} |
---|
| 64 | |
---|
| 65 | //Toggle AD resets (active low), Set SPI clock divider |
---|
| 66 | Xil_Out32(baseaddr + ADCTRL_REG_CONFIG, 0); |
---|
| 67 | Xil_Out32(baseaddr + ADCTRL_REG_CONFIG, (clkdiv & ADCTRL_REG_CONFIG_MASK_CLKDIV) | rstMask); |
---|
| 68 | |
---|
| 69 | //Toggle soft reset, set SDIO pin to bidirectional (only way to do SPI reads) |
---|
| 70 | ad_spi_write(baseaddr, (adSel), 0x00, 0xBD); //SDIO=1, LSB_first=0, reset=1 |
---|
| 71 | ad_spi_write(baseaddr, (adSel), 0x00, 0x99); //SDIO=1, LSB_first=0, reset=0 |
---|
| 72 | |
---|
| 73 | //Confirm the SPI ports are working |
---|
| 74 | //AD9963 reg5C should be 0x08 always, reg72 is 0x1 on boot |
---|
| 75 | reg5c = (ad_spi_read(baseaddr, (adSel), 0x5C))®5c_check; |
---|
| 76 | reg72 = (ad_spi_read(baseaddr, (adSel), 0x72))®72_check; |
---|
| 77 | if((reg5c != reg5c_check) || (reg72 != reg72_check)) { |
---|
| 78 | 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); |
---|
| 79 | print("Asserting AD9963 resets\n"); |
---|
| 80 | Xil_Out32(baseaddr + ADCTRL_REG_CONFIG, 0); |
---|
| 81 | |
---|
| 82 | return -1; |
---|
| 83 | } |
---|
| 84 | |
---|
| 85 | /* Default AD9963 configuration: |
---|
| 86 | -External ref resistor (NOTE: apparent datasheet bug!) |
---|
| 87 | -Full-duplex mode (Tx data on TXD port, Rx data on TRXD port) |
---|
| 88 | -Power up everything except: |
---|
| 89 | -DAC12A, DAC12B, AUXADC (all unconnected on PCB) |
---|
| 90 | -DLL |
---|
| 91 | -Clocking: |
---|
| 92 | -DLL disabled |
---|
| 93 | -ADC clock = DAC clock = ext clock (nominally 80MHz) |
---|
| 94 | -Tx path: |
---|
| 95 | -Data in 2's complement (NOTE: datasheet bug!) |
---|
| 96 | -TXCLK is input at TXD sample rate |
---|
| 97 | -TXD is DDR, I/Q interleaved, I first |
---|
| 98 | -Tx interpolation filters bypassed |
---|
| 99 | -Tx gains: |
---|
| 100 | -Linear gain set to 100% |
---|
| 101 | -Linear-in-dB gain set to -3dB |
---|
| 102 | -DAC RSET set to 100% |
---|
| 103 | -Tx DCO DACs: |
---|
| 104 | -Enabled, configured for [0,+2]v range |
---|
| 105 | -Set to mid-scale output (approx equal to common mode voltage of I/Q diff pairs) |
---|
| 106 | -Rx path: |
---|
| 107 | -Data in 2's complement (NOTE: datasheet bug!) |
---|
| 108 | -TRXCLK is output at TRXD sample rate |
---|
| 109 | -TRXD is DDR, I/Q interleaved, I first |
---|
| 110 | -Decimation filter bypassed |
---|
| 111 | -RXCML output enabled (used by ADC driver diff amp) |
---|
| 112 | -ADC common mode buffer off (required for DC coupled inputs) |
---|
| 113 | -Rx I path negated digitally (to match swap of p/n traces on PCB) |
---|
| 114 | */ |
---|
| 115 | |
---|
| 116 | //Power on/off blocks |
---|
| 117 | ad_spi_write(baseaddr, (adSel), 0x40, 0x00); //DAC12A, DAC12B off |
---|
| 118 | ad_spi_write(baseaddr, (adSel), 0x60, 0x00); //DLL off, everything else on |
---|
| 119 | ad_spi_write(baseaddr, (adSel), 0x61, 0x03); //LDOs on, AUXADC off |
---|
| 120 | //xil_printf("AD TEST: reg61=0x%08x\n", ad_spi_read(baseaddr, (adSel), 0x61)); |
---|
| 121 | |
---|
| 122 | //Clocking setup |
---|
| 123 | // [7]=0: ADCCLK=ext clk |
---|
| 124 | // [6]=0: DACCLK=ext clk |
---|
| 125 | // [4]=0: disable DLL input ref |
---|
| 126 | // [3:0]: DLL divide ratio (only 1, 2, 3, 4, 5, 6, 8 valid) |
---|
| 127 | ad_spi_write(baseaddr, (adSel), 0x71, 0x01); //DLL ref input off, ADCCLK=extCLK, DACCLK=extCLK, DLL_DIV=1 |
---|
| 128 | |
---|
| 129 | //Reference resistor selection |
---|
| 130 | // Datasheet says reg62[0]=0 for internal resistor, reg62[0]=1 for external resistor |
---|
| 131 | // But experimentally DAC currents are much more stable with temperature when reg62[0]=0 |
---|
| 132 | // I'm guessing this bit is flipped in the datasheet |
---|
| 133 | ad_spi_write(baseaddr, (adSel), 0x62, 0x00); |
---|
| 134 | |
---|
| 135 | //Clock disables and DCS |
---|
| 136 | // [7:3]=0: Enable internal clocks to ADCs/DACs |
---|
| 137 | // [1:0]=0: Set ADCCLK=ext clock (no division) |
---|
| 138 | // [2]=0: Enable ADC duty cycle stabilizer (recommended for ADC rates > 75MHz) |
---|
| 139 | ad_spi_write(baseaddr, (adSel), 0x66, 0x00); //Enable internal clocks, enable DCS |
---|
| 140 | |
---|
| 141 | //Aux DACs (Tx DC offset adjustment) |
---|
| 142 | // DAC10B=Q DCO, DAC10A=I DCO |
---|
| 143 | // DAC outputs update after LSB write (configured by reg40[0]) |
---|
| 144 | ad_spi_write(baseaddr, (adSel), 0x45, 0x88); //DAC10B on, full scale = [0,+2]v |
---|
| 145 | ad_spi_write(baseaddr, (adSel), 0x46, 0x80); //DAC10B data[9:2] |
---|
| 146 | ad_spi_write(baseaddr, (adSel), 0x47, 0x00); //DAC10B {6'b0, data[1:0]} |
---|
| 147 | |
---|
| 148 | ad_spi_write(baseaddr, (adSel), 0x48, 0x88); //DAC10A on, full scale = [0,+2]v |
---|
| 149 | ad_spi_write(baseaddr, (adSel), 0x49, 0x80); //DAC10A data[9:2] |
---|
| 150 | ad_spi_write(baseaddr, (adSel), 0x50, 0x00); //DAC10A {6'b0, data[1:0]} |
---|
| 151 | |
---|
| 152 | //ADC common mode buffer: disabled for DC coupled inputs |
---|
| 153 | ad_spi_write(baseaddr, (adSel), 0x7E, 0x01); |
---|
| 154 | |
---|
| 155 | //Spectral inversion |
---|
| 156 | // Invert RxI (reg3D[2]=1) to match PCB |
---|
| 157 | // TxI, TxQ also swapped on PCB, but ignored here since -1*(a+jb) is just one of many phase shifts the Tx signal sees |
---|
| 158 | ad_spi_write(baseaddr, (adSel), 0x3D, 0x04); //Invert RxI to match PCB (board swaps +/- for better routing) |
---|
| 159 | |
---|
| 160 | //Rx clock and data order/format |
---|
| 161 | // [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) |
---|
| 162 | // [0]=1 for 2's compliment (Datasheet says reg32[0]=0 for 2's compliment, but experiments show 0 is really straight-binary) |
---|
| 163 | ad_spi_write(baseaddr, (adSel), 0x32, 0x2B); |
---|
| 164 | |
---|
| 165 | //Full-duplex mode (DACs/TXD and ADCs/TRXD both active all the time) |
---|
| 166 | ad_spi_write(baseaddr, (adSel), 0x3F, 0x01); //FD mode |
---|
| 167 | |
---|
| 168 | //Tx data format (datasheet bug! reg[31][0] is flipped; 1=2's complement, 0=straight binary) |
---|
| 169 | //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)) |
---|
| 170 | ad_spi_write(baseaddr, (adSel), 0x31, 0x17); //Txdata=DDR two's complement, |
---|
| 171 | |
---|
| 172 | //Tx/Rx data paths |
---|
| 173 | ad_spi_write(baseaddr, (adSel), 0x30, 0x3F); //Bypass all rate change filters, enable Tx/Rx clocks |
---|
| 174 | // ad_spi_write(baseaddr, (adSel), 0x30, 0x37); //INT0 on, enable Tx/Rx clocks |
---|
| 175 | // ad_spi_write(baseaddr, (adSel), 0x30, 0x27); //INT0+INT1 on, enable Tx/Rx clocks |
---|
| 176 | // ad_spi_write(baseaddr, (adSel), 0x30, 0x23); //INT0+INT1+SRCC on, enable Tx/Rx clocks |
---|
| 177 | |
---|
| 178 | //ADC RXCML output buffer requires special register process (see AD9963 datasheet pg. 21 "sub serial interface communications") |
---|
| 179 | ad_spi_write(baseaddr, (adSel), 0x05, 0x03); //Address both ADCs |
---|
| 180 | ad_spi_write(baseaddr, (adSel), 0x0F, 0x02); //Enable RXCML output |
---|
| 181 | ad_spi_write(baseaddr, (adSel), 0x10, 0x00); //Set I/Q offset to 0 |
---|
| 182 | ad_spi_write(baseaddr, (adSel), 0xFF, 0x01); //Trigger ADC param update |
---|
| 183 | ad_spi_write(baseaddr, (adSel), 0x05, 0x00); //De-Address both ADCs |
---|
| 184 | |
---|
| 185 | //REFIO adjustment: set to default of 0.8v |
---|
| 186 | ad_spi_write(baseaddr, (adSel), 0x6E, 0x40); |
---|
| 187 | |
---|
| 188 | //Tx gains (it seems these registers default to non-zero, and maybe non-I/Q-matched values; safest to set them explicitly after reset) |
---|
| 189 | //I/Q GAIN1[5:0]: Fix5_0 value, Linear-in-dB, 0.25dB per bit |
---|
| 190 | // 0-25=>0dB-+6dB, 25-32:+6dB, 33-41:-6dB, 41-63=>-6dB-0dB |
---|
| 191 | ad_spi_write(baseaddr, (adSel), 0x68, 0); //IGAIN1 |
---|
| 192 | ad_spi_write(baseaddr, (adSel), 0x6B, 0); //QGAIN1 |
---|
| 193 | |
---|
| 194 | //I/Q GAIN2[5:0]: Fix5_0 value, Linear +/-2.5%, 0.08% per bit |
---|
| 195 | // 0:+0, 31:+max, 32:-max, 63:-0 |
---|
| 196 | ad_spi_write(baseaddr, (adSel), 0x69, 0); //IGAIN2 |
---|
| 197 | ad_spi_write(baseaddr, (adSel), 0x6C, 0); //QGAIN2 |
---|
| 198 | |
---|
| 199 | //I/Q RSET[5:0]: Fix5_0, Linear +/-20%, 0.625% per bit |
---|
| 200 | // 0:-0, 31:-max, 32:+max, 63:+0 |
---|
| 201 | ad_spi_write(baseaddr, (adSel), 0x6A, 0); //IRSET |
---|
| 202 | ad_spi_write(baseaddr, (adSel), 0x6D, 0); //QRSET |
---|
| 203 | |
---|
| 204 | //Digital output drive strengths: all 8mA |
---|
| 205 | ad_spi_write(baseaddr, (adSel), 0x63, 0x55); //2 bits each: TRXD TRXIQ TRXCLK TXCLK |
---|
| 206 | |
---|
| 207 | //Disable Tx and Rx BIST modes |
---|
| 208 | ad_spi_write(baseaddr, (adSel), 0x50, 0x00); //Tx BIST control |
---|
| 209 | ad_spi_write(baseaddr, (adSel), 0x51, 0x00); //Rx BIST control |
---|
| 210 | |
---|
| 211 | return 0; |
---|
| 212 | } |
---|
| 213 | |
---|
| 214 | /** |
---|
| 215 | \brief Reads the specified register from both AD9963s |
---|
| 216 | \param baseaddr Base memory address of w3_ad_controller pcore |
---|
| 217 | \param csMask OR'd combination of RFA_AD_CS and RFB_AD_CS |
---|
| 218 | \param regAddr Address of register to read, in [0x00, 0x82] |
---|
| 219 | \return Returns concatenation of current values of the specified register for both AD9963s; RFA is LSB |
---|
| 220 | */ |
---|
| 221 | u32 ad_spi_read(u32 baseaddr, u32 csMask, u8 regAddr) { |
---|
| 222 | //NOTE: reads from multiple SPI devices simultaneously |
---|
| 223 | // return value is 4 bytes |
---|
| 224 | // least-significant byte is RFA_AD, next is RFB_AD |
---|
| 225 | u32 txWord, rxWord; |
---|
| 226 | |
---|
| 227 | //SPI Tx register is 4 bytes: |
---|
| 228 | // [3]: chip selects (bitwise OR'd ADCTRL_REG_SPITX_ADx_CS) |
---|
| 229 | // [2]: {rnw n1 n0 5'b0}, rnw=1 for SPI write, n1=n0=0 for 1 byte write |
---|
| 230 | // [1]: reg addr[7:0] |
---|
| 231 | // [0]: ignored for read (read value captured in Rx register) |
---|
| 232 | txWord = (csMask & (AD_CTRL_ALL_RF_CS)) | //chip selects |
---|
| 233 | (ADCTRL_REG_SPITX_RNW) | //spi_tx_byte[2] = {rnw n1 n0 5'b0} |
---|
| 234 | ((regAddr & 0xFF)<<8) | //spi_tx_byte[1] = addr[7:0] |
---|
| 235 | (0x00); //spi_tx_byte[0] = ignored for read (AD drives data pin during this byte) |
---|
| 236 | |
---|
| 237 | Xil_Out32(baseaddr + ADCTRL_REG_SPITX, txWord); |
---|
| 238 | |
---|
| 239 | rxWord = Xil_In32(baseaddr + ADCTRL_REG_SPIRX); |
---|
| 240 | |
---|
| 241 | return(rxWord); |
---|
| 242 | } |
---|
| 243 | |
---|
| 244 | /** |
---|
| 245 | \brief Writes the specified register in selected AD9963s. Multiple AD9963s can be selected for simultaneous writes. |
---|
| 246 | \param baseaddr Base memory address of w3_ad_controller pcore |
---|
| 247 | \param csMask OR'd combination of RFA_AD_CS and RFB_AD_CS |
---|
| 248 | \param regAddr Address of register to write, in [0x00, 0xFF] |
---|
| 249 | \param txByte Byte value to write |
---|
| 250 | */ |
---|
| 251 | void ad_spi_write(u32 baseaddr, u32 csMask, u8 regAddr, u8 txByte) { |
---|
| 252 | u32 txWord; |
---|
| 253 | |
---|
| 254 | //SPI read process: |
---|
| 255 | // -Write full SPI word with RNW=1 and address of desired register |
---|
| 256 | // -Capture register value in last byte of SPI write process (handled automatically in logic) |
---|
| 257 | |
---|
| 258 | //SPI Tx register is 4 bytes: |
---|
| 259 | // [3]: chip selects (bitwise OR'd ADCTRL_REG_SPITX_ADx_CS) |
---|
| 260 | // [2]: {rnw n1 n0 5'b0}, rnw=0 for SPI write, n1=n0=0 for 1 byte write |
---|
| 261 | // [1]: reg addr[7:0] |
---|
| 262 | // [0]: reg data[7:0] |
---|
| 263 | txWord = (csMask & (AD_CTRL_ALL_RF_CS)) | //byte[3] |
---|
| 264 | (0x00) | |
---|
| 265 | ((regAddr & 0xFF)<<8) | |
---|
| 266 | (txByte & 0xFF); //byte[0] |
---|
| 267 | |
---|
| 268 | Xil_Out32(baseaddr + ADCTRL_REG_SPITX, txWord); |
---|
| 269 | |
---|
| 270 | return; |
---|
| 271 | } |
---|
| 272 | |
---|
| 273 | |
---|
| 274 | /** |
---|
| 275 | \brief Sets the DC offset for the selected path (I or Q) in the selected AD9963s |
---|
| 276 | \param baseaddr Base memory address of w3_ad_controller pcore |
---|
| 277 | \param csMask OR'd combination of RFA_AD_CS and RFB_AD_CS |
---|
| 278 | \param iqSel Select I or Q path; must be AD_CHAN_I or AD_CHAN_Q |
---|
| 279 | \param dco DC offset to apply, in [0,1024] |
---|
| 280 | */ |
---|
| 281 | int ad_set_TxDCO(u32 baseaddr, u32 csMask, u8 iqSel, u16 dco) { |
---|
| 282 | |
---|
| 283 | //Sanity check inputs |
---|
| 284 | if( ((csMask & (AD_CTRL_ALL_RF_CS)) == 0) || (dco>1023)) |
---|
| 285 | return -1; |
---|
| 286 | |
---|
| 287 | if(iqSel == AD_CHAN_I) { |
---|
| 288 | //AUXIO2=DAC10A=I DAC TxDCO |
---|
| 289 | ad_spi_write(baseaddr, csMask, 0x49, ((dco & 0x3FF) >> 2)); //DAC10A data[9:2] - 8 MSB |
---|
| 290 | ad_spi_write(baseaddr, csMask, 0x4A, (dco & 0x3)); //DAC10A {6'b0, data[1:0]} - 2 LSB |
---|
| 291 | } |
---|
| 292 | else if(iqSel == AD_CHAN_Q) { |
---|
| 293 | //AUXIO3=DAC10B=Q DAC TxDCO |
---|
| 294 | ad_spi_write(baseaddr, csMask, 0x46, ((dco & 0x3FF) >> 2)); //DAC10B data[9:2] - 8 MSB |
---|
| 295 | ad_spi_write(baseaddr, csMask, 0x47, (dco & 0x3)); //DAC10B {6'b0, data[1:0]} - 2 LSB |
---|
| 296 | } |
---|
| 297 | |
---|
| 298 | return 0; |
---|
| 299 | } |
---|
| 300 | |
---|
| 301 | /** |
---|
| 302 | \brief Sets the GAIN1 value (linear-in-dB adjustment +/- 6dB) for the selected path (I or Q) in the selected AD9963s. |
---|
| 303 | Changing this gain value also changes the common mode voltage and DC offset of the selected path. We recommend leaving |
---|
| 304 | this gain setting unchanged for optimal performance. |
---|
| 305 | \param baseaddr Base memory address of w3_ad_controller pcore |
---|
| 306 | \param csMask OR'd combination of RFA_AD_CS and RFB_AD_CS |
---|
| 307 | \param iqSel Select I or Q path; must be AD_CHAN_I or AD_CHAN_Q |
---|
| 308 | \param gain 6-bit gain value; [0:25] = [0:+6dB], [41,63] = [-6dB:0dB] |
---|
| 309 | */ |
---|
| 310 | int ad_set_TxGain1(u32 baseaddr, u32 csMask, u8 iqSel, u8 gain) { |
---|
| 311 | //6-bit Linear-in-dB gain +/- 6dB |
---|
| 312 | |
---|
| 313 | //Sanity check inputs |
---|
| 314 | if( ((csMask & (AD_CTRL_ALL_RF_CS)) == 0) || (gain>63)) |
---|
| 315 | return -1; |
---|
| 316 | |
---|
| 317 | if(iqSel == AD_CHAN_I) { |
---|
| 318 | ad_spi_write(baseaddr, csMask, 0x68, (gain&0x3F)); //IGAIN1 |
---|
| 319 | } |
---|
| 320 | else if(iqSel == AD_CHAN_Q) { |
---|
| 321 | ad_spi_write(baseaddr, csMask, 0x6B, (gain&0x3F)); //QGAIN1 |
---|
| 322 | } |
---|
| 323 | |
---|
| 324 | return 0; |
---|
| 325 | } |
---|
| 326 | |
---|
| 327 | /** |
---|
| 328 | \brief Sets the GAIN2 value (linear adjustment +/- 2.5%) for the selected path (I or Q) in the selected AD9963s |
---|
| 329 | Changing this gain value also changes the common mode voltage and DC offset of the selected path. We recommend leaving |
---|
| 330 | this gain setting unchanged for optimal performance. |
---|
| 331 | \param baseaddr Base memory address of w3_ad_controller pcore |
---|
| 332 | \param csMask OR'd combination of RFA_AD_CS and RFB_AD_CS |
---|
| 333 | \param iqSel Select I or Q path; must be AD_CHAN_I or AD_CHAN_Q |
---|
| 334 | \param gain 6-bit gain value; [0:25] = [0:+2.5%], [41,63] = [-2.5%:0] |
---|
| 335 | */ |
---|
| 336 | int ad_set_TxGain2(u32 baseaddr, u32 csMask, u8 iqSel, u8 gain) { |
---|
| 337 | //6-bit Linear gain +/- 2.5% |
---|
| 338 | |
---|
| 339 | //Sanity check inputs |
---|
| 340 | if( ((csMask & (AD_CTRL_ALL_RF_CS)) == 0) || (gain>63)) |
---|
| 341 | return -1; |
---|
| 342 | |
---|
| 343 | if(iqSel == AD_CHAN_I) { |
---|
| 344 | ad_spi_write(baseaddr, csMask, 0x69, (gain&0x3F)); //IGAIN2 |
---|
| 345 | } |
---|
| 346 | else if(iqSel == AD_CHAN_Q) { |
---|
| 347 | ad_spi_write(baseaddr, csMask, 0x6C, (gain&0x3F)); //QGAIN2 |
---|
| 348 | } |
---|
| 349 | |
---|
| 350 | return 0; |
---|
| 351 | } |
---|
| 352 | |
---|
| 353 | /** |
---|
| 354 | \brief Configures the digital rate-change filters in the AD9963. Changing filter settings affects the require data rate |
---|
| 355 | at the TXD and TRXD ports. You must ensure all related paramters (AD9963 filters, I/Q rate in FPGA, AD9512 dividers) are consistent. |
---|
| 356 | \param baseaddr Base memory address of w3_ad_controller pcore |
---|
| 357 | \param csMask OR'd combination of RFA_AD_CS and RFB_AD_CS |
---|
| 358 | \param interpRate Desired interpolation rate in AD9963; must be one of [1, 2, 4, 8] |
---|
| 359 | \param decimationRate Desired decimation rate in AD9963; must be one of [1, 2] |
---|
| 360 | \return Returns 0 on success, -1 for invalid paramters |
---|
| 361 | */ |
---|
| 362 | int ad_config_filters(u32 baseaddr, u32 csMask, u8 interpRate, u8 decimationRate) { |
---|
| 363 | |
---|
| 364 | //Sanity check inputs |
---|
| 365 | if( ((csMask & (AD_CTRL_ALL_RF_CS)) == 0)) |
---|
| 366 | return -1; |
---|
| 367 | |
---|
| 368 | /* AD9963 register 0x30: |
---|
| 369 | 7:6 Reserved |
---|
| 370 | 5: DEC_BP Bypass Rx decimation filter 0x20 |
---|
| 371 | 4: INT1_BP Bypass Tx INT1 filter 0x10 |
---|
| 372 | 3: INT0_BP Bypass Tx INT0 filter 0x08 |
---|
| 373 | 2: SRRC_BP Bypass Tx SRRC filter 0x04 |
---|
| 374 | 1: TXCLK_EN Enable Tx datapath clocks 0x02 |
---|
| 375 | 0: RXCLK_EN Enable Rx datapath clocks 0x01 |
---|
| 376 | |
---|
| 377 | Tx filter config: |
---|
| 378 | 1x: All Tx filters bypased |
---|
| 379 | 2x: INT0 enabled |
---|
| 380 | 4x: INT0, INT1 enbaled |
---|
| 381 | 8x: All Tx fitlers enabled |
---|
| 382 | */ |
---|
| 383 | |
---|
| 384 | u8 regVal; |
---|
| 385 | |
---|
| 386 | //Enable Tx/Rx clocks by default |
---|
| 387 | regVal = 0x3; |
---|
| 388 | |
---|
| 389 | switch(interpRate) { |
---|
| 390 | case 1: |
---|
| 391 | regVal = regVal | 0x1C; |
---|
| 392 | break; |
---|
| 393 | case 2: |
---|
| 394 | regVal = regVal | 0x14; |
---|
| 395 | break; |
---|
| 396 | case 4: |
---|
| 397 | regVal = regVal | 0x04; |
---|
| 398 | break; |
---|
| 399 | case 8: |
---|
| 400 | break; |
---|
| 401 | default: |
---|
| 402 | //Invalid interp rate; return error |
---|
| 403 | return -1; |
---|
| 404 | break; |
---|
| 405 | } |
---|
| 406 | |
---|
| 407 | if(decimationRate == 1) { |
---|
| 408 | regVal = regVal | 0x20; |
---|
| 409 | } else if(decimationRate != 2) { |
---|
| 410 | //Invalid decimation rate; return error |
---|
| 411 | return -1; |
---|
| 412 | } |
---|
| 413 | |
---|
| 414 | //Write reg 0x30 in selected AD9963's |
---|
| 415 | ad_spi_write(baseaddr, csMask, 0x30, regVal); |
---|
| 416 | |
---|
| 417 | return 0; |
---|
| 418 | } |
---|
| 419 | |
---|
| 420 | /** |
---|
| 421 | \brief Configures the ADC and DAC clock sources in the AD9963. Refer to the WARP v3 user guide and AD9963 for details |
---|
| 422 | on various clocking modes |
---|
| 423 | \param baseaddr Base memory address of w3_ad_controller pcore |
---|
| 424 | \param csMask OR'd combination of RFA_AD_CS and RFB_AD_CS |
---|
| 425 | \param DAC_clkSrc DAC clock source; must be AD_DACCLKSRC_DLL (use DLL clock) or AD_DACCLKSRC_EXT (use external reference clock) |
---|
| 426 | \param ADC_clkSrc ADC clock source; must be AD_ADCCLKSRC_DLL (use DLL clock) or AD_ADCCLKSRC_EXT (use external reference clock) |
---|
| 427 | \param ADC_clkDiv ADC clock divider; must be one of [AD_ADCCLKDIV_1, AD_ADCCLKDIV_2, AD_ADCCLKDIV_4] for divide-by of [1, 2, 4] |
---|
| 428 | \param ADC_DCS ADC duty cycle stabilizer; must be AD_DCS_ON or AD_DCS_OFF. AD9963 datasheet recommends DCS be enabled only for ADC rates above 75MHz. |
---|
| 429 | \return Returns 0 on success, -1 for invalid paramters |
---|
| 430 | */ |
---|
| 431 | int ad_config_clocks(u32 baseaddr, u32 csMask, u8 DAC_clkSrc, u8 ADC_clkSrc, u8 ADC_clkDiv, u8 ADC_DCS) { |
---|
| 432 | |
---|
| 433 | u8 regVal; |
---|
| 434 | u8 bitsToSet_reg66, bitsToSet_reg71; |
---|
| 435 | |
---|
| 436 | //Sanity check inputs |
---|
| 437 | if( ((csMask & (AD_CTRL_ALL_RF_CS)) == 0)) |
---|
| 438 | return -1; |
---|
| 439 | |
---|
| 440 | /* AD9963 reg 0x66: |
---|
| 441 | 7:6 Disable DAC clocks |
---|
| 442 | 4:3 Disable ADC clocks |
---|
| 443 | 2: Disable DCS |
---|
| 444 | 1:0 ADCDIV |
---|
| 445 | |
---|
| 446 | AD9963 reg 0x71: |
---|
| 447 | 7: ADC clock selection (1=DLL, 0=ext) |
---|
| 448 | 6: DAC clock selection (1=DLL, 0=ext) |
---|
| 449 | 4:0 DLL config |
---|
| 450 | */ |
---|
| 451 | |
---|
| 452 | //Assert sane default bits, and any config bits user options require |
---|
| 453 | bitsToSet_reg66 = (ADC_DCS & AD_DCS_OFF) | (ADC_clkDiv & (AD_ADCCLKDIV_1 | AD_ADCCLKDIV_2 | AD_ADCCLKDIV_4)); |
---|
| 454 | bitsToSet_reg71 = (DAC_clkSrc & AD_DACCLKSRC_DLL) | (ADC_clkSrc & AD_ADCCLKSRC_DLL); |
---|
| 455 | |
---|
| 456 | //For RFA and RFB, clear-then-set affected bits in clock config registers (0x66 and 0x71) |
---|
| 457 | if(csMask & RFA_AD_CS) { |
---|
| 458 | regVal = (u8)ad_spi_read(baseaddr, RFA_AD_CS, 0x66); |
---|
| 459 | regVal = regVal & ~(AD_DCS_OFF | AD_ADCCLKDIV_1 | AD_ADCCLKDIV_2 | AD_ADCCLKDIV_4); |
---|
| 460 | regVal = regVal | bitsToSet_reg66; |
---|
| 461 | ad_spi_write(baseaddr, RFA_AD_CS, 0x66, regVal); |
---|
| 462 | |
---|
| 463 | regVal = (u8)ad_spi_read(baseaddr, RFA_AD_CS, 0x71); |
---|
| 464 | regVal = regVal & ~(AD_DACCLKSRC_DLL | AD_ADCCLKSRC_DLL); |
---|
| 465 | regVal = regVal | bitsToSet_reg71; |
---|
| 466 | ad_spi_write(baseaddr, RFA_AD_CS, 0x71, regVal); |
---|
| 467 | } |
---|
| 468 | |
---|
| 469 | if(csMask & RFB_AD_CS) { |
---|
| 470 | regVal = (u8)(ad_spi_read(baseaddr, RFB_AD_CS, 0x66)>>8); |
---|
| 471 | regVal = regVal & ~(AD_DCS_OFF | AD_ADCCLKDIV_1 | AD_ADCCLKDIV_2 | AD_ADCCLKDIV_4); |
---|
| 472 | regVal = regVal | bitsToSet_reg66; |
---|
| 473 | ad_spi_write(baseaddr, RFB_AD_CS, 0x66, regVal); |
---|
| 474 | |
---|
| 475 | regVal = (u8)(ad_spi_read(baseaddr, RFB_AD_CS, 0x71)>>8); |
---|
| 476 | regVal = regVal & ~(AD_DACCLKSRC_DLL | AD_ADCCLKSRC_DLL); |
---|
| 477 | regVal = regVal | bitsToSet_reg71; |
---|
| 478 | ad_spi_write(baseaddr, RFB_AD_CS, 0x71, regVal); |
---|
| 479 | } |
---|
| 480 | |
---|
| 481 | if(csMask & RFC_AD_CS) { |
---|
| 482 | regVal = (u8)(ad_spi_read(baseaddr, RFC_AD_CS, 0x66)>>8); |
---|
| 483 | regVal = regVal & ~(AD_DCS_OFF | AD_ADCCLKDIV_1 | AD_ADCCLKDIV_2 | AD_ADCCLKDIV_4); |
---|
| 484 | regVal = regVal | bitsToSet_reg66; |
---|
| 485 | ad_spi_write(baseaddr, RFC_AD_CS, 0x66, regVal); |
---|
| 486 | |
---|
| 487 | regVal = (u8)(ad_spi_read(baseaddr, RFC_AD_CS, 0x71)>>8); |
---|
| 488 | regVal = regVal & ~(AD_DACCLKSRC_DLL | AD_ADCCLKSRC_DLL); |
---|
| 489 | regVal = regVal | bitsToSet_reg71; |
---|
| 490 | ad_spi_write(baseaddr, RFC_AD_CS, 0x71, regVal); |
---|
| 491 | } |
---|
| 492 | |
---|
| 493 | if(csMask & RFD_AD_CS) { |
---|
| 494 | regVal = (u8)(ad_spi_read(baseaddr, RFD_AD_CS, 0x66)>>8); |
---|
| 495 | regVal = regVal & ~(AD_DCS_OFF | AD_ADCCLKDIV_1 | AD_ADCCLKDIV_2 | AD_ADCCLKDIV_4); |
---|
| 496 | regVal = regVal | bitsToSet_reg66; |
---|
| 497 | ad_spi_write(baseaddr, RFD_AD_CS, 0x66, regVal); |
---|
| 498 | |
---|
| 499 | regVal = (u8)(ad_spi_read(baseaddr, RFD_AD_CS, 0x71)>>8); |
---|
| 500 | regVal = regVal & ~(AD_DACCLKSRC_DLL | AD_ADCCLKSRC_DLL); |
---|
| 501 | regVal = regVal | bitsToSet_reg71; |
---|
| 502 | ad_spi_write(baseaddr, RFD_AD_CS, 0x71, regVal); |
---|
| 503 | } |
---|
| 504 | |
---|
| 505 | return 0; |
---|
| 506 | |
---|
| 507 | } |
---|
| 508 | |
---|
| 509 | /** |
---|
| 510 | \brief Configures the AD9963 DLL block. DLL output clock is REFCLK*M/(N*DLL_DIV). REFCLK*M must be in [100,310]MHz. See the AD9963 for more details. |
---|
| 511 | \param baseaddr Base memory address of w3_ad_controller pcore |
---|
| 512 | \param csMask OR'd combination of RFA_AD_CS and RFB_AD_CS |
---|
| 513 | \param DLL_En DLL Enable (1=DLL enabled, 0=DLL disabled). Other arguments are ignored when DLL_En=0 |
---|
| 514 | \param DLL_M DLL multiplication (M) parameter; must be in [0,1,...,31] for multiplications of [1,2,...,32], constrained by M*REFCLK in [100, 310]MHz |
---|
| 515 | \param DLL_N DLL division (N) parameter; must be one of [1,2,3,4,5,6,8] |
---|
| 516 | \param DLL_DIV Secondary DLL divider; must be one of [1,2,4] |
---|
| 517 | \return Returns 0 on success, -1 for invalid paramters, -2 if DLLs fail to lock with new settings |
---|
| 518 | */ |
---|
| 519 | int ad_config_DLL(u32 baseaddr, u32 csMask, u8 DLL_En, u8 DLL_M, u8 DLL_N, u8 DLL_DIV) { |
---|
| 520 | |
---|
| 521 | u8 regVal; |
---|
| 522 | u8 bitsToSet_reg71; |
---|
| 523 | u8 lockAttempts = 100; |
---|
| 524 | |
---|
| 525 | //Sanity check inputs |
---|
| 526 | if( ((csMask & (AD_CTRL_ALL_RF_CS)) == 0)) |
---|
| 527 | return -1; |
---|
| 528 | |
---|
| 529 | if( (DLL_N == 7) || (DLL_N == 0) || (DLL_N > 8) || (DLL_M > 31) || (DLL_DIV == 0) || (DLL_DIV == 3) || (DLL_DIV > 4)) |
---|
| 530 | return -1; |
---|
| 531 | |
---|
| 532 | |
---|
| 533 | /* |
---|
| 534 | AD9963 reg 0x60: |
---|
| 535 | 7: DLL_EN (1=enable DLL) |
---|
| 536 | 6:0 other block disables |
---|
| 537 | |
---|
| 538 | AD9963 reg 0x71: |
---|
| 539 | 7:6 ADC/DAC clock selection |
---|
| 540 | 5: reserved |
---|
| 541 | 4: 1=enable DLL clock input |
---|
| 542 | 3:0 DLL N (only valid values: 1,2,3,4,5,6,8) |
---|
| 543 | |
---|
| 544 | AD9963 reg 0x72: |
---|
| 545 | 7: DLL locked (read-only) |
---|
| 546 | 6:5 DLLDIV (secondary divider value) |
---|
| 547 | 4:0 DLL M ([1:32] all valid) |
---|
| 548 | |
---|
| 549 | AD9963 reg 0x75: |
---|
| 550 | 7:4 must be 0 |
---|
| 551 | 3: DLL_RESB: DLL reset (must transition low-to-high after any DLL parameter change)* see NOTE below |
---|
| 552 | 2:0 must be 0 |
---|
| 553 | |
---|
| 554 | */ |
---|
| 555 | |
---|
| 556 | //NOTE! The AD9963 datasheet claims DLL_RESB (reg 0x75[3]) is active low. I'm pretty sure |
---|
| 557 | // that's wrong (yet another AD9963 datasheet bit-flip). The code below treats DLL_RESB as |
---|
| 558 | // active high, the only interpreation we've seen work in hardware. |
---|
| 559 | |
---|
| 560 | |
---|
| 561 | if(DLL_En == 0) { |
---|
| 562 | //Assert DLL reset |
---|
| 563 | ad_spi_write(baseaddr, csMask, 0x75, 0x08); |
---|
| 564 | |
---|
| 565 | //Power down DLL block, leave all other blocks powered on |
---|
| 566 | ad_spi_write(baseaddr, csMask, 0x60, 0x00); |
---|
| 567 | |
---|
| 568 | //Disable DLL clock input, set ADC/DAC clock sources to ext ref clock |
---|
| 569 | ad_spi_write(baseaddr, csMask, 0x71, 0xC0); |
---|
| 570 | |
---|
| 571 | return 0; |
---|
| 572 | |
---|
| 573 | } else { |
---|
| 574 | //Assert DLL reset |
---|
| 575 | ad_spi_write(baseaddr, csMask, 0x75, 0x08); |
---|
| 576 | |
---|
| 577 | //Power up DLL block, leave all other blocks powered on |
---|
| 578 | ad_spi_write(baseaddr, csMask, 0x60, 0x80); |
---|
| 579 | |
---|
| 580 | //Assert DLL clock enable, set DLL_N |
---|
| 581 | bitsToSet_reg71 = 0x10 | (DLL_N & 0xF); |
---|
| 582 | |
---|
| 583 | //reg71 has bits to preserve, so handle it separately for each AD |
---|
| 584 | if(csMask & RFA_AD_CS) { |
---|
| 585 | regVal = (u8)ad_spi_read(baseaddr, RFA_AD_CS, 0x71); |
---|
| 586 | regVal = (regVal & ~0x1F) | bitsToSet_reg71; |
---|
| 587 | ad_spi_write(baseaddr, RFA_AD_CS, 0x71, regVal); |
---|
| 588 | } |
---|
| 589 | if(csMask & RFB_AD_CS) { |
---|
| 590 | regVal = (u8)(ad_spi_read(baseaddr, RFB_AD_CS, 0x71)>>8); |
---|
| 591 | regVal = (regVal & ~0x1F) | bitsToSet_reg71; |
---|
| 592 | ad_spi_write(baseaddr, RFB_AD_CS, 0x71, regVal); |
---|
| 593 | } |
---|
| 594 | if(csMask & RFC_AD_CS) { |
---|
| 595 | regVal = (u8)(ad_spi_read(baseaddr, RFC_AD_CS, 0x71)>>8); |
---|
| 596 | regVal = (regVal & ~0x1F) | bitsToSet_reg71; |
---|
| 597 | ad_spi_write(baseaddr, RFC_AD_CS, 0x71, regVal); |
---|
| 598 | } |
---|
| 599 | if(csMask & RFD_AD_CS) { |
---|
| 600 | regVal = (u8)(ad_spi_read(baseaddr, RFD_AD_CS, 0x71)>>8); |
---|
| 601 | regVal = (regVal & ~0x1F) | bitsToSet_reg71; |
---|
| 602 | ad_spi_write(baseaddr, RFD_AD_CS, 0x71, regVal); |
---|
| 603 | } |
---|
| 604 | |
---|
| 605 | //Other registers are DLL-only, so we can write both ADs together |
---|
| 606 | |
---|
| 607 | //Set DLL_DIV and DLL_M |
---|
| 608 | ad_spi_write(baseaddr, csMask, 0x72, ((DLL_DIV&0x3)<<5) | (DLL_M & 0x1F)); |
---|
| 609 | |
---|
| 610 | //Release DLL reset (treating as active high) |
---|
| 611 | #ifdef rc_usleep |
---|
| 612 | rc_usleep(100); |
---|
| 613 | #else |
---|
| 614 | volatile int i; |
---|
| 615 | for(i=0; i<1000; i++) |
---|
| 616 | i++; |
---|
| 617 | #endif |
---|
| 618 | ad_spi_write(baseaddr, csMask, 0x75, 0x00); |
---|
| 619 | |
---|
| 620 | //Wait for both DLLs to lock |
---|
| 621 | while( (lockAttempts > 0) && ( (ad_spi_read(baseaddr, csMask, 0x72) & 0x8080) != 0x8080) ) {lockAttempts--; print(".");} |
---|
| 622 | |
---|
| 623 | //If the wait-for-lock loop timed out, return an error |
---|
| 624 | if(lockAttempts == 0) return -2; |
---|
| 625 | else return 0; |
---|
| 626 | } |
---|
| 627 | |
---|
| 628 | |
---|
| 629 | } |
---|
| 630 | |
---|
| 631 | /** |
---|
| 632 | \brief Shuts down or enables the selected AD9963. Starting up from shutdown is not instantaneous, so this function should only be used to |
---|
| 633 | disable an AD9963 that will be unsed for a while. If you shutdown a AD9963, you should also shutdown the corresponding MAX2829 with radio_controller_setMode_shutdown(). |
---|
| 634 | <b>Note:</b> this function will always leave the AD9963 DLL shutdown. You must call ad_config_DLL() again to re-configure and re-enable the DLL |
---|
| 635 | if your design uses the DLL clock for ADCs or DACs. |
---|
| 636 | \param baseaddr Base memory address of w3_ad_controller pcore |
---|
| 637 | \param csMask OR'd combination of RFA_AD_CS and RFB_AD_CS |
---|
| 638 | \param pwrState Desired AD9963 power state; must be one of [AD_PWR_ALLOFF, AD_PWR_ALLON] |
---|
| 639 | \return Returns 0 on success, -1 for invalid paramters |
---|
| 640 | */ |
---|
| 641 | int ad_config_power(u32 baseaddr, u32 csMask, u8 pwrState) { |
---|
| 642 | u8 regVal; |
---|
| 643 | |
---|
| 644 | //Sanity check inputs |
---|
| 645 | if( ((csMask & (AD_CTRL_ALL_RF_CS)) == 0)) |
---|
| 646 | return -1; |
---|
| 647 | |
---|
| 648 | /* AD9963 reg 0x60: |
---|
| 649 | 7: 0=power down DLL |
---|
| 650 | [6:0] 1=power down [DAC refrerence, IDAC, QDAC, clock input, ADC reference, QADC, IADC] |
---|
| 651 | */ |
---|
| 652 | |
---|
| 653 | //This funtion intentionally powers off the DLL, whether the user asks for all-on or all-off |
---|
| 654 | // This seemed like the safest approach, since the DLL requries an explicit reset whenever its config |
---|
| 655 | // changes or when it is powered up. This reset is done in the ad_config_DLL() function in the correct |
---|
| 656 | // order to bring the DLL up in a good state. |
---|
| 657 | |
---|
| 658 | if(pwrState == AD_PWR_ALLOFF) regVal = 0x3F; //everything powered down |
---|
| 659 | else if(pwrState == AD_PWR_ALLON) regVal = 0x00; //DLL off, everything else on |
---|
| 660 | else return -1; //invalid input |
---|
| 661 | |
---|
| 662 | ad_spi_write(baseaddr, csMask, 0x60, regVal); |
---|
| 663 | |
---|
| 664 | return 0; |
---|
| 665 | } |
---|
| 666 | /** @}*/ //END group user_functions |
---|