Exemplo n.º 1
0
 def _init_clk_peripherals(self):
     """
     Initialize objects for peripherals owned by this class. Most importantly,
     this includes the RPLL and SPLL control classes.
     """
     # Create SPI and I2C interfaces to the LMK registers
     spll_spi_node = dt_symbol_get_spidev('spll')
     sample_lmk_regs_iface = lib.spi.make_spidev_regs_iface(
         spll_spi_node,
         1000000,  # Speed (Hz)
         0x3,  # SPI mode
         8,  # Addr shift
         0,  # Data shift
         1 << 23,  # Read flag
         0,  # Write flag
     )
     # Initialize I2C connection to RPLL
     self._rpll_i2c_bus = i2c_dev.dt_symbol_get_i2c_bus(X400_RPLL_I2C_LABEL)
     if self._rpll_i2c_bus is None:
         raise RuntimeError("RPLL I2C bus could not be found")
     reference_lmk_regs_iface = lib.i2c.make_i2cdev_regs_iface(
         self._rpll_i2c_bus,
         0x54,  # addr
         False,  # ten_bit_addr
         100,  # timeout_ms
         1  # reg_addr_size
     )
     self._sample_pll = LMK04832X4xx(sample_lmk_regs_iface, self.log)
     self._reference_pll = LMK03328X4xx(reference_lmk_regs_iface, self.log)
     # Init BRC select GPIO control
     self._base_ref_clk_select = Gpio('BASE-REFERENCE-CLOCK-SELECT',
                                      Gpio.OUTPUT, 1)
Exemplo n.º 2
0
    def __init__(self, gpio_modprs, gpio_modsel, devsymbol, log):
        """
        modprs: Name of the GPIO pin that reports module presence
        modsel: Name of the GPIO pin that controls ModSel of QSFP module
        devsymbol: Symbol name of the device used for I2C communication
        """

        self.log = log.getChild('QSFP')

        # Hold the ModSelL GPIO low for communication over I2C. Because X4xx
        # uses a I2C switch to communicate with the QSFP modules we can keep
        # ModSelL low all the way long, because each QSFP module has
        # its own I2C address (see SFF-8486 rev 4.9, chapter 4.1.1.1).
        self.modsel = Gpio(gpio_modsel, Gpio.OUTPUT, 0)

        # ModPrs pin read pin MODPRESL from QSFP connector
        self.modprs = Gpio(gpio_modprs, Gpio.INPUT, 0)

        # resolve device node name for I2C communication
        devname = i2c_dev.dt_symbol_get_i2c_bus(devsymbol)

        # create an object to access I2C register interface
        self.qsfp_regs = lib.i2c.make_i2cdev_regs_iface(
            devname,  # dev node name
            0x50,  # start address according to SFF-8486 rev 4.9 chapter 7.6
            False,  # use 7 bit address schema
            100,  # timeout_ms
            1  # reg_addr_size
        )
Exemplo n.º 3
0
    def __init__(self, pll_regs_iface, log=None):
        LMK04832.__init__(self, pll_regs_iface, log)
        self._output_freq = None
        self._is_legacy_mode = None

        self._sclk_pll_reset = Gpio('SAMPLE-CLOCK-PLL-RESET', Gpio.OUTPUT, 0)
        self._sclk_pll_select = Gpio('SAMPLE-CLOCK-PLL-VCXO-SELECT',
                                     Gpio.OUTPUT, 0)
Exemplo n.º 4
0
        def __init__(self, port):
            assert port in DioControl.DIO_PORTS
            prefix = "DIOAUX_%s" % port

            self.enable = Gpio('%s_ENABLE' % prefix, Gpio.OUTPUT)
            self.en_3v3 = Gpio('%s_3V3' % prefix, Gpio.OUTPUT)
            self.en_2v5 = Gpio('%s_2V5' % prefix, Gpio.OUTPUT)
            self.ext_pwr = Gpio('%s_ENABLE_EXT_PWR' % prefix, Gpio.OUTPUT)
            self.power_good = Gpio('%s_PWR_GOOD' % prefix, Gpio.INPUT)
Exemplo n.º 5
0
    def __init__(self, slot_idx, motherboard):
        super().__init__(slot_idx, motherboard)
        self.db_cpld_iface = motherboard.ctrlport_regs.get_db_cpld_iface(
            self.slot_idx)
        self._power_enable = Gpio('DB{}_PWR_EN'.format(slot_idx), Gpio.OUTPUT)
        self._power_status = Gpio('DB{}_PWR_STATUS'.format(slot_idx),
                                  Gpio.INPUT)

        self.db_flash = DBFlash(slot_idx, log=self.log)
Exemplo n.º 6
0
    def __init__(self, slot_idx, path, adc_indexes, dac_indexes, loopback):
        self.log = get_logger("X4xxDebugDboard-{}-path-{}".format(slot_idx, path))

        self.rxa2_led = Gpio("DB{}_RX{}2_LED".format(slot_idx, path), Gpio.OUTPUT, 0)
        self.rxa_led = Gpio("DB{}_RX{}_LED".format(slot_idx, path), Gpio.OUTPUT, 0)
        self.txa_led = Gpio("DB{}_TX{}_LED".format(slot_idx, path), Gpio.OUTPUT, 0)

        self.trx_ctrl = Gpio("DB{}_TRX{}_CTRL".format(slot_idx, path), Gpio.OUTPUT, 0)
        self.rx_mux_ctrl = Gpio("DB{}_RX{}_MUX_CTRL".format(slot_idx, path), Gpio.OUTPUT, 0)
        self.tx_mux_ctrl = Gpio("DB{}_TX{}_MUX_CTRL".format(slot_idx, path), Gpio.OUTPUT, 0)

        self._adc_indices = adc_indexes
        self._dac_indices = dac_indexes
        self._loopback = loopback
        self._path = path
Exemplo n.º 7
0
 def _monitor_dio_fault(self, dio_port, fault, tear_down, fault_state):
     """
     Monitor the DIO_INT lines to detect an external power fault.
     If there is a fault, turn off external power.
     """
     self.log.trace("Launching monitor loop...")
     fault_line = Gpio(fault, Gpio.FALLING_EDGE)
     while True:
         try:
             if fault_line.event_wait():
                 # If we saw a fault, disable the external power
                 self.log.warning(
                     f"DIO fault occurred on port {dio_port} - "
                     "turning off external power")
                 self.set_external_power(dio_port, 0)
                 fault_state.value = 1
         # If the event wait gets interrupted because we are trying to tear
         # down then stop the monitoring process. If not, keep monitoring
         except InterruptedError:
             pass
         if tear_down.is_set():
             break
Exemplo n.º 8
0
    def __init__(self, pll_regs_iface, log=None):
        LMK03328.__init__(self, pll_regs_iface, log)

        self._pll_status_0 = Gpio('REFERENCE-CLOCK-PLL-STATUS-0', Gpio.INPUT)
        self._pll_status_1 = Gpio('REFERENCE-CLOCK-PLL-STATUS-1', Gpio.INPUT)
        self._pll_pwrdown_n = Gpio('REFERENCE-CLOCK-PLL-PWRDOWN', Gpio.OUTPUT,
                                   1)

        self._reference_rates = None
Exemplo n.º 9
0
class DebugDboardSignalPath:
    def __init__(self, slot_idx, path, adc_indexes, dac_indexes, loopback):
        self.log = get_logger("X4xxDebugDboard-{}-path-{}".format(slot_idx, path))

        self.rxa2_led = Gpio("DB{}_RX{}2_LED".format(slot_idx, path), Gpio.OUTPUT, 0)
        self.rxa_led = Gpio("DB{}_RX{}_LED".format(slot_idx, path), Gpio.OUTPUT, 0)
        self.txa_led = Gpio("DB{}_TX{}_LED".format(slot_idx, path), Gpio.OUTPUT, 0)

        self.trx_ctrl = Gpio("DB{}_TRX{}_CTRL".format(slot_idx, path), Gpio.OUTPUT, 0)
        self.rx_mux_ctrl = Gpio("DB{}_RX{}_MUX_CTRL".format(slot_idx, path), Gpio.OUTPUT, 0)
        self.tx_mux_ctrl = Gpio("DB{}_TX{}_MUX_CTRL".format(slot_idx, path), Gpio.OUTPUT, 0)

        self._adc_indices = adc_indexes
        self._dac_indices = dac_indexes
        self._loopback = loopback
        self._path = path

    def configure(self, adc, dac, loopback):
        """
        Configure this path with the appropriate settings
        """
        if adc.lower() not in self._adc_indices:
            error_msg = "Could not find ADC {} on path {}. Possible ADCs: {}".format(
                adc, self._path, ", ".join(self._adc_indices.keys())
            )
            self.log.error(error_msg)
            raise RuntimeError(error_msg)

        if dac.lower() not in self._dac_indices:
            error_msg = "Could not find DAC {} on path {}. Possible DACs: {}".format(
                dac, self._path, ", ".join(self._dac_indices.keys())
            )
            self.log.error(error_msg)
            raise RuntimeError(error_msg)

        self.rx_mux_ctrl.set(self._adc_indices[adc.lower()])
        self.tx_mux_ctrl.set(self._dac_indices[dac.lower()])
        self.trx_ctrl.set(self._loopback if loopback else not self._loopback)
