Statistics
| Branch: | Tag: | Revision:

root / host / lib / usrp / usrp2 / clock_ctrl.cpp @ ce5940f8

History | View | Annotate | Download (7.39 KB)

1
//
2
// Copyright 2010 Ettus Research LLC
3
//
4
// This program is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// This program is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
//
17

    
18
#include "clock_ctrl.hpp"
19
#include "ad9510_regs.hpp"
20
#include "usrp2_regs.hpp" //spi slave constants
21
#include <uhd/utils/assert.hpp>
22
#include <boost/cstdint.hpp>
23

    
24
using namespace uhd;
25

    
26
/*!
27
 * A usrp2 clock control specific to the ad9510 ic.
28
 */
29
class usrp2_clock_ctrl_impl : public usrp2_clock_ctrl{
30
public:
31
    usrp2_clock_ctrl_impl(usrp2_iface::sptr iface){
32
        _iface = iface;
33

    
34
        _ad9510_regs.cp_current_setting = ad9510_regs_t::CP_CURRENT_SETTING_3_0MA;
35
        this->write_reg(0x09);
36

    
37
        // Setup the clock registers to 100MHz:
38
        //  This was already done by the firmware (or the host couldnt communicate).
39
        //  We could remove this part, and just leave it to the firmware.
40
        //  But why not leave it in for those who want to mess with clock settings?
41
        //  100mhz = 10mhz/R * (P*B + A)
42

    
43
        _ad9510_regs.pll_power_down = ad9510_regs_t::PLL_POWER_DOWN_NORMAL;
44
        _ad9510_regs.prescaler_value = ad9510_regs_t::PRESCALER_VALUE_DIV2;
45
        this->write_reg(0x0A);
46

    
47
        _ad9510_regs.acounter = 0;
48
        this->write_reg(0x04);
49

    
50
        _ad9510_regs.bcounter_msb = 0;
51
        _ad9510_regs.bcounter_lsb = 5;
52
        this->write_reg(0x05);
53
        this->write_reg(0x06);
54

    
55
        _ad9510_regs.ref_counter_msb = 0;
56
        _ad9510_regs.ref_counter_lsb = 1; // r divider = 1
57
        this->write_reg(0x0B);
58
        this->write_reg(0x0C);
59

    
60
        /* regs will be updated in commands below */
61

    
62
        this->enable_external_ref(false);
63
        this->enable_rx_dboard_clock(false);
64
        this->enable_tx_dboard_clock(false);
65

    
66
        /* private clock enables, must be set here */
67
        this->enable_dac_clock(true);
68
        this->enable_adc_clock(true);
69

    
70
    }
71

    
72
    ~usrp2_clock_ctrl_impl(void){
73
        /* private clock enables, must be set here */
74
        this->enable_dac_clock(false);
75
        this->enable_adc_clock(false);
76
    }
77

    
78
    //uses output clock 7 (cmos)
79
    void enable_rx_dboard_clock(bool enb){
80
        _ad9510_regs.power_down_lvds_cmos_out7 = enb? 0 : 1;
81
        _ad9510_regs.lvds_cmos_select_out7 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT7_CMOS;
82
        _ad9510_regs.output_level_lvds_out7 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT7_1_75MA;
83
        this->write_reg(0x43);
84
        this->update_regs();
85
    }
86

    
87
    void set_rate_rx_dboard_clock(double rate){
88
        assert_has(get_rates_rx_dboard_clock(), rate, "rx dboard clock rate");
89
        size_t divider = size_t(get_master_clock_rate()/rate);
90
        //bypass when the divider ratio is one
91
        _ad9510_regs.bypass_divider_out7 = (divider == 1)? 1 : 0;
92
        //calculate the low and high dividers
93
        size_t high = divider/2;
94
        size_t low = divider - high;
95
        //set the registers (divider - 1)
96
        _ad9510_regs.divider_low_cycles_out7 = low - 1;
97
        _ad9510_regs.divider_high_cycles_out7 = high - 1;
98
        //write the registers
99
        this->write_reg(0x56);
100
        this->write_reg(0x57);
101
        this->update_regs();
102
    }
103

    
104
    std::vector<double> get_rates_rx_dboard_clock(void){
105
        std::vector<double> rates;
106
        for (size_t i = 1; i <= 16+16; i++) rates.push_back(get_master_clock_rate()/i);
107
        return rates;
108
    }
109

    
110
    //uses output clock 6 (cmos)
111
    void enable_tx_dboard_clock(bool enb){
112
        _ad9510_regs.power_down_lvds_cmos_out6 = enb? 0 : 1;
113
        _ad9510_regs.lvds_cmos_select_out6 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT6_CMOS;
114
        _ad9510_regs.output_level_lvds_out6 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT6_1_75MA;
115
        this->write_reg(0x42);
116
        this->update_regs();
117
    }
118

    
119
    void set_rate_tx_dboard_clock(double rate){
120
        assert_has(get_rates_tx_dboard_clock(), rate, "tx dboard clock rate");
121
        size_t divider = size_t(get_master_clock_rate()/rate);
122
        //bypass when the divider ratio is one
123
        _ad9510_regs.bypass_divider_out6 = (divider == 1)? 1 : 0;
124
        //calculate the low and high dividers
125
        size_t high = divider/2;
126
        size_t low = divider - high;
127
        //set the registers (divider - 1)
128
        _ad9510_regs.divider_low_cycles_out6 = low - 1;
129
        _ad9510_regs.divider_high_cycles_out6 = high - 1;
130
        //write the registers
131
        this->write_reg(0x54);
132
        this->write_reg(0x55);
133
        this->update_regs();
134
    }
135

    
136
    std::vector<double> get_rates_tx_dboard_clock(void){
137
        return get_rates_rx_dboard_clock(); //same master clock, same dividers...
138
    }
139

    
140
    /*!
141
     * If we are to use an external reference, enable the charge pump.
142
     * \param enb true to enable the CP
143
     */
144
    void enable_external_ref(bool enb){
145
        _ad9510_regs.charge_pump_mode = (enb)?
146
            ad9510_regs_t::CHARGE_PUMP_MODE_NORMAL :
147
            ad9510_regs_t::CHARGE_PUMP_MODE_3STATE ;
148
        _ad9510_regs.pll_mux_control = ad9510_regs_t::PLL_MUX_CONTROL_DLD_HIGH;
149
        _ad9510_regs.pfd_polarity = ad9510_regs_t::PFD_POLARITY_POS;
150
        this->write_reg(0x08);
151
        this->update_regs();
152
    }
153

    
154
    double get_master_clock_rate(void){
155
        return 100e6;
156
    }
157

    
158
private:
159
    /*!
160
     * Write a single register to the spi regs.
161
     * \param addr the address to write
162
     */
163
    void write_reg(boost::uint8_t addr){
164
        boost::uint32_t data = _ad9510_regs.get_write_reg(addr);
165
        _iface->transact_spi(SPI_SS_AD9510, spi_config_t::EDGE_RISE, data, 24, false /*no rb*/);
166
    }
167

    
168
    /*!
169
     * Tells the ad9510 to latch the settings into the operational registers.
170
     */
171
    void update_regs(void){
172
        _ad9510_regs.update_registers = 1;
173
        this->write_reg(0x5a);
174
    }
175

    
176
    //uses output clock 3 (pecl)
177
    void enable_dac_clock(bool enb){
178
        _ad9510_regs.power_down_lvpecl_out3 = (enb)?
179
            ad9510_regs_t::POWER_DOWN_LVPECL_OUT3_NORMAL :
180
            ad9510_regs_t::POWER_DOWN_LVPECL_OUT3_SAFE_PD;
181
        _ad9510_regs.output_level_lvpecl_out3 = ad9510_regs_t::OUTPUT_LEVEL_LVPECL_OUT3_810MV;
182
        _ad9510_regs.bypass_divider_out3 = 1;
183
        this->write_reg(0x3F);
184
        this->write_reg(0x4F);
185
        this->update_regs();
186
    }
187

    
188
    //uses output clock 4 (lvds)
189
    void enable_adc_clock(bool enb){
190
        _ad9510_regs.power_down_lvds_cmos_out4 = enb? 0 : 1;
191
        _ad9510_regs.lvds_cmos_select_out4 = ad9510_regs_t::LVDS_CMOS_SELECT_OUT4_LVDS;
192
        _ad9510_regs.output_level_lvds_out4 = ad9510_regs_t::OUTPUT_LEVEL_LVDS_OUT4_1_75MA;
193
        _ad9510_regs.bypass_divider_out4 = 1;
194
        this->write_reg(0x40);
195
        this->write_reg(0x51);
196
        this->update_regs();
197
    }
198

    
199
    usrp2_iface::sptr _iface;
200
    ad9510_regs_t _ad9510_regs;
201
};
202

    
203
/***********************************************************************
204
 * Public make function for the ad9510 clock control
205
 **********************************************************************/
206
usrp2_clock_ctrl::sptr usrp2_clock_ctrl::make(usrp2_iface::sptr iface){
207
    return sptr(new usrp2_clock_ctrl_impl(iface));
208
}