1#
2# Copyright 2018 Ettus Research, a National Instruments Company
3# Copyright 2019 Ettus Research, a National Instruments Brand
4#
5# SPDX-License-Identifier: GPL-3.0-or-later
6#
7"""
8N3xx peripherals
9"""
10
11from usrp_mpm import lib
12from usrp_mpm.sys_utils.sysfs_gpio import SysFSGPIO, GPIOBank
13from usrp_mpm.sys_utils import i2c_dev
14from usrp_mpm.chips.ds125df410 import DS125DF410
15from usrp_mpm.periph_manager.common import MboardRegsCommon
16
17# Map register values to SFP transport types
18N3XX_SFP_TYPES = {
19    0: "",    # Port not connected
20    1: "1G",
21    2: "10G",
22    3: "A",   # Aurora
23    4: "W"    # White Rabbit
24}
25
26N3XX_FPGA_TYPES_BY_SFP = {
27    ("", ""):       "",
28    ("1G", "10G"):  "HG",
29    ("10G", "10G"): "XG",
30    ("10G", "A"):   "XA",
31    ("A", "A"):     "AA",
32    ("W", "10G"):   "WX",
33}
34
35class TCA6424(object):
36    """
37    Abstraction layer for the port/gpio expander
38    pins_list is  an array of different version of TCA6424 pins map.
39    First element of this array corresponding to revC, second is revD etc...
40    """
41    pins_list = [
42        (
43            'PWREN-CLK-MGT156MHz',
44            'NETCLK-CE',         #revC name: 'PWREN-CLK-WB-CDCM',
45            'NETCLK-RESETn',     #revC name: 'WB-CDCM-RESETn',
46            'NETCLK-PR0',        #revC name: 'WB-CDCM-PR0',
47            'NETCLK-PR1',        #revC name: 'WB-CDCM-PR1',
48            'NETCLK-OD0',        #revC name: 'WB-CDCM-OD0',
49            'NETCLK-OD1',        #revC name: 'WB-CDCM-OD1',
50            'NETCLK-OD2',        #revC name: 'WB-CDCM-OD2',
51            'PWREN-CLK-MAINREF',
52            'CLK-MAINSEL-25MHz', #revC name: 'CLK-MAINREF-SEL1',
53            'CLK-MAINSEL-EX_B',  #revC name: 'CLK-MAINREF-SEL0',
54            '12',
55            'CLK-MAINSEL-GPS',   #revC name: '13',
56            'FPGA-GPIO-EN',
57            'PWREN-CLK-WB-20MHz',
58            'PWREN-CLK-WB-25MHz',
59            'GPS-PHASELOCK',
60            'GPS-nINITSURV',
61            'GPS-nRESET',
62            'GPS-WARMUP',
63            'GPS-SURVEY',
64            'GPS-LOCKOK',
65            'GPS-ALARM',
66            'PWREN-GPS',
67        ),
68        (
69            'NETCLK-PR1',
70            'NETCLK-PR0',
71            'NETCLK-CE',
72            'NETCLK-RESETn',
73            'NETCLK-OD2',
74            'NETCLK-OD1',
75            'NETCLK-OD0',
76            'PWREN-CLK-MGT156MHz',
77            'PWREN-CLK-MAINREF',
78            'CLK-MAINSEL-25MHz',
79            'CLK-MAINSEL-EX_B',
80            '12',
81            'CLK-MAINSEL-GPS',
82            'FPGA-GPIO-EN',
83            'PWREN-CLK-WB-20MHz',
84            'PWREN-CLK-WB-25MHz',
85            'GPS-PHASELOCK',
86            'GPS-nINITSURV',
87            'GPS-nRESET',
88            'GPS-WARMUP',
89            'GPS-SURVEY',
90            'GPS-LOCKOK',
91            'GPS-ALARM',
92            'PWREN-GPS',
93        )]
94
95    def __init__(self, rev):
96        # Default state: Turn on GPS power, take GPS out of reset or
97        # init-survey, turn on 156.25 MHz clock
98        # min Support from revC or rev = 2
99        if rev == 2:
100            self.pins = self.pins_list[0]
101        else:
102            self.pins = self.pins_list[1]
103
104        default_val = 0x860101 if rev == 2 else 0x860780
105        self._gpios = SysFSGPIO({'device/name': 'tca6424', 'device/of_node/name': 'gpio'}, 0xFFF7FF, 0x86F7FF, default_val)
106
107    def set(self, name, value=None):
108        """
109        Assert a pin by name
110        """
111        assert name in self.pins
112        self._gpios.set(self.pins.index(name), value=value)
113
114    def reset(self, name):
115        """
116        Deassert a pin by name
117        """
118        self.set(name, value=0)
119
120    def get(self, name):
121        """
122        Read back a pin by name
123        """
124        assert name in self.pins
125        return self._gpios.get(self.pins.index(name))
126
127
128class FrontpanelGPIO(GPIOBank):
129    """
130    Abstraction layer for the front panel GPIO
131    """
132    EMIO_BASE = 54
133    FP_GPIO_OFFSET = 32 # Bit offset within the ps_gpio_* pins
134
135    def __init__(self, ddr):
136        GPIOBank.__init__(
137            self,
138            {'label': 'zynq_gpio'},
139            self.FP_GPIO_OFFSET + self.EMIO_BASE,
140            0xFFF, # use_mask
141            ddr
142        )
143
144class BackpanelGPIO(GPIOBank):
145    """
146    Abstraction layer for the back panel GPIO
147    """
148    EMIO_BASE = 54
149    BP_GPIO_OFFSET = 45
150    LED_LINK = 0
151    LED_REF = 1
152    LED_GPS = 2
153
154    def __init__(self):
155        GPIOBank.__init__(
156            self,
157            {'label': 'zynq_gpio'},
158            self.BP_GPIO_OFFSET + self.EMIO_BASE,
159            0x7, # use_mask
160            0x7, # ddr
161        )
162
163class MboardRegsControl(MboardRegsCommon):
164    """
165    Control the FPGA Motherboard registers
166    """
167    # pylint: disable=bad-whitespace
168    # Motherboard registers (on top of the ones in MboardRegsCommon)
169    MB_CLOCK_CTRL     = 0x0018
170    MB_XADC_RB        = 0x001C
171    MB_BUS_CLK_RATE   = 0x0020
172    MB_BUS_COUNTER    = 0x0024
173    MB_SFP0_INFO      = 0x0028
174    MB_SFP1_INFO      = 0x002C
175    MB_GPIO_MASTER    = 0x0030
176    MB_GPIO_RADIO_SRC = 0x0034
177
178    # Bitfield locations for the MB_CLOCK_CTRL register.
179    MB_CLOCK_CTRL_PPS_SEL_INT_10 = 0 # pps_sel is one-hot encoded!
180    MB_CLOCK_CTRL_PPS_SEL_INT_25 = 1
181    MB_CLOCK_CTRL_PPS_SEL_EXT    = 2
182    MB_CLOCK_CTRL_PPS_SEL_GPSDO  = 3
183    MB_CLOCK_CTRL_PPS_SEL_SFP0   = 5
184    MB_CLOCK_CTRL_PPS_SEL_SFP1   = 6
185    MB_CLOCK_CTRL_PPS_OUT_EN = 4 # output enabled = 1
186    MB_CLOCK_CTRL_MEAS_CLK_RESET = 12 # set to 1 to reset mmcm, default is 0
187    MB_CLOCK_CTRL_MEAS_CLK_LOCKED = 13 # locked indication for meas_clk mmcm
188    MB_CLOCK_CTRL_DISABLE_REF_CLK = 16 # to disable the ref_clk, write a '1'
189    # pylint: enable=bad-whitespace
190
191    def __init__(self, label, log):
192        MboardRegsCommon.__init__(self, label, log)
193
194    def set_fp_gpio_master(self, value):
195        """set driver for front panel GPIO
196        Arguments:
197            value {unsigned} -- value is a single bit bit mask of 12 pins GPIO
198           0: means the pin is driven by PL
199           1: means the pin is driven by PS
200        """
201        with self.regs:
202            return self.poke32(self.MB_GPIO_MASTER, value)
203
204    def get_fp_gpio_master(self):
205        """get "who" is driving front panel gpio
206           The return value is a bit mask of 12 pins GPIO.
207           0: means the pin is driven by PL
208           1: means the pin is driven by PS
209        """
210        with self.regs:
211            return self.peek32(self.MB_GPIO_MASTER) & 0xfff
212
213    def set_fp_gpio_radio_src(self, value):
214        """set driver for front panel GPIO
215        Arguments:
216            value {unsigned} -- value is 2x12 bits, two bits per GPIO pin
217           00: means the pin is driven by radio 0
218           01: means the pin is driven by radio 1
219           10: means the pin is driven by radio 2
220           11: means the pin is driven by radio 3
221        """
222        with self.regs:
223            return self.poke32(self.MB_GPIO_RADIO_SRC, value)
224
225    def get_fp_gpio_radio_src(self):
226        """get which radio is driving front panel gpio
227           The return value is 2-bit bit mask of 12 pins GPIO.
228           00: means the pin is driven by radio 0
229           01: means the pin is driven by radio 1
230           10: means the pin is driven by radio 2
231           11: means the pin is driven by radio 3
232        """
233        with self.regs:
234            return self.peek32(self.MB_GPIO_RADIO_SRC) & 0xffffff
235
236    def set_time_source(self, time_source, ref_clk_freq):
237        """
238        Set time source
239        """
240        pps_sel_val = 0x0
241        if time_source == 'internal':
242            assert ref_clk_freq in (10e6, 25e6), \
243                "Invalid reference frequency for time source 'internal'. Must " \
244                "be either 10 MHz or 25 MHz. Check clock and time source match."
245            if ref_clk_freq == 10e6:
246                self.log.debug("Setting time source to internal "
247                               "(10 MHz reference)...")
248                pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_INT_10
249            elif ref_clk_freq == 25e6:
250                self.log.debug("Setting time source to internal "
251                               "(25 MHz reference)...")
252                pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_INT_25
253        elif time_source == 'external':
254            self.log.debug("Setting time source to external...")
255            pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_EXT
256        elif time_source == 'gpsdo':
257            self.log.debug("Setting time source to gpsdo...")
258            pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_GPSDO
259        elif time_source == 'sfp0':
260            self.log.debug("Setting time source to sfp0...")
261            pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_SFP0
262        elif time_source == 'sfp1':
263            self.log.debug("Setting time source to sfp1...")
264            pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_SFP1
265        else:
266            raise RuntimeError("Invalid time source: {}".format(time_source))
267
268        with self.regs:
269            reg_val = self.peek32(self.MB_CLOCK_CTRL) & 0xFFFFFF90
270            # prevent glitches by writing a cleared value first, then the final value.
271            self.poke32(self.MB_CLOCK_CTRL, reg_val)
272            reg_val = reg_val | (pps_sel_val & 0x6F)
273            self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val))
274            self.poke32(self.MB_CLOCK_CTRL, reg_val)
275
276    def enable_pps_out(self, enable):
277        """
278        Enables the PPS/Trig output on the back panel
279        """
280        self.log.trace("%s PPS/Trig output!",
281                       "Enabling" if enable else "Disabling")
282        mask = 0xFFFFFFFF ^ (0b1 << self.MB_CLOCK_CTRL_PPS_OUT_EN)
283        with self.regs:
284            # mask the bit to clear it:
285            reg_val = self.peek32(self.MB_CLOCK_CTRL) & mask
286            if enable:
287                # set the bit if desired:
288                reg_val = reg_val | (0b1 << self.MB_CLOCK_CTRL_PPS_OUT_EN)
289            self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val))
290            self.poke32(self.MB_CLOCK_CTRL, reg_val)
291
292    def enable_ref_clk(self, enable):
293        """
294        Enables the reference clock internal to the FPGA
295        """
296        self.log.trace("%s the Reference Clock!",
297                       "Enabling" if enable else "Disabling")
298        mask = 0xFFFFFFFF ^ (0b1 << self.MB_CLOCK_CTRL_DISABLE_REF_CLK)
299        with self.regs:
300            # mask the bit to clear it and therefore enable the clock:
301            reg_val = self.peek32(self.MB_CLOCK_CTRL) & mask
302            if not enable:
303                # set the bit if not enabled (note this is a DISABLE bit when = 1):
304                reg_val = reg_val | (0b1 << self.MB_CLOCK_CTRL_DISABLE_REF_CLK)
305            self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val))
306            self.poke32(self.MB_CLOCK_CTRL, reg_val)
307
308    def reset_meas_clk_mmcm(self, reset=True):
309        """
310        Reset or unreset the MMCM for the measurement clock in the FPGA TDC.
311        """
312        self.log.trace("%s measurement clock MMCM reset...",
313                       "Asserting" if reset else "Clearing")
314        mask = 0xFFFFFFFF ^ (0b1 << self.MB_CLOCK_CTRL_MEAS_CLK_RESET)
315        with self.regs:
316            # mask the bit to clear it
317            reg_val = self.peek32(self.MB_CLOCK_CTRL) & mask
318            if reset:
319                # set the bit if desired
320                reg_val = reg_val | (0b1 << self.MB_CLOCK_CTRL_MEAS_CLK_RESET)
321            self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val))
322            self.poke32(self.MB_CLOCK_CTRL, reg_val)
323
324    def get_meas_clock_mmcm_lock(self):
325        """
326        Check the status of the MMCM for the measurement clock in the FPGA TDC.
327        """
328        mask = 0b1 << self.MB_CLOCK_CTRL_MEAS_CLK_LOCKED
329        with self.regs:
330            reg_val = self.peek32(self.MB_CLOCK_CTRL)
331        locked = (reg_val & mask) > 0
332        if not locked:
333            self.log.warning("Measurement clock MMCM reporting unlocked. "
334                             "MB_CLOCK_CTRL reg: 0x{:08X}".format(reg_val))
335        else:
336            self.log.trace("Measurement clock MMCM locked!")
337        return locked
338
339    def get_fpga_type(self):
340        """
341        Reads the type of the FPGA image currently loaded
342        Returns a string with the type (ie HG, XG, AA, etc.)
343        """
344        with self.regs:
345            sfp0_info_rb = self.peek32(self.MB_SFP0_INFO)
346            sfp1_info_rb = self.peek32(self.MB_SFP1_INFO)
347        # Print the registers values as 32-bit hex values
348        self.log.trace("SFP0 Info: 0x{0:0{1}X}".format(sfp0_info_rb, 8))
349        self.log.trace("SFP1 Info: 0x{0:0{1}X}".format(sfp1_info_rb, 8))
350        sfp0_type = N3XX_SFP_TYPES.get((sfp0_info_rb & 0x0000FF00) >> 8, "")
351        sfp1_type = N3XX_SFP_TYPES.get((sfp1_info_rb & 0x0000FF00) >> 8, "")
352        self.log.trace("SFP types: ({}, {})".format(sfp0_type, sfp1_type))
353        try:
354            return N3XX_FPGA_TYPES_BY_SFP[(sfp0_type, sfp1_type)]
355        except KeyError:
356            self.log.warning("Unrecognized SFP type combination: ({}, {})"
357                             .format(sfp0_type, sfp1_type))
358        return ""
359
360
361class RetimerQSFP(DS125DF410):
362    """
363    Thin wrapper around an I2C device that controls the QSFP retimer
364    """
365    # (deemphasis, swing)
366    DRIVER_PRESETS = {
367        '1m': (0x00, 0x07), '3m': (0x41, 0x06), 'Optical': (0x41, 0x04)
368    }
369
370    def __init__(self, i2c_bus):
371        regs_iface = lib.i2c.make_i2cdev_regs_iface(
372            i2c_bus,
373            0x18,
374            False,
375            100,
376            1
377        )
378        super(RetimerQSFP, self).__init__(regs_iface)
379