Exemplo n.º 10
0
class ClockingAuxBrdControl:
    """
    Control interface for the Clocking Aux Board over I2C and SPI
    """
    SOURCE_INTERNAL = "internal"
    SOURCE_EXTERNAL = "external"
    SOURCE_GPSDO = "gpsdo"
    SOURCE_NSYNC = "nsync"

    SOURCE_NSYNC_LMK_PRI_FABRIC_CLK = "fabric_clk"
    SOURCE_NSYNC_LMK_PRI_GTY_RCV_CLK = "gty_rcv_clk"

    NSYNC_PRI_REF = "pri_ref"
    NSYNC_SEC_REF = "sec_ref"

    DIRECTION_INPUT = "input"
    DIRECTION_OUTPUT = "output"

    VALID_CLK_EXPORTS = (SOURCE_INTERNAL, SOURCE_GPSDO, SOURCE_NSYNC)
    VALID_NSYNC_LMK_PRI_REF_SOURCES = (SOURCE_NSYNC_LMK_PRI_FABRIC_CLK,
                                       SOURCE_NSYNC_LMK_PRI_GTY_RCV_CLK)

    def __init__(self, default_source=None, parent_log=None):
        self.log = \
            parent_log.getChild(self.__class__.__name__) if parent_log is not None \
            else get_logger(self.__class__.__name__)
        _check_i2c_bus()
        self._revision = self._get_eeprom_field('rev',
                                                X400_CLKAUX_DEFAULT_REVISION)
        self._pid = self._get_eeprom_field('pid', X400_CLKAUX_NOGPSDO_PID)
        self._nsync_support = self._revision >= 2
        self._gps_support = self._pid == X400_CLKAUX_GPSDO_PID
        default_source = default_source or ClockingAuxBrdControl.SOURCE_INTERNAL

        # Some GPIO lines are named differently in the overlays for rev 1 and
        # rev 2 and some perform different functions in rev 1 and 2 even if
        # named similarly.

        # GPIO common to rev 1 and 2
        self._3v3_power_good = Gpio("CLKAUX_3V3_CLK_PG", Gpio.INPUT)
        self._pps_term = Gpio("CLKAUX_PPS_TERM", Gpio.OUTPUT, 1)
        self._trig_oe_n = Gpio("CLKAUX_TRIG_OEn", Gpio.OUTPUT, 1)
        self._trig_dir = Gpio("CLKAUX_TRIG_DIR", Gpio.OUTPUT, 0)
        self._ref_lck_led = Gpio("CLKAUX_REF_LCK", Gpio.OUTPUT, 0)

        if self._revision == 1:
            self._ref_clk_sel_usr = Gpio("CLKAUX_REF_CLK_SEL_USR", Gpio.OUTPUT,
                                         1)
            self._mbrefclk_bias = Gpio("CLKAUX_MBRefCLK_En", Gpio.OUTPUT, 0)
            self._exportclk_bias = Gpio("CLKAUX_ExportClk_En", Gpio.OUTPUT, 0)
        elif self._revision >= 2:
            self._ref_clk_sel_usr = Gpio("CLKAUX_UsrRefSel", Gpio.OUTPUT, 0)
            self._mbrefclk_bias = Gpio("CLKAUX_MBRefCLK_Bias", Gpio.OUTPUT, 0)
            self._exportclk_bias = Gpio("CLKAUX_ExportClk_Bias", Gpio.OUTPUT,
                                        0)
            self._tcxo_en = Gpio("CLKAUX_TCXO_EN", Gpio.OUTPUT, 0)
            self._exportclk_en = Gpio("CLKAUX_EXP_CLK_EN", Gpio.OUTPUT, 0)
            self._nsync_refsel = Gpio("CLKAUX_NSYNC_REFSEL", Gpio.OUTPUT, 0)
            self._nsync_power_ctrl = Gpio("CLKAUX_NSYNC_PDN", Gpio.OUTPUT, 0)
            self._ref_clk_select_net = Gpio("CLKAUX_REF_CLK_SEL_NET",
                                            Gpio.OUTPUT, 0)
            self._nsync_gpio0 = Gpio("CLKAUX_NSYNC_GPIO0", Gpio.INPUT)
            self._nsync_status1 = Gpio("CLKAUX_NSYNC_STATUS1", Gpio.INPUT)
            self._nsync_status0 = Gpio("CLKAUX_NSYNC_STATUS0", Gpio.INPUT)
            self._fpga_clk_gty_fabric_sel = Gpio("CLKAUX_FPGA_CLK_SEL",
                                                 Gpio.OUTPUT, 0)
            self._lmk05318_bias = Gpio("CLKAUX_05318Ref_Bias", Gpio.OUTPUT, 0)

        if self._gps_support:
            self._gps_phase_lock = Gpio("CLKAUX_GPS_PHASELOCK", Gpio.INPUT)
            self._gps_warmup = Gpio("CLKAUX_GPS_WARMUP", Gpio.INPUT)
            self._gps_survey = Gpio("CLKAUX_GPS_SURVEY", Gpio.INPUT)
            self._gps_lock = Gpio("CLKAUX_GPS_LOCK", Gpio.INPUT)
            self._gps_alarm = Gpio("CLKAUX_GPS_ALARM", Gpio.INPUT)
            self._gps_rst_n = Gpio("CLKAUX_GPS_RSTn", Gpio.OUTPUT, 0)
            if self._revision == 1:
                self._gps_initsrv_n = Gpio("CLKAUX_GPS_INITSRVn", Gpio.OUTPUT,
                                           1)
            elif self._revision >= 2:
                self._gps_initsrv_n = Gpio("CLKAUX_GPS_INITSURVn", Gpio.OUTPUT,
                                           0)

        if self._revision >= 2:
            _check_spi_bus()
            # Create SPI interface to the LMK05318 registers
            nsync_spi_node = dt_symbol_get_spidev(X400_CLKAUX_SPI_LABEL)
            nsync_lmk_regs_iface = lib.spi.make_spidev_regs_iface(
                nsync_spi_node,
                1000000,  # Speed (Hz)
                0x0,  # SPI mode
                8,  # Addr shift
                0,  # Data shift
                1 << 23,  # Read flag
                0,  # Write flag
            )
            self._nsync_pll = LMK05318(nsync_lmk_regs_iface, self.log)

        self.set_source(default_source)
        self.set_trig(False, self.DIRECTION_OUTPUT)
        self._init_dac()

    def _init_dac(self):
        """
        Initializes i2c bus to communicate with the DAC and configures the
        tuning word for both voltage outputs
        """
        dac_i2c_bus = i2c_dev.dt_symbol_get_i2c_bus(X400_CLKAUX_I2C_LABEL)
        self._dac_i2c_iface = lib.i2c.make_i2cdev(
            dac_i2c_bus,
            0xC,  # addr
            False,  # ten_bit_addr
            100)
        tuning_word = self._get_eeprom_field('tuning_word',
                                             X400_CLKAUX_DEFAULT_TUNING_WORD)
        self.config_dac(tuning_word, 0)
        self.config_dac(tuning_word, 1)

    def _get_eeprom_field(self, field_name, default_val):
        """
        Return the value of the requested eeprom field.
        """
        eeprom_paths = get_eeprom_paths_by_symbol("clkaux_eeprom")
        path = eeprom_paths['clkaux_eeprom']
        val = default_val
        try:
            eeprom, _ = tlv_eeprom.read_eeprom(path, ClkAuxTagMap.tagmap,
                                               ClkAuxTagMap.magic, None)
            val = eeprom.get(field_name, default_val)
        except TypeError as err:
            self.log.warning(
                f"Error reading eeprom; will use defaults. ({err})")
        return val

    def store_tuning_word(self, tuning_word):
        """ Store the dac tuning word in the ID EEPROM"""
        cmd = [
            "eeprom-update", "clkaux", "--clkaux_tuning_word",
            str(tuning_word)
        ]
        try:
            subprocess.call(cmd)
        except subprocess.CalledProcessError as ex:
            self.log.warning("Failed to write to clkaux EEPROM: %s", str(ex))

    def config_dac(self, tuning_word, out_select):
        """Configure tuning word on the the selected DAC output through i2c"""
        high, low = divmod(tuning_word << 6, 0x100)
        command = 0x38 if out_select else 0x31
        tx_cmd = [command, high, low]
        # Send command to write tuning word and update output
        self._dac_i2c_iface.transfer(tx_cmd, 0, True)

    def read_dac(self, out_select):
        """Read the tuning word on the selected DAC output through i2c"""
        command = 0x8 if out_select else 0x1
        rx_buffer = self._dac_i2c_iface.transfer([command], 2, True)
        val = ((rx_buffer[0] << 8) | rx_buffer[1]) >> 6
        return val

    def is_nsync_supported(self):
        """Return True if nsync clock source is supported by hardware"""
        return self._nsync_support

    def _check_nsync_supported(self):
        """
        Assert nsync clock source is supported by hardware or throw otherwise
        """
        if not self.is_nsync_supported():
            raise RuntimeError("NSYNC related features are not supported!")

    def is_gps_supported(self):
        """Return True if GPS clock source is supported by hardware"""
        return self._gps_support

    def is_gps_enabled(self):
        """
        Return True if the GPS is currently enabled (i.e., not held in reset).
        """
        return bool(self._gps_rst_n.get())

    def _assert_gps_supported(self):
        """ Throw a RuntimeError if GPS is not supported on this board. """
        if not self.is_gps_supported():
            raise RuntimeError("GPS related features are not supported!")

    def _init_nsync_lmk(self):
        """Initialize the LMK05318 network sync IC"""
        self._check_nsync_supported()
        self.set_nsync_lmk_power_en(1)
        self._nsync_pll.soft_reset(True)
        self._nsync_pll.soft_reset(False)
        if not self._nsync_pll.is_chip_id_valid():
            raise RuntimeError(
                "ClockingAuxBrdControl Unable to locate LMK05318!")

    def set_source(self, clock_source, time_source=None):
        """
        Select the clock and time source on the clock auxbrd.

        Notes:
        - The clocking aux board has a PPS termination which must be disabled
          when the PPS input is active. Note that we can only have an external
          time source when the clock source is also external. We enable it in
          all other cases.
        - The pin that selects the reference clock (UsrRefSel or ref_clk_sel_usr)
          selects *both* the clock reference and the time reference (external
          or GPSDO). So if clock source is set to external, but time source to
          internal, then we are still connecting the PPS In SMA to the FPGA.
        - This function will disable clock export if we switch to external clock.
        - The time source is irrelevant unless the clock source is EXTERNAL, so
          we allow not specifying it for the other cases.
        - We actually put the GPS chip into reset when it's unused so we don't
          collect spurs from there. However, this means the GPS will need to
          warm up when being selected. Selecting the GPS as a source at runtime
          is possible, it just won't be available until it's warmed up.
        """
        if clock_source not in self.VALID_CLK_EXPORTS:
            self.export_clock(False)
        if clock_source == self.SOURCE_INTERNAL:
            self._set_gps_rstn(0)
            self._set_ref_clk_sel_usr(0)
            # If we are using an internal PPS, then we terminate the connector
            use_pps_term = time_source == self.SOURCE_INTERNAL
            self._pps_term.set(use_pps_term)
            self._mbrefclk_bias.set(1)
            if self._revision >= 2:
                self.set_nsync_lmk_power_en(0)
                self._lmk05318_bias.set(0)
                self._ref_clk_select_net.set(0)
        elif clock_source == self.SOURCE_EXTERNAL:
            self._set_gps_rstn(0)
            self._set_ref_clk_sel_usr(1)
            self._exportclk_bias.set(0)
            self._pps_term.set(1)
            self._mbrefclk_bias.set(1)
            if self._revision >= 2:
                self.set_nsync_lmk_power_en(0)
                self._lmk05318_bias.set(0)
                self._ref_clk_select_net.set(0)
        elif clock_source == self.SOURCE_GPSDO:
            self._assert_gps_supported()
            self._set_gps_rstn(1)
            self._set_ref_clk_sel_usr(0)
            self._pps_term.set(1)
            self._mbrefclk_bias.set(1)
            if self._revision >= 2:
                self.set_nsync_lmk_power_en(0)
                self._lmk05318_bias.set(0)
                self._ref_clk_select_net.set(0)
        elif clock_source == self.SOURCE_NSYNC:
            self._check_nsync_supported()
            self._set_gps_rstn(0)
            self._set_ref_clk_sel_usr(0)
            self._tcxo_en.set(1)
            self._nsync_refsel.set(1)
            self._mbrefclk_bias.set(0)
            self._lmk05318_bias.set(1)
            self._pps_term.set(1)
            self._ref_clk_select_net.set(1)
            self._init_nsync_lmk()
        else:
            raise RuntimeError('Invalid clock source {}'.format(clock_source))
        self._source = clock_source
        self.log.trace("set clock source to: {}".format(self._source))

    def export_clock(self, enable=True):
        """Export clock source to RefOut"""
        clock_source = self.get_clock_source()
        if not enable:
            self._exportclk_bias.set(0)
            self._pps_term.set(1)
            if self._revision >= 2:
                self._exportclk_en.set(0)
        elif clock_source in self.VALID_CLK_EXPORTS:
            self._exportclk_bias.set(1)
            self._pps_term.set(0)
            if self._revision >= 2:
                self._exportclk_en.set(1)
        else:
            raise RuntimeError(
                'Invalid source to export: {}'.format(clock_source))

    def set_trig(self, enable, direction=None):
        """Enable/disable the Trig IO out"""
        if direction is None:
            direction = ClockingAuxBrdControl.DIRECTION_OUTPUT

        if enable:
            self._trig_oe_n.set(0)
        else:
            self._trig_oe_n.set(1)

        if direction == self.DIRECTION_INPUT:
            self._trig_dir.set(0)
        elif direction == self.DIRECTION_OUTPUT:
            self._trig_dir.set(1)
        else:
            raise RuntimeError(
                'Invalid direction {}, valid options are {} and {}'.format(
                    direction, self.DIRECTION_INPUT, self.DIRECTION_OUTPUT))

    def get_clock_source(self):
        """Returns the clock source"""
        return self._source

    def get_gps_phase_lock(self):
        """Returns true if the GPS Phase is locked, and false if it is not"""
        return self._gps_phase_lock.get()

    def get_gps_warmup(self):
        """Returns true if the GPS is warming up"""
        return self._gps_warmup.get()

    def get_gps_survey(self):
        """Returns whether or not an auto survey is in progress"""
        return self._gps_survey.get()

    def get_gps_lock(self):
        """Returns whether or not the GPS has a lock"""
        return self._gps_lock.get()

    def get_gps_alarm(self):
        """Returns true if the GPS detects a hardware fault or software alarm"""
        return self._gps_alarm.get()

    def get_3v3_pg(self):
        """Returns true if the 3.3V rail is good, false otherwise"""
        return self._3v3_power_good.get()

    def _set_ref_clk_sel_usr(self, value):
        """Sets REF_CLK_SEL_USR to value"""
        value = int(value)
        assert value in (0, 1)
        if value == 1:
            #Never set REF_CLK_SEL_USR and GPS_RSTn high at the same time, hardware can be damaged
            self._set_gps_rstn(0)
        self._ref_clk_sel_usr.set(value)

    def _set_gps_rstn(self, value):
        """
        Sets GPS_RSTn to value

        If value == 0, then the GPS is held in reset and is not usable.
        """
        value = int(value)
        assert value in (0, 1)
        if value == 1:
            # Never set REF_CLK_SEL_USR and GPS_RSTn high at the same time,
            # hardware can be damaged
            self._set_ref_clk_sel_usr(0)
        if self._gps_support:
            self._gps_rst_n.set(value)
        elif value == 1:
            raise RuntimeError("No GPS, so can't bring it out of reset")

    def get_nsync_chip_id_valid(self):
        """Returns whether the chip ID of the LMK is valid"""
        return self._nsync_pll.is_chip_id_valid()

    def set_nsync_soft_reset(self, value=True):
        """Soft reset LMK chip"""
        return self._nsync_pll.soft_reset(value)

    def get_nsync_status0(self):
        """Returns value of STATUS0 pin on LMK05318 NSYNC IC"""
        self._check_nsync_supported()
        return self._nsync_status0.get()

    def get_nsync_status1(self):
        """Returns value of STATUS1 pin on LMK05318 NSYNC IC"""
        self._check_nsync_supported()
        return self._nsync_status1.get()

    def set_nsync_pri_ref_source(self, source):
        """Sets LMK05318 PRIMREF (primary reference) to specified source"""
        self._check_nsync_supported()

        if source not in self.VALID_NSYNC_LMK_PRI_REF_SOURCES:
            raise RuntimeError(
                "Invalid primary reference clock source for LMK05318 NSYNC IC")

        self.config_dpll(source)
        if source == self.SOURCE_NSYNC_LMK_PRI_FABRIC_CLK:
            self._fpga_clk_gty_fabric_sel.set(1)
        else:
            self._fpga_clk_gty_fabric_sel.set(0)

    def set_nsync_ref_select(self, source):
        """Sets LMK05318 REFSEL to PRIREF or SECREF"""
        self._check_nsync_supported()
        if source == self.NSYNC_PRI_REF:
            self._nsync_refsel.set(0)
        elif source == self.NSYNC_SEC_REF:
            self._nsync_refsel.set(1)
        else:
            raise RuntimeError("Invalid setting for LMK05318 NSYNC REFSEL")

    def set_nsync_tcxo_en(self, enable):
        """
        Enables/Disables the 10 MHz TCXO chip output; this signal serves as the
        oscillator input to the LMK05318 NSYNC IC.
        """
        self._check_nsync_supported()
        if enable:
            self._tcxo_en.set(1)
        else:
            self._tcxo_en.set(0)

    def set_nsync_lmk_power_en(self, enable):
        """Turn on/off the LMK05318 IC using the PDN pin"""
        self._check_nsync_supported()
        if enable:
            self.log.trace("enable LMK05318 power")
            self._nsync_power_ctrl.set(1)
        else:
            self.log.trace("disable LMK05318 power")
            self._nsync_power_ctrl.set(0)

    def write_nsync_lmk_cfg_regs_to_eeprom(self, method):
        """program the current LMK config to LMK eeprom"""
        self._check_nsync_supported()
        self.log.trace("LMK05318: store cfg in eeprom")
        self._nsync_pll.write_cfg_regs_to_eeprom(method)

    def write_nsync_lmk_eeprom_to_cfg_regs(self):
        """read register cfg from eeprom and store it into registers"""
        self._check_nsync_supported()
        self.log.trace("LMK05318: read cfg from eeprom")
        self._nsync_pll.write_eeprom_to_cfg_regs()

    def get_nsync_lmk_eeprom_prog_cycles(self):
        """
        returns the number of eeprom programming cycles
        note:
        the actual counter only increases after programming AND power-cycle/hard-reset
        so multiple programming cycles without power cycle will lead to wrong
        counter values
        """
        self._check_nsync_supported()
        return self._nsync_pll.get_eeprom_prog_cycles()

    def get_nsync_lmk_status_dpll(self):
        """
        returns the DPLL status register as human readable string
        """
        self._check_nsync_supported()
        return self._nsync_pll.get_status_dpll()

    def get_nsync_lmk_status_pll_xo(self):
        """
        returns the PLL and XO status register as human readable string
        """
        self._check_nsync_supported()
        return self._nsync_pll.get_status_pll_xo()

    def peek8(self, addr):
        """Read from addr over SPI"""
        self._check_nsync_supported()
        val = self._nsync_pll.peek8(addr)
        return val

    def poke8(self, addr, val, overwrite_mask=False):
        """
        Write val to addr over SPI
        Some register of the LMK IC are supposed not to be written and therefore
        the whole register or just some bits. are protected by masking.
        If you are really sure what you are doing you can overwrite the masking
        by setting overwrite_mask=True
        """
        self._check_nsync_supported()
        self._nsync_pll.poke8(addr, val, overwrite_mask)

    def config_dpll(self, source):
        """
        configures the dpll registers needed to lock to the expected signal

        Initial config files were created with TICSpro, then files were compared
        against each other to determine which registers needed to be changed
        """
        if source == self.SOURCE_NSYNC_LMK_PRI_GTY_RCV_CLK:
            self._nsync_pll.pokes8(
                ((0xC5, 0x0B), (0xCC, 0x05), (0xD1, 0x08), (0xD3, 0x0A),
                 (0xD5, 0x08), (0xD7, 0x0A), (0xDA, 0x02), (0xDB, 0xFA),
                 (0xDC, 0xF1), (0xDE, 0x06), (0xDF, 0x1A), (0xE0, 0x81),
                 (0xE3, 0x30), (0xE4, 0xD4), (0xE6, 0x06), (0xE7, 0x1A),
                 (0xE8, 0x80), (0x100, 0x00), (0x101, 0x7D), (0x103, 0x08),
                 (0x109, 0x0F), (0x10A, 0xA0), (0x10F, 0x78), (0x110, 0x00),
                 (0x111, 0x00), (0x112, 0x00), (0x113, 0x0F), (0x114, 0x0E),
                 (0x115, 0x0F), (0x116, 0x08), (0x118, 0x08), (0x119, 0x06),
                 (0x11A, 0x08), (0x11B, 0x06), (0x11E, 0x00), (0x11F, 0x71),
                 (0x121, 0xEB), (0x123, 0x09), (0x128, 0x03), (0x129, 0x05),
                 (0x12A, 0x03), (0x12D, 0x3E), (0x12E, 0x3F), (0x130, 0x01),
                 (0x133, 0x01), (0x134, 0x4D), (0x135, 0x55), (0x136, 0x55),
                 (0x137, 0x55), (0x138, 0x55), (0x139, 0x55), (0x13A, 0xFF),
                 (0x13B, 0xFF), (0x13C, 0xFF), (0x13D, 0xFF), (0x13E, 0xFF),
                 (0x141, 0x19), (0x145, 0x78), (0x147, 0x00), (0x148, 0x27),
                 (0x149, 0x10), (0x14B, 0x32), (0x14F,
                                                0x78), (0x151,
                                                        0x00), (0x152,
                                                                0x27), (0x153,
                                                                        0x10)))
        elif source == self.SOURCE_NSYNC_LMK_PRI_FABRIC_CLK:
            self._nsync_pll.pokes8(
                ((0xC5, 0x0D), (0xCC, 0x07), (0xD1, 0x04), (0xD3, 0x05),
                 (0xD5, 0x04), (0xD7, 0x05), (0xDA, 0x01), (0xDB, 0x2C),
                 (0xDC, 0x00), (0xDE, 0x03), (0xDF, 0x0D), (0xE0, 0x40),
                 (0xE3, 0x18), (0xE4, 0x6A), (0xE6, 0x03), (0xE7, 0x0D),
                 (0xE8, 0x40), (0x100, 0x06), (0x101, 0x00), (0x103, 0x7D),
                 (0x109, 0xF4), (0x10A, 0x24), (0x10F, 0x7A), (0x110, 0x1F),
                 (0x111, 0x1F), (0x112, 0x1F), (0x113, 0x13), (0x114, 0x10),
                 (0x115, 0x13), (0x116, 0x04), (0x118, 0x04), (0x119, 0x02),
                 (0x11A, 0x07), (0x11B, 0x02), (0x11E, 0x02), (0x11F, 0x6C),
                 (0x121, 0xE7), (0x123, 0x25), (0x128, 0x00), (0x129, 0x06),
                 (0x12A, 0x00), (0x12D, 0x17), (0x12E, 0x1B), (0x130, 0x00),
                 (0x133, 0x1E), (0x134, 0x84), (0x135, 0x80), (0x136, 0x00),
                 (0x137, 0x00), (0x138, 0x00), (0x139, 0x00), (0x13A, 0x00),
                 (0x13B, 0x00), (0x13C, 0x00), (0x13D, 0x00), (0x13E, 0x00),
                 (0x141, 0x0A), (0x145, 0x0A), (0x147, 0x03), (0x148, 0x0F),
                 (0x149, 0x49), (0x14B, 0x14), (0x14F,
                                                0x9A), (0x151,
                                                        0x03), (0x152,
                                                                0x0F), (0x153,
                                                                        0x49)))
        else:
            raise RuntimeError("Invalid source for dpll programming")

    def set_ref_lock_led(self, val):
        """
        Set the reference-locked LED on the back panel
        """
        self._ref_lck_led.set(int(val))
