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 | rc_usleep(100); |
---|
612 | ad_spi_write(baseaddr, csMask, 0x75, 0x00); |
---|
613 | |
---|
614 | //Wait for both DLLs to lock |
---|
615 | while( (lockAttempts > 0) && ( (ad_spi_read(baseaddr, csMask, 0x72) & 0x8080) != 0x8080) ) {lockAttempts--; print(".");} |
---|
616 | |
---|
617 | //If the wait-for-lock loop timed out, return an error |
---|
618 | if(lockAttempts == 0) return -2; |
---|
619 | else return 0; |
---|
620 | } |
---|
621 | |
---|
622 | |
---|
623 | } |
---|
624 | |
---|
625 | /** |
---|
626 | \brief Shuts down or enables the selected AD9963. Starting up from shutdown is not instantaneous, so this function should only be used to |
---|
627 | 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(). |
---|
628 | <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 |
---|
629 | if your design uses the DLL clock for ADCs or DACs. |
---|
630 | \param baseaddr Base memory address of w3_ad_controller pcore |
---|
631 | \param csMask OR'd combination of RFA_AD_CS and RFB_AD_CS |
---|
632 | \param pwrState Desired AD9963 power state; must be one of [AD_PWR_ALLOFF, AD_PWR_ALLON] |
---|
633 | \return Returns 0 on success, -1 for invalid paramters |
---|
634 | */ |
---|
635 | int ad_config_power(u32 baseaddr, u32 csMask, u8 pwrState) { |
---|
636 | u8 regVal; |
---|
637 | |
---|
638 | //Sanity check inputs |
---|
639 | if( ((csMask & (AD_CTRL_ALL_RF_CS)) == 0)) |
---|
640 | return -1; |
---|
641 | |
---|
642 | /* AD9963 reg 0x60: |
---|
643 | 7: 0=power down DLL |
---|
644 | [6:0] 1=power down [DAC refrerence, IDAC, QDAC, clock input, ADC reference, QADC, IADC] |
---|
645 | */ |
---|
646 | |
---|
647 | //This funtion intentionally powers off the DLL, whether the user asks for all-on or all-off |
---|
648 | // This seemed like the safest approach, since the DLL requries an explicit reset whenever its config |
---|
649 | // changes or when it is powered up. This reset is done in the ad_config_DLL() function in the correct |
---|
650 | // order to bring the DLL up in a good state. |
---|
651 | |
---|
652 | if(pwrState == AD_PWR_ALLOFF) regVal = 0x3F; //everything powered down |
---|
653 | else if(pwrState == AD_PWR_ALLON) regVal = 0x00; //DLL off, everything else on |
---|
654 | else return -1; //invalid input |
---|
655 | |
---|
656 | ad_spi_write(baseaddr, csMask, 0x60, regVal); |
---|
657 | |
---|
658 | return 0; |
---|
659 | } |
---|
660 | /** @}*/ //END group user_functions |
---|