Exemplo n.º 11
0
class LMK04832X4xx(LMK04832):
    """
    X4xx-specific subclass of the Sample Clock PLL LMK04832 controls.
    """
    def __init__(self, pll_regs_iface, log=None):
        LMK04832.__init__(self, pll_regs_iface, log)
        self._output_freq = None
        self._is_legacy_mode = None

        self._sclk_pll_reset = Gpio('SAMPLE-CLOCK-PLL-RESET', Gpio.OUTPUT, 0)
        self._sclk_pll_select = Gpio('SAMPLE-CLOCK-PLL-VCXO-SELECT',
                                     Gpio.OUTPUT, 0)

    @property
    def is_legacy_mode(self):
        if self._is_legacy_mode is None:
            self.log.error('The Sample PLL was never configured before '
                           'checking for legacy mode!')
            raise RuntimeError('The Sample PLL was never configured before '
                               'checking for legacy mode!')
        return self._is_legacy_mode

    @property
    def output_freq(self):
        if self._output_freq is None:
            self.log.error('The Sample PLL was never configured before '
                           'checking the output frequency!')
            raise RuntimeError('The Sample PLL was never configured before '
                               'checking the output frequency!')
        return self._output_freq

    def init(self):
        """
        Perform a soft reset, configure SPI readback, and verify chip ID
        """
        self.reset(False, hard=True)
        self.reset(True)
        self.reset(False)
        if not self.verify_chip_id():
            raise Exception("unable to locate LMK04832!")

    def reset(self, value=True, hard=False):
        """
        Perform a hard reset from the GPIO pins or a soft reset from the LMK register
        """
        if hard:
            self._sclk_pll_reset.set(value)
        else:
            self.soft_reset(value)

        if not value:
            # Enable 4-wire spi readback after a reset. 4-wire SPI is disabled
            # by default after a reset of the LMK, but is required to perform
            # SPI reads on the x4xx.
            self.enable_4wire_spi()

    def enable_4wire_spi(self):
        """ Enable 4-wire SPI readback from the CLKin_SEL0 pin """
        self.poke8(0x148, 0x33)
        self.enable_3wire_spi = False

    def set_vcxo(self, source_freq):
        """
        Selects either the 100e6 MHz or 122.88e6 MHz VCXO for the PLL1 loop of the LMK04832.
        """
        if source_freq == 100e6:
            source_index = 0
        elif source_freq == 122.88e6:
            source_index = 1
        else:
            self.log.warning(
                'Selected VCXO source of {:g} is not a valid selection'.format(
                    source_freq))
            return
        self.log.trace('Selected VCXO source of {:g}'.format(source_freq))
        self._sclk_pll_select.set(source_index)

    def get_status(self):
        """
        Returns PLL lock status
        """
        pll1_status = self.check_plls_locked(pll='PLL1')
        pll2_status = self.check_plls_locked(pll='PLL2')
        return {'PLL1 lock': pll1_status, 'PLL2 lock': pll2_status}

    def config(self, output_freq, brc_freq, is_legacy_mode=False):
        """
        Configures the LMK04832 to generate the desired output_freq
        """
        def calculate_vcxo_freq(output_freq):
            """
            Returns the vcxo frequency based on the desired output frequency
            """
            return {
                2.94912e9: 122.88e6,
                3e9: 100e6,
                3.072e9: 122.88e6
            }[output_freq]

        def calculate_pll1_n_div(output_freq):
            """
            Returns the PLL1 N divider value based on the desired output frequency
            """
            return {2.94912e9: 64, 3e9: 50, 3.072e9: 64}[output_freq]

        def calculate_pll2_n_div(output_freq):
            """
            Returns the PLL2 N divider value based on the desired output frequency
            """
            return {2.94912e9: 12, 3e9: 10, 3.072e9: 5}[output_freq]

        def calculate_pll2_pre(output_freq):
            """
            Returns the PLL2 prescaler value based on the desired output frequency
            """
            return {2.94912e9: 2, 3e9: 3, 3.072e9: 5}[output_freq]

        def calculate_n_cal_div(output_freq):
            """
            Returns the PLL2 N cal value based on the desired output frequency
            """
            return {2.94912e9: 12, 3e9: 10, 3.072e9: 5}[output_freq]

        def calculate_sysref_div(output_freq):
            """
            Returns the SYSREF divider value based on the desired output frequency
            """
            return {2.94912e9: 1152, 3e9: 1200, 3.072e9: 1200}[output_freq]

        def calculate_clk_in_0_r_div(output_freq, brc_freq):
            """
            Returns the CLKin0 R divider value based on the desired output frequency
            and current base reference clock frequency
            """
            pfd1 = {2.94912e9: 40e3, 3e9: 50e3, 3.072e9: 40e3}[output_freq]
            return int(brc_freq / pfd1)

        if output_freq not in (2.94912e9, 3e9, 3.072e9):
            # A failure to config the SPLL could lead to an invalid state for
            # downstream clocks, so throw here to alert the caller.
            raise RuntimeError(
                'Selected output_freq of {:g} is not a valid selection'.format(
                    output_freq))

        self._is_legacy_mode = is_legacy_mode
        self._output_freq = output_freq

        self.log.trace(
            f"Configuring SPLL to output frequency of {output_freq} Hz, used "
            f"BRC frquency is {brc_freq} Hz, legacy mode is {is_legacy_mode}")

        self.set_vcxo(calculate_vcxo_freq(output_freq))

        # Clear hard reset and trigger soft reset
        self.reset(False, hard=True)
        self.reset(True, hard=False)
        self.reset(False, hard=False)

        prc_divider = 0x3C if is_legacy_mode else 0x30

        # CLKout Config
        self.pokes8((
            (0x0100, 0x01),
            (0x0101, 0x0A),
            (0x0102, 0x70),
            (0x0103, 0x44),
            (0x0104, 0x10),
            (0x0105, 0x00),
            (0x0106, 0x00),
            (0x0107, 0x55),
            (0x0108, 0x01),
            (0x0109, 0x0A),
            (0x010A, 0x70),
            (0x010B, 0x44),
            (0x010C, 0x10),
            (0x010D, 0x00),
            (0x010E, 0x00),
            (0x010F, 0x55),
            (0x0110, prc_divider),
            (0x0111, 0x0A),
            (0x0112, 0x60),
            (0x0113, 0x40),
            (0x0114, 0x10),
            (0x0115, 0x00),
            (0x0116, 0x00),
            (0x0117, 0x44),
            (0x0118, prc_divider),
            (0x0119, 0x0A),
            (0x011A, 0x60),
            (0x011B, 0x40),
            (0x011C, 0x10),
            (0x011D, 0x00),
            (0x011E, 0x00),
            (0x011F, 0x44),
            (0x0120, prc_divider),
            (0x0121, 0x0A),
            (0x0122, 0x60),
            (0x0123, 0x40),
            (0x0124, 0x20),
            (0x0125, 0x00),
            (0x0126, 0x00),
            (0x0127, 0x44),
            (0x0128, 0x01),
            (0x0129, 0x0A),
            (0x012A, 0x60),
            (0x012B, 0x60),
            (0x012C, 0x20),
            (0x012D, 0x00),
            (0x012E, 0x00),
            (0x012F, 0x44),
            (0x0130, 0x01),
            (0x0131, 0x0A),
            (0x0132, 0x70),
            (0x0133, 0x44),
            (0x0134, 0x10),
            (0x0135, 0x00),
            (0x0136, 0x00),
            (0x0137, 0x55),
        ))

        # PLL Config
        sysref_div = calculate_sysref_div(output_freq)
        clk_in_0_r_div = calculate_clk_in_0_r_div(output_freq, brc_freq)
        pll1_n_div = calculate_pll1_n_div(output_freq)
        prescaler = self.pll2_pre_to_reg(calculate_pll2_pre(output_freq))
        pll2_n_cal_div = calculate_n_cal_div(output_freq)
        pll2_n_div = calculate_pll2_n_div(output_freq)
        self.pokes8((
            (0x0138, 0x20),
            (
                0x0139, 0x00
            ),  # Set SysRef source to 'Normal SYNC' as we initially use the sync signal to synchronize dividers
            (0x013A, (sysref_div & 0x1F00) >> 8),  # SYSREF Divide [12:8]
            (0x013B, (sysref_div & 0x00FF) >> 0),  # SYSREF Divide [7:0]
            (0x013C, 0x00),  # set sysref delay value
            (0x013D,
             0x20),  # shift SYSREF with respect to falling edge of data clock
            (0x013E, 0x03),  # set number of SYSREF pulse to 8(Default)
            (
                0x013F, 0x0F
            ),  # PLL1_NCLK_MUX = Feedback mux, FB_MUX = External, FB_MUX_EN = enabled
            (0x0140, 0x00),  # All power down controls set to false.
            (0x0141, 0x00),  # Disable dynamic digital delay.
            (0x0142, 0x00),  # Set dynamic digtial delay step count to 0.
            (
                0x0143, 0x81
            ),  # Enable SYNC pin, disable sync functionality, SYSREF_CLR='0, SYNC is level sensitive.
            (0x0144,
             0x00),  # Allow SYNC to synchronize all SysRef and clock outputs
            (
                0x0145, 0x10
            ),  # Disable PLL1 R divider SYNC, use SYNC pin for PLL1 R divider SYNC, disable PLL2 R divider SYNC
            (
                0x0146, 0x00
            ),  # CLKIN0/1 type = Bipolar, disable CLKin_sel pin, disable both CLKIn source for auto-switching.
            (
                0x0147, 0x06
            ),  # ClkIn0_Demux= PLL1, CLKIn1-Demux=Feedback mux (need for 0-delay mode)
            (0x0148,
             0x33),  # CLKIn_Sel0 = SPI readback with output set to push-pull
            (0x0149,
             0x02),  # Set SPI readback ouput to open drain (needed for 4-wire)
            (0x014A, 0x00),  # Set RESET pin as input
            (0x014B, 0x02),  # Default
            (0x014C, 0x00),  # Default
            (0x014D, 0x00),  # Default
            (0x014E, 0xC0),  # Default
            (0x014F, 0x7F),  # Default
            (0x0150, 0x00),  # Default and disable holdover
            (0x0151, 0x02),  # Default
            (0x0152, 0x00),  # Default
            (0x0153, (clk_in_0_r_div & 0x3F00) >>
             8),  # CLKin0_R divider [13:8], default = 0
            (0x0154, (clk_in_0_r_div & 0x00FF) >>
             0),  # CLKin0_R divider [7:0], default = d120
            (0x0155, 0x00),  # Set CLKin1 R divider to 1
            (0x0156, 0x01),  # Set CLKin1 R divider to 1
            (0x0157, 0x00),  # Set CLKin2 R divider to 1
            (0x0158, 0x01),  # Set CLKin2 R divider to 1
            (0x0159,
             (pll1_n_div & 0x3F00) >> 8),  # PLL1 N divider [13:8], default = 0
            (0x015A, (pll1_n_div & 0x00FF) >>
             0),  # PLL1 N divider [7:0], default = d120
            (
                0x015B, 0xCF
            ),  # Set PLL1 window size to 43ns, PLL1 CP ON, negative polarity, CP gain is 1.55 mA.
            (0x015C, 0x20),  # Pll1 lock detect count is 8192 cycles (default)
            (0x015D, 0x00),  # Pll1 lock detect count is 8192 cycles (default)
            (
                0x015E, 0x1E
            ),  # Default holdover relative time between PLL1 R and PLL1 N divider
            (
                0x015F, 0x1B
            ),  # PLL1 and PLL2 locked status in Status_LD1 pin. Status_LD1 pin is ouput (push-pull)
            (0x0160, 0x00),  # PLL2 R divider is 1
            (0x0161, 0x01),  # PLL2 R divider is 1
            (0x0162, prescaler
             ),  # PLL2 prescaler; OSCin freq; Lower nibble must be 0x4!!!
            (0x0163, (pll2_n_cal_div & 0x030000) >> 16),  # PLL2 N Cal [17:16]
            (0x0164, (pll2_n_cal_div & 0x00FF00) >> 8),  # PLL2 N Cal [15:8]
            (0x0165, (pll2_n_cal_div & 0x0000FF) >> 0),  # PLL2 N Cal [7:0]
            (
                0x0169, 0x59
            ),  # Write this val after x165. PLL2 CP gain is 3.2 mA, PLL2 window is 1.8 ns
            (0x016A, 0x20),  # PLL2 lock detect count is 8192 cycles (default)
            (0x016B, 0x00),  # PLL2 lock detect count is 8192 cycles (default)
            (0x016E,
             0x13),  # Stautus_LD2 pin not used. Don't care about this register
            (0x0173, 0x10),  # PLL2 prescaler and PLL2 are enabled.
            (0x0177, 0x00),  # PLL1 R divider not in reset
            (0x0166, (pll2_n_div & 0x030000) >> 16),  # PLL2 N[17:16]
            (0x0167, (pll2_n_div & 0x00FF00) >> 8),  # PLL2 N[15:8]
            (0x0168, (pll2_n_div & 0x0000FF) >> 0),  # PLL2 N[7:0]
        ))

        # Synchronize Output and SYSREF Dividers
        self.pokes8((
            (0x0143, 0x91),
            (0x0143, 0xB1),
            (0x0143, 0x91),
            (0x0144, 0xFF),
            (0x0143, 0x11),
            (0x0139, 0x12),
            (0x0143, 0x31),
        ))

        # Check for Lock
        # PLL2 should lock first and be relatively fast (300 us)
        if self.wait_for_pll_lock('PLL2', timeout=5):
            self.log.trace("PLL2 is locked after SPLL config.")
        else:
            self.log.error('Sample Clock PLL2 failed to lock!')
            raise RuntimeError('Sample Clock PLL2 failed to lock! '
                               'Check the logs for details.')
        # PLL1 may take up to 2 seconds to lock
        if self.wait_for_pll_lock('PLL1', timeout=2000):
            self.log.trace("PLL1 is locked after SPLL config.")
        else:
            self.log.error('Sample Clock PLL1 failed to lock!')
            raise RuntimeError('Sample Clock PLL1 failed to lock! '
                               'Check the logs for details.')
Exemplo n.º 12
0
class X4xxIfTestCCA(DboardManagerBase):
    """
    Holds all dboard specific information and methods of the X4xx IF Test CCA
    """
    #########################################################################
    # Overridables
    #
    # See DboardManagerBase for documentation on these fields
    #########################################################################
    pids = [0x4006]
    ### End of overridables #################################################

    def __init__(self, slot_idx, **kwargs):
        DboardManagerBase.__init__(self, slot_idx, **kwargs)
        self.log = get_logger("X4xxIfTestCCA-{}".format(slot_idx))
        self.log.trace("Initializing X4xxIfTestCCA, slot index %d",
                       self.slot_idx)

        # Interface with MB HW
        if 'db_iface' not in kwargs:
            self.log.error("Required DB Iface was not provided!")
            raise RuntimeError("Required DB Iface was not provided!")
        self.db_iface = kwargs['db_iface']

        # Power on the card
        self.db_iface.enable_daughterboard(enable=True)
        if not self.db_iface.check_enable_daughterboard():
            self.db_iface.enable_daughterboard(enable=False)
            self.log.error('IF Test CCA {} power up failed'.format(self.slot_idx))
            raise RuntimeError('IF Test CCA {} power up failed'.format(self.slot_idx))

        # [boolean for stage 1 mux , boolean for stage 2 mux]
        self._adc_mux_settings = {
            "adc0" : [0, 0],
            "adc1" : [1, 1],
            "adc2" : [1, 0],
            "adc3" : [0, 1],
        }

        self._dac_mux_settings = {
            "dac0" : [1, 0],
            "dac1" : [1, 1],
            "dac2" : [0, 0],
            "dac3" : [0, 1],
        }

        # There are 4 possible Tx (DAC) streams that are available to choose
        # to export to the SMA TX port using a 2-stage hardware mux.

        # Choose between 0 and 2 OR 1 and 3
        self.tx_0_2_1_3_mux_ctrl = Gpio("DB{}_TX0_2p_1_3n".format(slot_idx), Gpio.OUTPUT, 0)
        # Choose between 0 OR 2
        self.tx_0_2_mux_ctrl = Gpio("DB{}_TX_MUX_0p_2n".format(slot_idx), Gpio.OUTPUT, 0)
        # Choose between 1 OR 3
        self.tx_1_3_mux_ctrl = Gpio("DB{}_TX_MUX_1p_3n".format(slot_idx), Gpio.OUTPUT, 0)

        # The signal from the SMA RX port can be directed to one of the 4
        # available Rx (ADC) streams using a 2-stage hardware mux.

        # Choose between 0 and 2 OR 1 and 3
        self.rx_0_2_1_3_mux_ctrl = Gpio("DB{}_RX0_2p_1_3n".format(slot_idx), Gpio.OUTPUT, 0)
        # Choose between 0 OR 2
        self.rx_0_2_mux_ctrl = Gpio("DB{}_RX_MUX_0p_2n".format(slot_idx), Gpio.OUTPUT, 0)
        # Choose between 1 OR 3
        self.rx_1_3_mux_ctrl = Gpio("DB{}_RX_MUX_1p_3n".format(slot_idx), Gpio.OUTPUT, 0)

        self._tx_path = ""
        self._rx_path = ""

        # Controls to load the power supplies on the daughterboard. Enabling
        # these will increase the power draw of the daughterboard.
        self.enable_1v8_load = Gpio("DB{}_1V8_LOAD".format(slot_idx), Gpio.OUTPUT, 0)
        self.enable_2v5_load = Gpio("DB{}_2V5_LOAD".format(slot_idx), Gpio.OUTPUT, 0)
        self.enable_3v3_load = Gpio("DB{}_3V3_LOAD".format(slot_idx), Gpio.OUTPUT, 0)
        self.enable_3v3_mcu_load = Gpio("DB{}_3V3_MCU_LOAD".format(slot_idx), Gpio.OUTPUT, 0)
        self.enable_3v7_load = Gpio("DB{}_3V7_LOAD".format(slot_idx), Gpio.OUTPUT, 0)
        self.enable_12v_load = Gpio("DB{}_12V_LOAD".format(slot_idx), Gpio.OUTPUT, 0)

        # Control to choose between DAC output or MB VCM signals as the VCM
        # signal to use on board.
        self.disable_vcm_dac = Gpio("DB{}_VCM_MB_nDAC".format(slot_idx), Gpio.OUTPUT, 0)

        # Control to choose which MB clock to output to the SMA Clock port.
        # Choices are BaseRefClk and PllRefClk
        self.disable_vcm_dac = Gpio("DB{}_REF_CLK_SEL_USR".format(slot_idx), Gpio.OUTPUT, 0)


    def init(self, args):
        """
        Execute necessary init dance to bring up dboard
        """
        self.log.debug("init() called with args `{}'".format(
            ",".join(['{}={}'.format(x, args[x]) for x in args])
        ))
        self.config_tx_path("dac0")
        self.config_rx_path("adc0")
        return True

    def deinit(self):
        pass

    def tear_down(self):
        self.db_iface.tear_down()

    def config_tx_path(self, dac):
        """
        Configure the tx signal path on the daughterboard.
        dac - Select which DAC to connect to the Tx path (dac0 through dac3)

        Example MPM shell usage:
        > db_0_config_tx_path dac2
        """

        if dac.lower() not in self._dac_mux_settings:
            error_msg = "Could not find DAC {}. Possible DACs: {}".format(
                dac, ", ".join(self._dac_mux_settings.keys())
            )
            self.log.error(error_msg)
            raise RuntimeError(error_msg)

        # Only one of the following setting really matters; simplify logic
        # by toggling both since the stage 2 decides what gets connected.
        self.tx_0_2_mux_ctrl.set(self._dac_mux_settings[dac.lower()][0])
        self.tx_1_3_mux_ctrl.set(self._dac_mux_settings[dac.lower()][0])
        self.tx_0_2_1_3_mux_ctrl.set(self._dac_mux_settings[dac.lower()][1])
        self._tx_path = dac.upper()

    def get_tx_path(self):
        return self._tx_path

    def config_rx_path(self, adc):
        """
        Configure the rx signal path on the daughterboard.
        adc - Select which ADC to connect to the Rx path (adc0 through adc3)

        Example MPM shell usage:
        > db_0_config_rx_path adc0
        """

        if adc.lower() not in self._adc_mux_settings:
            error_msg = "Could not find ADC {}. Possible ADCs: {}".format(
                adc, ", ".join(self._adc_mux_settings.keys())
            )
            self.log.error(error_msg)
            raise RuntimeError(error_msg)

        self.rx_0_2_1_3_mux_ctrl.set(self._adc_mux_settings[adc.lower()][1])
        # Only one of the following setting really matters; simplify logic
        # by toggling both
        self.rx_0_2_mux_ctrl.set(self._adc_mux_settings[adc.lower()][0])
        self.rx_1_3_mux_ctrl.set(self._adc_mux_settings[adc.lower()][0])
        self._rx_path = adc.upper()

    def get_rx_path(self):
        return self._rx_path
Exemplo n.º 13
0
class X4xxClockMgr:
    """
    X4x0 Clocking Manager

    The clocking subsystem of X4x0 is very complex. This class is designed to
    capture all clocking-related logic specific to the X4x0.

    This class controls clock and time sources.
    """
    CLOCK_SOURCE_MBOARD = "mboard"
    CLOCK_SOURCE_INTERNAL = ClockingAuxBrdControl.SOURCE_INTERNAL
    CLOCK_SOURCE_EXTERNAL = ClockingAuxBrdControl.SOURCE_EXTERNAL
    CLOCK_SOURCE_GPSDO = ClockingAuxBrdControl.SOURCE_GPSDO
    CLOCK_SOURCE_NSYNC = ClockingAuxBrdControl.SOURCE_NSYNC

    TIME_SOURCE_INTERNAL = "internal"
    TIME_SOURCE_EXTERNAL = "external"
    TIME_SOURCE_GPSDO = "gpsdo"
    TIME_SOURCE_QSFP0 = "qsfp0"

    # All valid sync_sources for X4xx in the form of (clock_source, time_source)
    valid_sync_sources = {
        (CLOCK_SOURCE_MBOARD, TIME_SOURCE_INTERNAL),
        (CLOCK_SOURCE_INTERNAL, TIME_SOURCE_INTERNAL),
        (CLOCK_SOURCE_EXTERNAL, TIME_SOURCE_EXTERNAL),
        (CLOCK_SOURCE_EXTERNAL, TIME_SOURCE_INTERNAL),
        (CLOCK_SOURCE_GPSDO, TIME_SOURCE_GPSDO),
        (CLOCK_SOURCE_GPSDO, TIME_SOURCE_INTERNAL),
        (CLOCK_SOURCE_NSYNC, TIME_SOURCE_INTERNAL),
    }

    class SetSyncRetVal(Enum):
        OK = 'OK'
        NOP = 'nop'
        FAIL = 'failure'

    def __init__(self, clock_source, time_source, ref_clock_freq,
                 sample_clock_freq, is_legacy_mode, clk_aux_board,
                 cpld_control, log):
        # Store parent objects
        self.log = log.getChild("ClkMgr")
        self._cpld_control = cpld_control
        self._clocking_auxbrd = clk_aux_board
        self._time_source = time_source
        self._clock_source = clock_source
        self._int_clock_freq = X400_DEFAULT_INT_CLOCK_FREQ
        self._ext_clock_freq = ref_clock_freq
        # Preallocate other objects to satisfy linter
        self.mboard_regs_control = None
        self._sample_pll = None
        self._reference_pll = None
        self._rpll_i2c_bus = None
        self._base_ref_clk_select = None
        self._set_reset_rfdc = lambda **kwargs: None
        self._set_reset_db_clocks = lambda *args: None
        self._rpll_reference_sources = {}
        # Init peripherals
        self._init_available_srcs()
        self._init_clk_peripherals()
        # Now initialize the clocks themselves
        self._init_ref_clock_and_time(
            clock_source,
            ref_clock_freq,
            sample_clock_freq,
            is_legacy_mode,
        )
        self._init_meas_clock()
        self._cpld_control.enable_pll_ref_clk()

    ###########################################################################
    # Initialization code
    ###########################################################################
    def _init_available_srcs(self):
        """
        Initialize the available clock and time sources.
        """
        has_gps = self._clocking_auxbrd and self._clocking_auxbrd.is_gps_supported(
        )
        self._avail_clk_sources = [self.CLOCK_SOURCE_MBOARD]
        if self._clocking_auxbrd:
            self._avail_clk_sources.extend(
                [self.CLOCK_SOURCE_INTERNAL, self.CLOCK_SOURCE_EXTERNAL])
            if self._clocking_auxbrd.is_nsync_supported():
                self._avail_clk_sources.append(self.CLOCK_SOURCE_NSYNC)
            if has_gps:
                self._avail_clk_sources.append(self.CLOCK_SOURCE_GPSDO)
        self.log.trace(
            f"Available clock sources are: {self._avail_clk_sources}")
        self._avail_time_sources = [
            self.TIME_SOURCE_INTERNAL, self.TIME_SOURCE_EXTERNAL,
            self.TIME_SOURCE_QSFP0
        ]
        if has_gps:
            self._avail_time_sources.append(self.TIME_SOURCE_GPSDO)
        self.log.trace("Available time sources are: {}".format(
            self._avail_time_sources))

    def _init_clk_peripherals(self):
        """
        Initialize objects for peripherals owned by this class. Most importantly,
        this includes the RPLL and SPLL control classes.
        """
        # Create SPI and I2C interfaces to the LMK registers
        spll_spi_node = dt_symbol_get_spidev('spll')
        sample_lmk_regs_iface = lib.spi.make_spidev_regs_iface(
            spll_spi_node,
            1000000,  # Speed (Hz)
            0x3,  # SPI mode
            8,  # Addr shift
            0,  # Data shift
            1 << 23,  # Read flag
            0,  # Write flag
        )
        # Initialize I2C connection to RPLL
        self._rpll_i2c_bus = i2c_dev.dt_symbol_get_i2c_bus(X400_RPLL_I2C_LABEL)
        if self._rpll_i2c_bus is None:
            raise RuntimeError("RPLL I2C bus could not be found")
        reference_lmk_regs_iface = lib.i2c.make_i2cdev_regs_iface(
            self._rpll_i2c_bus,
            0x54,  # addr
            False,  # ten_bit_addr
            100,  # timeout_ms
            1  # reg_addr_size
        )
        self._sample_pll = LMK04832X4xx(sample_lmk_regs_iface, self.log)
        self._reference_pll = LMK03328X4xx(reference_lmk_regs_iface, self.log)
        # Init BRC select GPIO control
        self._base_ref_clk_select = Gpio('BASE-REFERENCE-CLOCK-SELECT',
                                         Gpio.OUTPUT, 1)

    def _init_ref_clock_and_time(
        self,
        clock_source,
        ref_clock_freq,
        sample_clock_freq,
        is_legacy_mode,
    ):
        """
        Initialize clock and time sources. After this function returns, the
        reference signals going to the FPGA are valid.

        This is only called once, during __init__(). Calling it again will set
        clocks to defaults, but is also redundant since clocks do not need to be
        initialized twice.
        """
        # A dictionary of tuples (source #, rate) corresponding to each
        # available RPLL reference source.
        # source # 1 => PRIREF source
        # source # 2 => SECREF source
        self._rpll_reference_sources = {
            X400_DEFAULT_RPLL_REF_SOURCE: (2, 100e6)
        }
        reference_rates = [None, None]
        for source, rate in self._rpll_reference_sources.values():
            reference_rates[source - 1] = rate
        self._reference_pll.reference_rates = reference_rates
        # Now initializes and reconfigure all clocks.
        # If clock_source and ref_clock_freq are not provided, they will not be changed.
        # If any other parameters are not provided, they will be configured with
        # default values.
        self._reset_clocks(value=True, reset_list=['cpld'])
        if clock_source is not None:
            self._set_brc_source(clock_source)
        if ref_clock_freq is not None:
            self._set_ref_clock_freq(ref_clock_freq)
        self._config_rpll(X400_DEFAULT_MGT_CLOCK_RATE,
                          X400_DEFAULT_INT_CLOCK_FREQ,
                          X400_DEFAULT_RPLL_REF_SOURCE)
        self._config_spll(sample_clock_freq, is_legacy_mode)
        self._reset_clocks(value=False, reset_list=['cpld'])

    def _init_meas_clock(self):
        """
        Initialize the TDC measurement clock. After this function returns, the
        FPGA TDC meas_clock is valid.
        """
        # This may or may not be used for X400. Leave as a place holder
        self.log.debug("TDC measurement clock not yet implemented.")

    ###########################################################################
    # Public APIs (that are not exposed as MPM calls)
    ###########################################################################
    @no_rpc
    def set_rfdc_reset_cb(self, rfdc_reset_cb):
        """
        Set reference to RFDC control. Ideally, we'd get that in __init__(), but
        due to order of operations, it's not ready yet when we call that.
        """
        self._set_reset_rfdc = rfdc_reset_cb

    @no_rpc
    def set_dboard_reset_cb(self, db_reset_cb):
        """
        Set reference to RFDC control. Ideally, we'd get that in __init__(), but
        due to order of operations, it's not ready yet when we call that.
        """
        self._set_reset_db_clocks = db_reset_cb

    @no_rpc
    def unset_cbs(self):
        """
        Removes any stored references to our owning X4xx class instance
        """
        self._set_reset_rfdc = None
        self._set_reset_db_clocks = None

    @no_rpc
    def config_pps_to_timekeeper(self, master_clock_rate):
        """ Configures the path from the PPS to the timekeeper"""
        pps_source = "internal_pps" \
            if self._time_source == self.TIME_SOURCE_INTERNAL \
            else "external_pps"
        self._sync_spll_clocks(pps_source)
        self._configure_pps_forwarding(True, master_clock_rate)

    @no_rpc
    def get_clock_sources(self):
        """
        Lists all available clock sources.
        """
        return self._avail_clk_sources

    @no_rpc
    def get_time_sources(self):
        " Returns list of valid time sources "
        return self._avail_time_sources

    @no_rpc
    def get_ref_clock_freq(self):
        " Returns the currently active reference clock frequency (BRC) "
        clock_source = self.get_clock_source()
        if clock_source == self.CLOCK_SOURCE_MBOARD:
            return self._int_clock_freq
        if clock_source == self.CLOCK_SOURCE_GPSDO:
            return X400_GPSDO_OCXO_CLOCK_FREQ
        # clock_source == "external":
        return self._ext_clock_freq

    @no_rpc
    def get_ref_locked(self):
        """
        Return lock status both RPLL and SPLL.
        """
        ref_pll_status = self._reference_pll.get_status()
        sample_pll_status = self._sample_pll.get_status()
        return all([
            ref_pll_status['PLL1 lock'],
            ref_pll_status['PLL2 lock'],
            sample_pll_status['PLL1 lock'],
            sample_pll_status['PLL2 lock'],
        ])

    @no_rpc
    def set_spll_rate(self, sample_clock_freq, is_legacy_mode):
        """
        Safely set the output rate of the sample PLL.

        This will do the required resets.
        """
        self._reset_clocks(value=True, reset_list=('rfdc', 'cpld', 'db_clock'))
        self._config_spll(sample_clock_freq, is_legacy_mode)
        self._reset_clocks(value=False,
                           reset_list=('rfdc', 'cpld', 'db_clock'))

    @no_rpc
    def set_sync_source(self, clock_source, time_source):
        """
        Selects reference clock and PPS sources. Unconditionally re-applies the
        time source to ensure continuity between the reference clock and time
        rates.
        Note that if we change the source such that the time source is changed
        to 'external', then we need to also disable exporting the reference
        clock (RefOut and PPS-In are the same SMA connector).
        """
        assert (clock_source, time_source) in self.valid_sync_sources, \
            f'Clock and time source pair ({clock_source}, {time_source}) is ' \
            'not a valid selection'
        # Now see if we can keep the current settings, or if we need to run an
        # update of sync sources:
        if (clock_source == self._clock_source) and (time_source
                                                     == self._time_source):
            spll_status = self._sample_pll.get_status()
            rpll_status = self._reference_pll.get_status()
            if (spll_status['PLL1 lock'] and spll_status['PLL2 lock']
                    and rpll_status['PLL1 lock'] and rpll_status['PLL2 lock']):
                # Nothing change no need to do anything
                self.log.trace("New sync source assignment matches "
                               "previous assignment. Ignoring update command.")
                return self.SetSyncRetVal.NOP
            self.log.debug(
                "Although the clock source has not changed, some PLLs "
                "are not locked. Setting clock source again...")
            self.log.trace("- SPLL status: {}".format(spll_status))
            self.log.trace("- RPLL status: {}".format(rpll_status))
        # Start setting sync source
        self.log.debug(f"Setting sync source to time_source={time_source}, "
                       f"clock_source={clock_source}")
        self._time_source = time_source
        # Reset downstream clocks (excluding RPLL)
        self._reset_clocks(value=True,
                           reset_list=('db_clock', 'cpld', 'rfdc', 'spll'))
        self._set_brc_source(clock_source)
        return self.SetSyncRetVal.OK

    @no_rpc
    def set_clock_source_out(self, enable=True):
        """
        Allows routing the clock configured as source on the clk aux board to
        the RefOut terminal. This only applies to internal, gpsdo and nsync.
        """
        clock_source = self.get_clock_source()
        if self.get_time_source() == self.TIME_SOURCE_EXTERNAL:
            raise RuntimeError(
                'Cannot export clock when using external time reference!')
        if clock_source not in self._clocking_auxbrd.VALID_CLK_EXPORTS:
            raise RuntimeError(f"Invalid source to export: `{clock_source}'")
        if self._clocking_auxbrd is None:
            raise RuntimeError("No clocking aux board available")
        return self._clocking_auxbrd.export_clock(enable)

    ###########################################################################
    # Top-level BIST APIs
    #
    # These calls will be available as MPM calls. They are only needed by BIST.
    ###########################################################################
    def enable_ecpri_clocks(self, enable=True, clock='both'):
        """
        Enable or disable the export of FABRIC and GTY_RCV eCPRI
        clocks. Main use case until we support eCPRI is manufacturing
        testing.
        """
        self.mboard_regs_control.enable_ecpri_clocks(enable, clock)

    def nsync_change_input_source(self, source):
        """
        Switches the input reference source of the clkaux lmk (the "eCPRI PLL").

        Valid options are: fabric_clk, gty_rcv_clk, and sec_ref.

        fabric_clk and gty_rcv_clk are clock sources from the mboard.
        They are both inputs to the primary reference source of the clkaux lmk.
        sec_ref is the default reference select for the clkaux lmk, it has
        two inputs: Ref in or internal and GPS mode

        Only a public API for the BIST.
        """
        assert source in (
            self._clocking_auxbrd.SOURCE_NSYNC_LMK_PRI_FABRIC_CLK,
            self._clocking_auxbrd.SOURCE_NSYNC_LMK_PRI_GTY_RCV_CLK,
            self._clocking_auxbrd.NSYNC_SEC_REF,
        )
        if source == self._clocking_auxbrd.SOURCE_NSYNC_LMK_PRI_FABRIC_CLK:
            self.enable_ecpri_clocks(True, 'fabric')
            self._clocking_auxbrd.set_nsync_ref_select(
                self._clocking_auxbrd.NSYNC_PRI_REF)
            self._clocking_auxbrd.set_nsync_pri_ref_source(source)
        elif source == self._clocking_auxbrd.SOURCE_NSYNC_LMK_PRI_GTY_RCV_CLK:
            self.enable_ecpri_clocks(True, 'gty_rcv')
            self._clocking_auxbrd.set_nsync_ref_select(
                self._clocking_auxbrd.NSYNC_PRI_REF)
            self._clocking_auxbrd.set_nsync_pri_ref_source(source)
        else:
            self._clocking_auxbrd.set_nsync_ref_select(
                self._clocking_auxbrd.NSYNC_SEC_REF)

    def config_rpll_to_nsync(self):
        """
        Configures the rpll to use the LMK28PRIRefClk output
        by the clkaux LMK
        """
        # LMK28PRIRefClk only available when nsync is source, as lmk
        # is powered off otherwise
        self.set_sync_source(clock_source='nsync',
                             time_source=self._time_source)

        # Add LMK28PRIRefClk as an available RPLL reference source
        # 1 => PRIREF source; source is output at 25 MHz
        # TODO: enable out4 on LMK
        previous_ref_rate = self._reference_pll.reference_rates[0]
        self._rpll_reference_sources['clkaux_nsync_clk'] = (1, 25e6)
        self._reference_pll.reference_rates[0] = 25e6
        self._config_rpll(X400_DEFAULT_MGT_CLOCK_RATE,
                          X400_DEFAULT_INT_CLOCK_FREQ, 'clkaux_nsync_clk')

        # remove clkaux_nsync_clk as a valid reference source for later calls
        # to _config_rpll(), it is only valid in this configuration
        self._reference_pll.reference_rates[0] = previous_ref_rate
        del self._rpll_reference_sources['clkaux_nsync_clk']

    def get_fpga_aux_ref_freq(self):
        """
        Return the tick count of an FPGA counter which measures the width of
        the PPS signal on the FPGA_AUX_REF FPGA input using a 40 MHz clock.
        Main use case until we support eCPRI is manufacturing testing.
        A return value of 0 indicates absence of a valid PPS signal on the
        FPGA_AUX_REF line.
        """
        return self.mboard_regs_control.get_fpga_aux_ref_freq()

    ###########################################################################
    # Top-level APIs
    #
    # These calls will be available as MPM calls
    ###########################################################################
    def get_clock_source(self):
        " Return the currently selected clock source "
        return self._clock_source

    def get_time_source(self):
        " Return the currently selected time source "
        return self._time_source

    def get_spll_freq(self):
        """ Returns the output frequency setting of the SPLL """
        return self._sample_pll.output_freq

    def get_prc_rate(self):
        """
        Returns the rate of the PLL Reference Clock (PRC) which is
        routed to the daughterboards.
        Note: The ref clock will change if the sample clock frequency
        is modified.
        """
        prc_clock_map = {
            2.94912e9: 61.44e6,
            3e9: 62.5e6,
            # 3e9:      50e6, RF Legacy mode will be checked separately
            3.072e9: 64e6,
        }

        # RF Legacy Mode always has a PRC rate of 50 MHz
        if self._sample_pll.is_legacy_mode:
            return 50e6
        # else:
        return prc_clock_map[self.get_spll_freq()]

    def set_ref_clk_tuning_word(self, tuning_word, out_select=0):
        """
        Set the tuning word for the clocking aux board DAC. This wull update the
        tuning word used by the DAC.
        """
        if self._clocking_auxbrd is not None:
            self._clocking_auxbrd.config_dac(tuning_word, out_select)
        else:
            raise RuntimeError("No clocking aux board available")

    def get_ref_clk_tuning_word(self, out_select=0):
        """
        Get the tuning word configured for the clocking aux board DAC.
        """
        if self._clocking_auxbrd is None:
            raise RuntimeError("No clocking aux board available")
        return self._clocking_auxbrd.read_dac(out_select)

    def store_ref_clk_tuning_word(self, tuning_word):
        """
        Store the given tuning word in the clocking aux board ID EEPROM.
        """
        if self._clocking_auxbrd is not None:
            self._clocking_auxbrd.store_tuning_word(tuning_word)
        else:
            raise RuntimeError("No clocking aux board available")

    def get_sync_sources(self):
        """
        Enumerates permissible sync sources.
        """
        return [{
            "time_source": time_source,
            "clock_source": clock_source
        } for (clock_source, time_source) in self.valid_sync_sources]

    ###########################################################################
    # Low-level controls
    ###########################################################################
    def _reset_clocks(self, value, reset_list):
        """
        Shuts down all clocks downstream to upstream or clears reset on all
        clocks upstream to downstream. Specify the list of clocks to reset in
        reset_list. The order of clocks specified in the reset_list does not
        affect the order in which the clocks are reset.
        """
        if value:
            self.log.trace("Reset clocks: {}".format(reset_list))
            if 'db_clock' in reset_list:
                self._set_reset_db_clocks(value)
            if 'cpld' in reset_list:
                self._cpld_control.enable_pll_ref_clk(enable=False)
            if 'rfdc' in reset_list:
                self._set_reset_rfdc(reset=True)
            if 'spll' in reset_list:
                self._sample_pll.reset(value, hard=True)
            if 'rpll' in reset_list:
                self._reference_pll.reset(value, hard=True)
        else:
            self.log.trace("Bring clocks out of reset: {}".format(reset_list))
            if 'rpll' in reset_list:
                self._reference_pll.reset(value, hard=True)
            if 'spll' in reset_list:
                self._sample_pll.reset(value, hard=True)
            if 'rfdc' in reset_list:
                self._set_reset_rfdc(reset=False)
            if 'cpld' in reset_list:
                self._cpld_control.enable_pll_ref_clk(enable=True)
            if 'db_clock' in reset_list:
                self._set_reset_db_clocks(value)

    def _config_rpll(self, usr_clk_rate, internal_brc_rate,
                     internal_brc_source):
        """
        Configures the LMK03328 to generate the desired MGT reference clocks
        and internal BRC rate.

        Currently, the MGT protocol selection is not supported, but a custom
        usr_clk_rate can be generated from PLL1.

        usr_clk_rate - the custom clock rate to generate from PLL1
        internal_brc_rate - the rate to output as the BRC
        internal_brc_source - the reference source which drives the RPLL
        """
        if internal_brc_source not in self._rpll_reference_sources:
            self.log.error(
                'Invalid internal BRC source of {} was selected.'.format(
                    internal_brc_source))
            raise RuntimeError(
                'Invalid internal BRC source of {} was selected.'.format(
                    internal_brc_source))
        ref_select = self._rpll_reference_sources[internal_brc_source][0]

        # If the desired rate matches the rate of the primary reference source,
        # directly passthrough that reference source
        if internal_brc_rate == self._reference_pll.reference_rates[0]:
            brc_select = 'bypass'
        else:
            brc_select = 'PLL'
        self._reference_pll.init()
        self._reference_pll.config(ref_select, internal_brc_rate, usr_clk_rate,
                                   brc_select)
        # The internal BRC rate will only change when _config_rpll is called
        # with a new internal BRC rate
        self._int_clock_freq = internal_brc_rate

    def _config_spll(self, sample_clock_freq, is_legacy_mode):
        """
        Configures the SPLL for the specified master clock rate.
        """
        self._sample_pll.init()
        self._sample_pll.config(sample_clock_freq, self.get_ref_clock_freq(),
                                is_legacy_mode)

    def _set_brc_source(self, clock_source):
        """
        Switches the Base Reference Clock Source between internal, external,
        mboard, and gpsdo using the GPIO pin and clocking aux board control.
        internal is a clock source internal to the clocking aux board, but
        external to the motherboard.
        Should not be called outside of set_sync_source or _init_ref_clock_and_time
        without proper reset and reconfig of downstream clocks.
        """
        if clock_source == self.CLOCK_SOURCE_MBOARD:
            self._base_ref_clk_select.set(1)
            if self._clocking_auxbrd:
                self._clocking_auxbrd.export_clock(False)
        else:
            if self._clocking_auxbrd is None:
                self.log.error('Invalid BRC selection {}. No clocking aux '
                               'board was found.'.format(clock_source))
                raise RuntimeError(
                    'Invalid BRC selection {}'.format(clock_source))
            self._base_ref_clk_select.set(0)
            if clock_source == self.CLOCK_SOURCE_EXTERNAL:
                # This case is a bit special: We also need to tell the clocking
                # aux board if we plan to consume the external time reference or
                # not.
                time_src_board = \
                    ClockingAuxBrdControl.SOURCE_EXTERNAL \
                    if self._time_source == self.TIME_SOURCE_EXTERNAL \
                    else ClockingAuxBrdControl.SOURCE_INTERNAL
                self._clocking_auxbrd.set_source(
                    ClockingAuxBrdControl.SOURCE_EXTERNAL, time_src_board)
            elif clock_source == self.CLOCK_SOURCE_INTERNAL:
                self._clocking_auxbrd.set_source(
                    ClockingAuxBrdControl.SOURCE_INTERNAL)
            elif clock_source == self.CLOCK_SOURCE_GPSDO:
                self._clocking_auxbrd.set_source(
                    ClockingAuxBrdControl.SOURCE_GPSDO)
            elif clock_source == self.CLOCK_SOURCE_NSYNC:
                self._clocking_auxbrd.set_source(
                    ClockingAuxBrdControl.SOURCE_NSYNC)
            else:
                self.log.error('Invalid BRC selection {}'.format(clock_source))
                raise RuntimeError(
                    'Invalid BRC selection {}'.format(clock_source))
        self._clock_source = clock_source
        self.log.debug(f"Base reference clock source is: {clock_source}")

    def _sync_spll_clocks(self, pps_source="internal_pps"):
        """
        Synchronize base reference clock (BRC) and PLL reference clock (PRC)
        at start of PPS trigger.

        Uses the LMK 04832 pll1_r_divider_sync to synchronize BRC with PRC.
        This sync method uses a callback to actual trigger the sync. Before
        the trigger is pulled (CLOCK_CTRL_PLL_SYNC_TRIGGER) PPS source is
        configured base on current reference clock and pps_source. After sync
        trigger the method waits for 1sec for the CLOCK_CTRL_PLL_SYNC_DONE
        to go high.

        :param pps_source: select whether internal ("internal_pps") or external
                           ("external_pps") PPS should be used. This parameter
                           is taken into account when the current clock source
                           is external. If the current clock source is set to
                           internal then this parameter is not taken into
                           account.
        :return:           success state of sync call
        :raises RuntimeError: for invalid combinations of reference clock and
                              pps_source
        """
        def select_pps():
            """
            Select PPS source based on current clock source and pps_source.

            This returns the bits for the motherboard CLOCK_CTRL register that
            control the PPS source.
            """
            EXT_PPS = "external_pps"
            INT_PPS = "internal_pps"
            PPS_SOURCES = (EXT_PPS, INT_PPS)
            assert pps_source in PPS_SOURCES, \
                "%s not in %s" % (pps_source, PPS_SOURCES)

            supported_configs = {
                (10E6, EXT_PPS): MboardRegsControl.CLOCK_CTRL_PPS_EXT,
                (10E6, INT_PPS): MboardRegsControl.CLOCK_CTRL_PPS_INT_10MHz,
                (25E6, INT_PPS): MboardRegsControl.CLOCK_CTRL_PPS_INT_25MHz
            }

            config = (self.get_ref_clock_freq(), pps_source)
            if config not in supported_configs:
                raise RuntimeError(
                    "Unsupported combination of reference clock "
                    "(%.2E) and PPS source (%s) for PPS sync." % config)
            return supported_configs[config]

        return self._sample_pll.pll1_r_divider_sync(
            lambda: self.mboard_regs_control.pll_sync_trigger(select_pps()))

    def _configure_pps_forwarding(self, enable, master_clock_rate, delay=1.0):
        """
        Configures the PPS forwarding to the sample clock domain (master
        clock rate). This function assumes _sync_spll_clocks function has
        already been executed.

        :param enable: Boolean to choose whether PPS is forwarded to the
                       sample clock domain.

        :param delay:  Delay in seconds from the PPS rising edge to the edge
                       occurence in the application. This value has to be in
                       range 0 < x <= 1. In order to forward the PPS signal
                       from base reference clock to sample clock an aligned
                       rising edge of the clock is required. This can be
                       created by the _sync_spll_clocks function. Based on the
                       greatest common divisor of the two clock rates there
                       are multiple occurences of an aligned edge each second.
                       One of these aligned edges has to be chosen for the
                       PPS forwarding by setting this parameter.

        :return:       None, Exception on error
        """
        return self.mboard_regs_control.configure_pps_forwarding(
            enable, master_clock_rate, self.get_prc_rate(), delay)

    def _set_ref_clock_freq(self, freq):
        """
        Tell our USRP what the frequency of the external reference clock is.

        Will throw if it's not a valid value.
        """
        if (freq < 1e6) or (freq > 50e6):
            raise RuntimeError(
                'External reference clock frequency is out of the valid range.'
            )
        if (freq % 40e3) != 0:
            # TODO: implement exception of a 50e3 step size for 200MSPS
            raise RuntimeError(
                'External reference clock frequency is of incorrect step size.'
            )
        self._ext_clock_freq = freq
        # If the external source is currently selected we also need to re-apply the
        # time_source. This call also updates the dboards' rates.
        if self.get_clock_source() == self.CLOCK_SOURCE_EXTERNAL:
            self.set_sync_source(self._clock_source, self._time_source)
Exemplo n.º 14
0
    def __init__(self, default_source=None, parent_log=None):
        self.log = \
            parent_log.getChild(self.__class__.__name__) if parent_log is not None \
            else get_logger(self.__class__.__name__)
        _check_i2c_bus()
        self._revision = self._get_eeprom_field('rev',
                                                X400_CLKAUX_DEFAULT_REVISION)
        self._pid = self._get_eeprom_field('pid', X400_CLKAUX_NOGPSDO_PID)
        self._nsync_support = self._revision >= 2
        self._gps_support = self._pid == X400_CLKAUX_GPSDO_PID
        default_source = default_source or ClockingAuxBrdControl.SOURCE_INTERNAL

        # Some GPIO lines are named differently in the overlays for rev 1 and
        # rev 2 and some perform different functions in rev 1 and 2 even if
        # named similarly.

        # GPIO common to rev 1 and 2
        self._3v3_power_good = Gpio("CLKAUX_3V3_CLK_PG", Gpio.INPUT)
        self._pps_term = Gpio("CLKAUX_PPS_TERM", Gpio.OUTPUT, 1)
        self._trig_oe_n = Gpio("CLKAUX_TRIG_OEn", Gpio.OUTPUT, 1)
        self._trig_dir = Gpio("CLKAUX_TRIG_DIR", Gpio.OUTPUT, 0)
        self._ref_lck_led = Gpio("CLKAUX_REF_LCK", Gpio.OUTPUT, 0)

        if self._revision == 1:
            self._ref_clk_sel_usr = Gpio("CLKAUX_REF_CLK_SEL_USR", Gpio.OUTPUT,
                                         1)
            self._mbrefclk_bias = Gpio("CLKAUX_MBRefCLK_En", Gpio.OUTPUT, 0)
            self._exportclk_bias = Gpio("CLKAUX_ExportClk_En", Gpio.OUTPUT, 0)
        elif self._revision >= 2:
            self._ref_clk_sel_usr = Gpio("CLKAUX_UsrRefSel", Gpio.OUTPUT, 0)
            self._mbrefclk_bias = Gpio("CLKAUX_MBRefCLK_Bias", Gpio.OUTPUT, 0)
            self._exportclk_bias = Gpio("CLKAUX_ExportClk_Bias", Gpio.OUTPUT,
                                        0)
            self._tcxo_en = Gpio("CLKAUX_TCXO_EN", Gpio.OUTPUT, 0)
            self._exportclk_en = Gpio("CLKAUX_EXP_CLK_EN", Gpio.OUTPUT, 0)
            self._nsync_refsel = Gpio("CLKAUX_NSYNC_REFSEL", Gpio.OUTPUT, 0)
            self._nsync_power_ctrl = Gpio("CLKAUX_NSYNC_PDN", Gpio.OUTPUT, 0)
            self._ref_clk_select_net = Gpio("CLKAUX_REF_CLK_SEL_NET",
                                            Gpio.OUTPUT, 0)
            self._nsync_gpio0 = Gpio("CLKAUX_NSYNC_GPIO0", Gpio.INPUT)
            self._nsync_status1 = Gpio("CLKAUX_NSYNC_STATUS1", Gpio.INPUT)
            self._nsync_status0 = Gpio("CLKAUX_NSYNC_STATUS0", Gpio.INPUT)
            self._fpga_clk_gty_fabric_sel = Gpio("CLKAUX_FPGA_CLK_SEL",
                                                 Gpio.OUTPUT, 0)
            self._lmk05318_bias = Gpio("CLKAUX_05318Ref_Bias", Gpio.OUTPUT, 0)

        if self._gps_support:
            self._gps_phase_lock = Gpio("CLKAUX_GPS_PHASELOCK", Gpio.INPUT)
            self._gps_warmup = Gpio("CLKAUX_GPS_WARMUP", Gpio.INPUT)
            self._gps_survey = Gpio("CLKAUX_GPS_SURVEY", Gpio.INPUT)
            self._gps_lock = Gpio("CLKAUX_GPS_LOCK", Gpio.INPUT)
            self._gps_alarm = Gpio("CLKAUX_GPS_ALARM", Gpio.INPUT)
            self._gps_rst_n = Gpio("CLKAUX_GPS_RSTn", Gpio.OUTPUT, 0)
            if self._revision == 1:
                self._gps_initsrv_n = Gpio("CLKAUX_GPS_INITSRVn", Gpio.OUTPUT,
                                           1)
            elif self._revision >= 2:
                self._gps_initsrv_n = Gpio("CLKAUX_GPS_INITSURVn", Gpio.OUTPUT,
                                           0)

        if self._revision >= 2:
            _check_spi_bus()
            # Create SPI interface to the LMK05318 registers
            nsync_spi_node = dt_symbol_get_spidev(X400_CLKAUX_SPI_LABEL)
            nsync_lmk_regs_iface = lib.spi.make_spidev_regs_iface(
                nsync_spi_node,
                1000000,  # Speed (Hz)
                0x0,  # SPI mode
                8,  # Addr shift
                0,  # Data shift
                1 << 23,  # Read flag
                0,  # Write flag
            )
            self._nsync_pll = LMK05318(nsync_lmk_regs_iface, self.log)

        self.set_source(default_source)
        self.set_trig(False, self.DIRECTION_OUTPUT)
        self._init_dac()
Exemplo n.º 15
0
class QSFPModule:
    """
    QSFPModule enables access to the I2C register interface of an QSFP module.

    The class queries the module register using I2C commands according to
    SFF-8486 rev 4.9 specification.
    """
    def __init__(self, gpio_modprs, gpio_modsel, devsymbol, log):
        """
        modprs: Name of the GPIO pin that reports module presence
        modsel: Name of the GPIO pin that controls ModSel of QSFP module
        devsymbol: Symbol name of the device used for I2C communication
        """

        self.log = log.getChild('QSFP')

        # Hold the ModSelL GPIO low for communication over I2C. Because X4xx
        # uses a I2C switch to communicate with the QSFP modules we can keep
        # ModSelL low all the way long, because each QSFP module has
        # its own I2C address (see SFF-8486 rev 4.9, chapter 4.1.1.1).
        self.modsel = Gpio(gpio_modsel, Gpio.OUTPUT, 0)

        # ModPrs pin read pin MODPRESL from QSFP connector
        self.modprs = Gpio(gpio_modprs, Gpio.INPUT, 0)

        # resolve device node name for I2C communication
        devname = i2c_dev.dt_symbol_get_i2c_bus(devsymbol)

        # create an object to access I2C register interface
        self.qsfp_regs = lib.i2c.make_i2cdev_regs_iface(
            devname,  # dev node name
            0x50,  # start address according to SFF-8486 rev 4.9 chapter 7.6
            False,  # use 7 bit address schema
            100,  # timeout_ms
            1  # reg_addr_size
        )

    def _peek8(self, address):
        """
        Helper method to read bytes from the I2C register interface.

        This helper returns None in case of failed communication
        (e.g. missing or broken adapter).
        """
        try:
            return self.qsfp_regs.peek8(address)
        except RuntimeError as err:
            self.log.debug("Could not read QSFP register ({})".format(err))
            return None

    def _revision_compliance(self, status):
        """
        Map the revison compliance status byte to a human readable string
        according to SFF-8636 rev 2.9 table 6-3
        """
        assert isinstance(status, int)
        assert 0 <= status <= 255
        if status > 0x08:
            return "Reserved"
        return QSFP_REVISION_COMPLIANCE[status]

    def is_available(self):
        """
        Checks whether QSFP adapter is available by checking modprs pin
        """
        return self.modprs.get() == 0  #modprs is active low

    def enable_i2c(self, enable):
        """
        Enable or Disable I2C communication with QSFP module. Use with
        care. Because X4xx uses an I2C switch to address the QSFP ports
        there is no need to drive the modsel high (inactive). Disabled
        I2C communication leads to unwanted result when query module
        state even if the module reports availability.
        """
        self.modsel.set("0" if enable else "1")  #modsel is active low

    def adapter_id(self):
        """
        Returns QSFP adapter ID as a byte (None if not present)
        """
        return self._peek8(0)

    def adapter_id_name(self):
        """
        Maps QSFP adapter ID to a human readable string according
        to SFF-8436 rev 4.9 table 30
        """
        adapter_id = self.adapter_id()
        if adapter_id is None:
            return adapter_id
        assert isinstance(adapter_id, int)
        assert 0 <= adapter_id <= 255
        if adapter_id > 0x7F:
            return "Vendor Specific"
        if adapter_id > 0x16:
            return "Reserved"
        return QSFP_IDENTIFIERS[adapter_id]

    def status(self):
        """
        Return the 2 byte QSFP adapter status according to SFF-8636
        rev 2.9 table 6-2
        """
        compliance = self._peek8(1)
        status = self._peek8(2)
        if compliance is None or status is None:
            return None
        assert isinstance(compliance, int)
        assert isinstance(status, int)
        return (compliance, status)

    def decoded_status(self):
        """
        Decode the 2 status bytes of the QSFP adapter into a tuple
        of human readable strings. See SFF-8436 rev 4.9 table 17
        """
        status = self.status()
        if not status:
            return None
        return (self._revision_compliance(status[0]),
                "Flat mem" if status[1] & 0b100 else "Paged mem",
                "IntL asserted" if status[1] & 0b010 else "IntL not asserted",
                "Data not ready" if status[1] & 0b001 else "Data ready")

    def vendor_name(self):
        """
        Return vendor name according to SFF-8436 rev 4.9 chapter 7.6.2.14
        """
        content = [self._peek8(i) for i in range(148, 163)]

        if all(content):  # list must not contain any None values
            # convert ASCII codes to string and strip whitespaces at the end
            return "".join([chr(i) for i in content]).rstrip()

        return None

    def connector_type(self):
        """
        Return connector type according to SFF-8029 rev 3.2 table 4-3
        """
        ctype = self._peek8(130)
        if ctype is None:
            return None
        assert isinstance(ctype, int)
        assert 0 <= ctype <= 255

        if (0x0D < ctype < 0x20) or (0x24 < ctype < 0x80):
            return "Reserved"
        if ctype > 0x7F:
            return "Vendor Specific"
        return QSFP_CONNECTOR_TYPE[ctype]

    def info(self):
        """
        Human readable string of important QSFP module information
        """
        if self.is_available():
            status = self.decoded_status()
            return "Vendor name:    {}\n" \
                   "id:             {}\n" \
                   "Connector type: {}\n" \
                   "Compliance:     {}\n" \
                   "Status:         {}".format(
                       self.vendor_name(), self.adapter_id_name(),
                       self.connector_type(), status[0], status[1:])

        return "No module detected"
Exemplo n.º 16
0
class LMK03328X4xx(LMK03328):
    """
    X4xx-specific subclass of the Reference Clock PLL LMK03328 controls.
    """
    def __init__(self, pll_regs_iface, log=None):
        LMK03328.__init__(self, pll_regs_iface, log)

        self._pll_status_0 = Gpio('REFERENCE-CLOCK-PLL-STATUS-0', Gpio.INPUT)
        self._pll_status_1 = Gpio('REFERENCE-CLOCK-PLL-STATUS-1', Gpio.INPUT)
        self._pll_pwrdown_n = Gpio('REFERENCE-CLOCK-PLL-PWRDOWN', Gpio.OUTPUT,
                                   1)

        self._reference_rates = None

    @property
    def reference_rates(self):
        """
        Gets a list of reference source rates indexed by [primary, secondary]
        """
        return self._reference_rates

    @reference_rates.setter
    def reference_rates(self, reference_rates):
        """
        Sets a list of reference source rates indexed by [primary, secondary]
        """
        assert len(reference_rates) == 2, 'Invalid number of reference rates'
        self._reference_rates = reference_rates

    def init(self):
        """
        Perform a soft reset and verify chip ID
        """
        # Clear hard reset
        self.reset(False, hard=True)

        # Enable sync mute
        self.poke8(0x0C, 0xC8)

        # Trigger soft reset
        self.reset(True, hard=False)
        self.reset(False, hard=False)

        if not self.verify_chip_id():
            raise Exception("unable to locate LMK03328!")

    def reset(self, value=True, hard=False):
        """
        Perform a hard reset from the GPIO pins or a soft reset from the LMK register
        """
        if hard:
            # The powerdown pin is active low
            self._pll_pwrdown_n.set(not value)
        else:
            self.soft_reset(value)

    def get_status(self):
        """
        Returns PLL lock and outgoing status indicators for the LMK03328
        """
        status_indicator_0 = self._pll_status_0.get()
        status_indicator_1 = self._pll_status_1.get()
        status_indicator = (status_indicator_1 << 1) | status_indicator_0
        return {
            'PLL1 lock': self.check_pll_locked(1),
            'PLL2 lock': self.check_pll_locked(2),
            'status indicator': status_indicator
        }

    def config(self,
               ref_select=2,
               brc_rate=25e6,
               usr_clk_rate=156.25e6,
               brc_select='PLL'):
        """
        Configure the RPLL to generate the desired MGT Reference clock sources
        using the specified internal BRC.
        ref_select - the reference source to use (primary=1, secondary=2)
        brc_rate   - specifies the desired rate of the output BRC
        usr_clk_rate - specifies the desired rate to configure PLL1
        brc_select - specifies whether the BRC out should be from the PLL ('PLL') or
                     a passthrough of the primary reference signal ('bypass')
        """
        def calculate_out7_mux(brc_select):
            """
            Returns the OUT7 Mux select register value based on the chosen BRC source.
            Note that OUT7 is wired to the InternalRef clock which is used as the default
            reference clock source.
            """
            return {'bypass': 0x98, 'PLL': 0x58}[brc_select]

        def calculate_out7_div(brc_rate):
            """ Returns the OUT7 Divider register value based on the chosen BRC rate """
            return {25e6: 0x31, 125e6: 0x09}[brc_rate]

        def calculate_pll2_input_select(ref_select):
            """ Returns the reference mux register value based on which reference should be used """
            assert ref_select in (1, 2)
            return {1: 0x5B, 2: 0x7F}[ref_select]

        def calculate_pll2_n_div(ref_rate):
            """ Returns the PLL2 N div value based on the rate of the reference source """
            return {25e6: 0x00C8, 100e6: 0x0032}[ref_rate]

        def calculate_pll1_post_div(usr_clk_rate):
            """ Returns the PLL1 post div value based the usr_clk_rate """
            assert usr_clk_rate in (156.25e6, 125e6, 312.5e6, 161.1328125e6)
            return {
                156.25e6: 0x0E,
                125e6: 0x0E,
                312.5e6: 0x0E,
                161.1328125e6: 0x1E,
            }[usr_clk_rate]

        def calculate_pll1_n_div(usr_clk_rate):
            """ Returns the PLL1 N div value based the usr_clk_rate """
            assert usr_clk_rate in (156.25e6, 125e6, 312.5e6, 161.1328125e6)
            return {
                156.25e6: 0x0339,
                125e6: 0x0339,
                312.5e6: 0x0032,
                161.1328125e6: 0x0339,
            }[usr_clk_rate]

        def calculate_pll1_m_div(usr_clk_rate):
            """ Returns the PLL1 M div value based the usr_clk_rate """
            assert usr_clk_rate in (156.25e6, 125e6, 312.5e6, 161.1328125e6)
            return {
                156.25e6: 0x0F,
                125e6: 0x0F,
                312.5e6: 0x00,
                161.1328125e6: 0x0F,
            }[usr_clk_rate]

        def calculate_pll_select(usr_clk_rate):
            """ Returns the PLL selection based on the usr_clk_rate """
            assert usr_clk_rate in (156.25e6, 125e6)
            return {156.25e6: 2, 125e6: 2}[usr_clk_rate]

        def get_register_from_pll(pll_selection, addr):
            """ Returns the value to write to a specified register given
            the desired PLL selection. """
            assert pll_selection in (1, 2)
            assert addr in (0x22, 0x29)
            return {
                0x22: [0x00, 0x80],
                0x29: [0x10, 0x50]
            }[addr][pll_selection - 1]

        def calculate_out_div(usr_clk_rate):
            """ Returns the output divider for a given clock rate """
            assert usr_clk_rate in (156.25e6, 125e6)
            return {156.25e6: 0x07, 125e6: 0x09}[usr_clk_rate]

        if self._reference_rates is None:
            self.log.error(
                'Cannot config reference PLL until the reference sources are set.'
            )
            raise RuntimeError(
                'Cannot config reference PLL until the reference sources are set.'
            )
        if ref_select not in (1, 2):
            raise RuntimeError(
                'Selected reference source {} is invalid'.format(ref_select))
        ref_rate = self._reference_rates[ref_select - 1]
        if ref_rate not in (25e6, 100e6):
            raise RuntimeError(
                'Selected reference rate {} Hz is invalid'.format(ref_rate))
        if brc_select not in ('bypass', 'PLL'):
            raise RuntimeError(
                'Selected BRC source {} is invalid'.format(brc_select))
        if brc_rate not in (25e6, 125e6):
            raise RuntimeError(
                'Selected BRC rate {} Hz is invalid'.format(brc_rate))
        if brc_select == 'bypass':
            # 'bypass' sends the primary reference directly to out7
            actual_brc_rate = self._reference_rates[0]
            if actual_brc_rate != brc_rate:
                self.log.error(
                    'The specified BRC rate does not match the actual '
                    'rate of the primary ref in bypass mode.')
                raise RuntimeError(
                    'The specified BRC rate does not match the actual '
                    'rate of the primary ref in bypass mode.')
        if usr_clk_rate not in (156.25e6, 125e6):
            raise RuntimeError(
                'Selected RPLL clock rate {} Hz is not supported'.format(
                    usr_clk_rate))

        self.log.trace(
            "Configuring RPLL to ref:{}, brc:{} {} Hz, clock rate:{}".format(
                ref_select, brc_select, brc_rate, usr_clk_rate))
        # Config
        pll2_input_mux = calculate_pll2_input_select(ref_select)
        pll2_n_div = calculate_pll2_n_div(ref_rate)
        pll1_post_div = calculate_pll1_post_div(usr_clk_rate)
        pll1_n_div = calculate_pll1_n_div(usr_clk_rate)
        pll1_m_div = calculate_pll1_m_div(usr_clk_rate)
        pll_select = calculate_pll_select(usr_clk_rate)
        out_div = calculate_out_div(usr_clk_rate)
        out7_mux = calculate_out7_mux(brc_select)
        out7_div = calculate_out7_div(brc_rate)

        self.pokes8((
            (0x0C, 0xDF),
            (0x0D, 0x00),
            (0x0E, 0x00),
            (0x0F, 0x00),
            (0x10, 0x00),
            (0x11, 0x00),
            (0x12, 0x00),
            (0x13, 0x00),
            (0x14, 0xFF),
            (0x15, 0xFF),
            (0x16, 0xFF),
            (0x17, 0x00
             ),  # Status 0/1 mute control is disabled. Both status always ON.
            (0x18, 0x00),
            (0x19, 0x55),
            (0x1A, 0x00),
            (0x1B, 0x58),
            (0x1C, 0x58),
            (0x1D, 0x8F),
            (0x1E, 0x01),
            (0x1F, 0x00),
            (0x20, 0x00),
            (0x21, 0x00),
            (0x22, get_register_from_pll(pll_select, 0x22)),
            (0x23, 0x20),
            (0x24, out_div),
            (0x25, 0xD0),
            (0x26, 0x00),
            (0x27, 0xD0),
            (0x28, 0x09),
            (0x29, get_register_from_pll(pll_select, 0x29)),
            (0x2A, out_div),
            (0x2B, out7_mux),
            (0x2C, out7_div),
            (
                0x2D, 0x0A
            ),  # Disable all PLL divider status outputs. Both status pins are set to normal operation.
            (0x2E, 0x00),  # Disable all PLL divider status outputs.
            (0x2F, 0x00),  # Disable all PLL divider status outputs.
            (0x30, 0xFF),  # Hidden register. Value from TICS software.
            (0x31, 0x0A),  # set both status slew rate to slow (2.1 ns)
            (0x32, pll2_input_mux),
            (0x33, 0x03),
            (0x34, 0x00),
            (0x35, pll1_m_div),
            (0x36, 0x00),
            (0x37, 0x00),
            (0x38,
             pll1_post_div),  # PLL1 enabled, PLL1 output reset sync enable
            (0x39, 0x08),
            (0x3A, (pll1_n_div & 0x0F00) >> 8),  # PLL1 N Divider [11:8]
            (0x3B, (pll1_n_div & 0x00FF) >> 0),  # PLL1 N Divider [7:0]
            (0x3C, 0x00),
            (0x3D, 0x00),
            (0x3E, 0x00),
            (0x3F, 0x00),
            (0x40, 0x00),
            (0x41, 0x01),
            (0x42, 0x0C),
            (0x43, 0x08),  # PLL1 loop filter R2 = 735 ohms
            (0x44, 0x00),  # PLL1 loop filter C1 = 5 pF
            (0x45, 0x00),  # PLL1 loop filter R3 = 18 ohms
            (0x46, 0x00),  # PLL1 loop filter C3 = 0 pF
            (0x47, 0x0E),
            (0x48, 0x08),
            (0x49, (pll2_n_div & 0x0F00) >> 8),  # PLL2 N Divider [11:8]
            (0x4A, (pll2_n_div & 0x00FF) >> 0),  # PLL2 N Divider [7:0]
            (0x4B, 0x00),
            (0x4C, 0x00),
            (0x4D, 0x00),
            (0x4E, 0x00),
            (0x4F, 0x00),
            (0x50, 0x01),
            (0x51, 0x0C),
            (0x52, 0x08),
            (0x53, 0x00),
            (0x54, 0x00),
            (0x55, 0x00),
            (0x56, 0x08),
            (0x57, 0x00),
            (0x58, 0x00),
            (0x59, 0xDE),
            (0x5A, 0x01),
            (0x5B, 0x18),
            (0x5C, 0x01),
            (0x5D, 0x4B),
            (0x5E, 0x01),
            (0x5F, 0x86),
            (0x60, 0x01),
            (0x61, 0xBE),
            (0x62, 0x01),
            (0x63, 0xFE),
            (0x64, 0x02),
            (0x65, 0x47),
            (0x66, 0x02),
            (0x67, 0x9E),
            (0x68, 0x00),
            (0x69, 0x00),
            (0x6A, 0x05),
            (0x6B, 0x0F),
            (0x6C, 0x0F),
            (0x6D, 0x0F),
            (0x6E, 0x0F),
            (0x6F, 0x00),
            (0x70, 0x00),
            (0x71, 0x00),
            (0x72, 0x00),
            (0x73, 0x08),
            (0x74, 0x19),
            (0x75, 0x00),
            (
                0x76, 0x03
            ),  # PLL1 uses 2nd order loop filter recommended for integer PLL mode.
            (0x77, 0x01),
            (0x78, 0x00),
            (0x79, 0x0F),
            (0x7A, 0x0F),
            (0x7B, 0x0F),
            (0x7C, 0x0F),
            (0x7D, 0x00),
            (0x7E, 0x00),
            (0x7F, 0x00),
            (0x80, 0x00),
            (0x81, 0x08),
            (0x82, 0x19),
            (0x83, 0x00),
            (
                0x84, 0x03
            ),  # PLL2 uses 2nd order loop filter recommended for integer PLL mode.
            (0x85, 0x01),
            (0x86, 0x00),
            (0x87, 0x00),
            (0x88, 0x00),
            (0x89, 0x10),
            (0x8A, 0x00),
            (0x8B, 0x00),
            (0x8C, 0x00),
            (0x8D, 0x00),
            (0x8E, 0x00),
            (0x8F, 0x00),
            (0x90, 0x00),
            (0x91, 0x00),
            (0xA9, 0x40),
            (0xAC, 0x24),
            (0xAD, 0x00),
            (0x0C, 0x5F),  # Initiate VCO calibration
            (0x0C, 0xDF),
        ))
        # wait for VCO calibration to be done and PLL to lock
        sleep(0.5)
        # Reset all output and PLL post dividers
        self.pokes8(((0x0C, 0x9F), (0x0C, 0xDF)))

        # Check for Lock
        if not self.check_pll_locked(1):
            raise RuntimeError('PLL1 did not lock!')
        if not self.check_pll_locked(2):
            raise RuntimeError('PLL2 did not lock!')
        self.log.trace("PLLs are locked!")
Exemplo n.º 17
0
class X4xxDboardIface(DboardIface):
    """
    X4xx DboardIface implementation

    slot_idx - The numerical ID of the daughterboard slot using this
               interface (e.g. 0, 1)
    motherboard - The instance of the motherboard class which implements
                  these controls
    """
    # The device tree label for the bus to the DB's Management EEPROM
    MGMT_EEPROM_DEVICE_LABEL = "e0004000.i2c"

    def __init__(self, slot_idx, motherboard):
        super().__init__(slot_idx, motherboard)
        self.db_cpld_iface = motherboard.ctrlport_regs.get_db_cpld_iface(
            self.slot_idx)
        self._power_enable = Gpio('DB{}_PWR_EN'.format(slot_idx), Gpio.OUTPUT)
        self._power_status = Gpio('DB{}_PWR_STATUS'.format(slot_idx),
                                  Gpio.INPUT)

        self.db_flash = DBFlash(slot_idx, log=self.log)

    def tear_down(self):
        self.log.trace("Tearing down X4xx daughterboard...")
        if self.db_flash:
            self.db_flash.deinit()
        super().tear_down()

    ####################################################################
    # Power
    #   Enable and disable the DB's power rails
    ####################################################################
    def enable_daughterboard(self, enable=True):
        """
        Enable or disable the daughterboard.
        """
        if self.db_flash and not enable:
            self.db_flash.deinit()
        self._power_enable.set(enable)
        self.mboard.cpld_control.enable_daughterboard(self.slot_idx, enable)
        if self.db_flash and enable:
            self.db_flash.init()

    def check_enable_daughterboard(self):
        """
        Return the enable state of the daughterboard.
        """
        return self._power_status.get()

    ####################################################################
    # CTRL SPI
    #   CTRL SPI lines are connected to the CPLD of the DB if it exists
    ####################################################################
    def peek_db_cpld(self, addr):
        return self.db_cpld_iface.peek32(addr)

    def poke_db_cpld(self, addr, val):
        self.db_cpld_iface.poke32(addr, val)

    ####################################################################
    # Management Bus
    ####################################################################

    ####################################################################
    # Calibration SPI
    #   The SPI/QSPI node used to interact with the DB
    #   Calibration EEPROM if it exists
    ####################################################################
    def get_cal_eeprom_spi_node(self, addr):
        """
        Returns the QSPI node leading to the calibration EEPROM of the
        given DB.
        """
        chip_select = self.mboard.qspi_cs.get(self.db_name, None)
        if chip_select is None:
            raise RuntimeError('No QSPI chip select corresponds ' \
                               'with daughterboard {}'.format(self.db_name))
        return self.mboard.qspi_nodes[chip_select]

    ####################################################################
    # MB Control
    #   Some of the MB settings may be controlled from the DB Driver
    ####################################################################
    def _find_converters(self, direction='both', channel='both'):
        """
        Returns a list of (tile_id, block_id, is_dac) tuples describing
        the data converters associated with a given channel and direction.
        """
        return self.mboard.rfdc._find_converters(self.slot_idx, direction,
                                                 channel)

    def set_if_freq(self, freq, direction='both', channel='both'):
        """
        Use the rfdc_ctrl object to set the IF frequency of the ADCs and
        DACs corresponding to the specified channels of the DB.
        By default, all channels and directions will be set.
        Returns True if the IF frequency was successfully set.
        """
        for tile_id, block_id, is_dac in self._find_converters(
                direction, channel):
            if not self.mboard.rfdc._rfdc_ctrl.set_if(tile_id, block_id,
                                                      is_dac, freq):
                return False
        return True

    def get_if_freq(self, direction, channel):
        """
        Gets the IF frequency of the ADC/DAC corresponding
        to the specified channel of the DB.
        """
        converters = self._find_converters(direction, channel)
        assert len(converters) == 1, \
            'Expected a single RFDC associated with {}{}. Instead found {}.' \
            .format(direction, channel, len(converters))
        (tile_id, block_id, is_dac) = converters[0]
        return self.mboard.rfdc._rfdc_ctrl.get_nco_freq(
            tile_id, block_id, is_dac)

    def enable_iq_swap(self, enable, direction, channel):
        """
        Enable or disable swap of I and Q samples from the RFDCs.
        """
        for tile_id, block_id, is_dac in self._find_converters(
                direction, channel):
            self.mboard.rfdc._rfdc_regs.enable_iq_swap(enable, self.slot_idx,
                                                       block_id, is_dac)

    def get_sample_rate(self):
        """
        Gets the sample rate of the RFDCs.
        """
        return self.mboard.get_spll_freq()

    def get_prc_rate(self):
        """
        Returns the rate of the PLL Reference Clock (PRC) which is
        routed to the daughterboard.
        Note: The ref clock will change if the sample clock frequency
        is modified.
        """
        return self.mboard.get_prc_rate()
Exemplo n.º 18
0
    def __init__(self, slot_idx, **kwargs):
        DboardManagerBase.__init__(self, slot_idx, **kwargs)
        self.log = get_logger("X4xxIfTestCCA-{}".format(slot_idx))
        self.log.trace("Initializing X4xxIfTestCCA, slot index %d",
                       self.slot_idx)

        # Interface with MB HW
        if 'db_iface' not in kwargs:
            self.log.error("Required DB Iface was not provided!")
            raise RuntimeError("Required DB Iface was not provided!")
        self.db_iface = kwargs['db_iface']

        # Power on the card
        self.db_iface.enable_daughterboard(enable=True)
        if not self.db_iface.check_enable_daughterboard():
            self.db_iface.enable_daughterboard(enable=False)
            self.log.error('IF Test CCA {} power up failed'.format(self.slot_idx))
            raise RuntimeError('IF Test CCA {} power up failed'.format(self.slot_idx))

        # [boolean for stage 1 mux , boolean for stage 2 mux]
        self._adc_mux_settings = {
            "adc0" : [0, 0],
            "adc1" : [1, 1],
            "adc2" : [1, 0],
            "adc3" : [0, 1],
        }

        self._dac_mux_settings = {
            "dac0" : [1, 0],
            "dac1" : [1, 1],
            "dac2" : [0, 0],
            "dac3" : [0, 1],
        }

        # There are 4 possible Tx (DAC) streams that are available to choose
        # to export to the SMA TX port using a 2-stage hardware mux.

        # Choose between 0 and 2 OR 1 and 3
        self.tx_0_2_1_3_mux_ctrl = Gpio("DB{}_TX0_2p_1_3n".format(slot_idx), Gpio.OUTPUT, 0)
        # Choose between 0 OR 2
        self.tx_0_2_mux_ctrl = Gpio("DB{}_TX_MUX_0p_2n".format(slot_idx), Gpio.OUTPUT, 0)
        # Choose between 1 OR 3
        self.tx_1_3_mux_ctrl = Gpio("DB{}_TX_MUX_1p_3n".format(slot_idx), Gpio.OUTPUT, 0)

        # The signal from the SMA RX port can be directed to one of the 4
        # available Rx (ADC) streams using a 2-stage hardware mux.

        # Choose between 0 and 2 OR 1 and 3
        self.rx_0_2_1_3_mux_ctrl = Gpio("DB{}_RX0_2p_1_3n".format(slot_idx), Gpio.OUTPUT, 0)
        # Choose between 0 OR 2
        self.rx_0_2_mux_ctrl = Gpio("DB{}_RX_MUX_0p_2n".format(slot_idx), Gpio.OUTPUT, 0)
        # Choose between 1 OR 3
        self.rx_1_3_mux_ctrl = Gpio("DB{}_RX_MUX_1p_3n".format(slot_idx), Gpio.OUTPUT, 0)

        self._tx_path = ""
        self._rx_path = ""

        # Controls to load the power supplies on the daughterboard. Enabling
        # these will increase the power draw of the daughterboard.
        self.enable_1v8_load = Gpio("DB{}_1V8_LOAD".format(slot_idx), Gpio.OUTPUT, 0)
        self.enable_2v5_load = Gpio("DB{}_2V5_LOAD".format(slot_idx), Gpio.OUTPUT, 0)
        self.enable_3v3_load = Gpio("DB{}_3V3_LOAD".format(slot_idx), Gpio.OUTPUT, 0)
        self.enable_3v3_mcu_load = Gpio("DB{}_3V3_MCU_LOAD".format(slot_idx), Gpio.OUTPUT, 0)
        self.enable_3v7_load = Gpio("DB{}_3V7_LOAD".format(slot_idx), Gpio.OUTPUT, 0)
        self.enable_12v_load = Gpio("DB{}_12V_LOAD".format(slot_idx), Gpio.OUTPUT, 0)

        # Control to choose between DAC output or MB VCM signals as the VCM
        # signal to use on board.
        self.disable_vcm_dac = Gpio("DB{}_VCM_MB_nDAC".format(slot_idx), Gpio.OUTPUT, 0)

        # Control to choose which MB clock to output to the SMA Clock port.
        # Choices are BaseRefClk and PllRefClk
        self.disable_vcm_dac = Gpio("DB{}_REF_CLK_SEL_USR".format(slot_idx), Gpio.OUTPUT, 0)