diff options
185 files changed, 61559 insertions, 160 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..dfdb8b771 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf diff --git a/host/docs/dboards.dox b/host/docs/dboards.dox index 7dc398f46..8898cfd37 100644 --- a/host/docs/dboards.dox +++ b/host/docs/dboards.dox @@ -467,6 +467,19 @@ Please refer to \ref e31x_dboards. Please refer to \ref n3xx_mg. +\subsection dboards_zbx ZBX XCVR board + +Features: +- Dual channel transceivers +- TX/RX and RX2 antenna ports per channel +- Frequency Range: 1 MHz to 8 GHz +- Relative Gain Range: 0 - 60 dB (RX gain range reduced below 500 MHz) + +The ZBX daughterboard only works with the X410 motherboard. + +More information: +\li \subpage page_zbx + \subsection dboards_clock_rate Daughterboard reference clock The USRP motherboard provides a reference clock to the daughterboards, which diff --git a/host/docs/devices.dox b/host/docs/devices.dox index dbe860382..c7609400d 100644 --- a/host/docs/devices.dox +++ b/host/docs/devices.dox @@ -32,6 +32,7 @@ ## USRP X-Series Devices \li \subpage page_usrp_x3x0 +\li \subpage page_usrp_x4xx ## USRP Legacy Series @@ -49,6 +50,7 @@ unless stated otherwise, they will still work with this version of UHD. \li \subpage page_dboards \li \subpage page_twinrx +\li \subpage page_zbx ## OctoClock diff --git a/host/docs/res/ZBX_simplified_blockdiagram.svg b/host/docs/res/ZBX_simplified_blockdiagram.svg new file mode 100644 index 000000000..53fa51649 --- /dev/null +++ b/host/docs/res/ZBX_simplified_blockdiagram.svg @@ -0,0 +1,14492 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generated by Microsoft Visio, SVG Export X410_RF_BlockDiagram.svg RevB_VnV_Update --> + +<svg + xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16.250069in" + height="8.5370684in" + viewBox="0 0 1170.0049 614.66892" + xml:space="preserve" + class="st43" + version="1.1" + id="svg18142" + sodipodi:docname="ZBX_simplified_blockdiagram.svg" + inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" + style="font-size:12px;overflow:visible;color-interpolation-filters:sRGB;fill:none;fill-rule:evenodd;stroke-linecap:square;stroke-miterlimit:3"><metadata + id="metadata18146"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="3840" + inkscape:window-height="2063" + id="namedview18144" + showgrid="false" + inkscape:zoom="0.7071068" + inkscape:cx="-522.85483" + inkscape:cy="723.81531" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg18142" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" /> + <v:documentProperties + v:langID="1033" + v:viewMarkup="false"> + <v:userDefs> + <v:ud + v:nameU="msvNoAutoConnect" + v:val="VT0(1):26" /> + </v:userDefs> + </v:documentProperties> + + <style + type="text/css" + id="style9098"> + <![CDATA[ + .st1 {stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5} + .st2 {fill:#ffffff;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75} + .st3 {fill:#ffffff;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5} + .st4 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75} + .st5 {fill:#000000;font-family:Calibri;font-size:0.666664em} + .st6 {font-size:1em} + .st7 {fill:none;stroke:#000000;stroke-dasharray:3.5,2.5,0,2.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5} + .st8 {fill:#000000;font-family:Calibri;font-size:0.75em;font-style:italic;font-weight:bold} + .st9 {font-size:1em;text-decoration:underline} + .st10 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.72} + .st11 {fill:#000000;font-family:Arial;font-size:0.666664em} + .st12 {stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.25} + .st13 {marker-end:url(#mrkr13-157);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5} + .st14 {fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:0.25773195876289} + .st15 {fill:#ffffff;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5} + .st16 {fill:#ffffff} + .st17 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5} + .st18 {fill:#000000;font-family:Calibri;font-size:0.499992em} + .st19 {marker-end:url(#mrkr5-252);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5} + .st20 {fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5} + .st21 {fill:#000000;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5} + .st22 {fill:#000000;font-family:Calibri;font-size:0.666664em;font-style:italic} + .st23 {fill:#000000;font-family:Arial;font-size:0.499992em} + .st24 {fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.25} + .st25 {fill:#000000;font-family:Arial;font-size:0.499992em;font-style:italic} + .st26 {font-family:Calibri;font-size:1em} + .st27 {fill:none;stroke:#000000;stroke-dasharray:0.5,1;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5} + .st28 {fill:#000000;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75} + .st29 {fill:none;stroke:#000000;stroke-dasharray:0,1.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5} + .st30 {fill:#000000;font-family:Arial;font-size:1.33333em;font-style:italic;font-weight:bold} + .st31 {fill:#000000;font-family:Arial;font-size:0.666664em;font-style:italic;font-weight:bold} + .st32 {fill:#000000;font-family:Arial;font-size:1.33333em} + .st33 {marker-end:url(#mrkr5-2463);marker-start:url(#mrkr5-2461);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5} + .st34 {fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:0.16556291390728} + .st35 {marker-end:url(#mrkr5-2463);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5} + .st36 {baseline-shift:32.4939%;font-size:0.649878em} + .st37 {stroke:#000000;stroke-dasharray:1.5,1.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5} + .st38 {marker-end:url(#mrkr5-2728);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.25} + .st39 {fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:0.14792899408284} + .st40 {stroke:#000000;stroke-dasharray:3.5,2.5;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5} + .st41 {marker-end:url(#mrkr5-252);marker-start:url(#mrkr5-3283);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.5} + .st42 {fill:#000000;font-family:Arial;font-size:0.333344em;font-style:italic} + .st43 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3} + ]]> + </style> + + <defs + id="Markers"> + <g + id="lend13"> + <path + d="M 3,1 0,0 3,-1 v 2" + style="stroke:none" + id="path9100" + inkscape:connector-curvature="0" /> + </g> + <marker + id="mrkr13-157" + class="st14" + v:arrowType="13" + v:arrowSize="0" + v:setback="11.64" + refX="-11.64" + orient="auto" + markerUnits="strokeWidth" + overflow="visible" + style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1"> + <use + xlink:href="#lend13" + transform="scale(-3.88)" + id="use9103" + x="0" + y="0" + width="100%" + height="100%" /> + </marker> + <g + id="lend5"> + <path + d="M 2,1 0,0 1.98117,-0.993387 C 1.67173,-0.364515 1.67301,0.372641 1.98465,1.00043" + style="stroke:none" + id="path9106" + inkscape:connector-curvature="0" /> + </g> + <marker + id="mrkr5-252" + class="st14" + v:arrowType="5" + v:arrowSize="0" + v:setback="6.79" + refX="-6.79" + orient="auto" + markerUnits="strokeWidth" + overflow="visible" + style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1"> + <use + xlink:href="#lend5" + transform="scale(-3.88)" + id="use9109" + x="0" + y="0" + width="100%" + height="100%" /> + </marker> + <marker + id="mrkr5-2461" + class="st34" + v:arrowType="5" + v:arrowSize="2" + v:setback="9.85" + refX="9.8500004" + orient="auto" + markerUnits="strokeWidth" + overflow="visible" + style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.16556291;stroke-opacity:1"> + <use + xlink:href="#lend5" + transform="scale(6.04)" + id="use9112" + x="0" + y="0" + width="100%" + height="100%" /> + </marker> + <marker + id="mrkr5-2463" + class="st34" + v:arrowType="5" + v:arrowSize="2" + v:setback="10.57" + refX="-10.57" + orient="auto" + markerUnits="strokeWidth" + overflow="visible" + style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.16556291;stroke-opacity:1"> + <use + xlink:href="#lend5" + transform="scale(-6.04)" + id="use9115" + x="0" + y="0" + width="100%" + height="100%" /> + </marker> + <marker + id="mrkr5-2728" + class="st39" + v:arrowType="5" + v:arrowSize="0" + v:setback="10.39" + refX="-10.39" + orient="auto" + markerUnits="strokeWidth" + overflow="visible" + style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.147929;stroke-opacity:1"> + <use + xlink:href="#lend5" + transform="scale(-6.76)" + id="use9118" + x="0" + y="0" + width="100%" + height="100%" /> + </marker> + <marker + id="mrkr5-3283" + class="st14" + v:arrowType="5" + v:arrowSize="0" + v:setback="6.07" + refX="6.0700002" + orient="auto" + markerUnits="strokeWidth" + overflow="visible" + style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1"> + <use + xlink:href="#lend5" + transform="scale(3.88)" + id="use9121" + x="0" + y="0" + width="100%" + height="100%" /> + </marker> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <marker + style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1" + id="mrkr5-252-3" + class="st14" + v:arrowType="5" + v:arrowSize="0" + v:setback="6.79" + refX="-6.79" + orient="auto" + markerUnits="strokeWidth" + overflow="visible"><use + height="100%" + width="100%" + y="0" + x="0" + xlink:href="#lend5" + transform="scale(-3.88)" + id="use36817" /></marker><marker + style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1" + id="mrkr5-252-6" + class="st14" + v:arrowType="5" + v:arrowSize="0" + v:setback="6.79" + refX="-6.79" + orient="auto" + markerUnits="strokeWidth" + overflow="visible"><use + height="100%" + width="100%" + y="0" + x="0" + xlink:href="#lend5" + transform="scale(-3.88)" + id="use9109-4" /></marker><marker + style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1" + id="marker56505" + class="st14" + v:arrowType="5" + v:arrowSize="0" + v:setback="6.79" + refX="-6.79" + orient="auto" + markerUnits="strokeWidth" + overflow="visible"><use + height="100%" + width="100%" + y="0" + x="0" + xlink:href="#lend5" + transform="scale(-3.88)" + id="use56503" /></marker><marker + style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1" + id="marker56509" + class="st14" + v:arrowType="5" + v:arrowSize="0" + v:setback="6.79" + refX="-6.79" + orient="auto" + markerUnits="strokeWidth" + overflow="visible"><use + height="100%" + width="100%" + y="0" + x="0" + xlink:href="#lend5" + transform="scale(-3.88)" + id="use56507" /></marker><marker + style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1" + id="mrkr5-252-9" + class="st14" + v:arrowType="5" + v:arrowSize="0" + v:setback="6.79" + refX="-6.79" + orient="auto" + markerUnits="strokeWidth" + overflow="visible"><use + height="100%" + width="100%" + y="0" + x="0" + xlink:href="#lend5" + transform="scale(-3.88)" + id="use9109-6" /></marker><marker + style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1" + id="marker57020" + class="st14" + v:arrowType="5" + v:arrowSize="0" + v:setback="6.79" + refX="-6.79" + orient="auto" + markerUnits="strokeWidth" + overflow="visible"><use + height="100%" + width="100%" + y="0" + x="0" + xlink:href="#lend5" + transform="scale(-3.88)" + id="use57018" /></marker><marker + style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1" + id="marker57024" + class="st14" + v:arrowType="5" + v:arrowSize="0" + v:setback="6.79" + refX="-6.79" + orient="auto" + markerUnits="strokeWidth" + overflow="visible"><use + height="100%" + width="100%" + y="0" + x="0" + xlink:href="#lend5" + transform="scale(-3.88)" + id="use57022" /></marker><marker + style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1" + id="mrkr5-252-9-0" + class="st14" + v:arrowType="5" + v:arrowSize="0" + v:setback="6.79" + refX="-6.79" + orient="auto" + markerUnits="strokeWidth" + overflow="visible"><use + height="100%" + width="100%" + y="0" + x="0" + xlink:href="#lend5" + transform="scale(-3.88)" + id="use9109-6-5" /></marker><marker + style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1" + id="marker57300" + class="st14" + v:arrowType="5" + v:arrowSize="0" + v:setback="6.79" + refX="-6.79" + orient="auto" + markerUnits="strokeWidth" + overflow="visible"><use + height="100%" + width="100%" + y="0" + x="0" + xlink:href="#lend5" + transform="scale(-3.88)" + id="use57298" /></marker><marker + style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.25773194;stroke-opacity:1" + id="marker57304" + class="st14" + v:arrowType="5" + v:arrowSize="0" + v:setback="6.79" + refX="-6.79" + orient="auto" + markerUnits="strokeWidth" + overflow="visible"><use + height="100%" + width="100%" + y="0" + x="0" + xlink:href="#lend5" + transform="scale(-3.88)" + id="use57302" /></marker></defs> + <path + d="m 479.25,244.04002 h 79.88 v -38.65" + class="st1" + id="path9129" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 479.25,206.45002 h 72 v -12.03" + class="st1" + id="path9134" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 757.57191,211.38094 -0.13676,62.13985" + class="st1" + id="path9139" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 944.52,184.44202 v -20.61 h 10.36" + class="st1" + id="path9144" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 944.544,178.70802 v 17.46 h -19" + class="st1" + id="path9149" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 824.676,196.64402 h 93" + class="st1" + id="path9154" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 955.922,170.03302 v 69.72 h -32.06" + class="st1" + id="path9159" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 823.17,205.64402 v 33.96 h 95.63" + class="st1" + id="path9164" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 14.4818,158.78402 h 29.88 l 16.19,-15.06 -16.19,-12.33 h -29.88 z" + class="st2" + id="path9169" + inkscape:connector-curvature="0" + style="fill:#ffffff;stroke:#000000;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><circle + cx="57.176804" + cy="148.66301" + class="st3" + id="ellipse9174" + r="1.125" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="17.070599" + y="131.78801" + width="24.075001" + height="18" + class="st4" + id="rect9194" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="17.070599" + y="138.38799" + class="st5" + v:langID="1033" + id="text9198" + style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RFSoC<v:newlineChar /><tspan + x="17.070599" + dy="14.400001" + class="st6" + id="tspan9196" + style="font-size:7.99996805px">DAC</tspan></text> + +<rect + x="17.070599" + y="149.50601" + width="24.075001" + height="9.28125" + class="st4" + id="rect9205" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="20.816799" + y="166.382" + width="47.970001" + height="30.6563" + class="st4" + id="rect9214" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 6.4353,508.89062 h 69.88 V 86.390917 h -69.88 z" + class="st7" + id="path9230" + inkscape:connector-curvature="0" + style="fill:none;stroke:#000000;stroke-width:0.40170002;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:2.81190007, 2.00850005, 0, 2.00850005" /><rect + x="12.1492" + y="90.894012" + width="51.837898" + height="17.7188" + class="st4" + id="rect9237" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="12.1492" + y="97.053017" + class="st8" + v:langID="1033" + id="text9245" + style="font-style:italic;font-weight:bold;font-size:9px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList /><tspan + class="st9" + id="tspan9239" + style="font-size:9px;text-decoration: underline;text-decoration-line: underline">X410 Mother</tspan><v:newlineChar /><tspan + x="12.1492" + dy="14.400001" + class="st9" + id="tspan9241" + style="font-size:9px;text-decoration: underline;text-decoration-line: underline"> </tspan><tspan + class="st9" + id="tspan9243" + style="font-size:9px;text-decoration: underline;text-decoration-line: underline">Board</tspan></text> + +<path + d="m 17.8191,448.58002 h 29.88 l 16.19,-15.06 -16.19,-12.33 h -29.88 z" + class="st2" + id="path9250" + inkscape:connector-curvature="0" + style="fill:#ffffff;stroke:#000000;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="20.407799" + y="421.58203" + width="24.075001" + height="18" + class="st4" + id="rect9270" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="20.407799" + y="428.18201" + class="st5" + v:langID="1033" + id="text9274" + style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RFSoC<v:newlineChar /><tspan + x="20.407799" + dy="14.400001" + class="st6" + id="tspan9272" + style="font-size:7.99996805px">ADC</tspan></text> + +<rect + x="20.407799" + y="439.30103" + width="24.075001" + height="9.28125" + class="st4" + id="rect9281" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 129.206,134.45202 a 2.06836,2.15275 0 1 0 0,4.3 2.06836,2.15275 0 1 0 0,4.3 2.0705218,2.155 0 1 0 0,4.31 2.06836,2.15275 0 1 0 0,4.3" + class="st1" + id="path9310" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 129.206,134.45202 h 1.08" + class="st1" + id="path9312" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 129.206,151.66202 h 1.08" + class="st1" + id="path9314" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 128.0839,155.02402 h 4.49 l -2.25,3.37 z" + class="st3" + id="path9328" + inkscape:connector-curvature="0" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 130.328,155.02702 v -3.37" + class="st1" + id="path9333" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 121.724,134.45202 a 2.06836,2.15275 0 1 1 0,4.3 2.06836,2.15275 0 1 1 0,4.3 2.0705218,2.155 0 1 1 0,4.31 2.06836,2.15275 0 1 1 0,4.3" + class="st1" + id="path9341" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 121.724,134.45202 h -1.08" + class="st1" + id="path9343" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 121.724,151.66202 h -1.08" + class="st1" + id="path9345" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="111.782" + y="158.70999" + width="30.7332" + height="19.066601" + class="st10" + id="rect9360" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 116.52538,134.46904 4.11997,-0.0173" + class="st1" + id="path9369" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 114.992,151.66002 h 5.66" + class="st1" + id="path9374" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" + id="rect9383" + class="st10" + height="10.125" + width="31.476" + y="124.08101" + x="110.589" /><path + d="m 171.165,115.68202 h -14.48 v 13.5" + class="st1" + id="path9394" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 129.128,134.45202 h 19.69" + class="st1" + id="path9399" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="156.69" + y="121.269" + width="57.375" + height="19.066601" + class="st10" + id="rect9406" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="154.66701" + y="166.50003" + width="64.125" + height="19.4767" + class="st10" + id="rect9417" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 170.19,151.64402 h -13.21 v -13.5" + class="st1" + id="path9426" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 173.25,115.63202 h 43.31 v 13.51" + class="st1" + id="path9431" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="219.937" + y="85.499992" + width="34.3125" + height="19.066601" + class="st10" + id="rect9438" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><circle + cx="60.439301" + cy="438.30704" + class="st3" + id="ellipse9447" + r="1.125" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 129.128,134.45202 h 5.99" + class="st13" + id="path9452" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path + d="m 224.25,134.41702 h 88.5" + class="st1" + id="path9457" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="125.918" + y="88.701996" + width="34.3125" + height="19.066601" + class="st10" + id="rect9493" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="270.58701" + y="145.23001" + width="25.825701" + height="10.125" + class="st10" + id="rect9504" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + style="fill:#ffffff" + inkscape:connector-curvature="0" + id="path9513" + class="st16" + d="m 297.785,134.59402 a 9.74,9.74 0 0 1 19.48,0 9.74,9.74 0 1 1 -19.48,0 z" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9515" + class="st1" + d="m 314.225,127.90402 -13.39,13.39" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9517" + class="st1" + d="m 300.835,127.90402 13.39,13.39" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9519" + class="st1" + d="m 317.265,134.59402 h 3.9" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9521" + class="st1" + d="m 297.785,134.59402 h -4.32" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9523" + class="st1" + d="m 297.785,134.59402 a 9.74,9.74 0 0 1 19.48,0 9.74,9.74 0 1 1 -19.48,0" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9525" + class="st1" + d="m 307.315,144.33402 v 4.28" /><rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect9532" + class="st17" + height="6.18751" + width="9.16047" + y="131.60687" + x="299.22333" /><text + style="font-size:5.99990416px;font-family:Calibri;fill:#000000" + id="text9534" + v:langID="1033" + class="st18" + y="137.00488" + x="299.22333"><v:paragraph /><v:tabList />IF</text> + +<rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect9541" + class="st17" + height="6.18751" + width="9.16047" + y="131.32562" + x="310.4733" /><text + style="font-size:5.99990416px;font-family:Calibri;fill:#000000" + id="text9543" + v:langID="1033" + class="st18" + y="136.72363" + x="310.4733"><v:paragraph /><v:tabList />RF</text> + +<rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect9550" + class="st17" + height="6.18751" + width="9.16047" + y="137.23187" + x="304.1254" /><text + style="font-size:5.99990416px;font-family:Calibri;fill:#000000" + id="text9552" + v:langID="1033" + class="st18" + y="142.62988" + x="304.1254"><v:paragraph /><v:tabList />LO</text> + +<rect + x="293.035" + y="104.99403" + width="40.317299" + height="19.4767" + class="st10" + id="rect9560" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="324" + y="146.02402" + width="25.825701" + height="10.125" + class="st10" + id="rect9571" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 321.3119,134.59581 90.43974,-0.21364" + class="st1" + id="path9578" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="347.08701" + y="105.73203" + width="30.9128" + height="19.4767" + class="st10" + id="rect9619" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect9630" + class="st15" + height="19.125" + width="9" + y="124.59827" + x="380.25" /><path + d="m 384.755,126.50402 2.92,1.22 -5.85,2.44 5.85,2.43 -5.85,2.44 5.85,2.44 -5.85,2.44 2.93,1.21" + class="st1" + id="path9637" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9646" + class="st1" + d="m 384.75,141.12902 v 4.11" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9651" + class="st1" + d="m 384.75,126.50402 v -3.2" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path9656" + class="st19" + d="m 377.99992,142.42991 12.60034,-16.96197" /><rect + x="378.888" + y="87.573029" + width="40.317299" + height="19.4767" + class="st10" + id="rect9664" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="368.388" + y="107.59601" + width="33.321201" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect9675" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="373.48801" + y="115.06104" + class="st11" + v:langID="1033" + id="text9677" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />DSA 1</text> + +<circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.125" + id="ellipse9684" + class="st3" + cy="137.86302" + cx="-156.69" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.125" + id="ellipse9689" + class="st3" + cy="130.55052" + cx="-156.69" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.125" + id="ellipse9694" + class="st3" + cy="134.48802" + cx="-149.94" + transform="scale(-1,1)" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9699" + class="st1" + d="m 150.7853,133.74368 4.95311,-2.59129" /><path + style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9704" + class="st21" + d="m 155.33052,131.36427 -1.17,2.28 -0.49,-1.41 -0.92,-0.91 z" /><path + d="m 176.625,151.64402 h 39.94 v -13.5" + class="st1" + id="path9710" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.125" + id="ellipse9717" + class="st3" + cy="137.81201" + cx="216.563" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.125" + id="ellipse9722" + class="st3" + cy="130.49951" + cx="216.563" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.125" + id="ellipse9727" + class="st3" + cy="134.43701" + cx="223.313" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9732" + class="st1" + d="m 222.4677,133.69268 -4.95311,-2.59129" /><path + style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9737" + class="st21" + d="m 217.92248,131.31327 1.17,2.28 0.49,-1.41 0.92,-0.91 z" /><path + d="m 732.61,125.59002 v -12.74 h 69.19" + class="st1" + id="path9743" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="M 746.01,197.39402 H 732.7 v -52.24" + class="st1" + id="path9748" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="744.422" + y="166.65503" + width="40.317299" + height="19.4767" + class="st10" + id="rect9755" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + style="fill:#ffffff" + inkscape:connector-curvature="0" + id="path9766" + class="st16" + d="m 748.046,197.38102 a 9.74,9.74 0 0 1 19.48,0 9.74,9.74 0 1 1 -19.48,0 z" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9768" + class="st1" + d="m 764.486,190.69102 -13.39,13.39" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9770" + class="st1" + d="m 751.096,190.69102 13.39,13.39" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9772" + class="st1" + d="m 767.526,197.38102 h 3.9" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9774" + class="st1" + d="m 748.046,197.38102 h -4.32" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9776" + class="st1" + d="m 748.046,197.38102 a 9.74,9.74 0 0 1 19.48,0 9.74,9.74 0 1 1 -19.48,0" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9778" + class="st1" + d="m 757.576,207.12102 v 4.28" /><rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect9785" + class="st17" + height="6.18751" + width="9.16047" + y="194.18713" + x="761.54541" /><text + style="font-size:5.99990416px;font-family:Calibri;fill:#000000" + id="text9787" + v:langID="1033" + class="st18" + y="199.58514" + x="761.54541"><v:paragraph /><v:tabList />IF</text> + +<rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect9794" + class="st17" + height="6.18751" + width="9.16047" + y="193.98163" + x="749.04993" /><text + style="font-size:5.99990416px;font-family:Calibri;fill:#000000" + id="text9796" + v:langID="1033" + class="st18" + y="199.37964" + x="749.04993"><v:paragraph /><v:tabList />RF</text> + +<rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect9803" + class="st17" + height="6.18751" + width="9.16047" + y="200.01888" + x="754.38641" /><text + style="font-size:5.99990416px;font-family:Calibri;fill:#000000" + id="text9805" + v:langID="1033" + class="st18" + y="205.41689" + x="754.38641"><v:paragraph /><v:tabList />LO</text> + +<path + d="m 640.59232,135.10702 78.98,-0.006" + class="st1" + id="path9811" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 646.981,135.10102 h 5.99" + class="st13" + id="path9816" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path + d="m 732.7,154.49002 v 5.99" + class="st13" + id="path9821" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path + d="m 735.422,112.85302 h 5.99" + class="st13" + id="path9826" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><rect + x="850.172" + y="127.321" + width="63" + height="8.7313404" + class="st4" + id="rect9833" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="851.09003" + y="159.34599" + width="46.0709" + height="8.7313404" + class="st4" + id="rect9844" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 771.57259,197.38224 38.15948,-0.19902" + class="st1" + id="path9851" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 823.28,187.64402 v -37.08 h 91.8" + class="st1" + id="path9856" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect9863" + class="st3" + height="17.4627" + width="17.4627" + y="140.73601" + x="852.95898" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9868" + class="st1" + d="m 854.98836,144.77544 8.44,-0.04 4.33,8.7" /><rect + x="859.62903" + y="173.963" + width="63" + height="8.7313404" + class="st4" + id="rect9876" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="834.85498" + y="205.10699" + width="46.0709" + height="8.7313404" + class="st4" + id="rect9887" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="883.38599" + y="206.09399" + width="19.295601" + height="8.7313404" + class="st4" + id="rect9896" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect9905" + class="st3" + height="17.4627" + width="17.4627" + y="187.194" + x="849.159" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9910" + class="st1" + d="m 863.9614,191.23344 -8.44,-0.04 -4.33,8.7" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect9947" + class="st3" + height="17.4627" + width="17.4627" + y="186.519" + x="907.815" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9952" + class="st1" + d="m 909.84436,190.55844 8.44,-0.04 4.33,8.7" /><rect + x="872.72601" + y="218.258" + width="46.0709" + height="8.7313404" + class="st4" + id="rect9960" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="882.53699" + y="250.47102" + width="26.1562" + height="19.4767" + class="st4" + id="rect9971" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="859.5" + y="252.72099" + width="19.295601" + height="8.7313404" + class="st4" + id="rect9982" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect9991" + class="st3" + height="17.4627" + width="17.4627" + y="230.125" + x="882.53699" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path9996" + class="st1" + d="m 897.3394,234.16444 -8.44,-0.04 -4.33,8.7" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path10046" + class="st19" + d="m 969.422,160.22643 h -5.8 l -4.92,-6.87" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse10051" + class="st3" + cy="169.201" + cx="955.79419" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse10056" + class="st3" + cy="163.55043" + cx="955.79419" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse10061" + class="st3" + cy="157.23511" + cx="955.79419" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse10066" + class="st3" + cy="150.5874" + cx="955.79419" /><path + d="m 955.359,149.46702 v -36.59 h -160.31" + class="st1" + id="path10072" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.2711" + id="ellipse10079" + class="st3" + cy="204.56702" + cx="-823.41193" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.2711" + id="ellipse10084" + class="st3" + cy="196.51669" + cx="-823.41193" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.2711" + id="ellipse10089" + class="st3" + cy="188.04262" + cx="-823.41193" + transform="scale(-1,1)" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path10094" + class="st19" + d="m 809.736,197.18357 h 5.8 l 4.92,-6.87" /><rect + x="775.27399" + y="208.16299" + width="19.295601" + height="8.7313404" + class="st4" + id="rect10131" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="903.047" + y="205.91299" + width="46.0709" + height="8.7313404" + class="st4" + id="rect10140" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 809.577,197.09902 -11.53,-7.69 v 15.38 z" + class="st3" + id="path10147" + inkscape:connector-curvature="0" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="823.284" + y="156.02899" + width="34.3125" + height="19.066601" + class="st10" + id="rect10154" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 944.44,151.65202 v 5.44 h 10.36" + class="st1" + id="path10163" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 944.437,152.73002 v -2.16 h -30.37" + class="st1" + id="path10168" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="814.172" + y="102.41299" + width="63" + height="8.7313404" + class="st4" + id="rect10175" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="791.672" + y="163.457" + width="26.537201" + height="21.9375" + class="st4" + id="rect10186" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1011.34,156.99802 h 75.2" + class="st1" + id="path10195" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1055.25,157.09302 -11.53,-7.69 v 15.38 z" + class="st3" + id="path10200" + inkscape:connector-curvature="0" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path10207" + class="st1" + d="m 1138.3944,166.96162 4.3629,4.36285" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path10212" + class="st1" + d="m 1138.3946,166.9616 4.3628,-4.36285" /><path + d="m 1097.8,176.97202 v 80.73" + class="st1" + id="path10218" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1097.8,203.78102 v 5.69" + class="st13" + id="path10223" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path + d="m 1073.75,156.99802 h 6.97" + class="st13" + id="path10228" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path + d="m 1122.42,167.06202 h 6.97" + class="st13" + id="path10233" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path + d="m 1128.08,167.06202 h -9" + class="st13" + id="path10238" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path + d="m 1138.76,167.06102 h -28.72" + class="st1" + id="path10243" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1124.67" + y="175.30901" + width="39.375" + height="32.051601" + class="st10" + id="rect10250" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text + x="1128.67" + y="184.13103" + class="st11" + v:langID="1033" + id="text10256" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList />Tx/Rx<v:newlineChar /><tspan + x="1128.67" + dy="14.400001" + class="st6" + id="tspan10252" + style="font-size:7.99996805px">1 MHz </tspan>–<v:newlineChar /><tspan + x="1128.67" + dy="14.400001" + class="st6" + id="tspan10254" + style="font-size:7.99996805px">8 GHz</tspan></text> + +<path + d="m 1042.55,288.72402 h 63.33 v 126.17" + class="st1" + id="path10261" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1012.22" + y="258.34604" + width="52.3125" + height="31.872601" + class="st10" + id="rect10268" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text + x="1016.22" + y="267.07901" + class="st11" + v:langID="1033" + id="text10274" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList />Cal <tspan + x="1016.22" + dy="14.400001" + class="st6" + id="tspan10270" + style="font-size:7.99996805px">Loopback </tspan><tspan + x="1016.22" + dy="14.400001" + class="st6" + id="tspan10272" + style="font-size:7.99996805px">Path</tspan></text> + +<path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path10281" + class="st19" + d="m 1111.305,167.00843 h -5.8 l -4.92,-6.87" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse10286" + class="st3" + cy="175.983" + cx="1097.6771" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse10291" + class="st3" + cy="170.33244" + cx="1097.6771" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse10296" + class="st3" + cy="164.01711" + cx="1097.6771" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse10301" + class="st3" + cy="157.3694" + cx="1097.6771" /><rect + x="1101.38" + y="130.44403" + width="26.537201" + height="19.4767" + class="st10" + id="rect10309" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="704.81299" + y="86.221024" + width="26.537201" + height="19.4767" + class="st10" + id="rect10320" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1011.38,163.59802 h 9.34 v 13.01" + class="st1" + id="path10329" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1097.51,156.14402 v -41.39 h -5.13" + class="st1" + id="path10334" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1089,170.56602 h 7.42" + class="st1" + id="path10339" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="983.25" + y="88.681023" + width="26.537201" + height="19.4767" + class="st10" + id="rect10346" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1092.38,114.75102 h -82.12 v 36.72" + class="st1" + id="path10355" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1042.55,288.72302 h -32.3 v -118.69" + class="st1" + id="path10360" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1105.88,308.97102 v 5.69" + class="st13" + id="path10365" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path10372" + class="st19" + d="m 719.109,135.11143 h 5.8 l 4.92,-6.87" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse10377" + class="st3" + cy="144.086" + cx="-732.73682" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse10382" + class="st3" + cy="138.43544" + cx="-732.73682" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse10387" + class="st3" + cy="132.1201" + cx="-732.73682" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse10392" + class="st3" + cy="125.47241" + cx="-732.73682" + transform="scale(-1,1)" /><rect + x="738.797" + y="127.62103" + width="20.25" + height="8.6133299" + class="st10" + id="rect10400" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text + x="742.797" + y="133.724" + class="st23" + v:langID="1033" + id="text10402" + style="font-size:5.99990416px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList />NC</text> + +<path + d="m 733.703,132.37002 h 7.42" + class="st1" + id="path10407" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 733.734,138.41302 h 7.42" + class="st1" + id="path10412" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="895.789" + y="161.32899" + width="19.295601" + height="8.7313404" + class="st4" + id="rect10419" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 536.512,280.51502 h -110.7 v -135.98" + class="st1" + id="path10455" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="478.633" + y="144.98999" + width="46.0709" + height="8.7313404" + class="st4" + id="rect10462" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path10471" + class="st19" + d="m 411.625,134.32443 h 5.8 l 4.92,-6.87" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse10476" + class="st3" + cy="143.29901" + cx="-425.25284" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse10481" + class="st3" + cy="137.64844" + cx="-425.25284" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse10486" + class="st3" + cy="131.3331" + cx="-425.25284" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse10491" + class="st3" + cy="124.68541" + cx="-425.25284" + transform="scale(-1,1)" /><rect + x="391.845" + y="137.97102" + width="26.537201" + height="19.4767" + class="st10" + id="rect10499" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 425.34,123.84602 v -13.6 h 14.63" + class="st1" + id="path10508" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="506.84003" + y="76.5" + width="46.0709" + height="8.7313404" + class="st4" + id="rect10515" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 528.332,96.096017 h -75.52 v 4.750003" + class="st1" + id="path10524" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="475.92899" + y="105.75" + width="46.0709" + height="8.7313404" + class="st4" + id="rect10531" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="525.375" + y="108.72099" + width="19.295601" + height="8.7313404" + class="st4" + id="rect10540" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="474.75" + y="117.269" + width="46.0709" + height="8.7313404" + class="st4" + id="rect10549" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="480.42899" + y="155.11499" + width="46.0709" + height="8.7313404" + class="st4" + id="rect10560" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="447.75" + y="190.52299" + width="46.0709" + height="8.7313404" + class="st4" + id="rect10571" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="456.215" + y="226.037" + width="46.0709" + height="8.7313404" + class="st4" + id="rect10582" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 514.831,96.092017 h 91.54 v 3.42" + class="st1" + id="path10591" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect10598" + class="st3" + height="17.4627" + width="17.4627" + y="86.874992" + x="553.50201" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path10603" + class="st1" + d="m 555.53136,90.914437 8.44,-0.04 4.33,8.7" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect10640" + class="st3" + height="17.4627" + width="17.4627" + y="87.840004" + x="488.78497" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path10645" + class="st1" + d="m 503.5874,91.879437 -8.44,-0.04 -4.33,8.700003" /><path + d="m 528.786,136.18602 h -61.35 v -17.34" + class="st1" + id="path10651" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 527.625,136.18202 h 63 v -23.87" + class="st1" + id="path10656" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect10663" + class="st3" + height="17.4627" + width="17.4627" + y="127.28101" + x="487.52698" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path10668" + class="st1" + d="m 490.43745,141.83357 c 0.34,-2.64 0.96,-5.44 1.1,-8.56 0.04,-0.79 0.04,-1.6 0.44,-2.09 0.92,-1.14 3.92,-0.63 4.64,-0.63" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path10673" + class="st1" + d="m 502.0792,141.83357 c -0.34,-2.64 -0.96,-5.44 -1.1,-8.56 -0.04,-0.79 -0.04,-1.6 -0.44,-2.09 -0.92,-1.14 -3.92,-0.63 -4.64,-0.63" /><path + d="m 426.551,131.44102 h 13.5 v 13.77" + class="st1" + id="path10679" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="480.42899" + y="180.72099" + width="46.0709" + height="8.7313404" + class="st4" + id="rect10686" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="458.12701" + y="215.18199" + width="46.0709" + height="8.7313404" + class="st4" + id="rect10695" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="458.12701" + y="253.274" + width="46.0709" + height="8.7313404" + class="st4" + id="rect10704" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 618.75,110.20802 h 10.41 v 14.2" + class="st1" + id="path10711" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 426.289,137.62902 h 8.55 v 13.77" + class="st1" + id="path10716" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 480.938,244.02002 h -46.07 v -93.8" + class="st1" + id="path10721" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 480.375,207.62602 h -40.32 v -65.84" + class="st1" + id="path10726" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 605.25,113.22102 h -7.88 v 31.5" + class="st1" + id="path10731" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 628.313,132.17502 h -15.19 v 20.62" + class="st1" + id="path10736" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 565.875,195.34102 h 53.46 v -37.12" + class="st1" + id="path10741" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 558.562,186.34502 h 54.56 v -34.3" + class="st1" + id="path10746" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 519.75,173.26602 h 77.63 v -29.53" + class="st1" + id="path10751" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect10758" + class="st3" + height="17.4627" + width="17.4627" + y="198.896" + x="466.28699" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path10763" + class="st1" + d="m 469.19745,213.44857 c 0.34,-2.64 0.96,-5.44 1.1,-8.56 0.04,-0.79 0.04,-1.6 0.44,-2.09 0.92,-1.14 3.92,-0.63 4.64,-0.63" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path10768" + class="st1" + d="m 480.8392,213.44857 c -0.34,-2.64 -0.96,-5.44 -1.1,-8.56 -0.04,-0.79 -0.04,-1.6 -0.44,-2.09 -0.92,-1.14 -3.92,-0.63 -4.64,-0.63" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect10776" + class="st3" + height="17.4627" + width="17.4627" + y="235.28999" + x="464.03699" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path10781" + class="st1" + d="m 466.94745,249.84257 c 0.34,-2.64 0.96,-5.44 1.1,-8.56 0.04,-0.79 0.04,-1.6 0.44,-2.09 0.92,-1.14 3.92,-0.63 4.64,-0.63" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path10786" + class="st1" + d="m 478.5892,249.84257 c -0.34,-2.64 -0.96,-5.44 -1.1,-8.56 -0.04,-0.79 -0.04,-1.6 -0.44,-2.09 -0.92,-1.14 -3.92,-0.63 -4.64,-0.63" /><rect + x="634.96301" + y="101.97102" + width="26.537201" + height="19.4767" + class="st10" + id="rect10794" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="525.85199" + y="148.769" + width="19.295601" + height="8.7313404" + class="st4" + id="rect10805" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="528.75" + y="183.23999" + width="19.295601" + height="8.7313404" + class="st4" + id="rect10843" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="506.25" + y="215.18199" + width="19.295601" + height="8.7313404" + class="st4" + id="rect10881" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="506.25" + y="252.99001" + width="19.295601" + height="8.7313404" + class="st4" + id="rect10919" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="919.922" + y="104.52898" + width="11.25" + height="6.6812701" + class="st4" + id="rect10991" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="919.922" + y="109.92999" + class="st18" + v:langID="1033" + id="text10993" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RF 4</text> + +<rect + x="918.797" + y="104.39401" + width="14.625" + height="6.75" + rx="3.375" + ry="3.375" + class="st24" + id="rect10998" + style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect + x="578.25" + y="87.447983" + width="13.5" + height="6.6812701" + class="st4" + id="rect11005" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="578.25" + y="92.848984" + class="st18" + v:langID="1033" + id="text11007" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 1</text> + +<rect + x="577.13507" + y="87.323074" + width="17.123955" + height="6.7298865" + rx="3.9516819" + ry="3.3649433" + class="st24" + id="rect11012" + style="fill:none;stroke:#000000;stroke-width:0.27011338;stroke-linecap:round;stroke-linejoin:round" /><rect + x="573.75" + y="127.94798" + width="13.5" + height="6.6812701" + class="st4" + id="rect11019" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="573.75" + y="133.34898" + class="st18" + v:langID="1033" + id="text11021" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 2</text> + +<rect + x="572.633" + y="127.82103" + width="16.597727" + height="6.7339883" + rx="3.8302445" + ry="3.3669941" + class="st24" + id="rect11026" + style="fill:none;stroke:#000000;stroke-width:0.26601166;stroke-linecap:round;stroke-linejoin:round" /><rect + x="574.875" + y="165.47699" + width="13.5" + height="6.6812701" + class="st4" + id="rect11033" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="574.875" + y="170.87799" + class="st18" + v:langID="1033" + id="text11035" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 3</text> + +<rect + x="573.75952" + y="165.35156" + width="16.992392" + height="6.730906" + rx="3.9213212" + ry="3.365453" + class="st24" + id="rect11040" + style="fill:none;stroke:#000000;stroke-width:0.26909411;stroke-linecap:round;stroke-linejoin:round" /><rect + x="535.5" + y="198.89398" + width="13.5" + height="6.6812701" + class="st4" + id="rect11047" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="535.5" + y="204.29498" + class="st18" + v:langID="1033" + id="text11049" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 4</text> + +<rect + x="534.38251" + y="198.7285" + width="16.466179" + height="6.7350245" + rx="3.7998877" + ry="3.3675122" + class="st24" + id="rect11054" + style="fill:none;stroke:#000000;stroke-width:0.26497579;stroke-linecap:round;stroke-linejoin:round" /><rect + x="536.625" + y="236.38499" + width="13.5" + height="6.6812701" + class="st4" + id="rect11061" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="536.625" + y="241.786" + class="st18" + v:langID="1033" + id="text11063" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 5</text> + +<rect + x="535.50854" + y="236.25853" + width="16.729277" + height="6.7329569" + rx="3.8606024" + ry="3.3664784" + class="st24" + id="rect11068" + style="fill:none;stroke:#000000;stroke-width:0.26704329;stroke-linecap:round;stroke-linejoin:round" /><rect + x="918.797" + y="141.62099" + width="11.25" + height="6.6812701" + class="st4" + id="rect11075" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="918.797" + y="147.02199" + class="st18" + v:langID="1033" + id="text11077" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RF 1</text> + +<rect + x="917.672" + y="141.48601" + width="14.625" + height="6.75" + rx="3.375" + ry="3.375" + class="st24" + id="rect11082" + style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect + x="929.75098" + y="188.06798" + width="11.25" + height="6.6812701" + class="st4" + id="rect11089" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="929.75098" + y="193.46898" + class="st18" + v:langID="1033" + id="text11091" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RF 2</text> + +<rect + x="927.797" + y="188.17001" + width="14.625" + height="6.75" + rx="3.375" + ry="3.375" + class="st24" + id="rect11096" + style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect + x="938.21802" + y="231.84598" + width="11.25" + height="6.6812701" + class="st4" + id="rect11103" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="938.21802" + y="237.24699" + class="st18" + v:langID="1033" + id="text11105" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RF 3</text> + +<rect + x="936.23401" + y="232.08101" + width="14.625" + height="6.75" + rx="3.375" + ry="3.375" + class="st24" + id="rect11110" + style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect + x="703.27399" + y="178.644" + width="19.295601" + height="8.7313404" + class="st4" + id="rect11146" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 693.936,135.09502 -11.53,-7.69 v 15.38 z" + class="st3" + id="path11153" + inkscape:connector-curvature="0" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="704.25" + y="147.23999" + width="19.295601" + height="8.7313404" + class="st4" + id="rect11160" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="672.71503" + y="106.64403" + width="30.9128" + height="19.4767" + class="st10" + id="rect11169" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 114.758,424.67502 a 2.06836,2.15275 0 1 0 0,4.3 2.06836,2.15275 0 1 0 0,4.3 2.0705218,2.155 0 1 0 0,4.31 2.06836,2.15275 0 1 0 0,4.3" + class="st1" + id="path11182" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 114.758,424.67502 h 1.08" + class="st1" + id="path11184" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 114.758,441.88502 h 1.08" + class="st1" + id="path11186" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 113.6359,445.24702 h 4.49 l -2.25,3.37 z" + class="st3" + id="path11200" + inkscape:connector-curvature="0" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 115.88,445.25002 v -3.37" + class="st1" + id="path11205" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 107.276,424.67502 a 2.06836,2.15275 0 1 1 0,4.3 2.06836,2.15275 0 1 1 0,4.3 2.0705218,2.155 0 1 1 0,4.31 2.06836,2.15275 0 1 1 0,4.3" + class="st1" + id="path11213" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 107.276,424.67502 h -1.08" + class="st1" + id="path11215" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 107.276,441.88502 h -1.08" + class="st1" + id="path11217" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="100.959" + y="448.93301" + width="30.7332" + height="19.066601" + class="st10" + id="rect11232" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 102.07739,424.69204 4.11996,-0.0173" + class="st1" + id="path11241" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 100.543,441.88302 h 5.66" + class="st1" + id="path11246" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" + id="rect11255" + class="st10" + height="10.125" + width="31.476" + y="414.17804" + x="94.512001" /><path + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path11260" + class="st12" + d="m 104.8062,414.43315 h 5.06" /><path + d="m 200.639,405.90002 h -14.48 v 13.5" + class="st1" + id="path11266" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 115.83396,424.67489 25.5399,-0.0718" + class="st1" + id="path11271" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="186.164" + y="413.08603" + width="57.375" + height="17.4627" + class="st10" + id="rect11278" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="187.875" + y="458.59705" + width="64.125" + height="19.4767" + class="st10" + id="rect11289" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 210.533,441.85702 h -24.08 v -13.5" + class="st1" + id="path11298" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 209.077,405.86002 h 33.34 v 13.5" + class="st1" + id="path11303" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="240.18799" + y="382.733" + width="34.3125" + height="19.066601" + class="st10" + id="rect11310" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="165.09399" + y="375.634" + width="34.3125" + height="19.066601" + class="st10" + id="rect11321" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.125" + id="ellipse11332" + class="st3" + cy="428.07602" + cx="-186.164" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.125" + id="ellipse11337" + class="st3" + cy="420.76352" + cx="-186.164" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.125" + id="ellipse11342" + class="st3" + cy="424.70102" + cx="-179.414" + transform="scale(-1,1)" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path11347" + class="st1" + d="m 180.2593,423.95668 4.95311,-2.59129" /><path + style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path11352" + class="st21" + d="m 184.80452,421.57727 -1.17,2.28 -0.49,-1.41 -0.92,-0.91 z" /><path + d="m 210.938,441.85702 h 31.48 v -13.5" + class="st1" + id="path11358" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 248.42106,424.456 120.21999,-0.0312" + class="st1" + id="path11363" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path11370" + class="st19" + d="m 1092.375,425.53043 h 5.8 l 4.92,-6.87" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse11375" + class="st3" + cy="434.50504" + cx="-1106.0028" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse11380" + class="st3" + cy="428.85446" + cx="-1106.0028" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse11385" + class="st3" + cy="422.53912" + cx="-1106.0028" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse11390" + class="st3" + cy="415.89142" + cx="-1106.0028" + transform="scale(-1,1)" /><path + d="m 1114.87,341.59602 v 80.93 H 1107" + class="st1" + id="path11396" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect11403" + class="st15" + height="13.7462" + width="6.4687901" + y="438.90686" + x="1108.0601" /><path + d="m 1111.292,440.27602 2.1,0.88 -4.2,1.75 4.2,1.75 -4.2,1.75 4.2,1.75 -4.2,1.76 2.1,0.87" + class="st1" + id="path11410" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path11419" + class="st1" + d="m 1111.294,450.78802 v 2.96" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path11424" + class="st1" + d="m 1111.294,440.27602 v -2.3" /><path + d="m 1112.9116,456.16602 h -3.23 l 1.61,2.43 z" + class="st3" + id="path11431" + inkscape:connector-curvature="0" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1111.2942,456.17002 v -2.43" + class="st1" + id="path11436" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1107,434.40802 h 4.3 v 4.5" + class="st1" + id="path11443" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="M 1133.14,428.75002 H 1107" + class="st1" + id="path11448" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path11455" + class="st1" + d="m 1133.2044,428.65858 4.3629,4.36285" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path11460" + class="st1" + d="m 1133.2046,428.6586 4.3628,-4.36285" /><rect + x="1126.13" + y="438.15002" + width="43.875" + height="34.6203" + class="st10" + id="rect11468" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text + x="1130.13" + y="448.26001" + class="st11" + v:langID="1033" + id="text11474" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList />Rx2 Input<v:newlineChar /><tspan + x="1130.13" + dy="14.400001" + class="st6" + id="tspan11470" + style="font-size:7.99996805px">1 MHz </tspan>–<v:newlineChar /><tspan + x="1130.13" + dy="14.400001" + class="st6" + id="tspan11472" + style="font-size:7.99996805px">8 GHz</tspan></text> + +<path + d="m 1130.63,428.75002 h -9" + class="st13" + id="path11479" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><rect + x="1068.05" + y="444.53302" + width="33.321201" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect11486" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="1073.15" + y="451.99805" + class="st11" + v:langID="1033" + id="text11488" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />DSA 1</text> + +<path + d="m 1092.38,425.53002 h -23.62" + class="st1" + id="path11493" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect11500" + class="st15" + height="19.125" + width="9" + y="416.75027" + x="1077.75" /><path + d="m 1082.255,418.65602 2.92,1.22 -5.85,2.44 5.85,2.43 -5.85,2.44 5.85,2.44 -5.85,2.44 2.93,1.21" + class="st1" + id="path11507" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path11516" + class="st1" + d="m 1082.25,433.28102 v 4.11" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path11521" + class="st1" + d="m 1082.25,418.65602 v -3.2" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path11526" + class="st19" + d="m 1075.4999,434.58191 12.6004,-16.96197" /><rect + x="1069.87" + y="439.11905" + width="30.375" + height="19.4767" + class="st10" + id="rect11534" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 846.543,485.98202 h 93" + class="st1" + id="path11543" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 741.45,414.92802 v -22.53 h 75.46" + class="st1" + id="path11548" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 783.555,486.55002 h -42.01 v -52.24" + class="st1" + id="path11553" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="780.56201" + y="456.18903" + width="40.317299" + height="19.4767" + class="st10" + id="rect11560" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + style="fill:#ffffff" + inkscape:connector-curvature="0" + id="path11571" + class="st16" + d="m 787.18,486.58902 a 9.74,9.74 0 0 1 19.48,0 9.74,9.74 0 1 1 -19.48,0 z" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path11573" + class="st1" + d="m 803.62,479.89902 -13.39,13.39" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path11575" + class="st1" + d="m 790.23,479.89902 13.39,13.39" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path11577" + class="st1" + d="m 806.66,486.58902 h 3.9" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path11579" + class="st1" + d="m 787.18,486.58902 h -4.32" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path11581" + class="st1" + d="m 787.18,486.58902 a 9.74,9.74 0 0 1 19.48,0 9.74,9.74 0 1 1 -19.48,0" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path11583" + class="st1" + d="m 796.71,496.32902 v 4.28" /><rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect11590" + class="st17" + height="6.18751" + width="9.16047" + y="483.39514" + x="800.67938" /><text + style="font-size:5.99990416px;font-family:Calibri;fill:#000000" + id="text11592" + v:langID="1033" + class="st18" + y="488.79315" + x="800.67938"><v:paragraph /><v:tabList />IF</text> + +<rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect11599" + class="st17" + height="6.18751" + width="9.16047" + y="483.18964" + x="788.1839" /><text + style="font-size:5.99990416px;font-family:Calibri;fill:#000000" + id="text11601" + v:langID="1033" + class="st18" + y="488.58765" + x="788.1839"><v:paragraph /><v:tabList />RF</text> + +<rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect11608" + class="st17" + height="6.18751" + width="9.16047" + y="489.2269" + x="793.52039" /><text + style="font-size:5.99990416px;font-family:Calibri;fill:#000000" + id="text11610" + v:langID="1033" + class="st18" + y="494.62491" + x="793.52039"><v:paragraph /><v:tabList />LO</text> + +<path + d="m 733.7628,424.45035 -4.06,-0.004" + class="st13" + id="path11616" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path + d="m 741.547,448.64402 v -5.43" + class="st13" + id="path11621" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><rect + x="874.82599" + y="416.70203" + width="63" + height="8.7313404" + class="st4" + id="rect11628" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="896.59399" + y="448.42801" + width="46.0709" + height="8.7313404" + class="st4" + id="rect11639" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 810.70682,486.59088 21.30989,-0.0689" + class="st1" + id="path11646" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 853.69,472.65802 v -32.76 h 83.08" + class="st1" + id="path11651" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect11658" + class="st3" + height="17.4627" + width="17.4627" + y="430.07401" + x="899.91498" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path11663" + class="st1" + d="m 901.94436,434.11344 8.44,-0.04 4.33,8.7" /><rect + x="881.49597" + y="466.177" + width="63" + height="8.7313404" + class="st4" + id="rect11671" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1055.25,414.86102 v -22.5 H 816.91" + class="st1" + id="path11718" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.2711" + id="ellipse11725" + class="st3" + cy="493.90506" + cx="-845.27887" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.2711" + id="ellipse11730" + class="st3" + cy="485.85471" + cx="-845.27887" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.2711" + id="ellipse11735" + class="st3" + cy="477.38065" + cx="-845.27887" + transform="scale(-1,1)" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path11740" + class="st19" + d="m 831.603,486.52157 h 5.8 l 4.92,-6.87" /><rect + x="797.34399" + y="495.72104" + width="52.3125" + height="21.9375" + class="st10" + id="rect11777" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="768.84497" + y="496.16299" + width="19.295601" + height="8.7313404" + class="st4" + id="rect11796" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="815.625" + y="442.31201" + width="34.3125" + height="19.066601" + class="st10" + id="rect11810" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 936.134,439.90002 h 33.32 v 15.43" + class="st1" + id="path11819" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="838.03998" + y="383.39401" + width="63" + height="8.7313404" + class="st4" + id="rect11826" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="741.453" + y="393.92303" + width="26.537201" + height="19.4767" + class="st10" + id="rect11837" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path11848" + class="st19" + d="m 727.953,424.45043 h 5.8 l 4.92,-6.87" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse11853" + class="st3" + cy="433.42502" + cx="-741.58081" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse11858" + class="st3" + cy="427.77444" + cx="-741.58081" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse11863" + class="st3" + cy="421.45911" + cx="-741.58081" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse11868" + class="st3" + cy="414.81143" + cx="-741.58081" + transform="scale(-1,1)" /><rect + x="1040.77" + y="405.79001" + width="11.25" + height="6.6812701" + class="st4" + id="rect11932" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="1040.77" + y="411.19101" + class="st18" + v:langID="1033" + id="text11934" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RF 3</text> + +<rect + x="1038.52" + y="405.72104" + width="14.625" + height="6.75" + rx="3.375" + ry="3.375" + class="st24" + id="rect11939" + style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect + x="941.83002" + y="431.793" + width="11.25" + height="6.6812701" + class="st4" + id="rect11946" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="941.83002" + y="437.194" + class="st18" + v:langID="1033" + id="text11948" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RF 1</text> + +<rect + x="940.70502" + y="431.65802" + width="14.625" + height="6.75" + rx="3.375" + ry="3.375" + class="st24" + id="rect11953" + style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect + x="946.39099" + y="478.07401" + width="11.25" + height="6.6812701" + class="st4" + id="rect11960" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="946.39099" + y="483.47501" + class="st18" + v:langID="1033" + id="text11962" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RF 2</text> + +<rect + x="944.43701" + y="478.17603" + width="14.625" + height="6.75" + rx="3.375" + ry="3.375" + class="st24" + id="rect11967" + style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1058.25" + y="376.85706" + width="26.537201" + height="19.4767" + class="st10" + id="rect11974" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 798.665,392.39002 h -5.05" + class="st13" + id="path11983" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><path + d="m 731.24918,424.45006 -80.99,-0.0226" + class="st1" + id="path11988" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="183.375" + y="108.06898" + width="13.5" + height="6.6812701" + class="st4" + id="rect12217" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="183.375" + y="113.46999" + class="st18" + v:langID="1033" + id="text12219" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF2 1</text> + +<rect + x="181.13644" + y="107.94547" + width="17.484533" + height="6.7271137" + rx="4.0348921" + ry="3.3635569" + class="st24" + id="rect12224" + style="fill:none;stroke:#000000;stroke-width:0.27288619;stroke-linecap:round;stroke-linejoin:round" /><rect + x="198.563" + y="144.13498" + width="13.5" + height="6.6812701" + class="st4" + id="rect12231" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="198.563" + y="149.53598" + class="st18" + v:langID="1033" + id="text12233" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF2 2</text> + +<rect + x="197.44254" + y="144.00455" + width="15.724568" + height="6.7409463" + rx="3.6287467" + ry="3.3704731" + class="st24" + id="rect12238" + style="fill:none;stroke:#000000;stroke-width:0.25905383;stroke-linecap:round;stroke-linejoin:round" /><rect + x="228.375" + y="408.47699" + width="13.5" + height="6.6812701" + class="st4" + id="rect12245" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="228.375" + y="413.87799" + class="st18" + v:langID="1033" + id="text12247" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF2 1</text> + +<rect + x="227.25" + y="408.34204" + width="14.625" + height="6.75" + rx="3.375" + ry="3.375" + class="st24" + id="rect12252" + style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect + x="225.68301" + y="434.125" + width="13.5" + height="6.6812701" + class="st4" + id="rect12259" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="225.68301" + y="439.526" + class="st18" + v:langID="1033" + id="text12261" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF2 2</text> + +<rect + x="224.55801" + y="433.99002" + width="14.625" + height="6.75" + rx="3.375" + ry="3.375" + class="st24" + id="rect12266" + style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 607.341,443.83902 h 19.37 v -9.59" + class="st1" + id="path12271" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 642.216,413.76702 v -25.24 h -55.5" + class="st1" + id="path12276" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 513.28,413.76902 v -25.24 h 73.44" + class="st1" + id="path12281" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path12288" + class="st19" + d="m 499.5,424.27943 h 5.8 l 4.92,-6.87" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse12293" + class="st3" + cy="433.25403" + cx="-513.12787" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse12298" + class="st3" + cy="427.60345" + cx="-513.12787" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse12303" + class="st3" + cy="421.28812" + cx="-513.12787" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse12308" + class="st3" + cy="414.64041" + cx="-513.12787" + transform="scale(-1,1)" /><rect + x="554.09003" + y="369" + width="46.0709" + height="8.7313404" + class="st4" + id="rect12316" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="622.68701" + y="380.15399" + width="13.5" + height="6.6812701" + class="st4" + id="rect12365" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="622.68701" + y="385.55499" + class="st18" + v:langID="1033" + id="text12367" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 1</text> + +<rect + x="621.56201" + y="380.01904" + width="14.625" + height="6.75" + rx="3.375" + ry="3.375" + class="st24" + id="rect12372" + style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 600.591,443.85002 h -69.63 v -9.59" + class="st1" + id="path12377" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="555.80402" + y="423" + width="46.0709" + height="8.7313404" + class="st4" + id="rect12384" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="611.396" + y="436.198" + width="13.5" + height="6.6812701" + class="st4" + id="rect12395" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="611.396" + y="441.599" + class="st18" + v:langID="1033" + id="text12397" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 2</text> + +<rect + x="610.27557" + y="436.06763" + width="15.742751" + height="6.7407994" + rx="3.6329424" + ry="3.3703997" + class="st24" + id="rect12402" + style="fill:none;stroke:#000000;stroke-width:0.25920072;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path12409" + class="st19" + d="m 655.959,424.42843 h -5.8 l -4.92,-6.87" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse12414" + class="st3" + cy="433.40302" + cx="642.33118" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse12419" + class="st3" + cy="427.75244" + cx="642.33118" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse12424" + class="st3" + cy="421.43713" + cx="642.33118" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse12429" + class="st3" + cy="414.78943" + cx="642.33118" /><rect + x="644.021" + y="379.49405" + width="26.537201" + height="19.4767" + class="st10" + id="rect12437" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + style="fill:#ffffff" + inkscape:connector-curvature="0" + id="path12486" + class="st16" + d="m 373.118,424.42402 a 9.74,9.74 0 0 1 19.48,0 9.74,9.74 0 1 1 -19.48,0 z" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path12488" + class="st1" + d="m 389.558,417.73402 -13.39,13.39" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path12490" + class="st1" + d="m 376.168,417.73402 13.39,13.39" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path12492" + class="st1" + d="m 392.598,424.42402 h 3.9" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path12494" + class="st1" + d="m 373.118,424.42402 h -4.32" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path12496" + class="st1" + d="m 373.118,424.42402 a 9.74,9.74 0 0 1 19.48,0 9.74,9.74 0 1 1 -19.48,0" /><rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect12505" + class="st17" + height="6.18751" + width="9.16047" + y="421.43689" + x="374.5563" /><text + style="font-size:5.99990416px;font-family:Calibri;fill:#000000" + id="text12507" + v:langID="1033" + class="st18" + y="426.8349" + x="374.5563"><v:paragraph /><v:tabList />IF</text> + +<rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect12514" + class="st17" + height="6.18751" + width="9.16047" + y="421.15564" + x="385.8063" /><text + style="font-size:5.99990416px;font-family:Calibri;fill:#000000" + id="text12516" + v:langID="1033" + class="st18" + y="426.55365" + x="385.8063"><v:paragraph /><v:tabList />RF</text> + +<rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect12523" + class="st17" + height="6.18751" + width="9.16047" + y="427.06189" + x="379.4584" /><text + style="font-size:5.99990416px;font-family:Calibri;fill:#000000" + id="text12525" + v:langID="1033" + class="st18" + y="432.4599" + x="379.4584"><v:paragraph /><v:tabList />LO</text> + +<rect + x="365.043" + y="393.69803" + width="40.317299" + height="19.4767" + class="st10" + id="rect12533" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="397.71201" + y="402.34604" + width="25.825701" + height="10.125" + class="st10" + id="rect12544" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="382.5" + y="435.53403" + width="52.3125" + height="21.9375" + class="st10" + id="rect12562" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.125" + id="ellipse12582" + class="st3" + cy="427.51602" + cx="242.75601" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.125" + id="ellipse12587" + class="st3" + cy="420.20352" + cx="242.75601" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.125" + id="ellipse12592" + class="st3" + cy="424.14102" + cx="249.50601" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path12597" + class="st1" + d="m 248.6607,423.39668 -4.95311,-2.5913" /><path + style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path12602" + class="st21" + d="m 244.11548,421.01727 1.17,2.28 0.49,-1.41 0.92,-0.91 z" /><path + d="m 396.64527,424.42565 89.35995,-0.0857" + class="st1" + id="path12608" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 754.54626,361.06691 5.62328,-8.99727" + class="st12" + id="path12642" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1084.1747,361.06691 -5.6232,-8.99727" + class="st12" + id="path12647" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path + d="M 1078.55,352.06702 H 760.17" + class="st12" + id="path12652" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect + x="845.60498" + y="338.39401" + width="69.882797" + height="9.28125" + class="st10" + id="rect12659" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 876.676,351.89402 v -3.94" + class="st12" + id="path12668" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 426.79627,361.1808 5.62327,-8.99727" + class="st12" + id="path12673" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 751.39474,361.25021 -5.62328,-8.99727" + class="st12" + id="path12678" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 745.76648,352.25 -312.97,-0.0694" + class="st12" + id="path12683" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect + x="577.789" + y="335.37677" + width="69.882797" + height="9.28125" + class="st10" + id="rect12690" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 608.859,351.89402 v -3.94" + class="st12" + id="path12699" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><g + id="g57527" + transform="translate(-31.5,-43.499983)"><path + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path12704" + class="st12" + d="m 132.75026,403.47078 5.62328,-8.99727" /><path + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path12709" + class="st12" + d="m 396.63474,403.51989 -5.62328,-8.99727" /><path + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path12714" + class="st12" + d="M 391.024,394.471 H 138.374" /><rect + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" + id="rect12721" + class="st10" + height="9.28125" + width="69.882797" + y="380.97101" + x="230.625" /><path + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path12730" + class="st12" + d="m 261.695,394.471 v -3.94" /></g><g + id="shape2201-1696" + v:mID="2201" + v:groupContext="shape" + v:layerMember="1" + transform="rotate(45,683.9744,1168.4508)"> + <title + id="title12749">Sheet.2201</title> + <v:userDefs> + <v:ud + v:nameU="SubType" + v:val="VT0(0):26" /> + </v:userDefs> + </g><rect + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" + id="rect12768" + class="st4" + height="9.28125" + width="38.8377" + y="435.79703" + x="456.16299" /><rect + x="274.79401" + y="396.05804" + width="30.9128" + height="19.4767" + class="st10" + id="rect12821" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="755.50098" + y="446.32401" + width="19.295601" + height="8.7313404" + class="st4" + id="rect12861" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1119.3" + y="388.58304" + width="26.537201" + height="19.4767" + class="st10" + id="rect12870" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="963" + y="127.49402" + width="40.317299" + height="19.4767" + class="st10" + id="rect13021" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="962.08899" + y="178.85802" + width="33.321201" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect13032" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="967.18903" + y="186.32304" + class="st11" + v:langID="1033" + id="text13034" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />DSA 2</text> + +<rect + x="939.41602" + y="77.994026" + width="26.537201" + height="19.4767" + class="st10" + id="rect13041" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 965.167,160.23802 h 32.71" + class="st1" + id="path13050" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="715.96301" + y="468.737" + width="26.537201" + height="19.066601" + class="st4" + id="rect13120" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="684.34802" + y="166.909" + width="46.0709" + height="8.7313404" + class="st4" + id="rect13131" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="971.44" + y="359.24405" + width="30.375" + height="19.4767" + class="st10" + id="rect13142" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="928.17902" + y="378.72104" + width="46.0709" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect13153" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="932.98901" + y="386.18607" + class="st11" + v:langID="1033" + id="text13155" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />DSA 2 HB</text> + +<rect + style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect13162" + class="st15" + height="19.125" + width="9" + y="382.82727" + x="975.38" /><path + d="m 979.885,384.73302 2.92,1.22 -5.85,2.44 5.85,2.43 -5.85,2.44 5.85,2.44 -5.85,2.44 2.93,1.21" + class="st1" + id="path13169" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path13178" + class="st1" + d="m 979.88,399.35802 v 4.11" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path13183" + class="st1" + d="m 979.88,384.73302 v -3.2" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path13188" + class="st19" + d="m 973.1299,400.6589 12.6004,-16.96197" /><rect + x="419.08701" + y="396.72104" + width="30.9128" + height="19.4767" + class="st10" + id="rect13201" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="666.58698" + y="397.72104" + width="30.9128" + height="19.4767" + class="st10" + id="rect13217" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="741.547" + y="460.21005" + width="30.9128" + height="19.4767" + class="st10" + id="rect13228" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect13239" + class="st15" + height="19.125" + width="9" + y="415.85629" + x="490.422" /><path + d="m 494.927,417.76202 2.92,1.22 -5.85,2.44 5.85,2.43 -5.85,2.44 5.85,2.44 -5.85,2.44 2.93,1.21" + class="st1" + id="path13246" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path13255" + class="st1" + d="m 494.922,432.38702 v 4.11" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path13260" + class="st1" + d="m 494.922,417.76202 v -3.2" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path13265" + class="st19" + d="m 488.17192,433.68791 12.60033,-16.96197" /><rect + x="471.61899" + y="400.50803" + width="38.035599" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect13273" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="476.849" + y="407.97305" + class="st11" + v:langID="1033" + id="text13275" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />DSA 3b</text> + +<rect + x="471.345" + y="380.82004" + width="30.375" + height="19.4767" + class="st10" + id="rect13282" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect13293" + class="st15" + height="19.125" + width="9" + y="415.67029" + x="706.474" /><path + d="m 710.979,417.57602 2.92,1.22 -5.85,2.44 5.85,2.43 -5.85,2.44 5.85,2.44 -5.85,2.44 2.93,1.21" + class="st1" + id="path13300" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path13309" + class="st1" + d="m 710.974,432.20102 v 4.11" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path13314" + class="st1" + d="m 710.974,417.57602 v -3.2" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path13319" + class="st19" + d="m 704.22392,433.50191 12.60033,-16.96197" /><rect + x="693.19" + y="399.81802" + width="38.035599" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect13327" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="698.41998" + y="407.28305" + class="st11" + v:langID="1033" + id="text13329" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />DSA 3a</text> + +<rect + x="697.021" + y="379.49405" + width="30.375" + height="19.4767" + class="st10" + id="rect13336" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="M 178.52575,424.60002 H 149.35743" + class="st13" + id="path13345" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.60871387;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><rect + x="758.59601" + y="268.47104" + width="42.932701" + height="10.125" + class="st10" + id="rect13385" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 382.65,434.22102 v 102.24" + class="st1" + id="path13392" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 796.68,498.73502 v 95.27" + class="st1" + id="path13397" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="412.24399" + y="77.994026" + width="26.537201" + height="19.4767" + class="st10" + id="rect13514" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect13525" + class="st3" + height="17.4627" + width="17.4627" + y="271.77902" + x="453.57401" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path13530" + class="st1" + d="m 456.48445,286.33157 c 0.34,-2.64 0.96,-5.44 1.1,-8.56 0.04,-0.79 0.04,-1.6 0.44,-2.09 0.92,-1.14 3.92,-0.63 4.64,-0.63" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path13535" + class="st1" + d="m 468.1262,286.33157 c -0.34,-2.64 -0.96,-5.44 -1.1,-8.56 -0.04,-0.79 -0.04,-1.6 -0.44,-2.09 -0.92,-1.14 -3.92,-0.63 -4.64,-0.63" /><rect + x="445.5" + y="262.52701" + width="46.0709" + height="8.7313404" + class="st4" + id="rect13543" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="444.965" + y="288.72101" + width="46.0709" + height="8.7313404" + class="st4" + id="rect13554" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path13601" + class="st19" + d="m 439.047,110.24343 h 5.8 l 4.92,-6.87" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse13606" + class="st3" + cy="119.218" + cx="-452.67484" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse13611" + class="st3" + cy="113.56743" + cx="-452.67484" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse13616" + class="st3" + cy="107.25211" + cx="-452.67484" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse13621" + class="st3" + cy="100.60441" + cx="-452.67484" + transform="scale(-1,1)" /><path + d="m 453.938,107.32602 h 13.5 v 13.77" + class="st1" + id="path13627" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect13634" + class="st15" + height="13.7462" + width="6.4687901" + y="125.52184" + x="449.578" /><path + d="m 452.81,126.89102 2.1,0.88 -4.2,1.75 4.2,1.75 -4.2,1.75 4.2,1.75 -4.2,1.76 2.1,0.87" + class="st1" + id="path13641" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path13650" + class="st1" + d="m 452.812,137.40302 v 2.96" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path13655" + class="st1" + d="m 452.812,126.89102 v -2.3" /><path + d="m 454.4296,142.78102 h -3.23 l 1.61,2.43 z" + class="st3" + id="path13662" + inkscape:connector-curvature="0" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 452.8122,142.78502 v -2.43" + class="st1" + id="path13667" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 452.813,124.59202 v -4.39" + class="st1" + id="path13674" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 453.938,113.54302 h 7.31 v 13.77" + class="st1" + id="path13679" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 522.599,173.26302 h -61.35 v -47.03" + class="st1" + id="path13684" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect13691" + class="st3" + height="17.4627" + width="17.4627" + y="164.114" + x="488.25" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path13696" + class="st1" + d="m 491.16045,178.66657 c 0.34,-2.64 0.96,-5.44 1.1,-8.56 0.04,-0.79 0.04,-1.6 0.44,-2.09 0.92,-1.14 3.92,-0.63 4.64,-0.63" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path13701" + class="st1" + d="m 502.8022,178.66657 c -0.34,-2.64 -0.96,-5.44 -1.1,-8.56 -0.04,-0.79 -0.04,-1.6 -0.44,-2.09 -0.92,-1.14 -3.92,-0.63 -4.64,-0.63" /><rect + x="538.875" + y="272.38501" + width="13.5" + height="6.6812701" + class="st4" + id="rect13709" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="538.875" + y="277.78601" + class="st18" + v:langID="1033" + id="text13711" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 6</text> + +<rect + x="537.75854" + y="272.25854" + width="16.729277" + height="6.7329569" + rx="3.8606024" + ry="3.3664784" + class="st24" + id="rect13716" + style="fill:none;stroke:#000000;stroke-width:0.26704329;stroke-linecap:round;stroke-linejoin:round" /><rect + x="547.92902" + y="104.22101" + width="46.0709" + height="8.7313404" + class="st4" + id="rect13723" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path13732" + class="st19" + d="m 619.879,110.17843 h -5.8 l -4.92,-6.87" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse13737" + class="st3" + cy="119.153" + cx="606.25116" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse13742" + class="st3" + cy="113.50244" + cx="606.25116" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse13747" + class="st3" + cy="107.1871" + cx="606.25116" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse13752" + class="st3" + cy="100.53941" + cx="606.25116" /><path + d="m 590.62,112.94002 v -5.91 h 14.63" + class="st1" + id="path13758" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect13765" + class="st15" + height="13.7462" + width="6.4687901" + y="125.72183" + x="603" /><path + d="m 606.232,127.09102 2.1,0.88 -4.2,1.75 4.2,1.75 -4.2,1.75 4.2,1.75 -4.2,1.76 2.1,0.87" + class="st1" + id="path13772" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path13781" + class="st1" + d="m 606.234,137.60302 v 2.96" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path13786" + class="st1" + d="m 606.234,127.09102 v -2.3" /><path + d="m 607.8516,142.98102 h -3.23 l 1.61,2.43 z" + class="st3" + id="path13793" + inkscape:connector-curvature="0" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 606.2342,142.98502 v -2.43" + class="st1" + id="path13798" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 606.234,124.79202 v -4.39" + class="st1" + id="path13805" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 628.643,138.72602 h -9.28 v 20.62" + class="st1" + id="path13810" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path13817" + class="st19" + d="m 642.938,135.10743 h -5.8 l -4.92,-6.87" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse13822" + class="st3" + cy="144.082" + cx="629.31018" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse13827" + class="st3" + cy="138.43144" + cx="629.31018" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse13832" + class="st3" + cy="132.1161" + cx="629.31018" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse13837" + class="st3" + cy="125.4684" + cx="629.31018" /><rect + x="610.21301" + y="74.971024" + width="26.537201" + height="19.4767" + class="st10" + id="rect13845" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 579.375,203.22102 h 49.96 v -57.81" + class="st1" + id="path13854" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 536.08,280.44002 h 34.19 v -68.22" + class="st1" + id="path13859" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 562.388,186.35602 h -11.14 v 10.34" + class="st1" + id="path13864" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 570.263,195.34602 h -11.14 v 10.34" + class="st1" + id="path13869" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 581.513,203.21002 h -11.25 v 10.34" + class="st1" + id="path13874" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="0" + y="0" + width="400.5" + height="23.941799" + class="st10" + id="rect13881" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="817.92902" + y="250.42201" + width="46.0709" + height="15.375" + class="st4" + id="rect13890" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="129.653" + y="106.66301" + width="26.1562" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect13923" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="133.843" + y="114.12804" + class="st11" + v:langID="1033" + id="text13925" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 1</text> + +<rect + x="221.91299" + y="105.34602" + width="26.1562" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect13932" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="226.103" + y="112.81104" + class="st11" + v:langID="1033" + id="text13934" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 2</text> + +<rect + x="394.8349" + y="142.24677" + width="26.1562" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect13941" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="399.0249" + y="149.71179" + class="st11" + v:langID="1033" + id="text13943" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 3</text> + +<rect + x="421.64062" + y="91.472122" + width="26.1562" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect13950" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="425.83063" + y="98.937149" + class="st11" + v:langID="1033" + id="text13952" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 4</text> + +<rect + x="610.172" + y="95.221016" + width="26.1562" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect13959" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="614.362" + y="102.68604" + class="st11" + v:langID="1033" + id="text13961" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 5</text> + +<rect + x="634.922" + y="121.09601" + width="26.1562" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect13968" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="639.112" + y="128.56104" + class="st11" + v:langID="1033" + id="text13970" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 6</text> + +<rect + x="705.00403" + y="106.51001" + width="26.1562" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect13977" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="709.19397" + y="113.97504" + class="st11" + v:langID="1033" + id="text13979" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 7</text> + +<rect + x="825.14203" + y="175.91501" + width="26.1562" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect13986" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="829.33197" + y="183.38004" + class="st11" + v:langID="1033" + id="text13988" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 8</text> + +<rect + x="939.797" + y="98.033012" + width="26.1562" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect13995" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="943.987" + y="105.49803" + class="st11" + v:langID="1033" + id="text13997" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 9</text> + +<rect + x="976.21997" + y="108.72101" + width="32.0625" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect14004" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="981.13" + y="116.18604" + class="st11" + v:langID="1033" + id="text14006" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 10</text> + +<rect + x="1104.47" + y="150.73302" + width="32.0625" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect14013" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="1109.38" + y="158.19804" + class="st11" + v:langID="1033" + id="text14015" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 11</text> + +<rect + x="1119.3" + y="408.23303" + width="26.537201" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect14031" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="1123.6801" + y="415.69806" + class="st11" + v:langID="1033" + id="text14033" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 1</text> + +<rect + x="1058.25" + y="397.84604" + width="26.537201" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect14040" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="1062.63" + y="405.31107" + class="st11" + v:langID="1033" + id="text14042" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 2</text> + +<rect + x="817.21301" + y="462.42603" + width="26.537201" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect14049" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="821.59302" + y="469.89105" + class="st11" + v:langID="1033" + id="text14051" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 3</text> + +<rect + x="644.021" + y="398.97104" + width="26.537201" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect14058" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="648.401" + y="406.43607" + class="st11" + v:langID="1033" + id="text14060" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 5</text> + +<rect + x="489.46301" + y="442.87503" + width="26.537201" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect14067" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="493.84302" + y="450.34006" + class="st11" + v:langID="1033" + id="text14069" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 6</text> + +<rect + x="490.73102" + y="438.24805" + width="26.537201" + height="19.4767" + class="st10" + id="rect14076" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="244.323" + y="402.39102" + width="26.537201" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect14087" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="248.703" + y="409.85605" + class="st11" + v:langID="1033" + id="text14089" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 7</text> + +<rect + x="154.59399" + y="405.57904" + width="26.537201" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect14096" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="158.974" + y="413.04407" + class="st11" + v:langID="1033" + id="text14098" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 8</text> + +<rect + x="765.91498" + y="398.59903" + width="26.537201" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect14114" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="770.29498" + y="406.06406" + class="st11" + v:langID="1033" + id="text14116" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 4</text> + +<rect + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" + id="rect14130" + class="st10" + height="9.28125" + width="13.7812" + y="141.45201" + x="1122.75" /><rect + x="744.60901" + y="120.51901" + width="13.7812" + height="9.28125" + class="st10" + id="rect14145" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" + id="rect14161" + class="st10" + height="9.28125" + width="13.7812" + y="397.42603" + x="1141.66" /><path + d="m 749.41127,56.250203 5.62328,-8.99727" + class="st12" + id="path14256" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1124.8347,56.423203 -5.6232,-8.99727" + class="st12" + id="path14261" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path + d="M 1119.2037,47.42293 755.03374,47.249903" + class="st12" + id="path14266" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect + x="879.51599" + y="33.750008" + width="69.882797" + height="9.28125" + class="st10" + id="rect14273" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 910.587,47.250017 v -3.94" + class="st12" + id="path14282" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 336.53627,56.509206 5.62327,-8.99727" + class="st12" + id="path14287" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 739.28474,56.509203 -5.62328,-8.99727" + class="st12" + id="path14292" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 733.661,47.510017 h -391.5" + class="st12" + id="path14297" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect + x="500.39099" + y="33.750008" + width="69.882797" + height="9.28125" + class="st10" + id="rect14304" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 531.462,47.250017 v -3.94" + class="st12" + id="path14313" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 148.50027,58.500199 5.62327,-8.99727" + class="st12" + id="path14318" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 303.91474,58.500203 -5.62328,-8.99727" + class="st12" + id="path14323" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><path + d="M 298.286,49.500017 H 154.126" + class="st12" + id="path14328" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect + x="173.25" + y="36.000008" + width="69.882797" + height="9.28125" + class="st10" + id="rect14335" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 204.32,49.499917 v -3.94" + class="st12" + id="path14344" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect + x="285" + y="108.511" + width="46.0709" + height="13.6007" + rx="4.5" + ry="4.5" + class="st20" + id="rect14562" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="292.03" + y="117.71199" + class="st11" + v:langID="1033" + id="text14568" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />2<tspan + dy="-2.8199999" + class="st36" + v:baseFontSize="8" + id="tspan14564" + style="font-size:5.19900322px;baseline-shift:32.49390125%">nd</tspan><tspan + dy="1.836" + class="st6" + id="tspan14566" + style="font-size:7.99996805px"> </tspan>Mixer</text> + +<rect + x="740.25" + y="151.37" + width="46.0709" + height="13.6007" + rx="4.5" + ry="4.5" + class="st20" + id="rect14575" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="747.06" + y="160.57098" + class="st11" + v:langID="1033" + id="text14577" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />1st Mixer</text> + +<rect + x="367.92899" + y="374.12003" + width="46.0709" + height="13.6007" + rx="4.5" + ry="4.5" + class="st20" + id="rect14584" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="374.95901" + y="383.32101" + class="st11" + v:langID="1033" + id="text14590" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />2<tspan + dy="-2.8199999" + class="st36" + v:baseFontSize="8" + id="tspan14586" + style="font-size:5.19900322px;baseline-shift:32.49390125%">nd</tspan><tspan + dy="1.836" + class="st6" + id="tspan14588" + style="font-size:7.99996805px"> </tspan>Mixer</text> + +<g + id="g57621" + transform="translate(-33,1.5000172)"><rect + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect14597" + class="st20" + ry="4.5" + rx="4.5" + height="13.6007" + width="46.0709" + y="457.37" + x="802.71503" /><text + style="font-size:7.99996805px;font-family:Arial;fill:#000000" + id="text14599" + v:langID="1033" + class="st11" + y="466.57098" + x="809.52502"><v:paragraph + v:horizAlign="1" /><v:tabList />1st Mixer</text> + +</g><rect + x="337.5" + y="139.5" + width="46.0709" + height="17.4627" + class="st10" + id="rect14606" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="667.07501" + y="141.75002" + width="38.8377" + height="13.5" + class="st10" + id="rect14615" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="732.79602" + y="493.74002" + width="38.035599" + height="8.7313404" + class="st10" + id="rect14624" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 846.81,477.32602 h 6.94 v -7.48" + class="st1" + id="path14631" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="670.21899" + y="433.073" + width="36.5625" + height="8.7313404" + class="st10" + id="rect14638" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text + x="674.21899" + y="439.23401" + class="st23" + v:langID="1033" + id="text14640" + style="font-size:5.99990416px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList />6.3V Bias</text> + +<rect + x="422.43799" + y="436.34702" + width="36.5625" + height="8.7313404" + class="st10" + id="rect14647" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="267.96399" + y="432.211" + width="38.035599" + height="8.7313404" + class="st10" + id="rect14656" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="717.04901" + y="226.49402" + width="30.9128" + height="19.4767" + class="st10" + id="rect14814" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="763.33698" + y="237.24001" + width="38.8377" + height="8.7313404" + class="st10" + id="rect14825" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="342.58701" + y="486.77304" + width="30.9128" + height="19.4767" + class="st10" + id="rect14834" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="759.66901" + y="534.02301" + width="30.9128" + height="19.4767" + class="st10" + id="rect14854" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="771.10199" + y="216.72099" + width="19.295601" + height="8.7313404" + class="st4" + id="rect14874" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path14911" + class="st19" + d="m 996.745,160.23843 h 5.8 l 4.92,-6.87" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse14916" + class="st3" + cy="169.21301" + cx="-1010.3728" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse14921" + class="st3" + cy="163.56244" + cx="-1010.3728" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse14926" + class="st3" + cy="157.2471" + cx="-1010.3728" + transform="scale(-1,1)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse14931" + class="st3" + cy="150.59941" + cx="-1010.3728" + transform="scale(-1,1)" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path14939" + class="st1" + d="m 743.49,138.48202 0.88,-2.1 1.75,4.2 1.75,-4.2 1.75,4.2 1.75,-4.2 1.76,4.2 0.87,-2.1" /><path + d="m 754.002,138.48002 h 2.96" + class="st1" + id="path14948" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 743.49,138.48002 h -2.3" + class="st1" + id="path14953" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path14960" + class="st3" + d="m 759.37966,136.86262 v 3.23 l 2.43,-1.61 z" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path14965" + class="st1" + d="m 759.38366,138.48002 h -2.43" /><rect + x="1053" + y="137.97102" + width="30.9128" + height="19.4767" + class="st10" + id="rect14973" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1043.72" + y="162" + width="38.652802" + height="8.7313404" + class="st10" + id="rect14984" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1077.295,179.56602 v -9 h 13.82" + class="st1" + id="path14991" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1030.03,195.45802 h -9.34 v -19.48" + class="st1" + id="path14996" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1077.29,177.90802 v 17.59 h -50.89" + class="st1" + id="path15001" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1056.37,195.53102 -11.53,-7.69 v 15.38 z" + class="st3" + id="path15006" + inkscape:connector-curvature="0" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1051.04" + y="199.49402" + width="30.9128" + height="19.4767" + class="st10" + id="rect15013" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 961.97692,108.12869 c 0.82176,4.05931 1.96281,7.91626 2.58035,12.23843 0.87824,6.12254 0.72031,13.16646 -1.20816,18.00394 -0.96877,2.41331 -2.37022,4.27591 -3.46678,5.98504 l -0.16883,0.31622" + class="st38" + id="path15022" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-2728)" /><path + d="m 999.6205,118.84507 c -0.409,3.36712 -1.1516,6.62885 -1.3345,10.16814 -0.2638,5.00828 0.5892,10.60318 2.9676,14.14765 1.0795,1.5928 2.4617,2.77404 3.6461,3.85859 l 0.2396,0.27176" + class="st38" + id="path15027" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-2728)" /><rect + x="1032.98" + y="122.22101" + width="36" + height="13.3594" + class="st10" + id="rect15034" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text + x="1036.98" + y="127.10004" + class="st23" + v:langID="1033" + id="text15038" + style="font-size:5.99990416px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList />600 MHz -<v:newlineChar /><tspan + x="1036.98" + dy="14.400001" + class="st6" + id="tspan15036" + style="font-size:5.99990416px">8 GHz</tspan></text> + +<rect + x="1010.25" + y="202.5" + width="42.932701" + height="13.3594" + class="st10" + id="rect15045" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text + x="1014.25" + y="210.979" + class="st23" + v:langID="1033" + id="text15047" + style="font-size:5.99990416px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList />1 - 600 MHz</text> + +<path + d="m 1010.25,223.47102 v 5.69" + class="st13" + id="path15052" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.2711" + id="ellipse15059" + class="st3" + cy="473.01602" + cx="969.79413" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.2711" + id="ellipse15064" + class="st3" + cy="464.96573" + cx="969.79413" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.2711" + id="ellipse15069" + class="st3" + cy="456.49164" + cx="969.79413" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path15074" + class="st19" + d="m 983.47,465.63257 h -5.8 l -4.92,-6.87" /><path + d="m 1109.05,265.51002 h -11.25 v -14.34" + class="st1" + id="path15080" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1105.88,265.51602 h 9 v 76.08" + class="st1" + id="path15085" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1114.87,330.08302 v 5.69" + class="st13" + id="path15090" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr13-157)" /><rect + x="1014.8" + y="370.84604" + width="30.9128" + height="19.4767" + class="st10" + id="rect15102" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1008.21" + y="398.51901" + width="38.035599" + height="8.7313404" + class="st10" + id="rect15113" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="969.19" + y="424.98203" + width="34.3125" + height="19.066601" + class="st10" + id="rect15122" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="970.78003" + y="445.09604" + width="33.321201" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect15133" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="976.32001" + y="452.56107" + class="st11" + v:langID="1033" + id="text15135" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Sw 11</text> + +<rect + x="1040.77" + y="419.22101" + width="11.25" + height="6.6812701" + class="st4" + id="rect15142" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="1040.77" + y="424.62201" + class="st18" + v:langID="1033" + id="text15144" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />NC</text> + +<rect + x="1040.77" + y="424.77701" + width="11.25" + height="6.6812701" + class="st4" + id="rect15151" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="1040.77" + y="430.17801" + class="st18" + v:langID="1033" + id="text15153" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />NC</text> + +<rect + x="749.16498" + y="418.09601" + width="11.25" + height="6.6812701" + class="st4" + id="rect15160" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="749.16498" + y="423.49701" + class="st18" + v:langID="1033" + id="text15162" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />NC</text> + +<rect + x="749.16498" + y="423.79001" + width="11.25" + height="6.6812701" + class="st4" + id="rect15169" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="749.16498" + y="429.19101" + class="st18" + v:langID="1033" + id="text15171" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />NC</text> + +<path + d="m 1055.812,434.88602 v 30.79 h -73.69" + class="st1" + id="path15176" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path15183" + class="st19" + d="m 1069.315,425.37643 h -5.8 l -4.92,-6.87" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse15188" + class="st3" + cy="434.35101" + cx="1055.6871" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse15193" + class="st3" + cy="428.70044" + cx="1055.6871" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse15198" + class="st3" + cy="422.38513" + cx="1055.6871" /><circle + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="0.997159" + id="ellipse15203" + class="st3" + cy="415.73743" + cx="1055.6871" /><rect + x="1020.9399" + y="474.34601" + width="36.5625" + height="13.6007" + class="st10" + id="rect15216" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 960.75,474.34602 v 11.65 h -21.38" + class="st1" + id="path15223" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1071" + y="479.93704" + width="36.5625" + height="22.5345" + class="st4" + id="rect15230" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="1071" + y="487.14102" + class="st22" + v:langID="1033" + id="text15234" + style="font-style:italic;font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />1 MHz –<v:newlineChar /><tspan + x="1071" + dy="14.400001" + class="st6" + id="tspan15232" + style="font-size:7.99996805px">3.0 GHz</tspan></text> + +<path + d="m 1068.7493,486.71635 c -2.7297,-1.37605 -5.5834,-2.4355 -8.3063,-4.08053 -3.8481,-2.31436 -7.4552,-5.80857 -8.6304,-9.47485 -0.4886,-1.5559 -0.5488,-3.14932 -0.6297,-4.56705 l -0.051,-0.35148" + class="st38" + id="path15239" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-2728)" /><path + d="m 1035.0012,133.47829 c -6.2623,1.95231 -7.0483,5.89398 -9.3927,10.14048 -1.5343,2.77441 -3.7435,5.68033 -4.1142,10.42525 v 0.35846" + class="st38" + id="path15244" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-2728)" /><path + d="m 742.577,421.51202 h 4.06" + class="st1" + id="path15249" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 742.577,427.82202 h 4.06" + class="st1" + id="path15254" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1050.21,428.91102 h 4.06" + class="st1" + id="path15259" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1050.21,422.55202 h 4.06" + class="st1" + id="path15264" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 307.31,148.38102 v 78.66" + class="st1" + id="path15269" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 254.2687,227.14565 53.07453,-0.26144" + class="st1" + id="path15323" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.375;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none" /><rect + x="339.75" + y="171.72101" + width="52.3125" + height="21.9375" + class="st10" + id="rect15439" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><g + id="g56342" + transform="translate(-31.5,-22.499983)"><path + style="fill:#ffffff" + inkscape:connector-curvature="0" + id="path15279" + class="st16" + d="m 272.26875,249.54775 a 6.75,6.75 0 0 1 13.5,0 6.75,6.75 0 1 1 -13.5,0 z" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15281" + class="st1" + d="m 279.01875,249.54775 a 2.25,2.25 0 0 0 4.5,0" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15283" + class="st1" + d="m 274.51875,249.54775 a 2.25,2.25 0 1 1 4.5,0" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15285" + class="st1" + d="m 272.26875,249.54775 a 6.75,6.75 0 0 1 13.5,0 6.75,6.75 0 1 1 -13.5,0" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect15290" + class="st3" + height="15.1875" + width="19.620001" + y="241.72276" + x="239.14874" /><rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect15297" + class="st17" + height="9.28125" + width="13.5" + y="244.67676" + x="244.14375" /><text + style="font-size:7.99996805px;font-family:Calibri;fill:#000000" + id="text15299" + v:langID="1033" + class="st5" + y="251.71777" + x="244.14375"><v:paragraph /><v:tabList />PLL</text> + +<path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path15304" + class="st19" + d="m 258.76875,249.54775 h 10.11" /><path + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.5, 1" + inkscape:connector-curvature="0" + id="path15309" + class="st27" + d="m 312.06575,269.73175 h -89.86 v -47.25 h 89.86 z" /><rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect15316" + class="st17" + height="9.28125" + width="58.682701" + y="222.48175" + x="223.71075" /><text + style="font-size:7.99996805px;font-family:Calibri;fill:#000000" + id="text15318" + v:langID="1033" + class="st5" + y="229.52277" + x="223.71075"><v:paragraph /><v:tabList />LMX2572</text> + +<path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15328" + class="st1" + d="m 292.51875,250.02075 v 15.75 h -13.5" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path15333" + class="st19" + d="m 280.70375,265.75575 h -36.56 v -5.38" /><circle + style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.40625" + id="ellipse15338" + class="st21" + cy="249.54773" + cx="292.51901" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path15343" + class="st19" + d="m 218.26875,248.80075 h 17.49" /><rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect15350" + class="st17" + height="18.0126" + width="11.8125" + y="248.91876" + x="223.33176" /><text + style="font-size:7.99996805px;font-family:Calibri;fill:#000000" + id="text15354" + v:langID="1033" + class="st5" + y="255.52179" + x="223.33176"><v:paragraph /><v:tabList />Ref<v:newlineChar /><tspan + style="font-size:7.99996805px" + id="tspan15352" + class="st6" + dy="14.400001" + x="223.33176">In</tspan></text> + +<rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect15381" + class="st17" + height="9.28125" + width="66.375" + y="231.34074" + x="223.89375" /><rect + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" + id="rect15512" + class="st10" + height="12.3166" + width="46.0709" + y="208.15073" + x="236.54575" /><text + style="font-style:italic;font-weight:bold;font-size:7.99996805px;font-family:Arial;fill:#000000" + id="text15516" + v:langID="1033" + class="st31" + y="216.70773" + x="240.54575"><v:paragraph /><v:tabList /><tspan + style="font-size:7.99996805px;text-decoration: underline;text-decoration-line: underline" + id="tspan15514" + class="st9">Tx LO2</tspan></text> + +</g><rect + x="313.58099" + y="188.59001" + width="38.8377" + height="15.75" + class="st10" + id="rect15543" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="253.181" + y="509.24203" + width="46.0709" + height="12.3166" + class="st10" + id="rect15755" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text + x="283.43234" + y="504.01047" + class="st31" + v:langID="1033" + id="text15759" + style="font-style:italic;font-weight:bold;font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList /><tspan + class="st9" + id="tspan15757" + style="font-size:7.99996805px;text-decoration: underline;text-decoration-line: underline">Rx LO2</tspan></text> + +<rect + x="1005.75" + y="435.74405" + width="30.375" + height="19.4767" + class="st10" + id="rect15841" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="974.84003" + y="481.09604" + width="46.0709" + height="10.125" + rx="4.5" + ry="4.5" + class="st20" + id="rect15852" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="980.31" + y="488.56107" + class="st11" + v:langID="1033" + id="text15854" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />DSA 2 LB</text> + +<rect + style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect15861" + class="st15" + height="19.125" + width="9" + y="457.57727" + x="1003.63" /><path + d="m 1008.135,459.48302 2.92,1.22 -5.85,2.44 5.85,2.43 -5.85,2.44 5.85,2.44 -5.85,2.44 2.93,1.21" + class="st1" + id="path15868" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15877" + class="st1" + d="m 1008.13,474.10802 v 4.11" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15882" + class="st1" + d="m 1008.13,459.48302 v -3.2" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252)" + inkscape:connector-curvature="0" + id="path15887" + class="st19" + d="m 1001.3799,475.40891 12.6004,-16.96198" /><rect + x="981.67999" + y="405.15802" + width="39.819099" + height="10.125" + class="st10" + id="rect15895" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text + x="985.67999" + y="412.62305" + class="st11" + v:langID="1033" + id="text15897" + style="font-size:7.99996805px;font-family:Arial;fill:#000000"><v:paragraph /><v:tabList />Ganged</text> + +<path + d="M 995.62,414.72002 H 1008 v 38.25" + class="st40" + id="path15902" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:3.5, 2.5" /><path + d="m 994.34,414.72102 h -14.62 v -10.05" + class="st40" + id="path15907" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:3.5, 2.5" /><rect + x="83.836998" + y="157.09601" + width="25.825701" + height="10.125" + class="st10" + id="rect15914" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15923" + class="st1" + d="m 87.75,139.30222 h 22.88 v -2.85" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15928" + class="st1" + d="m 116.4375,134.46102 h -5.8 v 3.82" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15933" + class="st1" + d="m 87.4144,148.60102 h 22.88 v 2.85" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15938" + class="st1" + d="m 114.795,151.67877 h -4.49 v -1.91" /><path + d="m 82.97196,137.41102 v 3.82" + class="st1" + id="path15948" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 84.88196,141.23102 a 2.39003,2.39003 0 0 1 0,-3.82" + class="st1" + id="path15950" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="M 87.75,139.32304 H 83.93" + class="st1" + id="path15955" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="M 82.97,139.32304 H 80.1" + class="st1" + id="path15957" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 82.97196,146.68102 v 3.82" + class="st1" + id="path15968" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 84.88196,150.50102 a 2.39003,2.39003 0 0 1 0,-3.82" + class="st1" + id="path15970" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="M 87.75,148.59304 H 83.93" + class="st1" + id="path15975" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="M 82.97,148.59304 H 80.1" + class="st1" + id="path15977" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15983" + class="st1" + d="m 55.125,139.32102 h 25.58" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15988" + class="st1" + d="m 58.30105,148.62154 h 22.14" /><g + id="shape2816-3202" + v:mID="2816" + v:groupContext="shape" + v:layerMember="1" + transform="rotate(45,833.77972,574.24788)"> + <title + id="title16005">Sheet.2816</title> + <v:userDefs> + <v:ud + v:nameU="SubType" + v:val="VT0(0):26" /> + </v:userDefs> + </g><path + d="m 73.356,429.43902 h 25.83 v -2.85" + class="st1" + id="path16023" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 102.375,424.67002 h -3.37 v 3.82" + class="st1" + id="path16028" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="M 73.02,438.73002 H 97.1 v 2.85" + class="st1" + id="path16033" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 102.375,441.87002 h -5.28 v -1.91" + class="st1" + id="path16038" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path16048" + class="st1" + d="m 69.47196,427.54002 v 3.82" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path16050" + class="st1" + d="m 71.38196,431.36002 a 2.39003,2.39003 0 0 1 0,-3.82" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path16055" + class="st1" + d="M 74.25,429.45204 H 70.43" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path16057" + class="st1" + d="M 69.47,429.45204 H 66.6" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path16068" + class="st1" + d="m 68.79596,436.81002 v 3.82" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path16070" + class="st1" + d="m 70.70596,440.63002 a 2.39003,2.39003 0 0 1 0,-3.82" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path16075" + class="st1" + d="m 73.574,438.72204 h -3.82" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path16077" + class="st1" + d="m 68.794,438.72204 h -2.87" /><path + d="m 58.5606,429.45702 h 8.04" + class="st1" + id="path16083" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 61.4701,438.75802 h 4.68" + class="st1" + id="path16088" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="74.836998" + y="445.20605" + width="25.825701" + height="12.7949" + class="st10" + id="rect16124" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect16187" + class="st3" + height="17.4627" + width="17.4627" + y="230.125" + x="914.03699" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path16192" + class="st1" + d="m 916.06636,234.16444 8.44,-0.04 4.33,8.7" /><rect + x="914.03699" + y="250.47102" + width="26.1562" + height="19.4767" + class="st4" + id="rect16200" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 969.75,474.48702 v 61.29 H 949.5" + class="st1" + id="path16209" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 960.75,476.34602 v -11.25 h 7.87" + class="st1" + id="path16214" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 854.123,513.43502 v -19.48 h -7.48" + class="st1" + id="path16219" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 854.09,509.98002 v 25.86 h 95.41" + class="st1" + id="path16224" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="949.20398" + y="527.57397" + width="11.25" + height="6.6812701" + class="st4" + id="rect16269" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="949.20398" + y="532.97498" + class="st18" + v:langID="1033" + id="text16271" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />RF 3</text> + +<rect + x="947.25" + y="527.67603" + width="14.625" + height="6.75" + rx="3.375" + ry="3.375" + class="st24" + id="rect16276" + style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round" /><rect + x="884.25" + y="513.98999" + width="63" + height="8.7313404" + class="st4" + id="rect16283" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="711" + y="196.37601" + width="52.3125" + height="21.9375" + class="st10" + id="rect16294" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 530.959,435.79302 v -14.48 h -16.51" + class="st1" + id="path16311" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 641.334,421.60002 h -14.63 v 12.79" + class="st1" + id="path16316" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 619.104,495.53302 h 11.98 v -56.22" + class="st1" + id="path16321" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 622.453,495.54102 h -96.42 v -36" + class="st1" + id="path16326" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="616.26599" + y="487.03601" + width="13.5" + height="6.6812701" + class="st4" + id="rect16333" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="616.26599" + y="492.43698" + class="st18" + v:langID="1033" + id="text16335" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 3</text> + +<rect + x="557.45001" + y="475.01901" + width="46.0709" + height="8.7313404" + class="st4" + id="rect16380" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="247.6875" + y="201.34676" + width="58.682701" + height="21.9375" + class="st4" + id="rect16409" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="729.77197" + y="572.06299" + width="58.682701" + height="21.9375" + class="st4" + id="rect16467" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="326.159" + y="513.263" + width="58.682701" + height="21.9375" + class="st4" + id="rect16612" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="194.674" + y="95.908012" + width="25.825701" + height="10.125" + class="st10" + id="rect16652" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="614.77478" + y="487.12885" + width="15.545476" + height="6.7423978" + rx="3.5874174" + ry="3.3711989" + class="st24" + id="rect16659" + style="fill:none;stroke:#000000;stroke-width:0.2576021;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 525.985,461.25002 v -33.75 h -11.53" + class="st1" + id="path16664" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 641.209,427.85002 h -10.13 v 12.79" + class="st1" + id="path16669" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="583.271" + y="553.08698" + width="46.0709" + height="8.7313404" + class="st4" + id="rect16676" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="619.271" + y="535.63495" + width="13.5" + height="6.6812701" + class="st4" + id="rect16694" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><text + x="619.271" + y="541.03595" + class="st18" + v:langID="1033" + id="text16696" + style="font-size:5.99990416px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />IF1 4</text> + +<rect + x="618.15271" + y="535.50671" + width="16.268867" + height="6.7365861" + rx="3.754354" + ry="3.368293" + class="st24" + id="rect16701" + style="fill:none;stroke:#000000;stroke-width:0.26341397;stroke-linecap:round;stroke-linejoin:round" /><rect + x="553.48602" + y="526.5" + width="46.0709" + height="8.7313404" + class="st4" + id="rect16713" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 520.219,469.33302 v -36 h -5.77" + class="st1" + id="path16722" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 614.771,544.50002 h -94.5 v -75.09" + class="st1" + id="path16727" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect16734" + class="st3" + height="17.4627" + width="17.4627" + y="536.14496" + x="591.88" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path16739" + class="st1" + d="m 594.79045,550.69757 c 0.34,-2.64 0.96,-5.44 1.1,-8.56 0.04,-0.79 0.04,-1.6 0.44,-2.09 0.92,-1.14 3.92,-0.63 4.64,-0.63" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path16744" + class="st1" + d="m 606.4322,550.69757 c -0.34,-2.64 -0.96,-5.44 -1.1,-8.56 -0.04,-0.79 -0.04,-1.6 -0.44,-2.09 -0.92,-1.14 -3.92,-0.63 -4.64,-0.63" /><path + d="m 615.007,544.50002 h 20.62 v -88.03" + class="st1" + id="path16779" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 641.209,433.48002 h -5.58 v 23.27" + class="st1" + id="path16784" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="305.81699" + y="375.75705" + width="58.682701" + height="38.243401" + class="st4" + id="rect16818" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="135" + y="395.07904" + width="30.9128" + height="19.4767" + class="st10" + id="rect16838" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="137.69" + y="432.26901" + width="36.5625" + height="8.7313404" + class="st10" + id="rect16849" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect16858" + class="st3" + height="11.5313" + width="11.5313" + y="435.23502" + x="210.938" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path16863" + class="st1" + d="m 212.85672,438.71863 4.98,-0.02 2.55,5.13" /><rect + x="162" + y="445.5" + width="47.970001" + height="12.3166" + class="st4" + id="rect16871" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="208.5" + y="445.50003" + width="46.0709" + height="13.5" + class="st4" + id="rect16880" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect16889" + class="st3" + height="11.5313" + width="11.5313" + y="435.23502" + x="192.73" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path16894" + class="st1" + d="m 201.96296,438.61183 -4.98,-0.02 -2.55,5.13" /><rect + x="191.924" + y="389.25" + width="47.970001" + height="12.3166" + class="st4" + id="rect16902" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect16911" + class="st3" + height="11.5313" + width="11.5313" + y="400.21902" + x="-210.51601" + transform="scale(-1,1)" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path16916" + class="st1" + d="m 201.28304,403.59583 4.98,-0.02 2.55,5.13" /><rect + x="226.211" + y="146.25703" + width="58.682701" + height="38.243401" + class="st4" + id="rect16951" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect16966" + class="st3" + height="11.5313" + width="11.5313" + y="145.617" + x="183.938" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path16971" + class="st1" + d="m 185.85672,149.10063 4.98,-0.02 2.55,5.13" /><rect + x="135" + y="155.882" + width="47.970001" + height="12.3166" + class="st4" + id="rect16979" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + x="181.5" + y="155.88301" + width="46.0709" + height="13.5" + class="st4" + id="rect16988" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect16997" + class="st3" + height="11.5313" + width="11.5313" + y="145.617" + x="165.73" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path17002" + class="st1" + d="m 174.96296,148.99383 -4.98,-0.02 -2.55,5.13" /><rect + x="153.467" + y="97.340996" + width="47.970001" + height="12.3166" + class="st4" + id="rect17010" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect17019" + class="st3" + height="11.5313" + width="11.5313" + y="107.563" + x="166.556" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path17024" + class="st1" + d="m 175.78896,110.93983 -4.98,-0.02 -2.55,5.13" /><rect + x="423" + y="132.18701" + width="19.295601" + height="6.75" + class="st10" + id="rect17032" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="423" + y="141.75002" + width="19.295601" + height="6.75" + class="st10" + id="rect17041" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="423" + y="126.00002" + width="19.295601" + height="6.75" + class="st10" + id="rect17050" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="423" + y="118.12502" + width="19.295601" + height="6.75" + class="st10" + id="rect17059" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="452.25" + y="95.625015" + width="19.295601" + height="6.75" + class="st10" + id="rect17068" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="452.25" + y="101.25002" + width="19.295601" + height="6.75" + class="st10" + id="rect17077" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="451.04001" + y="108.00002" + width="19.295601" + height="6.75" + class="st10" + id="rect17086" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="450" + y="117.05501" + width="19.295601" + height="6.75" + class="st10" + id="rect17095" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="594" + y="95.625015" + width="19.295601" + height="6.75" + class="st10" + id="rect17104" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="594" + y="101.25002" + width="19.295601" + height="6.75" + class="st10" + id="rect17113" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="594" + y="107.43701" + width="19.295601" + height="6.75" + class="st10" + id="rect17122" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="603.47699" + y="119.06302" + width="19.295601" + height="4.87501" + class="st10" + id="rect17131" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="618.10199" + y="119.25002" + width="19.295601" + height="6.75" + class="st10" + id="rect17140" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="618.10199" + y="126.56201" + width="19.295601" + height="6.75" + class="st10" + id="rect17149" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="618.10199" + y="133.31201" + width="19.295601" + height="6.75" + class="st10" + id="rect17158" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="627.10199" + y="141.75002" + width="19.295601" + height="6.75" + class="st10" + id="rect17167" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="730.60199" + y="121.50002" + width="19.295601" + height="6.75" + class="st10" + id="rect17176" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="731.25" + y="132.75002" + width="19.295601" + height="6.75" + class="st10" + id="rect17185" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="731.25" + y="126.56201" + width="19.295601" + height="6.75" + class="st10" + id="rect17194" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="730.60199" + y="140.62502" + width="19.295601" + height="6.75" + class="st10" + id="rect17203" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="812.25" + y="181.12502" + width="19.295601" + height="6.75" + class="st10" + id="rect17212" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="812.16498" + y="204.75002" + width="19.295601" + height="6.75" + class="st10" + id="rect17221" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="822.20398" + y="191.25002" + width="19.295601" + height="6.75" + class="st10" + id="rect17230" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="944.43701" + y="158.17502" + width="19.295601" + height="6.75" + class="st10" + id="rect17239" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="944.35199" + y="170.76302" + width="19.295601" + height="6.75" + class="st10" + id="rect17248" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="944.43701" + y="150.99901" + width="19.295601" + height="6.75" + class="st10" + id="rect17257" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="943.70398" + y="142.67502" + width="19.295601" + height="6.75" + class="st10" + id="rect17266" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1008" + y="144.00002" + width="19.295601" + height="6.75" + class="st10" + id="rect17275" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1008" + y="171.00002" + width="19.295601" + height="6.75" + class="st10" + id="rect17284" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1010.25" + y="150.27802" + width="19.295601" + height="6.75" + class="st10" + id="rect17293" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1010.25" + y="157.50002" + width="19.295601" + height="6.75" + class="st10" + id="rect17302" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1084.5" + y="164.25002" + width="19.295601" + height="6.75" + class="st10" + id="rect17311" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1047.17" + y="218.25" + width="38.652802" + height="8.7313404" + class="st10" + id="rect17320" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1086.16" + y="156.93701" + width="19.295601" + height="9" + class="st10" + id="rect17329" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1095.75" + y="176.62502" + width="19.295601" + height="6.75" + class="st10" + id="rect17338" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1086.16" + y="149.39401" + width="19.295601" + height="6.75" + class="st10" + id="rect17347" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1103.45" + y="416.25003" + width="19.295601" + height="6.75" + class="st10" + id="rect17356" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1092.38" + y="409.50003" + width="19.295601" + height="6.75" + class="st10" + id="rect17365" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1104.75" + y="423.00003" + width="19.295601" + height="6.75" + class="st10" + id="rect17374" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1104.75" + y="428.62503" + width="19.295601" + height="6.75" + class="st10" + id="rect17383" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1044" + y="416.25003" + width="19.295601" + height="6.75" + class="st10" + id="rect17392" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1044" + y="423.00003" + width="19.295601" + height="6.75" + class="st10" + id="rect17401" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1053" + y="435.37503" + width="19.295601" + height="6.75" + class="st10" + id="rect17410" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1021.71" + y="484.01901" + width="38.035599" + height="8.7313404" + class="st10" + id="rect17419" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="957.85199" + y="450.00003" + width="19.295601" + height="6.75" + class="st10" + id="rect17428" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="957.85199" + y="459.00003" + width="19.295601" + height="6.75" + class="st10" + id="rect17437" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="958.5" + y="473.62503" + width="19.295601" + height="6.75" + class="st10" + id="rect17446" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="1053" + y="408.37503" + width="19.295601" + height="6.75" + class="st10" + id="rect17455" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="729.95398" + y="407.25003" + width="19.295601" + height="6.75" + class="st10" + id="rect17464" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="738" + y="415.09702" + width="19.295601" + height="6.75" + class="st10" + id="rect17473" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="738" + y="421.87503" + width="19.295601" + height="6.75" + class="st10" + id="rect17482" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="729" + y="433.12503" + width="19.295601" + height="6.75" + class="st10" + id="rect17491" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="843.85498" + y="470.57104" + width="19.295601" + height="6.75" + class="st10" + id="rect17500" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="843.75" + y="479.25003" + width="19.295601" + height="6.75" + class="st10" + id="rect17509" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="843.75" + y="488.25003" + width="19.295601" + height="6.75" + class="st10" + id="rect17518" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="631.56097" + y="428.06204" + width="19.295601" + height="6.75" + class="st10" + id="rect17545" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="630.521" + y="422.43704" + width="19.295601" + height="6.75" + class="st10" + id="rect17554" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="630.521" + y="416.25003" + width="19.295601" + height="6.75" + class="st10" + id="rect17563" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="631.56097" + y="408.37503" + width="19.295601" + height="6.75" + class="st10" + id="rect17572" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="511.271" + y="427.50003" + width="19.295601" + height="6.75" + class="st10" + id="rect17581" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="512.22498" + y="421.87503" + width="19.295601" + height="6.75" + class="st10" + id="rect17590" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="511.271" + y="408.37503" + width="19.295601" + height="6.75" + class="st10" + id="rect17599" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="512.22498" + y="415.12503" + width="19.295601" + height="6.75" + class="st10" + id="rect17608" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="540.521" + y="380.24933" + width="17.4627" + height="17.4627" + class="st3" + id="rect17619" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 542.55036,384.28874 8.44,-0.04 4.33,8.7" + class="st1" + id="path17624" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" + id="rect17632" + class="st4" + height="17.4627" + width="23.0355" + y="398.259" + x="542.67249" /><rect + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" + id="rect17646" + class="st4" + height="17.4627" + width="26.537201" + y="398.78702" + x="591.73602" /><rect + x="592.11426" + y="379.88803" + width="17.4627" + height="17.4627" + class="st3" + id="rect17657" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 606.91664,383.92744 -8.44,-0.04 -4.33,8.7" + class="st1" + id="path17662" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" + id="rect17673" + class="st4" + height="19.4767" + width="23.0355" + y="453.02304" + x="587.50299" /><rect + x="590.28937" + y="434.16071" + width="17.4627" + height="17.4627" + class="st3" + id="rect17684" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 605.09179,438.20014 -8.44,-0.04 -4.33,8.7" + class="st1" + id="path17689" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="542.5282" + y="433.55542" + width="17.4627" + height="17.4627" + class="st3" + id="rect17700" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 544.55755,437.59484 8.44,-0.04 4.33,8.7" + class="st1" + id="path17705" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" + id="rect17713" + class="st4" + height="19.4767" + width="20.619301" + y="451.82404" + x="542.48999" /><rect + x="542.47241" + y="484.39441" + width="17.4627" + height="17.4627" + class="st3" + id="rect17727" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 544.50179,488.43384 8.44,-0.04 4.33,8.7" + class="st1" + id="path17732" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" + id="rect17740" + class="st4" + height="19.4767" + width="20.619301" + y="502.66306" + x="540.521" /><rect + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" + id="rect17754" + class="st4" + height="19.4767" + width="23.0355" + y="503.69205" + x="587.50299" /><rect + x="590.28937" + y="484.82971" + width="17.4627" + height="17.4627" + class="st3" + id="rect17765" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 605.09179,488.86914 -8.44,-0.04 -4.33,8.7" + class="st1" + id="path17770" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="240.10199" + y="413.43802" + width="19.295601" + height="6.75" + class="st10" + id="rect17797" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="239.54001" + y="428.06204" + width="19.295601" + height="6.75" + class="st10" + id="rect17806" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="174.85201" + y="429.46902" + width="19.295601" + height="6.75" + class="st10" + id="rect17815" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="174.20399" + y="414.56302" + width="19.295601" + height="6.75" + class="st10" + id="rect17824" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="145.60201" + y="123.75002" + width="19.295601" + height="6.75" + class="st10" + id="rect17833" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="145.60201" + y="138.14401" + width="19.295601" + height="6.75" + class="st10" + id="rect17842" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="214.22701" + y="136.68802" + width="19.295601" + height="6.75" + class="st10" + id="rect17851" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="214.70399" + y="124.36502" + width="19.295601" + height="6.75" + class="st10" + id="rect17860" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="758.25" + y="282.53302" + width="19.295601" + height="6.75" + class="st10" + id="rect17887" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="744.75" + y="282.37503" + width="19.295601" + height="6.75" + class="st10" + id="rect17896" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><rect + x="873" + y="476.02032" + width="17.4627" + height="17.4627" + class="st3" + id="rect17907" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 875.02936,480.05974 8.44,-0.04 4.33,8.7" + class="st1" + id="path17912" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" + id="rect17920" + class="st4" + height="17.4627" + width="21.375" + y="493.73904" + x="873" /><rect + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" + id="rect17934" + class="st4" + height="17.4627" + width="21.375" + y="494.02103" + x="918" /><rect + x="919.95618" + y="476.02103" + width="17.4627" + height="17.4627" + class="st3" + id="rect17945" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 934.75856,480.06044 -8.44,-0.04 -4.33,8.7" + class="st1" + id="path17950" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="873" + y="527.3183" + width="17.4627" + height="17.4627" + class="st3" + id="rect17961" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 875.02936,531.35774 8.44,-0.04 4.33,8.7" + class="st1" + id="path17966" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" + id="rect17974" + class="st4" + height="17.4627" + width="21.375" + y="545.03699" + x="873" /><rect + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" + id="rect17988" + class="st4" + height="17.4627" + width="21.375" + y="542.87097" + x="919.125" /><rect + x="921.08118" + y="524.87097" + width="17.4627" + height="17.4627" + class="st3" + id="rect17999" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 935.88356,528.91044 -8.44,-0.04 -4.33,8.7" + class="st1" + id="path18004" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1086.16,156.94002 h 1.71 v 7.31" + class="st1" + id="path18020" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 1087.9856,164.25002 8.83,2e-5" + class="st1" + id="path18025" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path18090" + class="st1" + d="m 134.859,444.79802 v -15.19" /><path + style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path18095" + class="st21" + d="m 134.85925,440.29752 2.25,-4.5 h -4.5 z" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path18100" + class="st1" + d="m 137.953,440.29752 h -5.91" /><rect + x="131.519" + y="452.25003" + width="29.3561" + height="13.5" + class="st10" + id="rect18108" + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" /><text + x="141.02901" + y="457.20004" + class="st23" + v:langID="1033" + id="text18112" + style="font-size:5.99990416px;font-family:Arial;fill:#000000"><v:paragraph + v:horizAlign="1" /><v:tabList />Pwr <tspan + x="136.35899" + dy="14.400001" + class="st6" + id="tspan18110" + style="font-size:5.99990416px">Limiter</tspan></text> + +<path + d="m 134.859,429.60902 v -4.78" + class="st1" + id="path18117" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><circle + cx="-424.75833" + cy="135" + class="st3" + id="ellipse18122" + r="1.05469" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + transform="rotate(-90)" /><path + d="m 134.999,458.32002 -2.04,-3.82 h 4.08 z" + class="st3" + id="path18127" + inkscape:connector-curvature="0" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 134.859,454.50002 v -9.7" + class="st1" + id="path18132" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 130.872,420.75002 v 22.5 h 9 v -22.5 z" + class="st27" + id="path18137" + inkscape:connector-curvature="0" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.5, 1" /> +<g + id="shape2253-1834" + v:mID="2253" + v:groupContext="shape" + transform="translate(975.4035,-621.19558)"><title + id="title40763">Sheet.2253</title><rect + x="0" + y="772.875" + width="9" + height="19.125" + class="st15" + id="rect40765" + style="fill:#ffffff;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /></g><g + id="group2254-1836" + transform="rotate(90,807.69667,961.28187)" + v:mID="2254" + v:groupContext="group" + v:layerMember="1"><v:custProps><v:cp + v:nameU="Label" + v:lbl="Label" + v:type="0" + v:format="@" + v:sortKey="" + v:invis="false" + v:ask="false" + v:langID="1033" + v:val="VT4()" /><v:cp + v:nameU="Slider_Position" + v:lbl="Slider position (0-1)" + v:type="2" + v:langID="1033" + v:val="VT0(0.5):26" /></v:custProps><v:userDefs><v:ud + v:nameU="Form" + v:val="VT0(1):5" /><v:ud + v:nameU="SubType" + v:val="VT0(0):26" /><v:ud + v:nameU="visDescription" + v:val="VT4(Represents a fixed or variable resistor.)" /><v:ud + v:nameU="visVersion" + v:val="VT0(11):26" /></v:userDefs><title + id="title40768">Resistor</title><g + id="shape2255-1837" + v:mID="2255" + v:groupContext="shape" + v:layerMember="1"><title + id="title40770">Sheet.2255</title><v:userDefs><v:ud + v:nameU="SubType" + v:val="VT0(0):26" /><v:ud + v:nameU="Form" + v:val="VT0(1):5" /></v:userDefs><path + d="m 0,789.07 1.22,-2.92 2.44,5.85 2.43,-5.85 2.44,5.85 2.44,-5.85 2.44,5.85 1.21,-2.93" + class="st1" + id="path40772" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /></g><g + id="shape2256-1840" + v:mID="2256" + v:groupContext="shape" + v:layerMember="1" + transform="rotate(-45,5.0429876,792.08413)"><title + id="title40775">Sheet.2256</title><v:userDefs><v:ud + v:nameU="SubType" + v:val="VT0(0):26" /></v:userDefs></g></g><g + id="shape2257-1842" + v:mID="2257" + v:groupContext="shape" + transform="rotate(90,801.84667,970.05689)"><title + id="title40779">Sheet.2257</title><path + d="M 0,792 H 4.11" + class="st1" + id="path40781" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /></g><g + id="shape2258-1845" + v:mID="2258" + v:groupContext="shape" + transform="rotate(-90,170.74439,-17.159199)"><title + id="title40784">Sheet.2258</title><path + d="M 0,792 H 3.2" + class="st1" + id="path40786" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /></g><g + id="shape2259-1848" + v:mID="2259" + v:groupContext="shape" + transform="rotate(-53.3929,-132.35949,-486.84399)"><title + id="title40789">Sheet.2259</title><path + d="M 0,792 H 21.13" + class="st19" + id="path40791" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-3)" /></g><text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:30px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="180.49945" + y="39.647911" + id="text55352"><tspan + sodipodi:role="line" + id="tspan55350" + x="180.49945" + y="39.647911" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:italic;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">IF2: 860-2250 MHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:30px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="508.771" + y="39.641914" + id="text55352-8"><tspan + sodipodi:role="line" + id="tspan55350-0" + x="508.771" + y="39.641914" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:italic;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">IF1: 2.8 - 8.2 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:30px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="884.37524" + y="39.641914" + id="text55352-8-0"><tspan + sodipodi:role="line" + id="tspan55350-0-5" + x="884.37524" + y="39.641914" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:italic;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">RF: 1 MHz - 8 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="165.375" + y="103.40627" + id="text55411"><tspan + sodipodi:role="line" + id="tspan55409" + x="165.375" + y="103.40627" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:8px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">1060 MHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="183.10202" + y="166.58252" + id="text55411-0"><tspan + sodipodi:role="line" + id="tspan55409-8" + x="183.10202" + y="166.58252" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:8px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">2050 MHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="505.6438" + y="80.485016" + id="text55411-0-6"><tspan + sodipodi:role="line" + id="tspan55409-8-4" + x="505.6438" + y="80.485016" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">3.0 - 4.3 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="480.14105" + y="122.00026" + id="text55411-0-6-2"><tspan + sodipodi:role="line" + id="tspan55409-8-4-5" + x="480.14105" + y="122.00026" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">4.3 - 5.1 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="473.4848" + y="159.87526" + id="text55411-0-6-8"><tspan + sodipodi:role="line" + id="tspan55409-8-4-6" + x="473.4848" + y="159.87526" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">5.1 - 5.7 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="452.31906" + y="195.02751" + id="text55411-0-6-8-2"><tspan + sodipodi:role="line" + id="tspan55409-8-4-6-8" + x="452.31906" + y="195.02751" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">5.7 - 6.4 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="451.38159" + y="231.96501" + id="text55411-0-6-8-2-4"><tspan + sodipodi:role="line" + id="tspan55409-8-4-6-8-7" + x="451.38159" + y="231.96501" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">6.4 - 7.0 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="444.10107" + y="269.09402" + id="text55411-0-6-8-2-4-2"><tspan + sodipodi:role="line" + id="tspan55409-8-4-6-8-7-4" + x="444.10107" + y="269.09402" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">7.0 - 8.0 GHz</tspan></text> + +<g + id="g56872" + transform="translate(-29.005631,-41.867401)"><rect + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" + id="rect13901" + class="st10" + height="12.3166" + width="46.0709" + y="269.59497" + x="667.151" /><text + style="font-style:italic;font-weight:bold;font-size:7.99996805px;font-family:Arial;fill:#000000" + id="text13905" + v:langID="1033" + class="st31" + y="283.25641" + x="680.89581"><v:paragraph /><v:tabList /><tspan + style="font-size:7.99996805px;text-decoration: underline;text-decoration-line: underline" + id="tspan13903" + class="st9">Tx LO1</tspan></text> + +<rect + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" + id="rect16438" + class="st4" + height="21.9375" + width="58.682701" + y="272.97098" + x="717.86298" /><path + style="stroke:#000000;stroke-width:0.375;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none" + inkscape:connector-curvature="0" + id="path15323-0" + class="st1" + d="m 733.2,315.73635 53.07453,-0.26144" /><path + style="fill:#ffffff" + inkscape:connector-curvature="0" + id="path15279-4" + class="st16" + d="m 719.70006,315.63848 a 6.75,6.75 0 0 1 13.5,0 6.75,6.75 0 1 1 -13.5,0 z" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15281-6" + class="st1" + d="m 726.45006,315.63848 a 2.25,2.25 0 0 0 4.5,0" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15283-2" + class="st1" + d="m 721.95006,315.63848 a 2.25,2.25 0 1 1 4.5,0" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15285-6" + class="st1" + d="m 719.70006,315.63848 a 6.75,6.75 0 0 1 13.5,0 6.75,6.75 0 1 1 -13.5,0" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect15290-7" + class="st3" + height="15.1875" + width="19.620001" + y="307.81351" + x="686.58008" /><rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect15297-5" + class="st17" + height="9.28125" + width="13.5" + y="310.76749" + x="691.57507" /><text + style="font-size:7.99996805px;font-family:Calibri;fill:#000000" + id="text15299-6" + v:langID="1033" + class="st5" + y="317.8085" + x="691.57507"><v:paragraph /><v:tabList />PLL</text> + +<path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-6)" + inkscape:connector-curvature="0" + id="path15304-9" + class="st19" + d="m 706.20006,315.63848 h 10.11" /><path + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.5, 1" + inkscape:connector-curvature="0" + id="path15309-8" + class="st27" + d="m 759.49706,335.82248 h -89.86 v -47.25 h 89.86 z" /><rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect15316-7" + class="st17" + height="9.28125" + width="58.682701" + y="288.57248" + x="671.14209" /><text + style="font-size:7.99996805px;font-family:Calibri;fill:#000000" + id="text15318-2" + v:langID="1033" + class="st5" + y="295.61349" + x="671.14209"><v:paragraph /><v:tabList />LMX2572</text> + +<path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15328-8" + class="st1" + d="m 739.95006,316.11148 v 15.75 h -13.5" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-6)" + inkscape:connector-curvature="0" + id="path15333-2" + class="st19" + d="m 728.13506,331.84648 h -36.56 v -5.38" /><circle + style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.40625" + id="ellipse15338-9" + class="st21" + cy="315.63846" + cx="739.95032" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-6)" + inkscape:connector-curvature="0" + id="path15343-9" + class="st19" + d="m 665.70006,314.89148 h 17.49" /><rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect15350-6" + class="st17" + height="18.0126" + width="11.8125" + y="315.00949" + x="670.76306" /><text + style="font-size:7.99996805px;font-family:Calibri;fill:#000000" + id="text15354-0" + v:langID="1033" + class="st5" + y="321.61252" + x="670.76306"><v:paragraph /><v:tabList />Ref<v:newlineChar /><tspan + style="font-size:7.99996805px" + id="tspan15352-2" + class="st6" + dy="14.400001" + x="670.76306">In</tspan></text> + +<rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect15381-7" + class="st17" + height="9.28125" + width="66.375" + y="297.43149" + x="671.32507" /><rect + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" + id="rect15512-6" + class="st10" + height="12.3166" + width="46.0709" + y="274.24146" + x="683.97705" /></g><text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="829.07629" + y="109.69658" + id="text55411-0-6-21"><tspan + sodipodi:role="line" + id="tspan55409-8-4-59" + x="829.07629" + y="109.69658" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">3.0 - 8 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="842.33459" + y="134.35692" + id="text55411-0-6-9"><tspan + sodipodi:role="line" + id="tspan55409-8-4-1" + x="842.33459" + y="134.35692" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">1 MHz - 1.8 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="860.93207" + y="179.69615" + id="text55411-0-6-9-4"><tspan + sodipodi:role="line" + id="tspan55409-8-4-1-9" + x="860.93207" + y="179.69615" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">1.8 - 2.3 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="889.09277" + y="222.91805" + id="text55411-0-6-9-4-1"><tspan + sodipodi:role="line" + id="tspan55409-8-4-1-9-0" + x="889.09277" + y="222.91805" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">2.3 - 3 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="685.80005" + y="183.14729" + id="text55411-0-6-21-7"><tspan + sodipodi:role="line" + id="tspan55409-8-4-59-5" + x="685.80005" + y="183.14729" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">3.2 - 8 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="186.95876" + y="393.17401" + id="text55411-8"><tspan + sodipodi:role="line" + id="tspan55409-7" + x="186.95876" + y="393.17401" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">1060 MHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="197.19902" + y="460.26077" + id="text55411-0-0"><tspan + sodipodi:role="line" + id="tspan55409-8-48" + x="197.19902" + y="460.26077" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">2050 MHz</tspan></text> + +<g + id="g57241" + transform="translate(124.42157,-133.27438)"><path + style="stroke:#000000;stroke-width:0.375;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none" + inkscape:connector-curvature="0" + id="path15323-1" + class="st1" + d="m 205.1539,670.52718 53.07453,-0.26144" /><path + style="fill:#ffffff" + inkscape:connector-curvature="0" + id="path15279-42" + class="st16" + d="m 191.65396,670.42931 a 6.75,6.75 0 0 1 13.5,0 6.75,6.75 0 1 1 -13.5,0 z" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15281-2" + class="st1" + d="m 198.40396,670.42931 a 2.25,2.25 0 0 0 4.5,0" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15283-20" + class="st1" + d="m 193.90396,670.42931 a 2.25,2.25 0 1 1 4.5,0" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15285-5" + class="st1" + d="m 191.65396,670.42931 a 6.75,6.75 0 0 1 13.5,0 6.75,6.75 0 1 1 -13.5,0" /><rect + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect15290-5" + class="st3" + height="15.1875" + width="19.620001" + y="662.60431" + x="158.53395" /><rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect15297-2" + class="st17" + height="9.28125" + width="13.5" + y="665.55829" + x="163.52896" /><text + style="font-size:7.99996805px;font-family:Calibri;fill:#000000" + id="text15299-9" + v:langID="1033" + class="st5" + y="672.5993" + x="163.52896"><v:paragraph /><v:tabList />PLL</text> + +<path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-9)" + inkscape:connector-curvature="0" + id="path15304-0" + class="st19" + d="m 178.15396,670.42931 h 10.11" /><path + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.5, 1" + inkscape:connector-curvature="0" + id="path15309-2" + class="st27" + d="m 231.45096,690.61331 h -89.86 v -47.25 h 89.86 z" /><rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect15316-8" + class="st17" + height="9.28125" + width="58.682701" + y="643.36328" + x="143.09596" /><text + style="font-size:7.99996805px;font-family:Calibri;fill:#000000" + id="text15318-3" + v:langID="1033" + class="st5" + y="650.4043" + x="143.09596"><v:paragraph /><v:tabList />LMX2572</text> + +<path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + inkscape:connector-curvature="0" + id="path15328-80" + class="st1" + d="m 211.90396,670.90231 v 15.75 h -13.5" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-9)" + inkscape:connector-curvature="0" + id="path15333-4" + class="st19" + d="m 200.08896,686.63731 h -36.56 v -5.38" /><circle + style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + r="1.40625" + id="ellipse15338-0" + class="st21" + cy="670.42926" + cx="211.90422" /><path + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-9)" + inkscape:connector-curvature="0" + id="path15343-91" + class="st19" + d="m 137.65396,669.68231 h 17.49" /><rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect15350-9" + class="st17" + height="18.0126" + width="11.8125" + y="669.80029" + x="142.71696" /><text + style="font-size:7.99996805px;font-family:Calibri;fill:#000000" + id="text15354-6" + v:langID="1033" + class="st5" + y="676.40332" + x="142.71696"><v:paragraph /><v:tabList />Ref<v:newlineChar /><tspan + style="font-size:7.99996805px" + id="tspan15352-25" + class="st6" + dy="14.400001" + x="142.71696">In</tspan></text> + +<rect + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" + id="rect15381-4" + class="st17" + height="9.28125" + width="66.375" + y="652.22229" + x="143.27896" /><rect + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" + id="rect16409-6" + class="st4" + height="21.9375" + width="58.682701" + y="644.72827" + x="198.57271" /></g><g + id="g57520" + transform="translate(-7.445517,-82.808191)"><rect + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" + id="rect13912" + class="st10" + height="12.3166" + width="46.0709" + y="634.94897" + x="702.36401" /><text + style="font-style:italic;font-weight:bold;font-size:7.99996805px;font-family:Arial;fill:#000000" + id="text13916" + v:langID="1033" + class="st31" + y="643.50598" + x="706.36401"><v:paragraph /><v:tabList /><tspan + style="font-size:7.99996805px;text-decoration: underline;text-decoration-line: underline" + id="tspan13914" + class="st9">Rx LO1</tspan></text> + +<rect + style="fill:none;stroke:none;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" + id="rect15755-2" + class="st10" + height="12.316599" + width="46.0709" + y="649.13013" + x="674.51044" /><rect + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" + id="rect16612-9" + class="st4" + height="21.9375" + width="58.682701" + y="653.15112" + x="747.48846" /><g + transform="translate(545.75103,6.6137609)" + id="g57241-4"><path + d="m 205.1539,670.52718 53.07453,-0.26144" + class="st1" + id="path15323-1-3" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.375;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3;stroke-dasharray:none" /><path + d="m 191.65396,670.42931 a 6.75,6.75 0 0 1 13.5,0 6.75,6.75 0 1 1 -13.5,0 z" + class="st16" + id="path15279-42-5" + inkscape:connector-curvature="0" + style="fill:#ffffff" /><path + d="m 198.40396,670.42931 a 2.25,2.25 0 0 0 4.5,0" + class="st1" + id="path15281-2-1" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 193.90396,670.42931 a 2.25,2.25 0 1 1 4.5,0" + class="st1" + id="path15283-20-7" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 191.65396,670.42931 a 6.75,6.75 0 0 1 13.5,0 6.75,6.75 0 1 1 -13.5,0" + class="st1" + id="path15285-5-4" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="158.53395" + y="662.60431" + width="19.620001" + height="15.1875" + class="st3" + id="rect15290-5-3" + style="fill:#ffffff;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="163.52896" + y="665.55829" + width="13.5" + height="9.28125" + class="st17" + id="rect15297-2-1" + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="163.52896" + y="672.5993" + class="st5" + v:langID="1033" + id="text15299-9-4" + style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />PLL</text> + +<path + d="m 178.15396,670.42931 h 10.11" + class="st19" + id="path15304-0-6" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-9-0)" /><path + d="m 231.45096,690.61331 h -89.86 v -47.25 h 89.86 z" + class="st27" + id="path15309-2-9" + inkscape:connector-curvature="0" + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.5, 1" /><rect + x="143.09596" + y="643.36328" + width="58.682701" + height="9.28125" + class="st17" + id="rect15316-8-4" + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="143.09596" + y="650.4043" + class="st5" + v:langID="1033" + id="text15318-3-2" + style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />LMX2572</text> + +<path + d="m 211.90396,670.90231 v 15.75 h -13.5" + class="st1" + id="path15328-80-2" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 200.08896,686.63731 h -36.56 v -5.38" + class="st19" + id="path15333-4-6" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-9-0)" /><circle + cx="211.90422" + cy="670.42926" + class="st21" + id="ellipse15338-0-4" + r="1.40625" + style="fill:#000000;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><path + d="m 137.65396,669.68231 h 17.49" + class="st19" + id="path15343-91-1" + inkscape:connector-curvature="0" + style="stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr5-252-9-0)" /><rect + x="142.71696" + y="669.80029" + width="11.8125" + height="18.0126" + class="st17" + id="rect15350-9-2" + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><text + x="142.71696" + y="676.40332" + class="st5" + v:langID="1033" + id="text15354-6-8" + style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph /><v:tabList />Ref<v:newlineChar /><tspan + x="142.71696" + dy="14.400001" + class="st6" + id="tspan15352-25-8" + style="font-size:7.99996805px">In</tspan></text> + +<rect + x="143.27896" + y="652.22229" + width="66.375" + height="9.28125" + class="st17" + id="rect15381-4-9" + style="fill:none;stroke:none;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round" /><rect + x="198.57271" + y="644.72827" + width="58.682701" + height="21.9375" + class="st4" + id="rect16409-6-2" + style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" /></g></g><text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:30px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="200.69856" + y="344.57404" + id="text55352-88"><tspan + sodipodi:role="line" + id="tspan55350-8" + x="200.69856" + y="344.57404" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:italic;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">IF2: 860-2250 MHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="552.70355" + y="374.84402" + id="text55411-0-6-6"><tspan + sodipodi:role="line" + id="tspan55409-8-4-8" + x="552.70355" + y="374.84402" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">3.0 - 4.2 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="552.89105" + y="425.84402" + id="text55411-0-6-6-3"><tspan + sodipodi:role="line" + id="tspan55409-8-4-8-8" + x="552.89105" + y="425.84402" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">4.2 - 5.6 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="554.48706" + y="480.59003" + id="text55411-0-6-6-3-3"><tspan + sodipodi:role="line" + id="tspan55409-8-4-8-8-3" + x="554.48706" + y="480.59003" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">5.6 - 6.8 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="560.31909" + y="529.34003" + id="text55411-0-6-6-3-3-3"><tspan + sodipodi:role="line" + id="tspan55409-8-4-8-8-3-8" + x="560.31909" + y="529.34003" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">6.8 - 8 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="834.90991" + y="382.55142" + id="text55411-0-6-6-0"><tspan + sodipodi:role="line" + id="tspan55409-8-4-8-4" + x="834.90991" + y="382.55142" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">3.0 - 8 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="696.40662" + y="468.99521" + id="text55411-0-6-6-0-7"><tspan + sodipodi:role="line" + id="tspan55409-8-4-8-4-6" + x="696.40662" + y="468.99521" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">3.2 - 8 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="888.38617" + y="424.97382" + id="text55411-0-6-6-0-7-8"><tspan + sodipodi:role="line" + id="tspan55409-8-4-8-4-6-9" + x="888.38617" + y="424.97382" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">1 MHz - 1.8 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="884.93896" + y="470.31705" + id="text55411-0-6-6-0-7-0"><tspan + sodipodi:role="line" + id="tspan55409-8-4-8-4-6-6" + x="884.93896" + y="470.31705" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">1.8 - 2.3 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="884.85016" + y="518.31189" + id="text55411-0-6-6-0-7-0-8"><tspan + sodipodi:role="line" + id="tspan55409-8-4-8-4-6-6-7" + x="884.85016" + y="518.31189" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.99999952px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">2.3 - 3 GHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:30px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="579.70264" + y="344.57404" + id="text55352-88-0"><tspan + sodipodi:role="line" + id="tspan55350-8-3" + x="579.70264" + y="344.57404" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:italic;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">IF1: 2.8-8.2 MHz</tspan></text> + +<text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:30px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75" + x="854.24133" + y="344.57404" + id="text55352-88-0-3"><tspan + sodipodi:role="line" + id="tspan55350-8-3-3" + x="854.24133" + y="344.57404" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:italic;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.75">RF: 1 MHz - 8 GHz</tspan></text> + +</svg>
\ No newline at end of file diff --git a/host/docs/res/x410.png b/host/docs/res/x410.png Binary files differnew file mode 100644 index 000000000..215527d22 --- /dev/null +++ b/host/docs/res/x410.png diff --git a/host/docs/res/x410_back_panel.png b/host/docs/res/x410_back_panel.png Binary files differnew file mode 100644 index 000000000..782305c04 --- /dev/null +++ b/host/docs/res/x410_back_panel.png diff --git a/host/docs/res/x410_front_panel.png b/host/docs/res/x410_front_panel.png Binary files differnew file mode 100644 index 000000000..9df5ce9b4 --- /dev/null +++ b/host/docs/res/x410_front_panel.png diff --git a/host/docs/res/x4xx_block_diagram.svg b/host/docs/res/x4xx_block_diagram.svg new file mode 100644 index 000000000..760b2f442 --- /dev/null +++ b/host/docs/res/x4xx_block_diagram.svg @@ -0,0 +1,962 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="390.99509mm" + height="266.53741mm" + viewBox="0 0 390.99509 266.53741" + version="1.1" + id="svg8" + inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" + sodipodi:docname="x4xx_block_diagram.svg"> + <defs + id="defs2"> + <marker + inkscape:stockid="TriangleInM" + orient="auto" + refY="0" + refX="0" + id="TriangleInM" + style="overflow:visible" + inkscape:isstock="true"> + <path + id="path1582" + d="M 5.77,0 -2.88,5 V -5 Z" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" + transform="scale(-0.4)" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:stockid="TriangleOutM" + orient="auto" + refY="0" + refX="0" + id="TriangleOutM" + style="overflow:visible" + inkscape:isstock="true"> + <path + id="path1591" + d="M 5.77,0 -2.88,5 V -5 Z" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" + transform="scale(0.4)" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:stockid="Arrow2Lstart" + orient="auto" + refY="0" + refX="0" + id="Arrow2Lstart" + style="overflow:visible" + inkscape:isstock="true"> + <path + id="path1464" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" + d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" + transform="matrix(1.1,0,0,1.1,1.1,0)" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:stockid="Arrow2Lend" + orient="auto" + refY="0" + refX="0" + id="Arrow2Lend" + style="overflow:visible" + inkscape:isstock="true"> + <path + id="path1467" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" + d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" + transform="matrix(-1.1,0,0,-1.1,-1.1,0)" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:stockid="TriangleInM" + orient="auto" + refY="0" + refX="0" + id="TriangleInM-4" + style="overflow:visible" + inkscape:isstock="true"> + <path + inkscape:connector-curvature="0" + id="path1582-6" + d="M 5.77,0 -2.88,5 V -5 Z" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" + transform="scale(-0.4)" /> + </marker> + <marker + inkscape:stockid="TriangleOutM" + orient="auto" + refY="0" + refX="0" + id="TriangleOutM-2" + style="overflow:visible" + inkscape:isstock="true"> + <path + inkscape:connector-curvature="0" + id="path1591-6" + d="M 5.77,0 -2.88,5 V -5 Z" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" + transform="scale(0.4)" /> + </marker> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath4388"> + <rect + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect4390" + width="3.9191406" + height="7.8382812" + x="-92.306511" + y="185.79233" /> + </clipPath> + <marker + inkscape:stockid="TriangleInM" + orient="auto" + refY="0" + refX="0" + id="TriangleInM-42" + style="overflow:visible" + inkscape:isstock="true"> + <path + inkscape:connector-curvature="0" + id="path1582-2" + d="M 5.77,0 -2.88,5 V -5 Z" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" + transform="scale(-0.4)" /> + </marker> + <marker + inkscape:stockid="TriangleOutM" + orient="auto" + refY="0" + refX="0" + id="TriangleOutM-20" + style="overflow:visible" + inkscape:isstock="true"> + <path + inkscape:connector-curvature="0" + id="path1591-5" + d="M 5.77,0 -2.88,5 V -5 Z" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" + transform="scale(0.4)" /> + </marker> + <marker + inkscape:stockid="TriangleInM" + orient="auto" + refY="0" + refX="0" + id="TriangleInM-4-5" + style="overflow:visible" + inkscape:isstock="true"> + <path + inkscape:connector-curvature="0" + id="path1582-6-2" + d="M 5.77,0 -2.88,5 V -5 Z" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" + transform="scale(-0.4)" /> + </marker> + <marker + inkscape:stockid="TriangleOutM" + orient="auto" + refY="0" + refX="0" + id="TriangleOutM-2-9" + style="overflow:visible" + inkscape:isstock="true"> + <path + inkscape:connector-curvature="0" + id="path1591-6-0" + d="M 5.77,0 -2.88,5 V -5 Z" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" + transform="scale(0.4)" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="2" + inkscape:cx="619.2311" + inkscape:cy="298.84026" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:window-width="3840" + inkscape:window-height="2063" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(153.93257,-181.94356)"> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect75" + width="222.2912" + height="94.039085" + x="-79.911568" + y="279.0575" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect75-3-6" + width="49.65358" + height="17.939219" + x="-153.16962" + y="236.56277" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect75-3-6-7" + width="49.65358" + height="17.939219" + x="-153.16962" + y="254.50182" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="-19.579166" + y="317.2406" + id="text1076"><tspan + sodipodi:role="line" + id="tspan1074" + x="-19.579166" + y="317.2406" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332">Xilinx RFSoC</tspan><tspan + sodipodi:role="line" + x="-19.579166" + y="322.52618" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1082" /><tspan + sodipodi:role="line" + x="-19.579166" + y="332.23364" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1084">- Embedded Linux</tspan><tspan + sodipodi:role="line" + x="-19.579166" + y="344.58087" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1086">- Programmable Logic</tspan><tspan + sodipodi:role="line" + x="-19.579166" + y="349.86646" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1080" /></text> + <g + id="g4037"> + <rect + y="204.5883" + x="-153.43257" + height="25.20031" + width="48.495682" + id="rect75-3" + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + id="text1076-1" + y="214.31781" + x="-146.09488" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + id="tspan1080-5" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + y="214.31781" + x="-146.09488" + sodipodi:role="line">Control</tspan><tspan + id="tspan1153" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + y="226.66504" + x="-146.09488" + sodipodi:role="line">CPLD</tspan></text> + </g> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="-148.03329" + y="243.46608" + id="text1076-1-6" + inkscape:transform-center-x="-8.2020833" + inkscape:transform-center-y="-34.660417"><tspan + sodipodi:role="line" + x="-148.03329" + y="243.46608" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1153-6">DB0 Digital</tspan><tspan + sodipodi:role="line" + x="-148.03329" + y="251.40358" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1180">Interface</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="-148.03329" + y="261.54572" + id="text1076-1-6-2" + inkscape:transform-center-x="-8.2020833" + inkscape:transform-center-y="-34.660417"><tspan + sodipodi:role="line" + x="-148.03329" + y="261.54572" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1153-6-5">DB1 Digital</tspan><tspan + sodipodi:role="line" + x="-148.03329" + y="269.48322" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1180-8">Interface</tspan></text> + <g + id="g3809" + transform="translate(0,-2.7797713)"> + <rect + y="304.30597" + x="-143.25504" + height="22.244499" + width="39.178856" + id="rect75-3-6-7-5" + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="326.55048" + x="-143.25504" + height="22.244499" + width="39.178856" + id="rect75-3-6-7-5-3" + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + inkscape:transform-center-y="-34.660417" + inkscape:transform-center-x="-8.2020833" + id="text1076-1-6-2-6" + y="335.61588" + x="-136.62737" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + id="tspan1153-6-5-2" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + y="335.61588" + x="-136.62737" + sodipodi:role="line">DB1 RF</tspan><tspan + id="tspan1180-8-8" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + y="343.55338" + x="-136.62737" + sodipodi:role="line">Interface</tspan></text> + <text + inkscape:transform-center-y="-34.660417" + inkscape:transform-center-x="-8.2020833" + id="text1076-1-6-2-6-4" + y="313.99454" + x="-136.62737" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + id="tspan1153-6-5-2-7" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + y="313.99454" + x="-136.62737" + sodipodi:role="line">DB0 RF</tspan><tspan + id="tspan1180-8-8-2" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + y="321.93204" + x="-136.62737" + sodipodi:role="line">Interface</tspan></text> + <text + inkscape:transform-center-y="-34.660417" + inkscape:transform-center-x="-8.2020833" + id="text1076-1-6-2-6-4-6" + y="324.84866" + x="-67.659241" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + id="tspan1180-8-8-2-8" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + y="324.84866" + x="-67.659241" + sodipodi:role="line">ADCs</tspan><tspan + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + y="332.78616" + x="-67.659241" + sodipodi:role="line" + id="tspan4026">DACs</tspan></text> + </g> + <g + id="g4046" + transform="translate(2.9104167,-6.35)"> + <g + id="g4314"> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect75-3-6-7-5-5-2-1" + width="45.697273" + height="25.021141" + x="-47.73143" + y="188.79356" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="-41.966232" + y="198.75139" + id="text1076-1-9"><tspan + sodipodi:role="line" + x="-41.966232" + y="198.75139" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1293">DIO</tspan><tspan + sodipodi:role="line" + x="-41.966232" + y="211.09862" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan4039">Board</tspan></text> + </g> + </g> + <g + id="g4320" + transform="translate(2.9104167,-7.7495056)"> + <rect + y="190.19307" + x="101.19689" + height="24.835871" + width="45.324921" + id="rect75-3-6-7-5-5-2-1-2-0" + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + id="text1076-1-9-0" + y="199.76915" + x="103.24946" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + id="tspan1293-9" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + y="199.76915" + x="103.24946" + sodipodi:role="line">Clocking</tspan><tspan + id="tspan4306" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + y="212.11638" + x="103.24946" + sodipodi:role="line">Board</tspan></text> + </g> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect75-3-6-7-5-5-6" + width="47.570648" + height="23.52692" + x="62.11293" + y="394.11664" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="76.652664" + y="409.40646" + id="text1076-1-9-1-0"><tspan + sodipodi:role="line" + x="76.652664" + y="409.40646" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1369-6">SCU</tspan></text> + <rect + style="fill:none;stroke:#000000;stroke-width:1.00479412;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect75-3-6-7-5-3-6" + width="39.553703" + height="44.484207" + x="-79.704971" + y="301.52859" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m -102.98906,263.46406 h 39.356769" + id="path1409" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M -63.632291,279.07448 V 263.46406" + id="path1411" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m -103.62525,244.93937 h 43.540931" + id="path1409-0" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m -60.084319,278.53227 v -33.5929" + id="path1411-0" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1.13276374;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#TriangleInM);marker-end:url(#TriangleOutM)" + d="m -100.54544,312.33209 h 17.182729" + id="path1442" + inkscape:connector-curvature="0" /> + <circle + id="path1444" + style="fill:#000000;stroke:none;stroke-width:0.26458332" + cx="-79.933632" + cy="316.27014" + r="0.39687499" /> + <path + style="fill:none;stroke:#000000;stroke-width:1.13276374;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#TriangleInM-4);marker-end:url(#TriangleOutM-2)" + d="m -100.54544,335.65398 h 17.182725" + id="path1442-7" + inkscape:connector-curvature="0" /> + <g + transform="translate(52.796333,90.404199)" + id="g4046-7"> + <g + id="g4104" + transform="translate(0,0.52916667)"> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect75-3-6-7-5-5-2-1-28" + width="45.96735" + height="13.252675" + x="-47.866467" + y="188.12936" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="-45.903725" + y="196.96545" + id="text1076-1-9-2"><tspan + sodipodi:role="line" + x="-45.903725" + y="196.96545" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.76111126px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan4039-0">DDR Bank 1</tspan></text> + </g> + </g> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect75-3-6-7-5-5-2-1-28-6" + width="45.96735" + height="13.252675" + x="50.897217" + y="279.06274" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="52.859959" + y="287.89883" + id="text1076-1-9-2-1"><tspan + sodipodi:role="line" + x="52.859959" + y="287.89883" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.76111126px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan4039-0-3">DDR Bank 0</tspan></text> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect75-3-6-7-5-5-2-1-2" + width="29.467161" + height="24.88065" + x="12.300733" + y="232.11459" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="13.338802" + y="241.87804" + id="text1076-1-4-8"><tspan + sodipodi:role="line" + x="13.338802" + y="241.87804" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1153-62-1">DDR4</tspan><tspan + sodipodi:role="line" + x="13.338802" + y="254.22527" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1268-3">4 GB</tspan></text> + <path + style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 27.034313,279.1742 V 257.14028" + id="path4135" + inkscape:connector-curvature="0" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect75-3-6-7-5-5-2-1-2-7" + width="29.467161" + height="24.88065" + x="60.625546" + y="232.11459" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="61.663616" + y="241.87804" + id="text1076-1-4-1"><tspan + sodipodi:role="line" + x="61.663616" + y="241.87804" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1153-62-10">DDR4</tspan><tspan + sodipodi:role="line" + x="61.663616" + y="254.22527" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1268-34">4 GB</tspan></text> + <path + style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 75.359127,279.1292 V 257.09528" + id="path4135-1" + inkscape:connector-curvature="0" /> + <g + id="g4252"> + <g + transform="translate(0.65024451,19.645313)" + id="g4239"> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect75-3-6-7-5-5" + width="29.282759" + height="24.322077" + x="-40.956207" + y="404.01358" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="-40.048008" + y="412.79932" + id="text1076-1-4"><tspan + sodipodi:role="line" + x="-40.048008" + y="412.79932" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1153-62">DDR4</tspan><tspan + sodipodi:role="line" + x="-40.048008" + y="425.14655" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:9.87777805px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1268">4 GB</tspan></text> + </g> + <g + id="g4046-7-5" + transform="translate(-0.78179189,171.02789)"> + <g + transform="translate(0,0.52916667)" + id="g4104-9"> + <rect + y="188.12936" + x="-47.866467" + height="13.252675" + width="45.96735" + id="rect75-3-6-7-5-5-2-1-28-9" + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + id="text1076-1-9-2-14" + y="196.96545" + x="-45.969872" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + id="tspan4039-0-9" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.76111126px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + y="196.96545" + x="-45.969872" + sodipodi:role="line">DDR Bank 2</tspan></text> + </g> + </g> + <path + inkscape:connector-curvature="0" + id="path4135-10" + d="M -25.664583,423.7609 V 372.89476" + style="fill:none;stroke:#000000;stroke-width:1.76895344;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + <path + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m -104.57491,224.30994 h 77.800501" + id="path1409-0-7" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.44413379;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m -26.774409,278.99934 v -54.6894" + id="path1411-0-5" + inkscape:connector-curvature="0" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="-73.949959" + y="229.86198" + id="text1076-1-6-8" + inkscape:transform-center-x="-8.2020833" + inkscape:transform-center-y="-34.660417"><tspan + sodipodi:role="line" + x="-73.949959" + y="229.86198" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1180-0">SPI</tspan></text> + <path + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m -105.17838,213.25937 h 77.800506" + id="path1409-0-7-4" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M -105.04608,218.41875 H 114.83567" + id="path1409-0-7-8" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 114.83567,218.41875 V 207.70312" + id="path4343" + inkscape:connector-curvature="0" /> + <circle + id="path4345" + style="fill:#000000;stroke:none;stroke-width:0.26458332" + cx="114.83566" + cy="207.70311" + r="0.39687499" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="12.822769" + y="216.6508" + id="text1076-1-6-8-0" + inkscape:transform-center-x="-8.2020833" + inkscape:transform-center-y="-34.660417"><tspan + sodipodi:role="line" + x="12.822769" + y="216.6508" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1180-0-4">SPI</tspan></text> + <path + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m -27.377874,213.25937 v -5.35781" + id="path4365" + inkscape:connector-curvature="0" /> + <circle + id="path4367" + style="fill:#000000;stroke:none;stroke-width:0.26458332" + cx="-27.377874" + cy="207.90155" + r="0.39687499" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.45002869;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M -13.229167,279.10095 V 220.97196" + id="path1411-0-5-2" + inkscape:connector-curvature="0" /> + <circle + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4384" + cx="-88.403908" + cy="189.14923" + r="2.4143229" + clip-path="url(#clipPath4388)" + transform="translate(75.204667,29.644417)" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.47673923;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m -13.278776,216.6262 v -9.09838" + id="path1411-0-5-2-9" + inkscape:connector-curvature="0" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect75-3-6-7-5-5-2" + width="40.137661" + height="17.029377" + x="195.92505" + y="347.04404" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect75-3-6-7-5-5-2-9" + width="40.137661" + height="17.029377" + x="195.92505" + y="325.99661" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="203.26215" + y="332.62393" + id="text1076-1-9-1"><tspan + sodipodi:role="line" + x="203.26215" + y="332.62393" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.05555534px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1293-6">QSFP28</tspan><tspan + sodipodi:role="line" + x="203.26215" + y="341.44339" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.05555534px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1369">Port 0</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="203.26215" + y="353.67136" + id="text1076-1-9-1-6"><tspan + sodipodi:role="line" + x="203.26215" + y="353.67136" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.05555534px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1293-6-1">QSFP28</tspan><tspan + sodipodi:role="line" + x="203.26215" + y="362.49081" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.05555534px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1369-0">Port 1</tspan></text> + <path + style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#TriangleInM-42);marker-end:url(#TriangleOutM-20)" + d="m 147.22644,334.5113 h 42.99011" + id="path1442-2" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#TriangleInM-4-5);marker-end:url(#TriangleOutM-2-9)" + d="M 147.82743,355.55873 H 190.5169" + id="path1442-7-8" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:1.29576683;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 124.61875,278.94219 V 206.97552" + id="path4343-3" + inkscape:connector-curvature="0" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="126.59361" + y="241.72008" + id="text1076-1-6-8-0-8" + inkscape:transform-center-x="-8.2020833" + inkscape:transform-center-y="-34.660417"><tspan + sodipodi:role="line" + x="126.59361" + y="241.72008" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1180-0-4-0">Clock / Time</tspan><tspan + sodipodi:role="line" + x="126.59361" + y="249.65758" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan6173">Reference</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="154.06625" + y="329.57532" + id="text1076-1-6-8-0-9" + inkscape:transform-center-x="-8.2020833" + inkscape:transform-center-y="-34.660417"><tspan + sodipodi:role="line" + x="154.06625" + y="329.57532" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1180-0-4-1">4x 25 Gbps</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="154.55893" + y="364.13202" + id="text1076-1-6-8-0-9-9" + inkscape:transform-center-x="-8.2020833" + inkscape:transform-center-y="-34.660417"><tspan + sodipodi:role="line" + x="154.55893" + y="364.13202" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1180-0-4-1-6">4x 25 Gbps</tspan></text> + <g + id="g6266" + transform="translate(-0.52916667)"> + <rect + y="380.9071" + x="196.95403" + height="17.029377" + width="40.137661" + id="rect75-3-6-7-5-5-2-2" + style="fill:none;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + id="text1076-1-9-1-6-5" + y="391.28445" + x="209.68861" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + id="tspan1369-0-4" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.05555534px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + y="391.28445" + x="209.68861" + sodipodi:role="line">RJ45</tspan></text> + </g> + <path + style="fill:none;stroke:#000000;stroke-width:0.70746088;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 82.844459,394.38052 V 372.92779" + id="path4343-3-9" + inkscape:connector-curvature="0" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="86.865738" + y="385.7019" + id="text1076-1-6-8-0-93" + inkscape:transform-center-x="-8.2020833" + inkscape:transform-center-y="-34.660417"><tspan + sodipodi:role="line" + x="86.865738" + y="385.7019" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1180-0-4-6">I2C</tspan></text> + <path + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 196.21616,389.28709 H 121.30007" + id="path1409-0-7-0" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 121.30007,373.01834 v 16.26875" + id="path1411-0-5-29" + inkscape:connector-curvature="0" /> + <rect + style="fill:none;stroke:#000000;stroke-width:0.45336637;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect1080" + width="95.75473" + height="43.819485" + x="3.7990155" + y="221.71759" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="6.273725" + y="228.47993" + id="text1076-1-6-9" + inkscape:transform-center-x="-8.2020833" + inkscape:transform-center-y="-34.660417"><tspan + sodipodi:role="line" + x="6.273725" + y="228.47993" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1180-6">PL DRAM</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="-57.121681" + y="418.91379" + id="text1076-1-6-9-0" + inkscape:transform-center-x="-8.2020833" + inkscape:transform-center-y="-34.660417"><tspan + sodipodi:role="line" + x="-57.121681" + y="418.91379" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.26458332" + id="tspan1180-6-2">PS DRAM</tspan></text> + </g> +</svg> diff --git a/host/docs/res/x4xx_rearpanel_status_leds.png b/host/docs/res/x4xx_rearpanel_status_leds.png Binary files differnew file mode 100644 index 000000000..e89563b3f --- /dev/null +++ b/host/docs/res/x4xx_rearpanel_status_leds.png diff --git a/host/docs/usrp_x4xx.dox b/host/docs/usrp_x4xx.dox new file mode 100644 index 000000000..f809e191e --- /dev/null +++ b/host/docs/usrp_x4xx.dox @@ -0,0 +1,953 @@ +/*! \page page_usrp_x4xx USRP X4x0 Series + +\tableofcontents + +\section x4xx_feature_list Comparative features list + +- Hardware Capabilities: + - Dual QSFP Ports (can be used with 10 GigE) + - External PPS input & output + - External 10 MHz input & output (other input reference frequencies also supported) + - Internal GPSDO for timing, location, and time/frequency reference + - External GPIO Connector (2xHDMI) + - USB-C debug port, providing JTAG and console access + - USB-C OTG port + - Xilinx RFSoC (XCZU28DR), includes quad-core ARM Cortex-A53 (1200 MHz), + dual-core ARM Cortex-R5F real-time unit, and UltraScale+ FPGA + - 4 GiB DDR4 RAM for Processing System, 2x4 GiB DDR4 RAM for fixed logic + - Up to 4x400 MHz of analog bandwidth, center frequency 1 MHz - 7.2 GHz using \ref page_zbx + - Configurable front-to-back or back-to-front airflow +- Software Capabilities: + - Full Linux system running on the ARM core + - Runs MPM (see also \ref page_mpm) +- FPGA Capabilities: + - Timed commands/sampling in FPGA + - RFNoC capable: Supports various CHDR bus widths from 64-bits (for minimal + footprint) up to 512 bits (for maximum throughput) +- Rack-mountable with additional rack mount kit (2 USRPs side-by-side, or 1 USRP per 1U) +- Stackable (with stack mount kit) +- Front-to-back or back-to-front airflow (switchable) + +\section x4xx_overview Overview and Features + +\image html x410.png "Ettus USRP X410" width=50% + +The Ettus USRP X410 is a fourth-generation Software Defined Radio (SDR) out of the USRP +family of SDRs. It contains two \ref page_zbx "ZBX Daughterboards" for a +total of 4 channels at up to 400 MHz of analog bandwidth each. The analog +features of the \ref page_zbx are described in a separate manual page. + +The USRP X410 features a Xilinx RFSoC, running an embedded Linux system. Like +other USRPs, it is addressable through a 1 GbE RJ45 connector, which allows full +access to the embedded Linux system, as well as data streaming at low rates. In +addition, it features two QSFP28 connectors, which allow for up to 4x10 GbE or +1x100 GbE connections each. + +The front panel provides access to the RF connectors (SMA), Tx/Rx status LEDs, +programmable GPIOs, and the power button. The rear panel is where the power and +data connections go (Ethernet, USB) as well as time/clock reference signals and +GPS antenna. + +X410's cooling system uses a field replaceable fan assembly and supports two +variants: one that pulls air front-to-back and one that pulls air back-to-front. +By default, the unit comes with the front-to-back fan assembly. + +\subsection x4xx_overview_rfsoc The RFSoC CPU/FPGA and host operating system + +The main chip (the SoC) of the X410 is a Xilinx RFSoC XCZU28DR. It contains an +ARM quad-core Cortex A53 CPU (referred to as the "APU"), an UltraScale+ FPGA +including peripherals such as built-in data converters and an SD-FEC core, and +an ARM Cortex-R5F real-time processor (the "RPU"). + +The programmable logic (PL, or FPGA) section of the SoC is responsible for +handling all sampling data, the high-speed network connections, and any other +high-speed utilities such as custom RFNoC logic. The processing system (PS, or CPU) +is running a custom-built OpenEmbedded-based Linux operating system. The OS is +responsible for all the device and peripheral management, such as running MPM, +configuring the network interfaces, running local UHD sessions, etc. + +The programmable logic bitfile contains certain hard-coded configurations of the +hardware, such as what type of connectivity the QSFP ports use, and how the RF +data converters are configured. That means to change the QSFP from a 10 GbE to a +100 GbE connection requires changing out the bitfile, as well as when +reconfiguring the data converters for different master clock rates. See +\ref x4xx_updating_fpga_types for more information. + +It is possible to connect to the host OS either via SSH or serial console (see +sections \ref x4xx_getting_started_ssh and \ref x4xx_getting_started_serial, +respectively). + +The X410 has a higher maximum analog bandwidth than previous USRPs. It can provide +rates up to 500 Msps, resulting in a usable analog bandwidth of up to 400 MHz. +In order to facilitate the higher bandwidth, UHD +uses a technology called \ref page_dpdk "Data Plane Development Kit (DPDK)". +See the DPDK page for details on how it can improve streaming, and how to use +it. + +\subsection x4xx_overview_dboards Daughterboard Connectivity + +The USRP X410 contains two ZBX daughterboards. They come pre-assembled. +To find out more about the capabilities of these analog front-end cards, see +\ref page_zbx. + +\subsection x4xx_overview_panels Front and Back Panels + +\image html x410_front_panel.png "X410 Front Panel" width=90% + +The X410 front panel provides access to the RF ports of the \ref page_zbx. +It also provides access to the front-panel GPIO connectors (2x HDMI) and the +power button. + +\image html x410_back_panel.png "X410 Back Panel" width=90% + +The back panel provides access to power, data connections, clocking and timing +related connections, and some status LEDs: + +- The QSFP28 connectors have different configurations dependent on the FPGA + image type (see also \ref x4xx_updating_fpga_types) +- The zHD/iPass connectors are unsupported +- GPS ANT, REF IN, and PPS IN allow connecting a GPS antenna, a reference clock + (e.g., 10 MHz) and a 1 PPS signal for timing purposes +- The TRIG IN/OUT port is not supported in default FPGA images +- The serial number is embedded in a QR code +- There are four user-configurable status LEDs (see also \ref x4xx_usage_rearpanelleds) +- The CONSOLE JTAG USB-C port is a debug port that allows serial access to the + SCU or the OS (see also \ref x4xx_getting_started_serial) +- The USB to PS USB-C port is accessible by the operating system, e.g., to + connect mass storage devices. It can also be used to expose the eMMC storage + as a mass storage device to an external computer, e.g., for updating the + filesystem +- The RJ45 Ethernet port allows accessing the operation system, e.g., via SSH. + It is also possible to stream data over this interface, albeit at a slow rate + (approx. 10 Msps). + +\subsection x4xx_overview_micro The STM32 microcontroller + +The STM32 microcontroller (also referred to as the "SCU") controls various +low-level features of the X4X0 series motherboard: It controls the power +sequencing, reads out fan speeds and some of the temperature sensors. +It is connected to the RFSoC via an I2C bus. It is running software based on +Chromium EC. + +It is possible to log into the STM32 using the serial interface +(see \ref x4xx_getting_started_serial_micro). This will allow certain low-level +controls, such as remote power cycling should the CPU have become unresponsive +for whatever reason. + +\subsection x4xx_overview_rackmount Rack Mounting and Cooling + +TODO fill out +- explain how to flip the fan direction +- maybe explain how to rack-mount + +\subsection x4xx_overview_storage eMMC Storage + +The main non-volatile storage of the USRP is a 16 GB eMMC storage. This storage +can be made accessible as a USB Mass Storage device through the USB-OTG connector +on the back panel. + +The entire root file system (Linux kernel, libraries) and any user data are +stored on the eMMC. It is partitioned into four partitions: + +1. Boot partition (contains the bootloader). This partition usually does not + require modification. +2. A data partition, mounted in /data. This is the only partition that is not + erased during file system updates. +3. Two identical system partitions (root file systems). These contain the + operating system and the home directory (anything mounted under / that is not + the data or boot partition). The reason there are two of these is to enable + remote updates: An update running on one partition can update the other one + without any effect to the currently running system. Note that the system + partitions are erased during updates and are thus unsuitable for permanently + storing information. + +Note: It is possible to access the currently inactive root file system by +mounting it. After logging into the device using serial console or SSH (see the +following two sections), run the following commands: + + $ mkdir temp + $ mount /dev/mmcblk0p3 temp # This assumes mmcblk0p3 is currently not mounted + $ ls temp # You are now accessing the idle partition: + bin data etc lib media proc sbin tmp usr + boot dev home lost+found mnt run sys uboot var + +The device node in the mount command might differ, depending on which partition +is currently already mounted. + +\section x4xx_getting_started Getting started + +Firstly, download and install UHD on a host computer following \ref page_install +or \ref page_build_guide. The USRP X410 requires UHD version 4.1 or above. + +\subsection x4xx_getting_started_assembling Assembling the X410 + +Inside the kit you will find the X410 and an X410 power supply. Plug these in, +connect the 1GbE RJ45 interface to your network, and power on the device by +pressing the power button. + +\subsection x4xx_network_connectivity Network Connectivity + +Once the X410 has booted, determine the IP address and verify network +connectivity by running `uhd_find_devices` on the host computer: + + $ uhd_find_devices + -------------------------------------------------- + -- UHD Device 0 + -------------------------------------------------- + Device Address: + serial: 1234ABC + addr: 10.2.161.10 + claimed: False + mgmt_addr: 10.2.161.10 + product: x410 + type: x4xx + +By default, an X410 will use DHCP to attempt to find an address. + +At this point, you should run: + + uhd_usrp_probe --args addr=<IP address> + +to ensure functionality of the device. + +Note: If you receive the following error: + + Error: RuntimeError: Graph edge list is empty for rx channel 0 + +then you will need to download a UHD-compatible FPGA as described in +\ref x4xx_updating_fpga or using the following command (it assumes that FPGA +images have been downloaded previously using uhd_images_downloader, or that the +command is run on the device itself): + + uhd_image_loader --args type=x4xx,addr=<ip address>,fpga=X4_200 + +When running on the device, use 127.0.0.1 as the IP address. + +You can now use existing UHD examples or applications (such as +rx_sample_to_file, rx_ascii_art_dft, or tx_waveforms) or other UHD-compatible +applications to start receiving and transmitting with the device. + +\subsection x4xx_getting_started_security Security-related settings + +The X410 ships without a root password set. It is possible to ssh into the +device by simply connecting as root, and thus gaining access to all subsystems. +To set a password, run the command + + $ passwd + +on the device. + +\subsection x4xx_getting_started_serial Serial connection + +It is possible to gain access to the device using a serial terminal +emulator. To do so, the USB debug port needs to be connected to a separate +computer to gain access. +Most Linux, OSX, or other Unix flavours have a tool called 'screen' +which can be used for this purpose, by running the following command: + + $ sudo screen /dev/ttyUSB2 115200 + +In this command, we prepend 'sudo' to elevate user privileges (by default, +accessing serial ports is not available to regular users), we specify the +device node (in this case, `/dev/ttyUSB2`), and the baud rate (115200). + +The exact device node depends on your operating system's driver and other USB +devices that might be already connected. Modern Linux systems offer alternatives +to simply trying device nodes; instead, the OS might have a directory of +symlinks under `/dev/serial/by-id`: + + $ ls /dev/serial/by-id + usb-Digilent_Digilent_USB_Device_2516351DDCC0-if02-port0 + usb-Digilent_Digilent_USB_Device_2516351DDCC0-if03-port0 + +Note: Exact names depend on the host operating system version and may differ. + +The first (with the `if02` suffix) connects to the STM32 microcontroller (SCU), whereas +the second (with the `if03` suffix) connects to Linux running on the RFSoC APU. + + $ sudo screen /dev/serial/by-id/usb-Digilent_Digilent_USB_Device_2516351DDCC0-if03-port0 115200 + +After entering the username `root` (no password is set by default), you should be presented with a shell prompt similar to the following: + + root@ni-x4xx-1234ABC:~# + +On this prompt, you can enter any Linux command available. Using the default +configuration, the serial console will also show all kernel log messages (unlike +when using SSH, for example), and give access to the boot loader (U-boot +prompt). This can be used to debug kernel or bootloader issues more efficiently +than when logged in via SSH. + +\subsubsection x4xx_getting_started_serial_micro Connecting to the microcontroller + +The microcontroller (which controls the power sequencing, among other things) +also has a serial console available. To connect to the microcontroller, use the +other UART device. In the example above: + + $ sudo screen /dev/serial/by-id/usb-Digilent_Digilent_USB_Device_2516351DDCC0-if02-port0 115200 + +It provides a very simple prompt. The command 'help' will list all available +commands. A direct connection to the microcontroller can be used to hard-reset +the device without physically accessing it (i.e., emulating a power button press) +and other low-level diagnostics. For example, running the command `reboot` will +reset the state of the device, and the command `powerbtn` will emulate a button +press, turning the device back on again. + +\subsection x4xx_getting_started_ssh SSH connection + +The USRP X4xx-Series devices have two network connections: The dual QSFP28 +ports, and an RJ-45 connector. The latter is by default configured by DHCP; by +plugging it into into 1 Gigabit switch on a DHCP-capable network, it will get +assigned an IP address and thus be accessible via ssh. + +In case your network setup does not include a DHCP server, refer to the section +\ref x4xx_getting_started_serial. A serial login can be used to assign an IP address manually. + +After the device obtained an IP address you can log in from a Linux or OSX +machine by typing: + + $ ssh root@ni-x4xx-1234ABC # Replace with your actual device name! + +Depending on your network setup, using a `.local` domain may work: + + $ ssh root@ni-x4xx-1234ABC.local + +Of course, you can also connect to the IP address directly if you know it (or +set it manually using the serial console). + +Note: The device's hostname is derived from its serial number by default +(`ni-x4xx-$SERIAL`). You can change the hostname by modifying the `/etc/hostname` +file and rebooting. + +On Microsoft Windows, the connection can be established using a tool such as +PuTTY, by selecting a username of root without password. + +Like with the serial console, you should be presented with a prompt like the +following: + + root@ni-x4xx-1234ABC:~# + +\subsection x4xx_updating_fpga Updating the FPGA + +The FPGA can be updated simply using `uhd_image_loader`: + + uhd_image_loader --args type=x4xx,addr=<IP address of device> --fpga-path <path to .bit> + +or + + uhd_image_loader --args type=x4xx,addr=<IP address of device>,fpga=FPGA_TYPE + +A UHD install will likely have pre-built images in `/usr/share/uhd/images/`. +Up-to-date images can be downloaded using the `uhd_images_downloader` script: + + uhd_images_downloader + +will download images into `/usr/share/uhd/images/` (the path may differ, +depending on how UHD was installed). + +Also note that the USRP already ships with compatible FPGA images on the device - +these images can be loaded by SSH'ing into the device and running: + + uhd_image_loader --args type=x4xx,mgmt_addr=127.0.0.1,fpga=X4_200 + +\subsubsection x4xx_updating_fpga_types FPGA Image Flavors + +Unlike the USRP X310 or other third-generation USRP devices, the FPGA image +flavors do not only encode how the QSFP28 connectors are configured, but also +which master clock rates are available. This is because the data converter +configuration is part of the FPGA image (the ADCs/DACs on the X410 are on the +same die as the FPGA). +The image flavors consist of two short strings, separated by an underscore, e.g. +`X4_200` is an image flavor which contains 4x10GbE, and can handle an analog +bandwidth of 200 MHz. The first two characters describe the configuration of +the QSFP ports: 'X' stands for 10 GbE, 'C' stands for 100 GbE. See the following +table for more details. + +| FPGA Image Flavor | QSFP28 Port 0 Interface | QSFP28 Port 1 Interface | +|---------------------|-------------------------|-------------------------| +| X1_100 | 1x 10GbE (Lane 0) | N/C | +| X4_{100, 200} | 4x 10 GbE | N/C | +| XG_{100, 200} | 1x 10GbE (Lane 0) | 1x 10GbE (Lane 0) | +| X4_{100, 200} | 4x 10GbE (All Lanes) | N/C | +| X4C_{100, 200} | 4x 10GbE (All Lanes) | 100GbE | +| C1_400 | 100GbE | N/C | +| CG_{100, 400} | 100GbE | 100GbE | + +The analog bandwidth determines the available master clock rates. As of UHD 4.1, +only the X4_200 image is shipped with UHD, which allows a a 245.76 MHz or +250 MHz master clock rate. The other images are considered experimental (unsupported). + +\section x4xx_updating_filesystems Updating Filesystems + +Mender is a third-party software that enables remote updating of the root file +system without physically accessing the device (see also the [Mender +website](https://mender.io)). Mender can be executed locally on the device, or a +Mender server can be set up which can be used to remotely update an arbitrary +number of USRP devices. Mender servers can be self-hosted, or hosted by Mender +(see [mender.io](https://mender.io) for pricing and availability). + +When updating the file system using Mender, the tool will overwrite the root +file system partition that is not currently mounted (note: the onboard flash +storage contains two separate root file system partitions, only one is ever used +at a single time). Any data stored on that partition will be permanently lost, +including the currently loaded FPGA image. After updating that partition, it +will reboot into the newly updated partition. Only if the update is confirmed by +the user, the update will be made permanent. This means that if an update fails, +the device will be always able to reboot into the partition from which the +update was originally launched (which presumably is in a working state). Another +update can be launched now to correct the previous, failed update, until it +works. + +To initiate an update from the device itself, download a Mender artifact +containing the update itself. These are files with a `.mender` suffix. They can +be downloaded by using the uhd_images_downloader utility: + + $ uhd_images_downloader -t mender -t x4xx + +Append the `-l` switch to print out the URLs only: + + $ uhd_images_downloader -t mender -t x4xx -l + +Then run mender on the command line: + + $ mender install /path/to/latest.mender + +The artifact can also be stored on a remote server: + + $ mender install http://server.name/path/to/latest.mender + +This procedure will take a while. If the new filesystem requires an update to +the MB CPLD, see \ref x4xx_updating_cpld before proceeding. After mender has +logged a successful update, reboot the device: + + $ reboot + +If the reboot worked, and the device seems functional, commit the changes so +the boot loader knows to permanently boot into this partition: + + $ mender commit + +To identify the currently installed Mender artifact from the command line, the +following file can be queried: + + $ cat /etc/mender/artifact_info + +If you are running a hosted server, the updates can be initiated from a web +dashboard. From there, you can start the updates without having to log into the +device, and can update groups of USRPs with a few clicks in a web GUI. The +dashboard can also be used to inspect the state of USRPs. This is a simple way +to update groups of rack-mounted USRPs with custom file systems. + + +\subsection x4xx_resetting_boot_environment Resetting Boot Environment + +In the event that the new system has problems booting, you can attempt to reset +the boot environment using the following instructions. + +First, connect to the USB serial console at a baud rate of 115200. Boot the +device, and stop the boot sequence by typing `noautoboot` at the prompt. Then, +run the following commands in the U-boot command prompt: + + env default -a + env save + reset + +The last command will reboot the USRP. If the `/` filesystem was mounted to `mmcblk0p2` as +described in \ref x4xx_updating_filesystems, then stop the boot again and run: + + run altbootcmd + +Otherwise, let the boot continue as normal. + +\subsection x4xx_updating_cpld Updating MB CPLD + +Caution! Updating the MB CPLD has the potential to brick your device if done +improperly. + +You can update the MB CPLD by running the following command on the X410: + + python3 /usr/lib/python3.7/site-packages/usrp_mpm/periph_manager/x4xx_update_cpld.py --file=<path to cpld-x410.rpd> + +Filesystems will usually contain a compatible `cpld-x410.rpd` file at +`/lib/firmware/ni/cpld-x410.rpd`. If you're installing a new filesystem via +mender, you may have to mount the new filesystem (before you boot into it) in +order to access the new firmware: + + mkdir /mnt/other + mount /dev/mmcblk0p3 /mnt/other + cp /mnt/other/lib/firmware/ni/cpld-x410.rpd ~ + umount /mnt/other + +Note that the other filesystem may be either `/dev/mmcblk0p2` or `/dev/mmcblk0p3`. + +If the `x4xx_update_cpld.py` script returns an error, diagnose the error before +proceeding. + +After updating the MB CPLD, a power cycle is required for the changes to take +effect. Shut down the device using: + + shutdown -h now + +and then un-plug, wait several seconds, then re-plug the power to the USRP. + +Alternatively, in lieu of physical access, the microcontroller can be accessed +using the USB serial port as described in \ref x4xx_getting_started_serial, and +can be used to reboot the device: + + reboot + powerbtn + + +\subsection x4xx_updating_scu Updating the SCU + +The writable SCU image file is stored on the filesystem under +`/lib/firmware/ni/ec-titanium-revX.RW.bin` (where X is a revision compatibility +number). To update, simply replace the `.bin` file with the updated version and +reboot. + +\subsection x4xx_accessing_emmc_usb USB access to eMMC + +While Mender should be used for routine filesystem updates (see \ref +x4xx_updating_filesystems), it is also possible to access the X410's internal +eMMC from an external host over USB. This allows accessing or modifying the +filesystem, as well as the ability to flash the device with an entirely new +filesystem. + +In order to do so, you'll need an external computer with two USB ports, and two +USB cables to connect the computer to your X410. The instructions below assume +a Linux host. + +First, connect to the APU serial console at a baud rate of 115200. Boot the +device, and stop the boot sequence by typing `noautoboot` at the prompt. Then, +run the following command in the U-boot command prompt: + + ums 0 mmc 0 + +This will start the USB mass storage gadget to expose the eMMC as a USB mass +storage device. You should see a spinning indicator on the console, which +indicates the gadget is active. + +Next, connect your external computer to the X410's USB to PS port using an OTG +cable. Your computer should recognize the X410 as a mass storage device, and you +should see an entry in your kernel logs (`dmesg`) that looks like this: + + usb 3-1: New USB device found, idVendor=3923, idProduct=7a7d, bcdDevice= 2.23 + usb 3-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0 + usb 3-1: Product: USB download gadget + usb 3-1: Manufacturer: National Instruments + sd 6:0:0:0: [sdc] 30932992 512-byte logical blocks: (15.8 GB/14.8 GiB) + sdc: sdc1 sdc2 sdc3 sdc4 + sd 6:0:0:0: [sdc] Attached SCSI removable disk + +The exact output will depend on your machine, but from this log you can see that +the X410 was recognized and `/dev/sdc` is the block device representing the +eMMC, with 4 partitions detected (see \ref x4xx_overview_storage for details on +the partition layout). + +It is now possible to treat the X410's eMMC as you would any other USB drive: +the individual partitions can be mounted and accessed, or the entire block +device can be read/written. + +Once you're finished accessing the device over USB, the u-boot gadget may be +stopped by hitting Ctrl-C at the APU serial console. + +\subsubsection x4xx_flash_emmc Flashing eMMC + +Once the X410's eMMC is accessible over USB, it's possible to write the +filesystem image using `bmaptool`. You can obtain the latest filesystem image by +running: + + uhd_images_downloader -t sdimg -t x4xx + +The output of this command will indicate where the downloaded image can be +found. + +Run: + + sudo bmaptool /path/to/usrp_x4xx_fs.sdimg.bz2 /dev/sdX + +to flash the eMMC with this image (replacing /dev/sdX with the block device +of the X410's eMMC as indicated by your kernel log). + +\subsection x4xx_jtag_boot Booting X410 over JTAG + +If the X410 is no longer able to boot from eMMC, it is possible to boot the +device into u-boot over JTAG. This will allow the filesystem to be reflashed +using the process described in \ref x4xx_accessing_emmc_usb. + +In order to boot the X410 over JTAG, you'll first need to have either the +Xilinx SDK, or the freely available Vivado Lab Edition. The following steps +require that one of these is installed and available in your environment. + +For convenience, pre-compiled bootloader binaries are provided, along with a +script to handle downloading these into the X410's memory and booting the +device. These are included in the sdimg package with the name +`usrp_x4xx_recovery.zip`, which can be downloaded using: + + uhd_images_downloader -t sdimg -t x4xx + +To boot the device over JTAG, first ensure the X410 is powered off, and that you +have serial consoles open to both the SCU and the APU. Configure the device to +boot over JTAG by running `zynqmp bootmode jtag` on the SCU console, and press +the power button (or run the `powerbtn` command at the SCU console). At this +point, the device is powered on and the APU is held in reset. + +Run `xsdb boot_u-boot.tcl` in the directory where you've extracted the +bootloader binaries. This will download the various binaries needed to boot the +device into memory, and bring the APU out of reset. Once this script completes, +you should see u-boot loading on the APU serial console. From here, you can +follow the steps in \ref x4xx_accessing_emmc_usb to reflash the eMMC. + +After the eMMC has been flashed, run `reboot` at the SCU console to reset the +device and return back to the default boot mode. A subsequent press of the power +button will boot the device from the eMMC. + +\section x4xx_usage Using a USRP X4x0 from UHD + +Like any other USRP, all X4x0 USRPs are controlled by the UHD software. To +integrate a USRP X4x0 into your C++ application, you would generate a UHD +device in the same way you would for any other USRP: + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} +auto usrp = uhd::usrp::multi_usrp::make("type=x4xx"); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For a list of which arguments can be passed into make(), see Section +\ref x4xx_usage_args. + +\subsection x4xx_usage_args Device Arguments + + Key | Description | Example Value +-----------------------|------------------------------------------------------------------------------|--------------------- + addr | IPv4 address of primary SFP+ port to connect to. | addr=192.168.30.2 + second_addr | IPv4 address of secondary SFP+ port to connect to. | second_addr=192.168.40.2 + mgmt_addr | IPv4 address or hostname to which to connect the RPC client. Defaults to `addr'.| mgmt_addr=ni-sulfur-311FE00 + find_all | When using broadcast, find all devices, even if unreachable via CHDR. | find_all=1 + master_clock_rate | Master Clock Rate in Hz. | master_clock_rate=250e6 + serialize_init | Force serial initialization of daughterboards. | serialize_init=1 + skip_init | Skip the initialization process for the device. | skip_init=1 + time_source | Specify the time (PPS) source. | time_source=internal + clock_source | Specify the reference clock source. | clock_source=internal + ref_clk_freq | Specify the external reference clock frequency, default is 10 MHz. | ref_clk_freq=20e6 + discovery_port | Override default value for MPM discovery port. | discovery_port=49700 + rpc_port | Override default value for MPM RPC port. | rpc_port=49701 + +\subsection x4xx_usage_gps GPS + +The X410 includes a Jackson Labs LTE-Lite GPS module. Its antenna port is on the +rear panel (see \ref x4xx_overview_panels). When the X410 has access to GPS +satellite signals, it can use this module to read out the current GPS time and +location as well as to discipline an onboard OCXO. + +To use the GPS as a clock and time reference, simply use `gpsdo` as a clock or +time source. Alternatively, set `gpsdo` as a synchronisation source: + +~~~{.cpp} +// Set clock/time individually: +usrp->set_clock_source("gpsdo"); +usrp->set_time_source("gpsdo"); +// This is equivalent to the previous commands, but faster, as it sets +// both settings simultaneously and avoids duplicating settings that are shared +// between these calls. +usrp->set_sync_source("clock_source=gpsdo,time_source=gpsdo"); +~~~ + +Note the GPS module is not always enabled. Its power-on status can be queried +using the `gps_enabled` GPS sensor (see also \ref x4xx_usage_sensors). When +disabled, none of the sensors will return useful (if any) values. + +When selecting `gpsdo` as a clock source, the GPS will always be enabled. Note +that acquiring a GPS lock can take some time after enabling the GPS, so if a UHD +application is enabling the GPS dynamically, it might take some time before a +GPS lock is reported. + +\subsection x4xx_usage_gpio Front-Panel Programmable GPIOs + +The USRP X410 has two HDMI front-panel connectors, which are connected to the +FPGA. + +Support for using these with UHD is not yet available. + + +\subsection x4xx_usage_subdevspec Subdev Specifications + +The RF ports on the front panel of the X410 + ZBX correspond to the following +subdev specifications: + +Label | Subdev Spec +------------|------------ +DB 0 / RF 0 | A:0 +DB 0 / RF 1 | A:1 +DB 1 / RF 0 | B:0 +DB 1 / RF 1 | B:1 + +The subdev spec slot identifiers "A" and "B" are not reflected on the front panel. +They were set to match valid subdev specifications of previous USRPs, maintaining +backward compatibility. + +These values can be used for uhd::usrp::multi_usrp::set_rx_subdev_spec() and +uhd::usrp::multi_usrp::set_tx_subdev_spec() as with other USRPs. + +\subsection x4xx_usage_sensors The Sensor API + +Like other USRPs, the X4x0 series have daughterboard and motherboard sensors. +For daughterboard sensors, cf. \ref zbx_sensors. + +When using uhd::usrp::multi_usrp, the following API calls are relevant to +interact with the motherboard sensor API: + +- uhd::usrp::multi_usrp::get_mboard_sensor_names() +- uhd::usrp::multi_usrp::get_mboard_sensor() + +The following motherboard sensors are always available: + +- `ref_locked`: This will check that all the daughterboards have locked to the + external reference. +- `temp_fpga`: The temperature of the RFSoC die itself. +- `temp_main_power`: The temperature of the PM-BUS devices which supply 0.85V + to the RFSoC. +- `temp_scu_internal`: The internal temperature reading of the STM32 microcontroller. +- `fan0`: Fan 0 speed (RPM) +- `fan1`: Fan 1 speed (RPM) + +The GPS sensors will return empty values if the GPS is inactive (note it may be +inactive when using a different clock than `gpsdo`, see also \ref x4xx_usage_gps). +There are two types of GPS sensors. The first set requires an active GPS module +and is acquired by calling into gpsd on the embedded device, which in turn +communicates with the GPS via a serial interface. For this reason, these sensors +can take a few seconds before returning a valid value: + +- `gps_time`: GPS time in seconds since the epoch +- `gps_tpv`: A TPV report from GPSd serialized as JSON +- `gps_sky`: A SKY report from GPSd serialized as JSON +- `gps_gpgga`: GPGGA string + +The seconds set of GPS sensors probes pins on the GPS module. They are all boolean +sensors values. If the GPS is disabled, they will always return false. + +- `gps_enabled`: Returns true if the GPS module is powered on. +- `gps_locked`: Returns the state of the 'LOCK_OK' pin. +- `gps_alarm`: Returns the state of the 'ALARM' pin. +- `gps_warmed_up`: Returns the state of the 'WARMUP_TRAINING' pin. Indicates + warmup phase, can be high for minutes after enabling GPS. +- `gps_survey`: Returns the state of the 'SURVEY_ACTIVE' pin. Indicates state of + auto survey process. Indicates that module is locked to GPS, and + that there are no events on the GPS module pending. + +\subsection x4xx_usage_rearpanelleds Rear Panel Status LEDs + +The USRP X410 is equipped with three user-configurable LEDs located on the +device's rear panel: `LED 0`, `LED 1`, and `LED 2`. +Each LED supports four different states: Off, Green, Red, and Amber. +Different behaviors are supported for each LED (see \ref x4xx_usage_rearpanelleds_suppbeh +below) which are user-configurable. Refer to \ref x4xx_usage_rearpanelleds_defaults +for default functionality. + +<img src="x4xx_rearpanel_status_leds.png" align="right"/> + +\subsubsection x4xx_usage_rearpanelleds_suppbeh Supported LED Behaviors + +- `activity`: flash green LED for CPU activity +- `emmc`: flash green LED for eMMC activity +- `heartbeat`: flash green LED with a heartbeat +- `fpga`: change LED to green when FPGA is loaded +- `netdev <interface>`: green LED indicates interface link, amber indicates activity + - Where `<interface>` is the name of any network interface (e.g. `eth0`) +- `none`: LED is constantly off +- `panic`: red LED turns on when kernel panics +- `user0`: off, green, red or amber LED state is controlled by FPGA application, User LED 0 +- `user1`: off, green, red or amber LED state is controlled by FPGA application, User LED 1 +- `user2`: off, green, red or amber LED state is controlled by FPGA application, User LED 2 + +\subsubsection x4xx_usage_rearpanelleds_defaults Behavior + +| LED Number | Default Behavior | +|------------|------------------| +| LED 0 | `heartbeat` | +| LED 1 | `fpga` | +| LED 2 | `emmc` | + +A user may change the X410 LEDs' default behavior via running a utility on the +on-board ARM processor (Linux). + +### Temporarily change the LED Behavior + +1. Establish a connection (serial or SSH) to the X410's Linux terminal. +2. Use the `ledctrl` utility to configure each LED based on desired supported behavior + + ledctrl <led> <command> + +Where `<led>` valid options are: `led0`, `led1`, and `led2`. These options +correspond to the rear panel labels. +The `<command>` valid options are listed in the \ref x4xx_usage_rearpanelleds_suppbeh +section above, with their corresponding description. + +Examples: + + root@ni-x4xx-1111111:~# ledctrl led0 user0 + +Sets the X410's LED 0 to be controlled via the FPGA application using "User LED 0". + +### Persistently change the LED + +The above method will not persist across reboots. In order to persist the +changes, modify the ledctrl service unit files which are run by the init +system at boot. These files can be found on a running filesystem at, e.g., +`/lib/systemd/system/ledctrl-led0.service`. + + +### Using FPGA LED Control + +When selecting `user0`, `user1`, and/or `user2` as LED behavior (see +\ref x4xx_usage_rearpanelleds_suppbeh above), the FPGA application gains control +of that given LED. The following paragraph describes how the FPGA application can +control the state for each setting. + +FPGA application access to User LED 0-2 requires modification of the FPGA source +code and is achieved directly via Verilog, using a 2-bit vector to control the state. + +Below is an excerpt of the FPGA source code, setting the `user0`, `user1`, and +`user2` values to green, red, and amber respectively. +~~~{.v} + // Rear panel LEDs control + // Each LED is comprised of a green (LSB) and a red (MSB) LED + // which the user can control through a 2-bit vector once fabric + // LED control is configured on the X410's Linux shell. + localparam LED_OFF = 2'b00; + localparam LED_GREEN = 2'b01; + localparam LED_RED = 2'b10; + localparam LED_AMBER = 2'b11; + + wire [1:0] user_led_ctrl [0:2]; + assign user_led_ctrl[0] = LED_GREEN; + assign user_led_ctrl[1] = LED_RED; + assign user_led_ctrl[2] = LED_AMBER; +~~~ + +\section x4xx_too Theory of Operation + +\image html x4xx_block_diagram.svg "X4x0 Motherboard Block Diagram" width=60% + +The USRP X410 has three processors on the motherboard: The RFSoC, the SCU, and a +control CPLD. The RFSoC does the bulk of the work. It houses the programmable +logic (PL), the APU and RPU processors (the former running the embedded Linux +system), connects to the data ports (RJ45, QSFP28) and also includes RF data +converters (ADC/DAC) which are exposed to the daughterboards through a connector. +The FPGA configuration for the RFSoC can be found in the source code repository +under `fpga/usrp3/top/x400`. The OpenEmbedded Linux configuration can be found +on a [separate repository](https://github.com/EttusResearch/meta-ettus). + +The SCU is a microcontroller running a baremetal control stack. It controls the +power sequencing, the fan speeds, connects various peripherals and sensors to +the Linux kernel, and performs other low-level tasks. It can be accessed through +a serial console directly from the back-panel. This can be a useful debugging +tool if the device is not responding to other inputs, and can be used to +power-cycle and reboot the USRP. It is connected to the RFSoC using an I2C +interface. + +The motherboard control CPLD performs various control tasks, such as controlling +the clocking card and the DIO connector (note that the DIO pins are also available +without using the CPLD, which is the normal case when programming the pins for +an application with higher rates and precise timing). +The motherboard CPLD is accessible from the RFSoC through a SPI interface, and +also acts as a SPI mux for accessing peripherals such as the clocking card. +Access to the motherboard CPLD from within a UHD session always goes through MPM, +meaning it is not used for high-speed or high-precision control. +Its source code can be found in the UHD source code repository under +`fpga/usrp3/top/x400/cpld`. + +The RJ45 Ethernet connector is connected directly to the PS and is made available +in Linux as a regular Ethernet interface. It is possible to stream data to and +from the FPGA, but the data is tunneled through the operating system, which +makes it a relatively slow interface. +The QSFP28 connectors are directly connected to the RFSoC transceivers. Different +FPGA images configure these either as 10 GbE or 100 GbE interfaces. It +is possible to access the PS through these interfaces (when configured as Ethernet +interfaces), but their main purpose is to stream data from and to the FPGA at +high rates. + +\subsection x4xx_too_clocking Clocking + +The clocking architecture of the motherboard is spread out between a clocking +auxiliary board, which contains an OCXO (either GPS-disciplined or controlled by +a DAC), but also connects an external reference to the motherboard. Furthermore, +it houses a PLL for deriving a clock from the network (eCPRI). + +The motherboard itself has two main PLLs for clocking purposes: The Sample PLL +(also SPLL) is used to create all clocks used for RF-related purposes. It creates +the sample clock (a very fast clock, ~3 GHz) and the PLL reference clock (PRC) +which is used as a reference for the LO synthesizers (50-64 MHz). + +Its input is called the base reference clock (BRC). It has four possible sources: +- The OCXO, which always produces a 10 MHz reference clock. When the clock source + is set to `internal`, this OCXO is only disciplined by a DAC (control of that + DAC is not exposed in this version of UHD), but there is no further control + loop. By selecting `gpsdo` as a clock source, a GPS module is used to + discipline the OCXO (see also \ref x4xx_usage_gps). +- The external reference input SMA port. When an external reference is used (by + selecting `external` as the clock source), the X410 assumes a 10 MHz reference + clock (it is possible to drive the device with a different external clock + frequency by providing the `ref_clk_freq` device argument, but this is not a + supported use case for this UHD version). Note the clocking card can also import + a PPS signal (when setting the time source to `external`) as well as export it. +- The eCPRI PLL (when using the `nsync` clock source). It will generate a 10 MHz + BRC. Note the intention is to use this for scenarios where the clock is derived + from the network port (e.g., eCPRI), but this version of UHD does not include + such a feature. +- The reference PLL, when the clock source is set to `mboard` (this is a 25 MHz + BRC). This is not a common use case, as it is not possible to synchronize the + on-board clock. This is the only clock that does not come from the auxiliary + clocking board. + +The reference PLL (RPLL) produces clocks that are consumed by the GTY banks (for +Ethernet), as well as the on-board BRC. By default, its reference is a fixed +100 MHz clock, but it can also be driven by the eCPRI PLL. + +The eCPRI PLL is typically driven by a clock derived from the GTY banks, which +is the assumption if the clock source is set to 'nsync'. The eCPRI PLL can also be +driven from the RFSoC ("fabric clock") for testing purposes. + +The master clock rate (MCR) depends on the sample clock rate. It also depends on +the RFDC settings, which are different for different flavours of FPGA images (see +also \ref x4xx_updating_fpga_types). The actual clock running at this frequency +is generated digitally, within the RFSoC, and is derived from the SPLL clock. + +Block diagram: +``` + ┌────────────────────────────────────────────────────────┐ + │ Clocking Aux Board │ + │ ┌──────┐ ┌───────┐ ┌────────┐ │ + │ │GPSDO │ │ DAC │ │External│ │ + │ └─────┬┘ └─┬─────┘ └───┬────┘ │ + │ ┌v────v┐ │ ┌──────┐ │ + │ │ OCXO │ │ │ <───────┼──┐ + │ └──┬───┘ │ ┌───┐ │ MUX <───────┼─┐│ + │ │ │ │ │ └──┬───┘ │ ││ + │ ┌────v──────────────v───v─┐ │ ┌───────v───┐ │ ││ + │ │ │ └─┤eCPRI PLL │ │ ││ + │ └┐ MUX ┌┘ │LMK05318 │ │ ││ + │ └─┐ ┌─┘ │ │ │ ││ + │ └─┬─────────────────┘ └──┬────────┘ │ ││ + │ │ │ │ ││ + └───────────┼───────────────────────────┼────────────────┘ ││ + │ │ ││ + │ ┌─────────────┐ │ ││ + ┌──v──v┐ │ │ ││ + │ MUX │ │ │ ┌───── 100 MHz ││ + └──┬───┘ │ │ │ ││ + │Base Ref. Clock │ │ │ ││ + ┌───────v───────┐ │ ┌───────v──v──┐ ││ + │ Sample PLL │ └──┤Reference PLL│ ││ + │ LMK04832 │ │LMK03328 │ ││ + └──┬─────────┬──┘ └────┬────────┘ │└─ PL/Fabric Clock + │ │ │ │ + v v v │ + Sample PLL Reference GTY Banks GTY Recovered + Clock Clock Clock +``` + +Note that this section does not cover every single clock signal present on the +X410, but mainly those clock signals relevant for the operation of the RF +components. Refer to the schematic for more details. + +*/ +// vim:ft=doxygen: diff --git a/host/docs/zbx.dox b/host/docs/zbx.dox new file mode 100644 index 000000000..76c5585f6 --- /dev/null +++ b/host/docs/zbx.dox @@ -0,0 +1,469 @@ +/*! \page page_zbx ZBX Daughterboard + +\tableofcontents + +\section zbx_overview Overview + +The ZBX daughterboard is a two-channel superheterodyne transceiver with a focus +of telecommunication applications in the frequency range below 8 GHz. It supports +analog bandwidths of up to 400 MHz. + +The ZBX daughterboard is designed for use with the Ettus USPR X410. + +Feature list: +- Frequency range (Tx and Rx): 1 MHz - 7.2 GHz (Note: Tune range extends to 8 GHz) +- Maximum analog bandwidth: 400 MHz +- Gain range: 0-60 dB. + - Note: Rx gain range is reduced to 0-38 dB for frequencies below 500 MHz. +- On-board CPLD for high flexibility +- Maximum output power: 5-20 dBm (depending on frequency) +- Maximum input power limit: +15 dBm + +\section zbx_too Theory of Operations + +The ZBX daughterboard has two transceiver chains. The following simplified block +diagram shows their structure: + +\image html ZBX_simplified_blockdiagram.svg "ZBX Block Diagram" + +It is a superheterodyne transceiver with up to two IF stages. The second IF +stage is only used for center frequencies below 3 GHz. Above that frequency, the +desired center frequency becomes the first intermediate frequency (IF1). The +second LO stage is always enabled, and moves the IF to a value between 1 and 2 +GHz. The USRP ADC/DAC (running at a sampling rate of approx. 3 GHz) will sample +the IF directly, and downconvert to or from DC digitally. + +The TX and RX paths are almost symmetric, with slight variations on the various +frequency bands. The various gain stages are spread out along the TX and RX +paths (see also \ref zbx_gain_control, note that the TX path includes selectable +amplifiers as well as DSAs). All LO synthesizers are identical (LMX2572). + +\subsection zbx_too_cpld Digital Control + +For digital controls, the ZBX includes a CPLD (its source code is part of the +UHD repository, and can be found under `fpga/usrp3/top/x400/dboards/zr/cpld/`). +The CPLD is controlled via registers. Its register space is exposed as a subset +of the Radio RFNoC block register space (starting at address 0x80000). The CPLD +is used to control all switches, DSAs, amplifiers, LEDs, LO synthesizers and +power rails. The CPLD also controls state-dependent behaviour of the ZBX (i.e., +behaviour depending on the RX/TX state). For this purpose, ATR signals from the +FPGA are routed to the CPLD. Parts of the CPLD feature set are also described in +\ref zbx_gain_control. + + +\subsection zbx_too_lo_control LO Control + +The normal operation of the ZBX daughterboard is to simply tune it to a desired +center frequency, and UHD will internally calculate frequencies for the +individual LOs as well as the NCO. UHD uses a few rules when calculating LO and +NCO frequencies: + +- To simplify the algorithms, LO frequencies are quantized to multiples + of the LO reference frequency (which itself depends on the master clock rate). +- To reduce LO spurs, the LO synthesizer outputs are filtered with an analog + bandpass filter with a minimum frequency of 3.2 GHz. +- To avoid <em>injection locking</em> (an effect where nearby synthesizers + influence each other), UHD is programmed to choose different frequencies for + the various synthesizers. When programming the two channels to the same + frequency, the LOs will thus intentionally run at different frequencies. The + combination of the various LOs and the NCO frequency still results in the same + center frequency. + +The state of individual LOs can be queried and configured independently. Use the +following API calls to do so: +- multi_usrp API: + - uhd::usrp::multi_usrp::get_rx_lo_freq() + - uhd::usrp::multi_usrp::get_rx_lo_freq_range() + - uhd::usrp::multi_usrp::set_rx_lo_freq() + - uhd::usrp::multi_usrp::get_tx_lo_freq() + - uhd::usrp::multi_usrp::get_tx_lo_freq_range() + - uhd::usrp::multi_usrp::set_tx_lo_freq() +- RFNoC API: + - uhd::rfnoc::radio_control::get_rx_lo_freq() + - uhd::rfnoc::radio_control::get_rx_lo_freq_range() + - uhd::rfnoc::radio_control::set_rx_lo_freq() + - uhd::rfnoc::radio_control::get_tx_lo_freq() + - uhd::rfnoc::radio_control::get_tx_lo_freq_range() + - uhd::rfnoc::radio_control::set_tx_lo_freq() + +Note that manually modifying LOs is considered advanced behaviour, and may result +in a bad state of the device. To undo manual changes, use the regular API calls +to set a center frequency. + +\section zbx_ant_ports Antenna Ports + +The ZBX has two SMA ports per channel, called "TX/RX0" and "RX1". +In addition, the antenna values can be set to "CAL_LOOPBACK" +to loop back the Tx path into the Rx path (this is sometimes required for +calibration purposes). The Rx antenna value can also be set to "TERMINATION" to +terminate the Rx path. + +Use the uhd::usrp::multi_usrp::get_rx_antennas() or uhd::usrp::multi_usrp::get_tx_antennas() +API calls to enumerate the valid antenna names. When using RFNoC API, use the +uhd::rfnoc::radio_control::get_rx_antennas() and +uhd::rfnoc::radio_control::get_tx_antennas() calls, respectively. + +\section zbx_phase_alignment Phase Alignment + +Like the UBX and SBX daughterboards, the ZBX allows to be phase-aligned. This is +done by setting a command time before tuning the individual channels: +~~~{.cpp} +// Assume that `usrp` is a multi_usrp object +// 1) Set a command time in the future, e.g. 500ms from now: +usrp->set_command_time(usrp->get_time_now() + .5); +// 2) Tune to a new frequency on all channels, e.g., 2 GHz: +usrp->set_rx_freq(2e9); // The ALL_CHANS argument is implied here +// 3) Wait until we're past the command time: +std::this_thread::sleep_for(500ms); +// Channel phases are now at a deterministic offset. Repeating this procedure +// will lead to the same offset. +~~~ + +\section zbx_pwr_cal Power Calibration + +The ZBX supports the UHD power API (see also \ref page_power). UHD ships with +nominal calibration data which will allow setting the reference power levels +without previously manually calibrating the device. + +\section zbx_sensors Sensors + +Every channel has three "locked" sensors for the LO stages (`lo1_locked`, +`lo2_locked`, and `nco_locked`). A "virtual" sensor called `lo_locked` confirms +that all LOs that are currently engaged are locked. The "NCO lock" sensor is a +special case: The NCO is not on the daughterboard (it is part of the +RFSoC FPGA), but to simplify the API it was placed together with the LO lock +sensors. Unlike the (analog) synthesizers on the daughterboard, the NCO "unlock" +is not used to signify a loss of reference lock, but to signal that the NCO is +still in reset. + +Additionally, the ZBX has a temperature sensors `temperature`. While the UHD +API allows addressing a sensors based on direction (RX/TX) and channel (0/1), +there is only one physical temperature sensor, and it will return the same value +regardless of which channel or direction is selected. + +The following API calls can be used to enumerate available sensors, and query +their values: +- multi_usrp API: + - uhd::usrp::multi_usrp::get_rx_sensor_names() + - uhd::usrp::multi_usrp::get_rx_sensor() + - uhd::usrp::multi_usrp::get_tx_sensor_names() + - uhd::usrp::multi_usrp::get_tx_sensor() +- RFNoC API: + - uhd::rfnoc::radio_control::get_rx_sensor_names() + - uhd::rfnoc::radio_control::get_rx_sensor() + - uhd::rfnoc::radio_control::get_tx_sensor_names() + - uhd::rfnoc::radio_control::get_tx_sensor() + +\section zbx_gain_control Gain Control + +The ZBX has a sophisticated gain control, capable of controlling either an +overall gain, or manually controlling its individual gain stages. Furthermore, +the onboard CPLD can store gain tables as well, which can be accessed from other +RFNoC blocks via RFNoC commands. + +The TX path has three gain-related components: Two DSAs, as well an amplifier path. +The former have an individual gain range of 31 dB. The amplifier path allows +selecting one of two amplifiers, one with a nominal gain of 14 dB for lower +frequencies, and one with a nominal gain of 21 dB for higher frequencies. Their +actual amplification values depend on the specific frequency, and may also vary +from device to device. The amplifiers can be bypassed. + +The RX path consists of four DSAs, each with a gain range of 15 dB. + +Different gain behaviours of the ZBX daughterboard are controlled by <em>gain +profiles</em>, which may be set independently for TX and RX. Different gain +profiles have different API behaviours as explained in the rest of this section. + +To switch between gain profiles, use the uhd::usrp::multi_usrp::set_tx_gain_profile() +or uhd::usrp::multi_usrp::set_rx_gain_profile() API calls. When using the RFNoC +API, use the uhd::rfnoc::radio_control::set_tx_gain_profile() or +uhd::rfnoc::radio_control::set_rx_gain_profile() API calls. The main difference +between these APIs is that the multi_usrp API calls allow a default channel +value, which the RFNoC API calls do not. + +As with all other devices, the following API calls set or query gain values: +- Multi USRP: + - uhd::usrp::multi_usrp::set_tx_gain() + - uhd::usrp::multi_usrp::get_tx_gain() + - uhd::usrp::multi_usrp::set_rx_gain() + - uhd::usrp::multi_usrp::get_rx_gain() +- RFNoC + - uhd::rfnoc::radio_control::set_tx_gain() + - uhd::rfnoc::radio_control::get_tx_gain() + - uhd::rfnoc::radio_control::set_rx_gain() + - uhd::rfnoc::radio_control::get_rx_gain() + +These API calls come in different flavours, with an optional 'gain name' argument. +Note the multi_usrp API calls default to setting the overall gain value, which +is not allowed in all gain profiles. All API calls have a corresponding API call +to query the allowable gain range. + +\b Note: The RX gain range is not consistent on ZBX. Below 500 MHz, the gain +range is reduced to 0-38 dB. It is recommended to use get_rx_gain_range() to +query the currently valid gain range. + +\b Note: Some gain profiles require changing on both TX and RX. For example, +changing from `default` to `table_noatr` must happen on TX and RX at the same +time. UHD will automatically change the gain profile accordingly. It is therefore +recommended to call get_rx_gain_profile() or get_tx_gain_profile() to verify the +correct gain profile when in doubt. + +\subsection zbx_gain_default Default Gain Profile + +This gain profile is active by default. It allows setting a single, scalar gain +value. UHD will internally use a gain table to linearize the overall gain (meaning +that a 1 dB gain increase will also increase the transmit or receive power by 1 dB). +However, the UHD-internal gain table is not calibrated per-device, nor does it +take into account temperature or other changes. + +In this gain mode, it is not possible to set the individual gain stages directly, +but it is possible to read them back. This may be helpful when trying to fine-tune +gain settings in software. + +~~~{.cpp} +// Assumption: 'usrp' is a multi_usrp object +usrp->set_tx_gain_profile("default"); // Only necessary if the gain profile was set to something else before +usrp->set_tx_gain(30); // Will set the gain to 30 dB on all associated daughterboards +std::cout << usrp->get_tx_gain() << std::endl; // Should print "30" +usrp->set_tx_gain(0, "DSA1"); // Will cause an exception +// Individual DSAs may still be queried. Note that even though this is an +// attenuator, the return value is a gain (higher values mean more TX power): +auto dsa1_gain = usrp->get_tx_gain("DSA1"); +~~~ + +<b>ATR Behaviour:</b> The gains will apply to their respective ATR state, i.e., +RX gains will be applied to both the RX and full-duplex state, and TX gains will +be applied to the TX and full-duplex state. In the idle state, gains are set to +minimum gain. + +<b>RFNoC Commands:</b> All DSA values are on one register for a given ATR state, +and the TX amplifier shares a register with the antenna controls. +That means that changing the DSA values +in this gain profile will cause two register writes (one for RX/TX, and one for +full-duplex). Changing the TX amplifier gain value will incur another two writes. + +\subsection zbx_gain_manual Manual Gain Profile + +When more control is desired, the manual gain profile can be applied. Here, it +is no longer possible to request an overall gain value. However, it is now +possible to set the DSA and amplifier values directly. + +~~~{.cpp} +usrp->set_tx_gain_profile("manual"); +usrp->set_tx_gain(30); // ERROR: Now, we have to specify a name +usrp->set_tx_gain(5, "DSA1"); // Set DSA1 to 5 dB gain (equals 26 dB attenuation) +// The following line will return an undefined value and print a warning, but +// will not throw. That's because calling the overall gain is a common API call +// done by many utilities, and this behaviour is considered most backward compatible. +std::cout << usrp->get_tx_gain() << std::endl; +std::cout << usrp->get_tx_gain("DSA1") << std::endl; // Should print '5' +~~~ + +\subsection zbx_gain_table CPLD-Table Gain Profile with ATR control + +In this profile, UHD exposes access to the gain table stored on the CPLD. By default, +the CPLD is initialized with the same gain table as UHD uses internally, i.e., +there is no difference in behaviour when using this profile, unless the gain +table is modified. + +Setting DSA values directly from UHD is not possible in this profile, nor is setting +an overall gain value. However, it is now possible to load an entry from the +CPLD gain table and apply it to the current DSA settings. +In this profile, the ATR behaviour for the DSAs is the same as in the 'default' +or 'manual' profiles. That means loading a DSA table entry requires two writes +to the CPLD, one for TX/RX, and one for full-duplex. + +The gain "values" are no longer interpreted as dB values, but refer to DSA table +indices. + +An important use case of this gain profile is when testing CPLD gain tables, but +not changing other aspects of the software control. Often, this is an intermediate +debugging step while developing applications that use RFNoC commands to control +the gain. + +Another use case of this profile is when running RFNoC applications, where the +gain is controlled from another RFNoC block, but the ATR behaviour is left in +its default state. + +~~~{.cpp} +usrp->set_tx_gain_profile("table"); +usrp->set_tx_gain(30); // ERROR: Now, we have to specify a name +usrp->get_radio_control().set_tx_gain(5); // This works, though. The radio_control + // object is smart enough to infer that + // this is the only action left. +// The previous line and the following have the same effect: +usrp->set_tx_gain(5, "TABLE"); +// The CPLD DSA table entry at index 5 is now loaded and applied to the TX and +// full-duplex ATR modes. In other words, the register values TX0_TABLE_DSA* +// are copied to TX0_DSA*. +// The following line will return an undefined value and print a warning, but +// will not throw. That's because calling the overall gain is a common API call +// done by many utilities, and this behaviour is considered most backward compatible. +std::cout << usrp->get_tx_gain() << std::endl; +// The following line will print the actual value of DSA1. Note, however, that +// UHD needs to read back the value from the CPLD, since it can't know what's +// stored on the CPLD. That means the following call will require a read from +// the CPLD, which is not possible if there are timed commands queued up. +std::cout << usrp->get_tx_gain("DSA1") << std::endl; // Should print whatever + // was originally stored + // in TX0_TABLE_DSA1, which + // was copied to TX0_DSA1 +~~~ + +\subsection zbx_gain_tablenoatr CPLD-Table Gain Profile without ATR control + +When running applications where the FPGA has full control over the gain, and the +ATR behaviour should also be replaced by register writes, this profile may be +used. + +The main difference to the previous profile is that when the radio switches +between RX, TX, full duplex, and idle states, there is no automatic update of +the gain values. Instead, the current gain values are selected by writing to the +`SW_RF0_DSA_CONFIG` and `SW_RF1_DSA_CONFIG` registers. Changing these registers +will select an entry from the `RX/TX DSA` tables. Unlike the gain tables, these +are not prepopulated. + +~~~{.cpp} +usrp->set_tx_gain_profile("table_noatr"); +usrp->set_tx_gain(30); // ERROR: Now, we have to specify a name +usrp->get_radio_control().set_tx_gain(5); // This works, though. The radio_control + // object is smart enough to infer that + // this is the only action left. +// The previous line and the following have the same effect (but it's a different +// effect than when using gain profile 'table'): +usrp->set_tx_gain(5, "TABLE"); +// The CPLD SW_RF0_DSA_CONFIG register is now set to 5. That means the DSA values +// stored in TX0_DSA1[5] and TX0_DSA2[5] are now being used, assuming no other +// entity is sending commands to the CPLD via RFNoC. +// Copying CPLD gain table entries into the TX0_DSA* registers is not possible +// from software in this profile. Rather, we are hands-off and let the FPGA take +// control. +// +// Let's assume that an RFNoC block has sent register writes to the radio block +// in order to load new gain table entries, and apply them. We can still read +// back the current DSA values (the ones being currently used on the RF chain) +// by reading back from the CPLD. +// This is not possible if there are timed commands queued up. +std::cout << usrp->get_tx_gain("DSA1") << std::endl; // Should print whatever + // is currently in TX0_DSA1[i], + // where i is the value of + // SW_RF0_DSA_CONFIG. +~~~ + +\section zbx_atr Auto-Transmit-Receive Registers (ATR) + +Like other USRPs, the X410 provides GPIOs to the daughterboards that communicate +the RX/TX state. The ZBX is, by default, configured to switch settings based on +the current state (RX, TX, full duplex, idle). For example, the TX/RX antenna +is switched between the TX and RX channels depending on the state. A total of 4 +GPIOs between the motherboard FPGA and the daughterboard CPLD are used for this +purpose, two pins per channel. + +ZBX uses these pins to select between different RF control values +(e.g., the aforementioned TX/RX antenna switch position; this also includes the +front-panel LEDs) as well as DSA controls (e.g., when doing RX only, the TX gain +stages can be set to zero to minimize leakage). Internally, this works by +converting the ATR pins into a control word. This control word is used to index +tables that contain different values for RF control/LEDs as well as DSAs. + +Example: Assume the device is transmitting, but not receiving, on channel 0. The +FPGA will set the ATR pins for channel 0 to a binary value of 0b10, which equals +a decimal value of 2. The transmit gain is controlled by DSA values that are +stored in tables called TX0_DSA1 and TX0_DSA2, respectively. For the duration +of the transmission, the DSA values at table position TX0_DSA1[2] and TX0_DSA2[2] +will thus be used. Similarly, tables for RF path control and LEDs are used to +configure those. This mode of using the ATR pins is called the "classic ATR" mode +and is the default behaviour. + +The ZBX daughterboard provides two additional modes of utilizing those pins: +- "Software Defined": In this mode, the ATR pins are ignored. The control word + is derived from another register. In this case, changing the table index + requires another register write as opposed to the almost instantaneous tracking + of the ATR state in the other modes. +- "FPGA controlled": This is similar to the classic ATR mode, but it combines + the pins from channel 0 and 1. The downside is that channel 0 and 1 are no + longer independent, but it allows using 16 entries from the tables instead of + four as in the classic ATR mode. + +The ATR pin mode can be set independently for channel 0 and 1, and also for DSA +tables vs. RF control/LED tables. Note that combining the "FPGA controlled" mode +on one channel with the "classic" mode on the other channel would yield a possibly +conflicting configuration. + +Usage of these modes is considered highly advanced usage of ZBX. The "FPGA +controlled" mode is not supported by UHD without custom modifications (it is +possible, however, to manually write to the appropriate registers to use this +mode). Using this mode would also require modifications of the FPGA image to +add custom controls to the ATR GPIO pins. + +The "software defined" mode can be enabled for the DSA tables by using the +`table_noatr` gain profile (see the previous section). + +\section zbx_updating_cpld Updating the ZBX CPLD + +If you need to update the ZBX CPLD, you can do so by running the following command +on the device: + + python3 /usr/lib/python3.?/site-packages/usrp_mpm/dboard_manager/zbx_update_cpld.py + +By default, the script will attempt to install an image from the default path to +both daughterboards. To specify which file or dboards to program, use the +following options when running the updater script: + +- `--dboards=0,1` +- `--file=<path_to_cpld_image>` + +By default, the `cpld-zbx.rpd` file will be provided at +`/lib/firmware/ni/cpld-zbx.rpd`. Note that after downloading the ZBX CPLD, you +will need to completely shut down and power-cycle the device. + +\subsection zbx_updating_cpld_details CPLD Programming: Details + +Read this section if you want to create your own ZBX CPLD image, or require more +information on how the CPLD image is updated. + +The source code for the ZBX CPLD is provided within the UHD code repository, at +`fpga/usrp3/top/x400/dboards/zbx/cpld`. Read the Makefile for more instructions +on how to build images. + +The build process will produce two CPLD bitfiles: A `.rpd` file and a `.svf` file. +Both are required for different programming methods. There are two programming +methods: + +- Flash mode: This requires the `.rpd` file. It uses the motherboard CPLD as a + programming device. This is the default mode. +- Legacy mode: This directly calls into openocd to flash the ZBX CPLD. It requires + the `.svf` file. + +Before running the updater, ensure that MPM is installed on your device as some +resources from MPM code are used. + +You should also ensure that the power rails to the daughterboard are up and that +MPM can successfully communicate with the daughterboard when running (running +`uhd_usrp_probe` successfully is sufficient). + +MPM does not need to be running when the updater script is executed, but you +should start it once to ensure a valid FPGA image is loaded and communication to +the daughterboard is working before attempting this. + +To specify a programming mode, use the `--updater` argument. For example, to +force the legacy mode, use the following command: + + python3 /usr/lib/python3.?/site-packages/usrp_mpm/dboard_manager/zbx_update_cpld.py \ + --updater=legacy \ + --file=/lib/firmware/ni/cpld-zbx.svf + +This will program the image from the default location onto the CPLD using the +'legacy' method. + +\section zbx_flash Flash Memory and EEPROM + +Every ZBX daughterboard has 2 MB of non-volatile flash memory which can be used +to store data such as daughterboard-specific information. When +logged into the X410 Linux system, the flash memory is mounted into the +filesystem under `/mnt/db0_flash` and `/mnt/db1_flash`, respectively. The +daughterboard also uses a separate EEPROM to store revision, serial, and product +ID of the daughterboard. + +*/ +// vim:ft=doxygen: diff --git a/host/include/uhd/cal/CMakeLists.txt b/host/include/uhd/cal/CMakeLists.txt index 38b4885ef..482502c89 100644 --- a/host/include/uhd/cal/CMakeLists.txt +++ b/host/include/uhd/cal/CMakeLists.txt @@ -4,21 +4,38 @@ # SPDX-License-Identifier: GPL-3.0-or-later # +set(FLATBUFFER_SCHEMA_FILES + "cal_metadata.fbs" + "iq_cal.fbs" + "pwr_cal.fbs" + "dsa_cal.fbs" +) + +set(FLATBUFFER_GEN_HEADER_FILES + "cal_metadata_generated.h" + "iq_cal_generated.h" + "pwr_cal_generated.h" + "dsa_cal_generated.h" +) + UHD_INSTALL(FILES container.hpp database.hpp iq_cal.hpp pwr_cal.hpp - iq_cal_generated.h - pwr_cal_generated.h + dsa_cal.hpp DESTINATION ${INCLUDE_DIR}/uhd/cal COMPONENT headers ) UHD_INSTALL(FILES - iq_cal.fbs - pwr_cal.fbs + ${FLATBUFFER_SCHEMA_FILES} DESTINATION ${PKG_DATA_DIR}/cal COMPONENT headers ) +UHD_INSTALL(FILES + ${FLATBUFFER_GEN_HEADER_FILES} + DESTINATION ${INCLUDE_DIR}/uhd/cal + COMPONENT headers + ) diff --git a/host/include/uhd/cal/dsa_cal.fbs b/host/include/uhd/cal/dsa_cal.fbs new file mode 100644 index 000000000..dc33da497 --- /dev/null +++ b/host/include/uhd/cal/dsa_cal.fbs @@ -0,0 +1,36 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +// DSA step setting table + +include "cal_metadata.fbs"; + +namespace uhd.usrp.cal; + +// DSA step settings for a given gain in a band +table DsaStep +{ + steps: [uint]; +} + +// DSA settings for all gains of a band +table BandDsaMap +{ + max_freq: ulong (key); // max frequency for the band + gains: [DsaStep]; // DSA step setting for all gain values + name: string; // human readable frequency band name +} + +// Band settings +table DsaCal +{ + metadata: Metadata; // useful additional information + band_dsa_map: [BandDsaMap]; // settings for all bands +} + +root_type DsaCal; +file_identifier "dsas"; +file_extension "cal"; diff --git a/host/include/uhd/cal/dsa_cal.hpp b/host/include/uhd/cal/dsa_cal.hpp new file mode 100644 index 000000000..e8ead87ee --- /dev/null +++ b/host/include/uhd/cal/dsa_cal.hpp @@ -0,0 +1,130 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_CAL_GAIN_HPP +#define INCLUDED_LIBUHD_CAL_GAIN_HPP + +#include <uhd/cal/container.hpp> +#include <uhd/config.hpp> +#include <boost/optional.hpp> +#include <array> +#include <map> + +namespace uhd { namespace usrp { namespace cal { + +/*! Class that stores DSA indices for all ZBX TX bands. + */ +class UHD_API zbx_tx_dsa_cal : public container +{ +public: + static constexpr uint32_t NUM_AMP = 1; + static constexpr uint32_t NUM_DSA = 2 + NUM_AMP; + static constexpr uint32_t NUM_GAIN_STAGES = 61; + + using sptr = std::shared_ptr<zbx_tx_dsa_cal>; + using step_settings = std::array<uint32_t, NUM_DSA>; + + /*! Add a new band description + * + * max_freq is the (inclusive) upper limit + * for the band (lower limit derives from the other bands). Name is an + * text representation (human readable) for the band. dsa_steps is an + * array of DSA settings for all gains in the band. + */ + virtual void add_frequency_band(const double max_freq, + const std::string& name, + std::array<step_settings, NUM_GAIN_STAGES> dsa_steps) = 0; + + /*! Retrieves DSA settings for frequency and gain_index. + * + * The settings are + * retrieved from the band with the biggest max_freq that is smaller or + * equal to freq. DSA settings are the settings at gain_index in that band. + * Value errors are thrown if freq is larger that the largest freq_max of + * all bands or gain_index is not within range. + */ + virtual const step_settings get_dsa_setting( + const double freq, const size_t gain_index) const = 0; + + /* Check whether two frequencies map to the same band. + */ + virtual bool is_same_band(double freq1, double freq2) const = 0; + + /*! Retrieves DSA settings as flat list. + * The values are flattened by frequency band, gain and values in that order. + * Use NUM_DSA and NUM_GAIN_STAGES to find values in the list. + */ + virtual std::vector<uint32_t> get_band_settings(double freq, uint8_t dsa) const = 0; + + /*! + * Clear all stored values + */ + virtual void clear() = 0; + + //! Factory for new cal data sets + static sptr make( + const std::string& name, const std::string& serial, const uint64_t timestamp); + + //! Default factory + static sptr make(); +}; + +/*! Class that stores DSA indices for all ZBX TX bands. + */ +class UHD_API zbx_rx_dsa_cal : public container +{ +public: + static constexpr uint32_t NUM_DSA = 4; + static constexpr uint32_t NUM_GAIN_STAGES = 61; + + using sptr = std::shared_ptr<zbx_rx_dsa_cal>; + using step_settings = std::array<uint32_t, NUM_DSA>; + + /*! Add a new band description. + * + * max_freq is the (inclusive) upper limit + * for the band (lower limit derives from the other bands). Name is an + * text representation (human readable) for the band. dsa_steps is an + * array of DSA settings for all gains in the band. + */ + virtual void add_frequency_band(const double max_freq, + const std::string& name, + std::array<step_settings, NUM_GAIN_STAGES> dsa_steps) = 0; + + virtual bool is_same_band(double freq1, double freq2) const = 0; + /*! Retrieves DSA settings for frequency and gain_index. + * + * The settings are + * retrieved from the band with the biggest max_freq that is smaller or + * equal to freq. DSA settings are the settings at gain_index in that band. + * Value errors are thrown if freq is larger that the largest freq_max of + * all bands or gain_index is not within range. + */ + virtual const step_settings get_dsa_setting( + const double freq, const size_t gain_index) const = 0; + + /*! Retrieves DSA settings as flat list. + * The values are flattend by frequency band, gain and values in that order. + * Use NUM_DSA and NUM_GAIN_STAGES to find values in the list. + */ + virtual std::vector<uint32_t> get_band_settings(double freq, uint8_t dsa) const = 0; + + /*! + * Clear all stored values + */ + virtual void clear() = 0; + + //! Factory for new cal data sets + static sptr make( + const std::string& name, const std::string& serial, const uint64_t timestamp); + + //! Default factory + static sptr make(); +}; + +}}} // namespace uhd::usrp::cal + +#endif /* INCLUDED_LIBUHD_CAL_GAIN_HPP */ diff --git a/host/include/uhd/cal/dsa_cal_generated.h b/host/include/uhd/cal/dsa_cal_generated.h new file mode 100644 index 000000000..18e8a2713 --- /dev/null +++ b/host/include/uhd/cal/dsa_cal_generated.h @@ -0,0 +1,274 @@ +// automatically generated by the FlatBuffers compiler, do not modify + + +#ifndef FLATBUFFERS_GENERATED_DSACAL_UHD_USRP_CAL_H_ +#define FLATBUFFERS_GENERATED_DSACAL_UHD_USRP_CAL_H_ + +#include "flatbuffers/flatbuffers.h" + +#include "cal_metadata_generated.h" + +namespace uhd { +namespace usrp { +namespace cal { + +struct DsaStep; +struct DsaStepBuilder; + +struct BandDsaMap; +struct BandDsaMapBuilder; + +struct DsaCal; +struct DsaCalBuilder; + +struct DsaStep FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { + typedef DsaStepBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_STEPS = 4 + }; + const flatbuffers::Vector<uint32_t> *steps() const { + return GetPointer<const flatbuffers::Vector<uint32_t> *>(VT_STEPS); + } + bool Verify(flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_STEPS) && + verifier.VerifyVector(steps()) && + verifier.EndTable(); + } +}; + +struct DsaStepBuilder { + typedef DsaStep Table; + flatbuffers::FlatBufferBuilder &fbb_; + flatbuffers::uoffset_t start_; + void add_steps(flatbuffers::Offset<flatbuffers::Vector<uint32_t>> steps) { + fbb_.AddOffset(DsaStep::VT_STEPS, steps); + } + explicit DsaStepBuilder(flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + DsaStepBuilder &operator=(const DsaStepBuilder &); + flatbuffers::Offset<DsaStep> Finish() { + const auto end = fbb_.EndTable(start_); + auto o = flatbuffers::Offset<DsaStep>(end); + return o; + } +}; + +inline flatbuffers::Offset<DsaStep> CreateDsaStep( + flatbuffers::FlatBufferBuilder &_fbb, + flatbuffers::Offset<flatbuffers::Vector<uint32_t>> steps = 0) { + DsaStepBuilder builder_(_fbb); + builder_.add_steps(steps); + return builder_.Finish(); +} + +inline flatbuffers::Offset<DsaStep> CreateDsaStepDirect( + flatbuffers::FlatBufferBuilder &_fbb, + const std::vector<uint32_t> *steps = nullptr) { + auto steps__ = steps ? _fbb.CreateVector<uint32_t>(*steps) : 0; + return uhd::usrp::cal::CreateDsaStep( + _fbb, + steps__); +} + +struct BandDsaMap FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { + typedef BandDsaMapBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_MAX_FREQ = 4, + VT_GAINS = 6, + VT_NAME = 8 + }; + uint64_t max_freq() const { + return GetField<uint64_t>(VT_MAX_FREQ, 0); + } + bool KeyCompareLessThan(const BandDsaMap *o) const { + return max_freq() < o->max_freq(); + } + int KeyCompareWithValue(uint64_t val) const { + return static_cast<int>(max_freq() > val) - static_cast<int>(max_freq() < val); + } + const flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::DsaStep>> *gains() const { + return GetPointer<const flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::DsaStep>> *>(VT_GAINS); + } + const flatbuffers::String *name() const { + return GetPointer<const flatbuffers::String *>(VT_NAME); + } + bool Verify(flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField<uint64_t>(verifier, VT_MAX_FREQ) && + VerifyOffset(verifier, VT_GAINS) && + verifier.VerifyVector(gains()) && + verifier.VerifyVectorOfTables(gains()) && + VerifyOffset(verifier, VT_NAME) && + verifier.VerifyString(name()) && + verifier.EndTable(); + } +}; + +struct BandDsaMapBuilder { + typedef BandDsaMap Table; + flatbuffers::FlatBufferBuilder &fbb_; + flatbuffers::uoffset_t start_; + void add_max_freq(uint64_t max_freq) { + fbb_.AddElement<uint64_t>(BandDsaMap::VT_MAX_FREQ, max_freq, 0); + } + void add_gains(flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::DsaStep>>> gains) { + fbb_.AddOffset(BandDsaMap::VT_GAINS, gains); + } + void add_name(flatbuffers::Offset<flatbuffers::String> name) { + fbb_.AddOffset(BandDsaMap::VT_NAME, name); + } + explicit BandDsaMapBuilder(flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + BandDsaMapBuilder &operator=(const BandDsaMapBuilder &); + flatbuffers::Offset<BandDsaMap> Finish() { + const auto end = fbb_.EndTable(start_); + auto o = flatbuffers::Offset<BandDsaMap>(end); + return o; + } +}; + +inline flatbuffers::Offset<BandDsaMap> CreateBandDsaMap( + flatbuffers::FlatBufferBuilder &_fbb, + uint64_t max_freq = 0, + flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::DsaStep>>> gains = 0, + flatbuffers::Offset<flatbuffers::String> name = 0) { + BandDsaMapBuilder builder_(_fbb); + builder_.add_max_freq(max_freq); + builder_.add_name(name); + builder_.add_gains(gains); + return builder_.Finish(); +} + +inline flatbuffers::Offset<BandDsaMap> CreateBandDsaMapDirect( + flatbuffers::FlatBufferBuilder &_fbb, + uint64_t max_freq = 0, + const std::vector<flatbuffers::Offset<uhd::usrp::cal::DsaStep>> *gains = nullptr, + const char *name = nullptr) { + auto gains__ = gains ? _fbb.CreateVector<flatbuffers::Offset<uhd::usrp::cal::DsaStep>>(*gains) : 0; + auto name__ = name ? _fbb.CreateString(name) : 0; + return uhd::usrp::cal::CreateBandDsaMap( + _fbb, + max_freq, + gains__, + name__); +} + +struct DsaCal FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { + typedef DsaCalBuilder Builder; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_METADATA = 4, + VT_BAND_DSA_MAP = 6 + }; + const Metadata *metadata() const { + return GetPointer<const Metadata *>(VT_METADATA); + } + const flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::BandDsaMap>> *band_dsa_map() const { + return GetPointer<const flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::BandDsaMap>> *>(VT_BAND_DSA_MAP); + } + bool Verify(flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_METADATA) && + verifier.VerifyTable(metadata()) && + VerifyOffset(verifier, VT_BAND_DSA_MAP) && + verifier.VerifyVector(band_dsa_map()) && + verifier.VerifyVectorOfTables(band_dsa_map()) && + verifier.EndTable(); + } +}; + +struct DsaCalBuilder { + typedef DsaCal Table; + flatbuffers::FlatBufferBuilder &fbb_; + flatbuffers::uoffset_t start_; + void add_metadata(flatbuffers::Offset<Metadata> metadata) { + fbb_.AddOffset(DsaCal::VT_METADATA, metadata); + } + void add_band_dsa_map(flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::BandDsaMap>>> band_dsa_map) { + fbb_.AddOffset(DsaCal::VT_BAND_DSA_MAP, band_dsa_map); + } + explicit DsaCalBuilder(flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + DsaCalBuilder &operator=(const DsaCalBuilder &); + flatbuffers::Offset<DsaCal> Finish() { + const auto end = fbb_.EndTable(start_); + auto o = flatbuffers::Offset<DsaCal>(end); + return o; + } +}; + +inline flatbuffers::Offset<DsaCal> CreateDsaCal( + flatbuffers::FlatBufferBuilder &_fbb, + flatbuffers::Offset<Metadata> metadata = 0, + flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<uhd::usrp::cal::BandDsaMap>>> band_dsa_map = 0) { + DsaCalBuilder builder_(_fbb); + builder_.add_band_dsa_map(band_dsa_map); + builder_.add_metadata(metadata); + return builder_.Finish(); +} + +inline flatbuffers::Offset<DsaCal> CreateDsaCalDirect( + flatbuffers::FlatBufferBuilder &_fbb, + flatbuffers::Offset<Metadata> metadata = 0, + std::vector<flatbuffers::Offset<uhd::usrp::cal::BandDsaMap>> *band_dsa_map = nullptr) { + auto band_dsa_map__ = band_dsa_map ? _fbb.CreateVectorOfSortedTables<uhd::usrp::cal::BandDsaMap>(band_dsa_map) : 0; + return uhd::usrp::cal::CreateDsaCal( + _fbb, + metadata, + band_dsa_map__); +} + +inline const uhd::usrp::cal::DsaCal *GetDsaCal(const void *buf) { + return flatbuffers::GetRoot<uhd::usrp::cal::DsaCal>(buf); +} + +inline const uhd::usrp::cal::DsaCal *GetSizePrefixedDsaCal(const void *buf) { + return flatbuffers::GetSizePrefixedRoot<uhd::usrp::cal::DsaCal>(buf); +} + +inline const char *DsaCalIdentifier() { + return "dsas"; +} + +inline bool DsaCalBufferHasIdentifier(const void *buf) { + return flatbuffers::BufferHasIdentifier( + buf, DsaCalIdentifier()); +} + +inline bool VerifyDsaCalBuffer( + flatbuffers::Verifier &verifier) { + return verifier.VerifyBuffer<uhd::usrp::cal::DsaCal>(DsaCalIdentifier()); +} + +inline bool VerifySizePrefixedDsaCalBuffer( + flatbuffers::Verifier &verifier) { + return verifier.VerifySizePrefixedBuffer<uhd::usrp::cal::DsaCal>(DsaCalIdentifier()); +} + +inline const char *DsaCalExtension() { + return "cal"; +} + +inline void FinishDsaCalBuffer( + flatbuffers::FlatBufferBuilder &fbb, + flatbuffers::Offset<uhd::usrp::cal::DsaCal> root) { + fbb.Finish(root, DsaCalIdentifier()); +} + +inline void FinishSizePrefixedDsaCalBuffer( + flatbuffers::FlatBufferBuilder &fbb, + flatbuffers::Offset<uhd::usrp::cal::DsaCal> root) { + fbb.FinishSizePrefixed(root, DsaCalIdentifier()); +} + +} // namespace cal +} // namespace usrp +} // namespace uhd + +#endif // FLATBUFFERS_GENERATED_DSACAL_UHD_USRP_CAL_H_ diff --git a/host/include/uhd/features/CMakeLists.txt b/host/include/uhd/features/CMakeLists.txt index 798844077..64e50bb12 100644 --- a/host/include/uhd/features/CMakeLists.txt +++ b/host/include/uhd/features/CMakeLists.txt @@ -7,6 +7,7 @@ UHD_INSTALL(FILES discoverable_feature.hpp discoverable_feature_getter_iface.hpp + ref_clk_calibration_iface.hpp DESTINATION ${INCLUDE_DIR}/uhd/features COMPONENT headers ) diff --git a/host/include/uhd/features/adc_self_calibration_iface.hpp b/host/include/uhd/features/adc_self_calibration_iface.hpp new file mode 100644 index 000000000..fbacccf77 --- /dev/null +++ b/host/include/uhd/features/adc_self_calibration_iface.hpp @@ -0,0 +1,45 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include <uhd/features/discoverable_feature.hpp> +#include <memory> + +namespace uhd { namespace features { + +/*! Interface for running ADC self-calibration on supported devices. + * Currently, only the X4xx series of devices supports calibrating the + * internal ADCs. + */ +class adc_self_calibration_iface : public discoverable_feature +{ +public: + using sptr = std::shared_ptr<adc_self_calibration_iface>; + + static discoverable_feature::feature_id_t get_feature_id() + { + return discoverable_feature::ADC_SELF_CALIBRATION; + } + + std::string get_feature_name() const + { + return "ADC Self Calibration"; + } + + virtual ~adc_self_calibration_iface() = default; + + //! Runs calibration on the specified channel. This will momentarily + // reconfigure both the specified RX channel as well as the matching + // TX channel for the operation. + // + // If you would like to calibrate the ADCs without interrupting the + // signal chain, use the rx_codec/<n>/calibration_frozen property on the + // motherboard's property tree. + virtual void run(const size_t chan) = 0; +}; + +}} // namespace uhd::features diff --git a/host/include/uhd/features/discoverable_feature.hpp b/host/include/uhd/features/discoverable_feature.hpp index afe25cb63..8cbe8e13f 100644 --- a/host/include/uhd/features/discoverable_feature.hpp +++ b/host/include/uhd/features/discoverable_feature.hpp @@ -31,6 +31,9 @@ public: enum feature_id_t { RESERVED0, RESERVED1, + FPGA_LOAD_NOTIFICATION, + ADC_SELF_CALIBRATION, + REF_CLK_CALIBRATION, }; virtual ~discoverable_feature() = default; diff --git a/host/include/uhd/features/ref_clk_calibration_iface.hpp b/host/include/uhd/features/ref_clk_calibration_iface.hpp new file mode 100644 index 000000000..83082da68 --- /dev/null +++ b/host/include/uhd/features/ref_clk_calibration_iface.hpp @@ -0,0 +1,44 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include <uhd/features/discoverable_feature.hpp> +#include <memory> + +namespace uhd { namespace features { + +/*! Interface to provide access to functions (set, get and store the tuning + * word) to perform calibration of the DAC for the internal reference clock + * source on supported devices. Currently, only the X4xx series of devices + * supports this. + */ +class ref_clk_calibration_iface : public discoverable_feature +{ +public: + using sptr = std::shared_ptr<ref_clk_calibration_iface>; + + static discoverable_feature::feature_id_t get_feature_id() + { + return discoverable_feature::REF_CLK_CALIBRATION; + } + + std::string get_feature_name() const + { + return "Ref Clk Calibration"; + } + + virtual ~ref_clk_calibration_iface() = default; + + //! Set the tuning word to be configured on the internal reference clock DAC. + virtual void set_ref_clk_tuning_word(uint32_t tuning_word) = 0; + //! Returns the tuning word configured on the internal reference clock DAC. + virtual uint32_t get_ref_clk_tuning_word() = 0; + //! Writes the reference clock tuning word to the clocking board EEPROM. + virtual void store_ref_clk_tuning_word(uint32_t tuning_word) = 0; +}; + +}} // namespace uhd::features diff --git a/host/include/uhd/rfnoc/blocks/radio.yml b/host/include/uhd/rfnoc/blocks/radio.yml index 81b96bf07..bae9fe128 100644 --- a/host/include/uhd/rfnoc/blocks/radio.yml +++ b/host/include/uhd/rfnoc/blocks/radio.yml @@ -52,17 +52,17 @@ data: mdata_sig: ~ io_ports: - ctrl_port: + ctrlport: type: ctrl_port drive: master rename: pattern: (.*) repl: m_\1 - time_keeper: + time: type: time_keeper drive: listener - x300_radio: - type: radio_1x32 + radio: + type: radio_8x32 drive: slave registers: diff --git a/host/include/uhd/rfnoc/core/io_signatures.yml b/host/include/uhd/rfnoc/core/io_signatures.yml index 9b1d729f6..de8ab0050 100644 --- a/host/include/uhd/rfnoc/core/io_signatures.yml +++ b/host/include/uhd/rfnoc/core/io_signatures.yml @@ -34,6 +34,7 @@ time_keeper: - name: radio_time width: 64 +# Single channel radio interface radio_1x32: type: master-slave ports: @@ -56,6 +57,30 @@ radio_1x32: type: to-master width: 1 +# Two channel radio interface, or single channel with 2 SPC +radio_2x32: + type: master-slave + ports: + - name: radio_rx_data + type: from-master + width: 64 + - name: radio_rx_stb + type: from-master + width: 2 + - name: radio_rx_running + type: to-master + width: 2 + - name: radio_tx_data + type: to-master + width: 64 + - name: radio_tx_stb + type: from-master + width: 2 + - name: radio_tx_running + type: to-master + width: 2 + +# Same as radio_2x32, but kept for backwards compatibility with the old name x300_radio: type: master-slave ports: @@ -78,6 +103,29 @@ x300_radio: type: to-master width: 2 +# Eight channel radio interface, or two channels with 4 SPC +radio_8x32: + type: master-slave + ports: + - name: radio_rx_data + type: from-master + width: 256 + - name: radio_rx_stb + type: from-master + width: 8 + - name: radio_rx_running + type: to-master + width: 8 + - name: radio_tx_data + type: to-master + width: 256 + - name: radio_tx_stb + type: from-master + width: 8 + - name: radio_tx_running + type: to-master + width: 8 + # A 4-port AXI4 memory-mapped interface with 64-bit data, and 4 GiB address # space (32-bit). axi4_mm_4x64_4g: diff --git a/host/include/uhd/rfnoc/core/rfnoc_imagebuilder_args.json b/host/include/uhd/rfnoc/core/rfnoc_imagebuilder_args.json index 2d76643be..120ecd5dd 100644 --- a/host/include/uhd/rfnoc/core/rfnoc_imagebuilder_args.json +++ b/host/include/uhd/rfnoc/core/rfnoc_imagebuilder_args.json @@ -3,12 +3,13 @@ "properties": { "schema": { "const": "rfnoc_imagebuilder_args" }, "copyright": { "type": "string" }, - "version": { "type": "number" }, + "version": { "type": "string" }, "license": { "type": "string" }, "device": { "type": "string" }, + "image_core_name": { "type": "string" }, "default_target": { "type": "string" }, - "rfnoc_version": { "type": "number" }, - "chdr_width": { "enum": [64, 256] }, + "rfnoc_version": { "type": "string" }, + "chdr_width": { "enum": [64, 128, 256, 512] }, "stream_endpoints": { "$ref": "#/definitions/stream_endpoints" }, "noc_blocks": { "$ref": "#/definitions/noc_blocks" }, @@ -45,9 +46,15 @@ "data": { "type": "boolean" }, "num_data_i":{ "type": "integer", "minimum": 1 }, "num_data_o":{ "type": "integer", "minimum": 1 }, - "buff_size": { "type": "integer", "minimum": 0 } + "buff_size": { "type": "integer", "minimum": 0 }, + "buff_size_bytes": { "type": "integer", "minimum": 0 } }, - "additionalProperties": false + "additionalProperties": false, + "not": { + "anyOf": [ + {"required": ["buff_size", "buff_size_bytes"]} + ] + } }, "noc_block": { diff --git a/host/include/uhd/rfnoc/core/x410_bsp.yml b/host/include/uhd/rfnoc/core/x410_bsp.yml new file mode 100644 index 000000000..66b6d77b0 --- /dev/null +++ b/host/include/uhd/rfnoc/core/x410_bsp.yml @@ -0,0 +1,57 @@ +type: x410 +type_id: A400 +family: ULTRASCALE +transports: +# QSFP Ethernet Ports: +- name: eth0 + type: 10G + width: 64 +- name: eth1 + type: 10G + width: 64 +- name: eth2 + type: 10G + width: 64 +- name: eth3 + type: 10G + width: 64 +- name: eth4 + type: 10G + width: 64 +# ARM CPU: +- name: dma + type: dma + width: 64 + +clocks: +- name: radio +- name: radio_2x + +io_ports: + ctrlport_radio0: + type: ctrl_port + drive: slave + rename: + pattern: (ctrlport_)(.*) + repl: m_\1radio0_\2 + ctrlport_radio1: + type: ctrl_port + drive: slave + rename: + pattern: (ctrlport_)(.*) + repl: m_\1radio1_\2 + time: + type: time_keeper + drive: broadcaster + radio0: + type: radio_8x32 + drive: master + rename: + pattern: (.*) + repl: \1_radio0 + radio1: + type: radio_8x32 + drive: master + rename: + pattern: (.*) + repl: \1_radio1 diff --git a/host/include/uhd/rfnoc/defaults.hpp b/host/include/uhd/rfnoc/defaults.hpp index 1b9cb5016..d0f6c2311 100644 --- a/host/include/uhd/rfnoc/defaults.hpp +++ b/host/include/uhd/rfnoc/defaults.hpp @@ -72,6 +72,8 @@ static const device_type_t N300 = 0x1300; static const device_type_t N320 = 0x1320; //! X300 device family (X300, X310) static const device_type_t X300 = 0xA300; +//! X400 device family +static const device_type_t X400 = 0xA400; // block identifiers static const noc_id_t ADDSUB_BLOCK = 0xADD00000; diff --git a/host/include/uhd/rfnoc/mb_controller.hpp b/host/include/uhd/rfnoc/mb_controller.hpp index e1268d923..5ca0ce52a 100644 --- a/host/include/uhd/rfnoc/mb_controller.hpp +++ b/host/include/uhd/rfnoc/mb_controller.hpp @@ -7,6 +7,7 @@ #pragma once #include <uhd/config.hpp> +#include <uhd/features/discoverable_feature_getter_iface.hpp> #include <uhd/types/device_addr.hpp> #include <uhd/types/sensors.hpp> #include <uhd/types/time_spec.hpp> @@ -21,7 +22,8 @@ namespace uhd { namespace rfnoc { /*! A default block controller for blocks that can't be found in the registry */ -class UHD_API mb_controller : public uhd::noncopyable +class UHD_API mb_controller : public uhd::noncopyable, + public virtual ::uhd::features::discoverable_feature_getter_iface { public: using sptr = std::shared_ptr<mb_controller>; diff --git a/host/include/uhd/rfnoc/rf_control/core_iface.hpp b/host/include/uhd/rfnoc/rf_control/core_iface.hpp index ff7ec3550..a96ed2f60 100644 --- a/host/include/uhd/rfnoc/rf_control/core_iface.hpp +++ b/host/include/uhd/rfnoc/rf_control/core_iface.hpp @@ -6,6 +6,7 @@ #pragma once +#include <uhd/types/device_addr.hpp> #include <uhd/types/ranges.hpp> #include <stdint.h> #include <string> diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt index 4fff30eaf..910fe3c70 100644 --- a/host/lib/CMakeLists.txt +++ b/host/lib/CMakeLists.txt @@ -74,6 +74,7 @@ LIBUHD_REGISTER_COMPONENT("N300" ENABLE_N300 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF LIBUHD_REGISTER_COMPONENT("N320" ENABLE_N320 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF OFF) LIBUHD_REGISTER_COMPONENT("E320" ENABLE_E320 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF OFF) LIBUHD_REGISTER_COMPONENT("E300" ENABLE_E300 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF OFF) +LIBUHD_REGISTER_COMPONENT("X400" ENABLE_X400 ON "ENABLE_LIBUHD;ENABLE_MPMD" OFF OFF) LIBUHD_REGISTER_COMPONENT("OctoClock" ENABLE_OCTOCLOCK ON "ENABLE_LIBUHD" OFF OFF) LIBUHD_REGISTER_COMPONENT("DPDK" ENABLE_DPDK ON "ENABLE_MPMD;DPDK_FOUND" OFF OFF) diff --git a/host/lib/cal/CMakeLists.txt b/host/lib/cal/CMakeLists.txt index a50d654ff..da6bdf1a2 100644 --- a/host/lib/cal/CMakeLists.txt +++ b/host/lib/cal/CMakeLists.txt @@ -12,5 +12,6 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/database.cpp ${CMAKE_CURRENT_SOURCE_DIR}/iq_cal.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pwr_cal.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dsa_cal.cpp ) diff --git a/host/lib/cal/cal_python.hpp b/host/lib/cal/cal_python.hpp index fa38c568b..919f86e30 100644 --- a/host/lib/cal/cal_python.hpp +++ b/host/lib/cal/cal_python.hpp @@ -10,6 +10,7 @@ #include <uhd/cal/database.hpp> #include <uhd/cal/iq_cal.hpp> #include <uhd/cal/pwr_cal.hpp> +#include <uhd/cal/dsa_cal.hpp> #include <uhd/utils/interpolation.hpp> #include <uhd/utils/pybind_adaptors.hpp> #include <pybind11/stl.h> @@ -124,6 +125,49 @@ void export_cal(py::module& m) py::arg("power_dbm"), py::arg("freq"), py::arg("temperature") = boost::optional<int>()); + + py::class_<zbx_tx_dsa_cal, container, zbx_tx_dsa_cal::sptr>(m, "zbx_tx_dsa_cal") + .def(py::init([](const std::string& name, + const std::string& serial, + const uint64_t timestamp) { + return zbx_tx_dsa_cal::make(name, serial, timestamp); + })) + .def(py::init([]() { return zbx_tx_dsa_cal::make(); })) + .def(py::init([](const py::bytes data) { + return container::make<zbx_tx_dsa_cal>(pybytes_to_vector(data)); + })) + .def("add_frequency_band", + &zbx_tx_dsa_cal::add_frequency_band, + py::arg("max_freq"), + py::arg("name"), + py::arg("steps")) + .def("clear", &zbx_tx_dsa_cal::clear) + .def("get_dsa_setting", + &zbx_tx_dsa_cal::get_dsa_setting, + py::arg("freq"), + py::arg("gain_index")); + + py::class_<zbx_rx_dsa_cal, container, zbx_rx_dsa_cal::sptr>(m, "zbx_rx_dsa_cal") + .def(py::init([](const std::string& name, + const std::string& serial, + const uint64_t timestamp) { + return zbx_rx_dsa_cal::make(name, serial, timestamp); + })) + .def(py::init([]() { return zbx_rx_dsa_cal::make(); })) + .def(py::init([](const py::bytes data) { + return container::make<zbx_rx_dsa_cal>(pybytes_to_vector(data)); + })) + .def("add_frequency_band", + &zbx_rx_dsa_cal::add_frequency_band, + py::arg("max_freq"), + py::arg("name"), + py::arg("steps")) + .def("clear", &zbx_rx_dsa_cal::clear) + .def("get_dsa_setting", + &zbx_rx_dsa_cal::get_dsa_setting, + py::arg("freq"), + py::arg("gain_index")); + } #endif /* INCLUDED_UHD_CAL_PYTHON_HPP */ diff --git a/host/lib/cal/dsa_cal.cpp b/host/lib/cal/dsa_cal.cpp new file mode 100644 index 000000000..c0890005c --- /dev/null +++ b/host/lib/cal/dsa_cal.cpp @@ -0,0 +1,253 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// +#include <uhd/cal/dsa_cal.hpp> +#include <uhd/cal/dsa_cal_generated.h> +#include <uhd/utils/log.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/math.hpp> +#include <map> +#include <string> +#include <array> +#include <boost/range/adaptor/indexed.hpp> + +using namespace uhd::usrp::cal; + +namespace { +constexpr size_t VERSION_MAJOR = 2; +constexpr size_t VERSION_MINOR = 0; + +/*********************************************************************** + * Helper routines + **********************************************************************/ +template<typename base, size_t num_gain_stages, int num_dsa> +class dsa_cal_impl : public base +{ +public: + dsa_cal_impl(const std::string& name = "", + const std::string& serial = "", + const uint64_t timestamp = 0) + : _name(name) + , _serial(serial) + , _timestamp(timestamp) + { + } + + /************************************************************************** + * Container API (Basics) + *************************************************************************/ + std::string get_name() const + { + return _name; + } + + std::string get_serial() const + { + return _serial; + } + + uint64_t get_timestamp() const + { + return _timestamp; + } + + /************************************************************************** + * Specific APIs + *************************************************************************/ + void add_frequency_band( + const double max_freq, + const std::string& name, + const std::array<std::array<uint32_t, num_dsa>, num_gain_stages> dsa_values) + { + _data[max_freq] = name_dsas_pair(name, dsa_values); + } + + const std::array<uint32_t, num_dsa> get_dsa_setting(double freq, size_t gain_index) const + { + if (_data.empty()) { + throw uhd::runtime_error("Cannot get DSA settings from an empty container."); + } + // find the lowest band with band_freq_max <= freq + const uint64_t freqi = static_cast<uint64_t>(freq); + const auto freq_it = _data.lower_bound(freqi); + if (freq_it == _data.end()) { + throw uhd::value_error("No DSA band found for freq " + std::to_string(freq)); + } + // select DSA setting using gain_index + if (gain_index >= num_gain_stages) { + throw uhd::value_error( + "gain index " + std::to_string(gain_index) + " out of bounds."); + } + return freq_it->second.second[gain_index]; + } + + bool is_same_band(double freq1, double freq2) const + { + const uint64_t freqi1 = static_cast<uint64_t>(freq1); + const auto freq_it1 = _data.lower_bound(freqi1); + const uint64_t freqi2 = static_cast<uint64_t>(freq2); + const auto freq_it2 = _data.lower_bound(freqi2); + return freq_it1 == freq_it2; + } + + std::vector<uint32_t> get_band_settings(double freq, uint8_t dsa) const + { + std::vector<uint32_t> result; + // find the lowest band with band_freq_max <= freq + const uint64_t freqi = static_cast<uint64_t>(freq); + const auto freq_it = _data.lower_bound(freqi); + if (freq_it == _data.end()) { + throw uhd::value_error("No DSA band found for freq " + std::to_string(freq)); + } + // select DSA setting using gain_index + for (auto item : freq_it->second.second) { + result.push_back(item[dsa]); + } + return result; + } + + void clear() + { + _data.clear(); + } + + + /************************************************************************** + * Container API (Serialization/Deserialization) + *************************************************************************/ + std::vector<uint8_t> serialize() + { + const size_t initial_size_bytes = 2 * num_gain_stages * num_dsa * sizeof(uint32_t); // 2 band of DSA values + flatbuffers::FlatBufferBuilder builder(initial_size_bytes); + + std::vector<flatbuffers::Offset<BandDsaMap>> band_dsas; + band_dsas.reserve(_data.size()); + for (auto& band_dsas_pair : _data) { + const uint64_t freq = band_dsas_pair.first; + std::vector<flatbuffers::Offset<DsaStep>> dsa_steps; + auto name_dsas = band_dsas_pair.second; + for (auto const& values: name_dsas.second) // iterate over gain indizes + { + std::vector<uint32_t> steps(values.begin(), values.end()); + dsa_steps.push_back(CreateDsaStepDirect(builder, &steps)); + } + + band_dsas.push_back( + CreateBandDsaMapDirect(builder, freq, &dsa_steps, name_dsas.first.c_str())); + + } + + // Now load it all into the FlatBuffer + auto metadata = CreateMetadataDirect(builder, + _name.c_str(), + _serial.c_str(), + _timestamp, + VERSION_MAJOR, + VERSION_MINOR); + auto gain_dsa_cal = + CreateDsaCalDirect(builder, metadata, &band_dsas); + FinishDsaCalBuffer(builder, gain_dsa_cal); + const size_t table_size = builder.GetSize(); + const uint8_t* table = builder.GetBufferPointer(); + return std::vector<uint8_t>(table, table + table_size); + } + + // This will amend the existing table. If that's not desired, then it is + // necessary to call clear() ahead of time. + void deserialize(const std::vector<uint8_t>& data) + { + clear(); + auto verifier = flatbuffers::Verifier(data.data(), data.size()); + if (!VerifyDsaCalBuffer(verifier)) { + throw uhd::runtime_error("dsa_cal: Invalid data provided! "); + } + auto cal_table = GetDsaCal(static_cast<const void*>(data.data())); + if (cal_table->metadata()->version_major() != VERSION_MAJOR) { + throw uhd::runtime_error("dsa_cal: Compat number mismatch!"); + } + if (cal_table->metadata()->version_minor() != VERSION_MINOR) { + UHD_LOG_WARNING("CAL", + "gain_cal: Expected compat number " + << VERSION_MAJOR << "." << VERSION_MINOR << ", got " + << cal_table->metadata()->version_major() << "." + << cal_table->metadata()->version_minor()); + } + _name = std::string(cal_table->metadata()->name()->c_str()); + _serial = std::string(cal_table->metadata()->serial()->c_str()); + _timestamp = cal_table->metadata()->timestamp(); + + auto band_dsa_map = cal_table->band_dsa_map(); + for (auto it = band_dsa_map->begin(); it != band_dsa_map->end(); ++it) { + const uint64_t max_freq = it->max_freq(); + const std::string name(it->name()->c_str()); + auto gains = it->gains(); + if (gains->size() != num_gain_stages) { + UHD_LOG_ERROR("CAL", "Invalid number of gain indizes. Got: " + << gains->size() << " expected: " << num_gain_stages); + throw uhd::runtime_error("Invalid number of gain indizes"); + } + std::array<std::array<uint32_t, num_dsa>, num_gain_stages> indizes; + int i = 0; + for (auto gain_it = gains->begin(); gain_it != gains->end(); ++gain_it) { + if (gain_it->steps()->size() != num_dsa) { + UHD_LOG_ERROR("CAL", "Invalid number of attenuator indizes. Got: " + << gain_it->steps()->size() << " expected: " << num_dsa); + throw uhd::runtime_error("Invalid number of attenuator indizes"); + } + std::copy(gain_it->steps()->begin(), + gain_it->steps()->end(), + indizes[i++].begin()); + } + add_frequency_band(max_freq, name, indizes); + } + } + + +private: + std::string _name; + std::string _serial; + uint64_t _timestamp; + + using dsa_steps = std::array<std::array<uint32_t, num_dsa>, num_gain_stages>; + using name_dsas_pair = std::pair<std::string, dsa_steps>; + + std::map<uint64_t /* freq */, name_dsas_pair> _data; + +}; + +} //namespace +std::shared_ptr<zbx_tx_dsa_cal> zbx_tx_dsa_cal::make() +{ + return std::make_shared<dsa_cal_impl< + zbx_tx_dsa_cal, + zbx_tx_dsa_cal::NUM_GAIN_STAGES, + zbx_tx_dsa_cal::NUM_DSA>>(); +} + +std::shared_ptr<zbx_tx_dsa_cal> zbx_tx_dsa_cal::make( + const std::string& name, const std::string& serial, const uint64_t timestamp) +{ + return std::make_shared<dsa_cal_impl< + zbx_tx_dsa_cal, + zbx_tx_dsa_cal::NUM_GAIN_STAGES, + zbx_tx_dsa_cal::NUM_DSA>>(name, serial, timestamp); +} + +std::shared_ptr<zbx_rx_dsa_cal> zbx_rx_dsa_cal::make() +{ + return std::make_shared<dsa_cal_impl< + zbx_rx_dsa_cal, + zbx_rx_dsa_cal::NUM_GAIN_STAGES, + zbx_rx_dsa_cal::NUM_DSA>>(); +} + +std::shared_ptr<zbx_rx_dsa_cal> zbx_rx_dsa_cal::make( + const std::string& name, const std::string& serial, const uint64_t timestamp) +{ + return std::make_shared<dsa_cal_impl< + zbx_rx_dsa_cal, + zbx_rx_dsa_cal::NUM_GAIN_STAGES, + zbx_rx_dsa_cal::NUM_DSA>>(name, serial, timestamp); +} diff --git a/host/lib/ic_reg_maps/CMakeLists.txt b/host/lib/ic_reg_maps/CMakeLists.txt index 42ac4d051..039614c03 100644 --- a/host/lib/ic_reg_maps/CMakeLists.txt +++ b/host/lib/ic_reg_maps/CMakeLists.txt @@ -128,6 +128,11 @@ LIBUHD_PYTHON_GEN_SOURCE( ) LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_lmx2572_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/lmx2572_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( ${CMAKE_CURRENT_SOURCE_DIR}/gen_lmk04828_regs.py ${CMAKE_CURRENT_BINARY_DIR}/lmk04828_regs.hpp ) @@ -142,4 +147,9 @@ LIBUHD_PYTHON_GEN_SOURCE( ${CMAKE_CURRENT_BINARY_DIR}/rhodium_cpld_regs.hpp ) +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_zbx_cpld_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/zbx_cpld_regs.hpp + ) + set(LIBUHD_PYTHON_GEN_SOURCE_DEPS) diff --git a/host/lib/ic_reg_maps/common.py b/host/lib/ic_reg_maps/common.py index 96b63410f..59a24d298 100755 --- a/host/lib/ic_reg_maps/common.py +++ b/host/lib/ic_reg_maps/common.py @@ -136,7 +136,7 @@ class ${name}_t: % if reg.is_array: self.${reg.get_name()} = [self.${reg.get_name()}_t.${reg.get_default()},] * ${reg.get_array_len()} % else: - self.${reg.get_name()} = ${reg.get_default()} + self.${reg.get_name()} = self.${reg.get_name()}_t.${reg.get_default()} % endif % else: % if reg.is_array: @@ -156,7 +156,11 @@ class ${name}_t: if self._state is None: self._state = ${name}_t() % for reg in regs: + % if reg.is_array: + self._state.${reg.get_name()} = self.${reg.get_name()}.copy() + % else: self._state.${reg.get_name()} = self.${reg.get_name()} + % endif % endfor def get_changed_addrs(self): diff --git a/host/lib/ic_reg_maps/gen_lmk04816_regs.py b/host/lib/ic_reg_maps/gen_lmk04816_regs.py index ccc82804f..1f6db9c5b 100755 --- a/host/lib/ic_reg_maps/gen_lmk04816_regs.py +++ b/host/lib/ic_reg_maps/gen_lmk04816_regs.py @@ -380,11 +380,29 @@ uint32_t get_reg(int addr){ } """ +PY_BODY_TMPL = """\ +def get_reg(self, addr): + reg = 0 + % for addr in sorted(set(map(lambda r: r.get_addr(), regs))): + <% if_state = 'if' if loop.index == 0 else 'elif' %> + ${if_state} addr == ${addr}: + % for reg in filter(lambda r: r.get_addr() == addr, regs): + % if reg.get_enums(): + reg |= (self.${reg.get_name()}.value & ${reg.get_mask()}) << ${reg.get_shift()} + % else: + reg |= (self.${reg.get_name()} & ${reg.get_mask()}) << ${reg.get_shift()} + % endif + % endfor + % endfor + return reg + +""" + if __name__ == '__main__': import common; common.generate( name='lmk04816_regs', regs_tmpl=REGS_TMPL, body_tmpl=BODY_TMPL, + py_body_tmpl=PY_BODY_TMPL, file=__file__, ) - diff --git a/host/lib/ic_reg_maps/gen_lmx2572_regs.py b/host/lib/ic_reg_maps/gen_lmx2572_regs.py new file mode 100755 index 000000000..0995f57e2 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_lmx2572_regs.py @@ -0,0 +1,663 @@ +#!/usr/bin/env python +# +# Copyright 2019 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Register map for LMX2572 +""" + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## + +REGS_TMPL = """\ +######################################################################## +## address 0 +######################################################################## +powerdown 0[0] 0 normal_operation, power_down +reset 0[1] 0 normal_operation, reset +muxout_ld_sel 0[2] 1 register_readback, lock_detect +fcal_en 0[3] 1 invalid, enable +reg0_reserved0 0[4] 1 +fcal_lpfd_adj 0[5:6] 0 +fcal_hpfd_adj 0[7:8] 0 +out_mute 0[9] 1 disabled, muted +reg0_reserved1 0[10] 0 +add_hold 0[11] 0 +reg0_reserved2 0[12:13] 0x2 +vco_phase_sync_en 0[14] 0 normal_operation, phase_sync_mode +ramp_en 0[15] 0 normal_operation, frequency_ramping +######################################################################## +## address 1 +######################################################################## +cal_clk_div 1[0:2] 0 +reg1_reserved0 1[3:15] 0x101 +######################################################################## +## address 2 +######################################################################## +reg2_reserved0 2[0:15] 0x500 +######################################################################## +## address 3 +######################################################################## +reg3_reserved0 3[0:15] 0x782 +######################################################################## +## address 4 +######################################################################## +reg4_reserved0 4[0:15] 0xA43 +######################################################################## +## address 5 +######################################################################## +reg5_reserved0 5[0:10] 0xC8 +ipbuf_term 5[11] 0 normal_operation, internally_terminated +ipbuf_type 5[12] 1 differential, single_ended +reg5_reserved1 5[13:15] 1 +######################################################################## +## address 6 +######################################################################## +reg6_reserved0 6[0:10] 0x2 +ldo_dly 6[11:15] 0x19 +######################################################################## +## address 7 +######################################################################## +reg7_reserved0 7[0:13] 0xB2 +out_force 7[14] 0 use_out_mute, no_mute +reg7_reserved1 7[15] 0 +######################################################################## +## address 8 +######################################################################## +reg8_reserved0 8[0:10] 0 +vco_capctrl_force 8[11] 0 normal_operation, use_vco_capctrl +reg8_reserved1 8[12:13] 0x2 +vco_daciset_force 8[14] 0 normal_operation, use_vco_daciset +reg8_reserved2 8[15] 0 +######################################################################## +## address 9 +######################################################################## +reg9_reserved0 9[0:11] 0x4 +osc_2x 9[12] 0 disabled, enabled +reg9_reserved1 9[13] 0 +mult_hi 9[14] 0 less_than_equal_to_100M, greater_than_100M +reg9_reserved2 9[15] 0 +######################################################################## +## address 10 +######################################################################## +reg10_reserved0 10[0:6] 0x78 +mult 10[7:11] 1 +reg10_reserved1 10[12:15] 1 +######################################################################## +## address 11 +######################################################################## +reg11_reserved0 11[0:3] 0x8 +pll_r 11[4:11] 1 +reg11_reserved1 11[12:15] 0xB +######################################################################## +## address 12 +######################################################################## +pll_r_pre 12[0:11] 1 +reg12_reserved0 12[12:15] 0x5 +######################################################################## +## address 13 +######################################################################## +reg13_reserved0 13[0:15] 0x4000 +######################################################################## +## address 14 +######################################################################## +reg14_reserved0 14[0:2] 0x0 +cpg 14[3:6] 0x8 +reg14_reserved1 14[7:15] 0x30 +######################################################################## +## address 15 +######################################################################## +reg15_reserved0 15[0:15] 0x60E +######################################################################## +## address 16 +######################################################################## +vco_daciset 16[0:8] 0x80 +reg16_reserved0 16[9:15] 0 +######################################################################## +## address 17 +######################################################################## +vco_daciset_strt 17[0:8] 0x96 +reg17_reserved0 17[9:15] 0 +######################################################################## +## address 18 +######################################################################## +reg18_reserved0 18[0:15] 0x64 +######################################################################## +## address 19 +######################################################################## +vco_capctrl 19[0:7] 0xB7 +reg19_reserved0 19[8:15] 0x27 +######################################################################## +## address 20 +######################################################################## +reg20_reserved0 20[0:9] 0x48 +vco_sel_force 20[10] 0 disabled, enabled +vco_sel 20[11:13] 0x6 +reg20_reserved1 20[14:15] 1 +######################################################################## +## address 21 +######################################################################## +reg21_reserved0 21[0:15] 0x409 +######################################################################## +## address 22 +######################################################################## +reg22_reserved0 22[0:15] 1 +######################################################################## +## address 23 +######################################################################## +reg23_reserved0 23[0:15] 0x7C +######################################################################## +## address 24 +######################################################################## +reg24_reserved0 24[0:15] 0x71A +######################################################################## +## address 25 +######################################################################## +reg25_reserved0 25[0:15] 0x624 +######################################################################## +## address 26 +######################################################################## +reg26_reserved0 26[0:15] 0x808 +######################################################################## +## address 27 +######################################################################## +reg27_reserved0 27[0:15] 0x2 +######################################################################## +## address 28 +######################################################################## +reg28_reserved0 28[0:15] 0x488 +######################################################################## +## address 29 +######################################################################## +reg29_reserved0 29[0:15] 0x18C6 +######################################################################## +## address 30 +######################################################################## +reg30_reserved0 30[0:15] 0x18C6 +######################################################################## +## address 31 +######################################################################## +reg31_reserved0 31[0:15] 0xC3E6 +######################################################################## +## address 32 +######################################################################## +reg32_reserved0 32[0:15] 0x5BF +######################################################################## +## address 33 +######################################################################## +reg33_reserved0 33[0:15] 0x1E01 +######################################################################## +## address 34 +######################################################################## +pll_n_upper_3_bits 34[0:2] 0 +reg34_reserved0 34[3:15] 0x2 +######################################################################## +## address 35 +######################################################################## +reg35_reserved0 35[0:15] 0x4 +######################################################################## +## address 36 +######################################################################## +pll_n_lower_16_bits 36[0:15] 0x28 +######################################################################## +## address 37 +######################################################################## +reg37_reserved0 37[0:7] 0x5 +pfd_dly_sel 37[8:13] 0x2 +reg37_reserved1 37[14] 0 +mash_seed_en 37[15] 0 disabled, enabled +######################################################################## +## address 38 +######################################################################## +pll_den_upper 38[0:15] 0xFFFF +######################################################################## +## address 39 +######################################################################## +pll_den_lower 39[0:15] 0xFFFF +######################################################################## +## address 40 +######################################################################## +mash_seed_upper 40[0:15] 0 +######################################################################## +## address 41 +######################################################################## +mash_seed_lower 41[0:15] 0 +######################################################################## +## address 42 +######################################################################## +pll_num_upper 42[0:15] 0 +######################################################################## +## address 43 +######################################################################## +pll_num_lower 43[0:15] 0 +######################################################################## +## address 44 +######################################################################## +mash_order 44[0:2] 0x2 interger_mode, first_order, second_order, third_order, fourth_order +reg44_reserved0 44[3:4] 0 +mash_reset_n 44[5] 1 reset, normal_operation +outa_pd 44[6] 0 normal_operation, power_down +outb_pd 44[7] 1 normal_operation, power_down +outa_pwr 44[8:13] 0x22 +reg44_reserved1 44[14:15] 0 +######################################################################## +## address 45 +######################################################################## +outb_pwr 45[0:5] 0x22 +register45_reserved0 45[6:10] 0x18 +outa_mux 45[11:12] 0 channel_divider, vco, not_used, high_impedance +register45_reserved1 45[13:15] 0x6 +######################################################################## +## address 46 +######################################################################## +outb_mux 46[0:1] 0 channel_divider, vco, sysref, high_impedance +register46_reserved0 46[2:15] 0x1FC +######################################################################## +## address 47 +######################################################################## +reg47_reserved0 47[0:15] 0x300 +######################################################################## +## address 48 +######################################################################## +reg48_reserved0 48[0:15] 0x3E0 +######################################################################## +## address 49 +######################################################################## +reg49_reserved0 49[0:15] 0x4180 +######################################################################## +## address 50 +######################################################################## +reg50_reserved0 50[0:15] 0x80 +######################################################################## +## address 51 +######################################################################## +reg51_reserved0 51[0:15] 0x80 +######################################################################## +## address 52 +######################################################################## +reg52_reserved0 52[0:15] 0x420 +######################################################################## +## address 53 +######################################################################## +reg53_reserved0 53[0:15] 0 +######################################################################## +## address 54 +######################################################################## +reg54_reserved0 54[0:15] 0 +######################################################################## +## address 55 +######################################################################## +reg55_reserved0 55[0:15] 0 +######################################################################## +## address 56 +######################################################################## +reg56_reserved0 56[0:15] 0 +######################################################################## +## address 57 +######################################################################## +reg57_reserved0 57[0:15] 0 +######################################################################## +## address 58 +######################################################################## +reg58_reserved0 58[0:8] 1 +inpin_fmt 58[9:11] 0 SYNC_EQUALS_SYSREFREQ_EQUALS_CMOS, SYNC_EQUALS_LVDS_SYSREFREQ_EQUAL_CMOS, SYNC_EQUALS_CMOS_SYSREFREQ_EQUAL_LVDS, SYNC_EQUALS_SYSREFREQ_EQUALS_LVDS, SYNC_EQUALS_SYSREFREQ_EQUALS_CMOS2, SYNC_EQUALS_LVDSFILTERED_SYSREFREQ_EQUALS_CMOS, SYNC_EQUALS_CMOS_SYSREFREQ_EQUALS_LVDSFILTERED, SYNC_EQUALS_SYSREFREQ_EQUALS_LVDSFILTERED +inpin_lvl 58[12:13] 0 vin_divide_by_4, vin, vin_divide_by_2, invalid +inpin_hyst 58[14] 0 disabled, enabled +inpin_ignore 58[15] 1 +######################################################################## +## address 59 +######################################################################## +ld_type 59[0] 1 vcocal, vtune_and_vcocal +reg59_reserved0 59[1:15] 0 +######################################################################## +## address 60 +######################################################################## +ld_dly 60[0:15] 0x3E8 +######################################################################## +## address 61 +######################################################################## +reg61_reserved0 61[0:15] 0xA8 +######################################################################## +## address 62 +######################################################################## +reg62_reserved0 62[0:9] 0xAF +dblbuf_en_0 62[10] 0 disabled, enabled +dblbuf_en_1 62[11] 0 disabled, enabled +dblbuf_en_2 62[12] 0 disabled, enabled +dblbuf_en_3 62[13] 0 disabled, enabled +dblbuf_en_4 62[14] 0 disabled, enabled +dblbuf_en_5 62[15] 0 disabled, enabled +######################################################################## +## address 63 +######################################################################## +reg63_reserved0 63[0:15] 0 +######################################################################## +## address 64 +######################################################################## +reg64_reserved0 64[0:15] 0x1388 +######################################################################## +## address 65 +######################################################################## +reg65_reserved0 65[0:15] 0 +######################################################################## +## address 66 +######################################################################## +reg66_reserved0 6[0:15] 0x1F4 +######################################################################## +## address 67 +######################################################################## +reg67_reserved0 67[0:15] 0 +######################################################################## +## address 68 +######################################################################## +reg68_reserved0 68[0:15] 0x3E8 +######################################################################## +## address 69 +######################################################################## +mash_rst_count_upper 69[0:15] 0 +######################################################################## +## address 70 +######################################################################## +mash_rst_count_lower 70[0:15] 0xC350 +######################################################################## +## address 71 +######################################################################## +reg71_reserved0 71[0:1] 1 +sysref_repeat 71[2] 0 master_mode, repeater_mode +sysref_en 71[3] 0 disabled, enabled +sysref_pulse 71[4] 0 disabled, enabled +sysref_div_pre 71[5:7] 0x4 divide_by_2=2, divide_by_4=4 +reg71_reserved1 71[8:15] 0 +######################################################################## +## address 72 +######################################################################## +sysref_div 72[0:10] 1 +reg72_reserved0 72[11:15] 0 +######################################################################## +## address 73 +######################################################################## +jesd_dac1_ctrl 73[0:5] 0x3F +jesd_dac2_ctrl 73[6:11] 0 +reg73_reserved0 73[12:15] 0 +######################################################################## +## address 74 +######################################################################## +jesd_dac3_ctrl 74[0:5] 0 +jesd_dac4_ctrl 74[6:11] 0 +sysref_pulse_cnt 74[12:15] 0 +######################################################################## +## address 75 +######################################################################## +reg75_reserved0 75[0:5] 0 +chdiv 75[6:10] 0 divide_by_2=0, divide_by_4=1, divide_by_8=3, divide_by_16=5, divide_by_32=7, divide_by_64=9, divide_by_128=12, divide_by_256=14 +reg75_reserved1 75[11:15] 1 +######################################################################## +## address 76 +######################################################################## +reg76_reserved0 76[0:15] 0xC +######################################################################## +## address 77 +######################################################################## +reg77_reserved0 77[0:15] 0 +######################################################################## +## address 78 +######################################################################## +reg78_reserved0 78[0] 0 +vco_capctrl_strt 78[1:8] 0x32 +quick_recal_en 78[9] 0 +reg78_reserved1 78[10] 0 +ramp_thresh_33rd 78[11] 0 +reg78_reserved2 78[12:15] 0 +######################################################################## +## address 79 +######################################################################## +ramp_thresh_upper 79[0:15] 0 +######################################################################## +## address 80 +######################################################################## +ramp_thresh_lower 80[0:15] 0 +######################################################################## +## address 81 +######################################################################## +ramp_limit_high_33rd 81[0] 0 +reg81_reserved0 81[1:15] 0 +######################################################################## +## address 82 +######################################################################## +ramp_limit_high_upper 82[0:15] 0 +######################################################################## +## address 83 +######################################################################## +ramp_limit_high_lower 83[0:15] 0 +######################################################################## +## address 84 +######################################################################## +ramp_limit_low_33rd 84[0] 0 +reg84_reserved0 84[1:15] 0 +######################################################################## +## address 85 +######################################################################## +ramp_limit_low_upper 85[0:15] 0 +######################################################################## +## address 86 +######################################################################## +ramp_limit_low_lower 86[0:15] 0 +######################################################################## +## address 87 +######################################################################## +reg87_reserved0 87[0:15] 0 +######################################################################## +## address 88 +######################################################################## +reg88_reserved0 88[0:15] 0 +######################################################################## +## address 89 +######################################################################## +reg89_reserved0 89[0:15] 0 +######################################################################## +## address 90 +######################################################################## +reg90_reserved0 90[0:15] 0 +######################################################################## +## address 91 +######################################################################## +reg91_reserved0 91[0:15] 0 +######################################################################## +## address 92 +######################################################################## +reg92_reserved0 92[0:15] 0 +######################################################################## +## address 93 +######################################################################## +reg93_reserved0 93[0:15] 0 +######################################################################## +## address 94 +######################################################################## +reg94_reserved0 94[0:15] 0 +######################################################################## +## address 95 +######################################################################## +reg95_reserved0 95[0:15] 0 +######################################################################## +## address 96 +######################################################################## +reg96_reserved0 96[0:1] 0 +ramp_burst_count 96[2:14] 0 +ramp_burst_en 96[15] 0 disabled, enabled +######################################################################## +## address 97 +######################################################################## +ramp_burst_trig 97[0:1] 0 ramp_transistion=0, trigger_a=1, trigger_b=2 +reg97_reserved0 97[2] 0 +ramp_triga 97[3:6] 0 disabled=0, rampclk_rising_edge=1, rampdir_rising_edge=2, always_triggered=4, rampclk_falling_edge=9, rampdir_falling_edge=10 +ramp_trgb 97[7:10] 0 disabled=0, rampclk_rising_edge=1, rampdir_rising_edge=2, always_triggered=4, rampclk_falling_edge=9, rampdir_falling_edge=10 +reg97_reserved1 97[11:14] 0 +ramp0_rst 97[15] 0 disabled, reset +######################################################################## +## address 98 +######################################################################## +ramp0_dly 98[0] 0 +reg98_reserved0 98[1] 0 +ramp0_inc_upper_14 98[15:2] 0 +######################################################################## +## address 99 +######################################################################## +ramp0_inc_lower 99[0:15] 0 +######################################################################## +## address 100 +######################################################################## +ramp0_len 100[0:15] 0 +######################################################################## +## address 101 +######################################################################## +ramp0_next_trig 101[0:1] 0 timeout_counter, trigger_a, trigger_b +reg101_reserved0 101[2:3] 0 +ramp0_next 101[4] 0 ramp0, ramp1 +ramp1_rst 101[5] 0 disabled, reset +ramp1_dly 101[6] 0 +reg101_reserved1 101[7:15] 0 +######################################################################## +## address 102 +######################################################################## +ramp1_inc_upper_14 102[13:0] 0 +reg102_reserved0 102[14:15] 0 +######################################################################## +## address 103 +######################################################################## +ramp1_inc_lower 103[0:15] 0 +######################################################################## +## address 104 +######################################################################## +ramp1_len 104[0:15] 0 +######################################################################## +## address 105 +######################################################################## +ramp1_next_trig 105[0:1] 0 timeout_counter, trigger_a, trigger_b +reg105_reserved0 105[2:3] 0 +ramp1_next 105[4] 0 ramp0, ramp1 +ramp_manual 105[5] 0 automatic_ramping, manual_ramping +ramp_dly_cnt 105[6:15] 0x111 +######################################################################## +## address 106 +######################################################################## +ramp_scale_count 106[0:2] 0x7 +reg106_reserved0 106[3] 0 +ramp_trig_cal 106[4] 0 disabled, enabled +reg106_reserved1 106[5:15] 0 +######################################################################## +## address 114 +######################################################################## +fsk_mode_sel 114[0:1] 0 unused=0,fsk_spi=2, fsk_spi_fast=3 +fsk_spi_dev_sel 114[2:4] 0 +fsk_spi_level 114[5:6] 0 disabled, 2fsk, 4fsk, 8fsk +reg114_reserved0 114[7:9] 0 +fsk_en 114[10] 0 disabled, enabled +reg114_reserved1 114[11:15] 0xF +######################################################################## +## address 115 +######################################################################## +reg115_reserved0 115[0:2] 0 +fsk_dev_scale 115[3:7] 0 +reg115_reserved1 115[8:15] 0 +######################################################################## +## address 116 +######################################################################## +fsk_dev0 116[0:15] 0 +######################################################################## +## address 117 +######################################################################## +fsk_dev1 117[0:15] 0 +######################################################################## +## address 118 +######################################################################## +fsk_dev2 118[0:15] 0 +######################################################################## +## address 119 +######################################################################## +fsk_dev3 119[0:15] 0 +######################################################################## +## address 120 +######################################################################## +fsk_dev4 120[0:15] 0 +######################################################################## +## address 121 +######################################################################## +fsk_dev5 121[0:15] 0 +######################################################################## +## address 122 +######################################################################## +fsk_dev6 122[0:15] 0 +######################################################################## +## address 123 +######################################################################## +fsk_dev7 123[0:15] 0 +######################################################################## +## address 124 +######################################################################## +fsk_spi_dev 124[0:15] 0 +######################################################################## +## address 125 +######################################################################## +reg125 125[0:16] 0x2288 +""" +######################################################################## +# Template for methods in the body of the struct +######################################################################## + +BODY_TMPL = """\ +uint16_t get_reg(int addr){ + uint16_t reg = 0; + switch(addr){ + % for addr in sorted(set(map(lambda r: r.get_addr(), regs))): + case ${addr}: + % for reg in filter(lambda r: r.get_addr() == addr, regs): + reg |= (uint16_t(${reg.get_name()}) & ${reg.get_mask()}) << ${reg.get_shift()}; + % endfor + break; + % endfor + } + return reg; +} + +std::set<uint8_t> get_ro_regs() +{ + return {107, 108, 109, 110, 111, 112, 113}; +} + +int get_num_regs() +{ + return 126; +} +""" + +PY_BODY_TMPL = """\ +def get_reg(self, addr): + reg = 0 + % for addr in sorted(set(map(lambda r: r.get_addr(), regs))): + <% if_state = 'if' if loop.index == 0 else 'elif' %> + ${if_state} addr == ${addr}: + % for reg in filter(lambda r: r.get_addr() == addr, regs): + % if reg.get_enums(): + reg |= (self.${reg.get_name()}.value & ${reg.get_mask()}) << ${reg.get_shift()} + % else: + reg |= (self.${reg.get_name()} & ${reg.get_mask()}) << ${reg.get_shift()} + % endif + % endfor + % endfor + return reg + +""" + +if __name__ == '__main__': + import common + common.generate( + name='lmx2572_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + py_body_tmpl=PY_BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/ic_reg_maps/gen_zbx_cpld_regs.py b/host/lib/ic_reg_maps/gen_zbx_cpld_regs.py new file mode 100755 index 000000000..0efc8a4d7 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_zbx_cpld_regs.py @@ -0,0 +1,450 @@ +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Register map for the ZBX CPLD. Note that the ground truth for this register map +is in the fpga/usrp3/top/x400/dboards/zbx/cpld subdirectory, and can be looked +up in fpga/usrp3/top/x400/dboards/zbx/cpld/doc/ZBX_CPLD.htm + +This CPLD regmap works with RevB daughterboards only. +""" + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL = """\ +######################################################################## +## SLAVE SETUP +######################################################################## +BOARD_ID 0x0000[0:15] 0 +REVISION 0x0004[0:31] 0 +OLDEST_COMPAT_REVISION 0x0008[0:31] 0 +SCRATCH 0x000C[0:31] 0 +GIT_HASH 0x0010[0:31] 0 +ENABLE_TX_POS_7V0 0x0040[0] 0 disable, enable +ENABLE_RX_POS_7V0 0x0040[1] 0 disable, enable +ENABLE_POS_3V3 0x0040[2] 0 disable, enable +P7V_B_STATUS 0x0044[0] 0 +P7V_A_STATUS 0x0044[1] 0 +PLL_REF_CLOCK_ENABLE 0x0048[0] 0 disable, enable +######################################################################## +## ATR +######################################################################## +CURRENT_RF0_CONFIG 0x1000[0:7] 1 +CURRENT_RF1_CONFIG 0x1000[8:15] 1 +CURRENT_RF0_DSA_CONFIG 0x1000[23:16] 1 +CURRENT_RF1_DSA_CONFIG 0x1000[31:24] 1 +RF0_OPTION 0x1004[0:1] 0 sw_defined, classic_atr, fpga_state +RF1_OPTION 0x1004[8:9] 0 sw_defined, classic_atr, fpga_state +RF0_DSA_OPTION 0x1004[17:16] 0 sw_defined, classic_atr, fpga_state +RF1_DSA_OPTION 0x1004[25:24] 0 sw_defined, classic_atr, fpga_state +SW_RF0_CONFIG 0x1008[0:7] 0 +SW_RF1_CONFIG 0x1008[8:15] 0 +SW_RF0_DSA_CONFIG 0x1008[23:16] 0 +SW_RF1_DSA_CONFIG 0x1008[31:24] 0 +######################################################################## +## LO SPI +######################################################################## +DATA 0x1020[0:15] 0 +ADDRESS 0x1020[16:22] 0 +READ_FLAG 0x1020[23] 1 write, read +LO_SELECT 0x1020[24:26] 0 TX0_LO1, TX0_LO2, TX1_LO1, TX1_LO2, RX0_LO1, RX0_LO2, RX1_LO1, RX1_LO2 +START_TRANSACTION 0x1020[28] 0 disable, enable +SPI_READY 0x1020[30] 0 +DATA_VALID 0x1020[31] 0 +######################################################################## +## LO SYNC +######################################################################## +PULSE_TX0_LO1_SYNC 0x1024[0] 0 +PULSE_TX0_LO2_SYNC 0x1024[1] 0 +PULSE_TX1_LO1_SYNC 0x1024[2] 0 +PULSE_TX1_LO2_SYNC 0x1024[3] 0 +PULSE_RX0_LO1_SYNC 0x1024[4] 0 +PULSE_RX0_LO2_SYNC 0x1024[5] 0 +PULSE_RX1_LO1_SYNC 0x1024[6] 0 +PULSE_RX1_LO2_SYNC 0x1024[7] 0 +BYPASS_SYNC_REGISTER 0x1024[8] 0 disable, enable +######################################################################## +## LED CONTROL +######################################################################## +RX0_RX_LED[256] 0x1400[0] 0 disable, enable +RX0_TRX_LED[256] 0x1400[1] 0 disable, enable +TX0_TRX_LED[256] 0x1400[2] 0 disable, enable +RX1_RX_LED[256] 0x1400[16] 0 disable, enable +RX1_TRX_LED[256] 0x1400[17] 0 disable, enable +TX1_TRX_LED[256] 0x1400[18] 0 disable, enable +######################################################################## +## PATH CONTROL - TX 0 +######################################################################## +TX0_IF2_1_2[256] 0x2000[0] 0 filter_2, filter_1 +TX0_IF1_3[256] 0x2000[2:3] 0 filter_0_3, filter_4, filter_6, filter_5 +TX0_IF1_4[256] 0x2000[4:5] 0 termination, filter_1, filter_2, filter_3 +TX0_IF1_5[256] 0x2000[6:7] 0 filter_3, filter_2, filter_1, termination +TX0_IF1_6[256] 0x2000[8:9] 0 filter_5, filter_6, filter_4, filter_0_3 +TX0_7[256] 0x2000[10:11] 0 termination, no_connect, highband, lowband +TX0_RF_8[256] 0x2000[12:14] 0 invalid_0, rf_3, rf_1, invalid_1, rf_2 +TX0_RF_9[256] 0x2000[16:17] 0 rf_3, rf_1, rf_2, highband +TX0_ANT_10[256] 0x2000[18:19] 0 bypass_amp, cal_loopback, lowband_amp, highband_amp +TX0_ANT_11[256] 0x2000[20:21] 0 tx_rx, highband_amp, lowband_amp, bypass_amp +TX0_LO_13[256] 0x2000[24] 0 internal, external +TX0_LO_14[256] 0x2000[26] 0 external, internal +######################################################################## +## PATH CONTROL - TX 1 +######################################################################## +TX1_IF2_1_2[256] 0x2400[0] 0 filter_1, filter_2 +TX1_IF1_3[256] 0x2400[2:3] 0 filter_5, filter_6, filter_4, filter_0_3 +TX1_IF1_4[256] 0x2400[4:5] 0 filter_3, filter_2, filter_1, termination +TX1_IF1_5[256] 0x2400[6:7] 0 termination, filter_1, filter_2, filter_3 +TX1_IF1_6[256] 0x2400[8:9] 0 filter_0_3, filter_4, filter_6, filter_5 +TX1_7[256] 0x2400[10:11] 0 lowband, highband, no_connect, termination +TX1_RF_8[256] 0x2400[12:14] 0 invalid_0, rf_2, rf_1, invalid_1, rf_3 +TX1_RF_9[256] 0x2400[16:17] 0 highband, rf_2, rf_1, rf_3 +TX1_ANT_10[256] 0x2400[18:19] 0 highband_amp, lowband_amp, bypass_amp, cal_loopback +TX1_ANT_11[256] 0x2400[20:21] 0 tx_rx, bypass_amp, lowband_amp, highband_amp +TX1_LO_13[256] 0x2400[24] 0 external, internal +TX1_LO_14[256] 0x2400[26] 0 internal, external +######################################################################## +## PATH CONTROL - RX 0 +######################################################################## +RX0_ANT_1[256] 0x2800[0:1] 0 cal_loopback, termination, tx_rx, rx2 +RX0_2[256] 0x2800[2] 0 highband, lowband +RX0_RF_3[256] 0x2800[4:6] 0 invalid_0, rf_2, rf_1, invalid_1, rf_3 +RX0_4[256] 0x2800[8] 0 lowband, highband +RX0_IF1_5[256] 0x2800[10:11] 0 filter_4, filter_3, filter_2, filter_1 +RX0_IF1_6[256] 0x2800[12:13] 0 filter_1, filter_2, filter_3, filter_4 +RX0_IF2_7_8[256] 0x2800[14] 0 filter_2, filter_1 +RX0_LO_9[256] 0x2800[16] 0 internal, external +RX0_LO_10[256] 0x2800[18] 0 internal, external +RX0_RF_11[256] 0x2800[20:22] 0 invalid_0, rf_3, rf_1, invalid_1, rf_2, invalid_2, invalid_3, invalid_4 +######################################################################## +## PATH CONTROL - RX 1 +######################################################################## +RX1_ANT_1[256] 0x2C00[0:1] 0 cal_loopback, tx_rx, rx2, termination +RX1_2[256] 0x2C00[2] 0 lowband, highband +RX1_RF_3[256] 0x2C00[4:6] 0 invalid_0, rf_3, rf_1, invalid_1, rf_2 +RX1_4[256] 0x2C00[8] 0 highband, lowband +RX1_IF1_5[256] 0x2C00[10:11] 0 filter_1, filter_2, filter_3, filter_4 +RX1_IF1_6[256] 0x2C00[12:13] 0 filter_4, filter_3, filter_2, filter_1 +RX1_IF2_7_8[256] 0x2C00[14] 0 filter_1, filter_2 +RX1_LO_9[256] 0x2C00[16] 0 external, internal +RX1_LO_10[256] 0x2C00[18] 0 external, internal +RX1_RF_11[256] 0x2C00[20:22] 0 invalid_0, rf_2, rf_1, invalid_1, rf_3, invalid_2, invalid_3, invalid_4 +######################################################################## +## DSA CONTROL +######################################################################## +TX0_DSA1[256] 0x3000[0:4] 31 +TX0_DSA2[256] 0x3000[8:12] 31 +TX1_DSA1[256] 0x3400[0:4] 31 +TX1_DSA2[256] 0x3400[8:12] 31 +RX0_DSA1[256] 0x3800[0:3] 15 +RX0_DSA2[256] 0x3800[4:7] 15 +RX0_DSA3_A[256] 0x3800[8:11] 15 +RX0_DSA3_B[256] 0x3800[12:15] 15 +RX1_DSA1[256] 0x3C00[0:3] 15 +RX1_DSA2[256] 0x3C00[4:7] 15 +RX1_DSA3_A[256] 0x3C00[8:11] 15 +RX1_DSA3_B[256] 0x3C00[12:15] 15 +TX0_TABLE_SELECT[256] 0x4000[0:7] 0 +TX1_TABLE_SELECT[256] 0x4400[0:7] 0 +RX0_TABLE_SELECT[256] 0x4800[0:7] 0 +RX1_TABLE_SELECT[256] 0x4C00[0:7] 0 +TX0_TABLE_DSA1[256] 0x5000[0:4] 31 +TX0_TABLE_DSA2[256] 0x5000[8:12] 31 +TX1_TABLE_DSA1[256] 0x5400[0:4] 31 +TX1_TABLE_DSA2[256] 0x5400[8:12] 31 +RX0_TABLE_DSA1[256] 0x5800[0:3] 15 +RX0_TABLE_DSA2[256] 0x5800[4:7] 15 +RX0_TABLE_DSA3_A[256] 0x5800[8:11] 15 +RX0_TABLE_DSA3_B[256] 0x5800[12:15] 15 +RX1_TABLE_DSA1[256] 0x5C00[0:3] 15 +RX1_TABLE_DSA2[256] 0x5C00[4:7] 15 +RX1_TABLE_DSA3_A[256] 0x5C00[8:11] 15 +RX1_TABLE_DSA3_B[256] 0x5C00[12:15] 15 +""" + +######################################################################## +# Template for python methods in the body of the struct +######################################################################## + +PY_BODY_TMPL = """\ +def get_reg(self, addr): + "Return the value of register at address addr" + reg = 0 + # First the regular registers + % for addr in sorted(set([r.get_addr() for r in regs if not r.is_array])): + if addr == ${addr}: + % for reg in filter(lambda r: r.get_addr() == addr, regs): + % if reg.get_enums(): + reg |= (self.${reg.get_name()}.value & ${reg.get_mask()}) << ${reg.get_shift()} + % else: + reg |= (self.${reg.get_name()} & ${reg.get_mask()}) << ${reg.get_shift()} + % endif + % endfor + % endfor + # Now the arrays + # We can do this because all arrays have a base address that is a multiple + # of 256 (0x100). In other words, this is a hack that only works if you + # know exactly what you're doing, which is OK in this case, because its in + # this same file as the register map. + array_offset = addr % 256 + base_addr = addr - array_offset + index = int(array_offset / 4) + % for addr in sorted(set([r.get_addr() for r in regs if r.is_array])): + if base_addr == ${addr}: + % for reg in filter(lambda r: r.get_addr() == addr, regs): + <% + assert reg.get_array_len() == 256, "Arrays must be length 256!" + assert reg.get_addr() % 256 == 0, "Arrays must start at a multiple of 0x100!" + %> + % if reg.get_enums(): + reg |= (int(self.${reg.get_name()}[index].value) & ${reg.get_mask()}) << ${reg.get_shift()} + % else: + reg |= (int(self.${reg.get_name()}[index]) & ${reg.get_mask()}) << ${reg.get_shift()} + % endif + % endfor + % endfor + return reg + +<% + all_addrs = set() + for reg in regs: + for index in range(reg.get_array_len() if reg.is_array else 1): + all_addrs.add(reg.get_addr() + index * reg.get_addr_step_size()) +%> +def get_all_regs(self): + addrs = { + % for addr in sorted(all_addrs): + ${addr}, + % endfor + } + return addrs + +def get_addr(self, reg_name): + "returns the address of a register with the given name" + return { + % for reg in regs: + '${reg.get_name()}': ${reg.get_addr()}, + % endfor + }[reg_name] +""" + + +######################################################################## +# Template for C++ methods in the body of the struct +######################################################################## +BODY_TMPL = """\ +enum class zbx_cpld_field_t { + % for addr in sorted(set(map(lambda r: r.get_addr(), regs))): + % for reg in filter(lambda r: r.get_addr() == addr, regs): + ${reg.get_name()}, + % endfor + % endfor +}; + +zbx_cpld_field_t get_field_type(const std::string& field_name) { + % for addr in sorted(set(map(lambda r: r.get_addr(), regs))): + % for reg in filter(lambda r: r.get_addr() == addr, regs): + if (field_name == "${reg.get_name()}") { + return zbx_cpld_field_t::${reg.get_name()}; + } else + % endfor + % endfor + { + UHD_ASSERT_THROW(false); + } +} + +uint32_t get_array_size(zbx_cpld_field_t field) { + switch(field) { + % for addr in sorted(set([r.get_addr() for r in regs if r.is_array])): + % for reg in filter(lambda r: r.get_addr() == addr, regs): + case zbx_cpld_field_t::${reg.get_name()}: + return uint32_t(${reg.get_name()}.size()); + % endfor + % endfor + default: + UHD_ASSERT_THROW(false); + return 0; + } +} + +uint32_t get_field(zbx_cpld_field_t field) { + switch(field) { + % for addr in sorted(set([r.get_addr() for r in regs if not r.is_array])): + % for reg in filter(lambda r: r.get_addr() == addr, regs): + case zbx_cpld_field_t::${reg.get_name()}: + return uint32_t(${reg.get_name()}); + % endfor + % endfor + default: + UHD_ASSERT_THROW(false); + return 0; + } +} + +uint32_t get_field(zbx_cpld_field_t field, const size_t idx) { + switch(field) { + % for addr in sorted(set([r.get_addr() for r in regs if r.is_array])): + % for reg in filter(lambda r: r.get_addr() == addr, regs): + case zbx_cpld_field_t::${reg.get_name()}: + return uint32_t(${reg.get_name()}[idx]); + % endfor + % endfor + default: + UHD_ASSERT_THROW(false); + return 0; + } +} + +void set_field(zbx_cpld_field_t field, uint32_t value) { + switch(field) { + % for addr in sorted(set([r.get_addr() for r in regs if not r.is_array])): + % for reg in filter(lambda r: r.get_addr() == addr, regs): + case zbx_cpld_field_t::${reg.get_name()}: + ${reg.get_name()} = static_cast<${reg.get_type()}>(value); + break; + % endfor + % endfor + default: + UHD_ASSERT_THROW(false); + } +} + +void set_field(zbx_cpld_field_t field, uint32_t value, const size_t idx) +{ + switch(field) { + % for addr in sorted(set([r.get_addr() for r in regs if r.is_array])): + % for reg in filter(lambda r: r.get_addr() == addr, regs): + case zbx_cpld_field_t::${reg.get_name()}: + ${reg.get_name()}[idx] = static_cast<${reg.get_type()}>(value); + break; + % endfor + % endfor + default: + UHD_ASSERT_THROW(false); + } +} + +uint32_t get_reg(uint16_t addr) +{ + uint32_t reg = 0; + // First the regular registers + switch(addr) { + % for addr in sorted(set([r.get_addr() for r in regs if not r.is_array])): + case ${addr}: + % for reg in filter(lambda r: r.get_addr() == addr, regs): + reg |= (uint32_t(${reg.get_name()}) & ${reg.get_mask()}) << ${reg.get_shift()}; + % endfor + break; + % endfor + default: + break; + } + // Now the arrays + // We can do this because all arrays have a base address that is a multiple + // of 256 (0x100). In other words, this is a hack that only works if you + // know exactly what you're doing, which is OK in this case, because it's in + // this same file as the register map. + const uint16_t array_offset = addr % 256; + const uint16_t base_addr = addr - array_offset; + const size_t index = array_offset / 4; + switch(base_addr) { + % for addr in sorted(set([r.get_addr() for r in regs if r.is_array])): + case ${addr}: + % for reg in filter(lambda r: r.get_addr() == addr, regs): + <% assert reg.get_array_len() == 256, "Arrays must be length 256!" %> + <% assert reg.get_addr() % 256 == 0, "Arrays must start at a multiple of 0x100!" %> + reg |= (uint32_t(${reg.get_name()}[index]) & ${reg.get_mask()}) << ${reg.get_shift()}; + % endfor + break; + % endfor + default: + break; + } + return reg; +} + +void set_reg(uint16_t addr, uint32_t val) +{ + switch(addr) { + % for addr in sorted(set([r.get_addr() for r in regs if not r.is_array])): + case ${addr}: + % for reg in filter(lambda r: r.get_addr() == addr, regs): + ${reg.get_name()} = static_cast<${reg.get_type()}>((val & (${reg.get_mask()} << ${reg.get_shift()})) >> ${reg.get_shift()}); + % endfor + break; + % endfor + default: + break; + } + // Now the arrays + // We can do this because all arrays have a base address that is a multiple + // of 256 (0x100). In other words, this is a hack that only works if you + // know exactly what you're doing, which is OK in this case, because it's in + // this same file as the register map. + const uint16_t array_offset = addr % 256; + const uint16_t base_addr = addr - array_offset; + const size_t index = array_offset / 4; + switch(base_addr) { + % for addr in sorted(set([r.get_addr() for r in regs if r.is_array])): + case ${addr}: + % for reg in filter(lambda r: r.get_addr() == addr, regs): + ${reg.get_name()}[index] = static_cast<${reg.get_type()}>((val & (${reg.get_mask()} << ${reg.get_shift()})) >> ${reg.get_shift()}); + % endfor + break; + % endfor + default: break; + } +} + +<% + all_addrs = set() + for reg in regs: + for index in range(reg.get_array_len() if reg.is_array else 1): + all_addrs.add(reg.get_addr() + index * reg.get_addr_step_size()) +%> +std::set<size_t> get_all_addrs() +{ + std::set<size_t> addrs; + % for addr in sorted(all_addrs): + addrs.insert(${addr}); + % endfor + return addrs; +} + +uint16_t get_addr(zbx_cpld_field_t field) { + switch(field) { + % for addr in sorted(set([r.get_addr() for r in regs if r.is_array])): + % for reg in filter(lambda r: r.get_addr() == addr, regs): + case zbx_cpld_field_t::${reg.get_name()}: + return ${addr}; + % endfor + % endfor + default: + UHD_ASSERT_THROW(false); + return 0; + } +} + +uint16_t get_addr(const std::string& reg_name) +{ + % for reg in regs: + if ("${reg.get_name()}" == reg_name) { + return ${reg.get_addr()}; + } + % endfor + return 0; +} +""" + +if __name__ == '__main__': + import common + common.generate( + name='zbx_cpld_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + py_body_tmpl=PY_BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/include/uhdlib/features/fpga_load_notification_iface.hpp b/host/lib/include/uhdlib/features/fpga_load_notification_iface.hpp new file mode 100644 index 000000000..bd88db222 --- /dev/null +++ b/host/lib/include/uhdlib/features/fpga_load_notification_iface.hpp @@ -0,0 +1,40 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include <uhd/features/discoverable_feature.hpp> +#include <memory> +#include <string> + +namespace uhd { namespace features { + +/*! This is the mechanism by which USRPs can perform actions whenever the FPGA + * is loaded. + */ +class fpga_load_notification_iface : public discoverable_feature +{ +public: + using sptr = std::shared_ptr<fpga_load_notification_iface>; + + static discoverable_feature::feature_id_t get_feature_id() + { + return discoverable_feature::FPGA_LOAD_NOTIFICATION; + } + + std::string get_feature_name() const + { + return "FPGA Load Notification"; + } + + virtual ~fpga_load_notification_iface() = default; + + /*! Called after the FPGA has finished loading. + */ + virtual void onload() = 0; +}; + +}} // namespace uhd::features diff --git a/host/lib/include/uhdlib/rfnoc/rf_control/dboard_iface.hpp b/host/lib/include/uhdlib/rfnoc/rf_control/dboard_iface.hpp index 1651a1580..b7759098a 100644 --- a/host/lib/include/uhdlib/rfnoc/rf_control/dboard_iface.hpp +++ b/host/lib/include/uhdlib/rfnoc/rf_control/dboard_iface.hpp @@ -7,6 +7,9 @@ #pragma once #include <uhd/rfnoc/rf_control/core_iface.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/types/time_spec.hpp> +#include <uhd/types/eeprom.hpp> #include <uhdlib/rfnoc/rf_control/gain_profile_iface.hpp> #include <uhdlib/usrp/common/pwr_cal_mgr.hpp> #include <memory> @@ -36,6 +39,11 @@ public: virtual std::vector<uhd::usrp::pwr_cal_mgr::sptr>& get_pwr_mgr( uhd::direction_t trx) = 0; + + virtual uhd::eeprom_map_t get_db_eeprom() = 0; + + //! See radio_control::set_command_time() + virtual void set_command_time(uhd::time_spec_t time, const size_t chan) = 0; }; }}} // namespace uhd::rfnoc::rf_control diff --git a/host/lib/include/uhdlib/rfnoc/rf_control/gain_profile_iface.hpp b/host/lib/include/uhdlib/rfnoc/rf_control/gain_profile_iface.hpp index f93a42936..c65b6bd25 100644 --- a/host/lib/include/uhdlib/rfnoc/rf_control/gain_profile_iface.hpp +++ b/host/lib/include/uhdlib/rfnoc/rf_control/gain_profile_iface.hpp @@ -6,6 +6,7 @@ #pragma once +#include <functional> #include <memory> #include <string> #include <vector> @@ -22,6 +23,8 @@ class gain_profile_iface { public: using sptr = std::shared_ptr<gain_profile_iface>; + using subscriber_type = + std::function<void(const std::string& profile, const size_t chan)>; virtual ~gain_profile_iface() = default; @@ -36,6 +39,13 @@ public: /*! Return the gain profile */ virtual std::string get_gain_profile(const size_t chan) const = 0; + + /*! Register a subscriber to a property tree node + * + * This is useful for all those cases where the gain profile is also a + * property in the tree, and setting it here requires also updating the tree. + */ + virtual void add_subscriber(subscriber_type&& sub) = 0; }; /*! "Default" implementation for gain_profile_iface @@ -52,9 +62,11 @@ public: void set_gain_profile(const std::string& profile, const size_t chan) override; std::string get_gain_profile(const size_t chan) const override; + void add_subscriber(subscriber_type&& sub) override; private: static const std::string DEFAULT_GAIN_PROFILE; + subscriber_type _sub = nullptr; }; /*! "Enumerated" implementation for gain_profile_iface @@ -77,10 +89,19 @@ public: std::vector<std::string> get_gain_profile_names(const size_t) const override; + /*! Register a subscriber to a property tree node + * + * This is useful for all those cases where the gain profile is also a + * property in the tree, and setting it here requires also updating the tree. + */ + void add_subscriber(subscriber_type&& sub) override; + private: std::vector<std::string> _possible_profiles; std::vector<std::string> _gain_profile; + + subscriber_type _sub = nullptr; }; }}} // namespace uhd::rfnoc::rf_control diff --git a/host/lib/include/uhdlib/usrp/common/lmx2572.hpp b/host/lib/include/uhdlib/usrp/common/lmx2572.hpp new file mode 100644 index 000000000..600153f1a --- /dev/null +++ b/host/lib/include/uhdlib/usrp/common/lmx2572.hpp @@ -0,0 +1,102 @@ +// +// Copyright 2020 Ettus Research, A National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include <uhd/types/time_spec.hpp> +#include <functional> +#include <memory> + +//! Control interface for an LMX2572 synthesizer +class lmx2572_iface +{ +public: + using sptr = std::shared_ptr<lmx2572_iface>; + + virtual ~lmx2572_iface() = default; + + enum output_t { RF_OUTPUT_A, RF_OUTPUT_B }; + + enum mux_in_t { DIVIDER, VCO, HIGH_IMPEDANCE, SYSREF }; + + //! Category of phase sync procedure. See Section 8.1.6 ("Application for + // SYNC") in the datasheet. Category NONE applies when no phase + // synchronization is required. + enum sync_cat { CAT1A, CAT1B, CAT2, CAT3, CAT4, NONE }; + + //! Write functor: Take address / data pair, craft SPI transaction + using write_fn_t = std::function<void(uint8_t, uint16_t)>; + + //! Read functor: Return value given address + using read_fn_t = std::function<uint16_t(uint8_t)>; + + //! Sleep functor: sleep for the specified time + using sleep_fn_t = std::function<void(const uhd::time_spec_t&)>; + + //! Factory + // + // \param write SPI write function object + // \param read SPI read function object + // \param sleep sleep function object + static sptr make(write_fn_t&& poke16, read_fn_t&& peek16, sleep_fn_t&& sleep); + + //! Save state to chip + virtual void commit() = 0; + + //! Get enabled status + virtual bool get_enabled() = 0; + + //! Enable/disable + virtual void set_enabled(const bool enabled = true) = 0; + + //! Performs a reset of the LMX2572 by using the software reset register + virtual void reset() = 0; + + //! Returns True if the PLL is locked, False otherwise. + virtual bool get_lock_status() = 0; + + //! Enables or disables the phase synchronization + // + // NOTE: This does not write anything to the device, it just sets the + // VCO_PHASE_SYNC_EN high. + virtual void set_sync_mode(const bool enable) = 0; + + //! Returns the enabled/disabled state of the phase synchronization + virtual bool get_sync_mode() = 0; + + //! Enables or disables the output on both ports + virtual void set_output_enable_all(const bool enable) = 0; + + //! Sets output A or B (OUTA_PD or OUTB_PD) + virtual void set_output_enable(const output_t output, const bool enable) = 0; + + //! Sets the output power + // + // \param output Choose which output to control + // \param power Power control bits. Higher values mean more power, but the + // function that maps power control bits to power is non-linear, + // and it is also frequency-dependent. For more detail, check + // the data sheet, section 8.1.5.1. Ballpark numbers: 0 dBm is + // at about power==27, over 35 the increase becomes "not obvious". + virtual void set_output_power(const output_t output, const uint8_t power) = 0; + + //! Sets the OUTA_MUX or OUTB_MUX input + virtual void set_mux_input(const output_t output, const mux_in_t input) = 0; + + //! Set the output frequency + // + // A note on phase synchronization: If set_sync_mode(true) was called + // previously, then this method will set up the PLL in a phase-sync mode. + // However, this specific implementation assumes that the SYNC pin is + // populated, and will be pulsed after calling this command. + // + // \param target_freq The target frequency + // \param ref_freq The input reference frequency + // \param spur_dodging Set to true to enable spur dodging + virtual double set_frequency(const double target_freq, + const double ref_freq, + const bool spur_dodging) = 0; +}; diff --git a/host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp b/host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp index 98e7f2ac4..a40398991 100644 --- a/host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp +++ b/host/lib/include/uhdlib/usrp/common/mpmd_mb_controller.hpp @@ -6,8 +6,11 @@ #pragma once +#include <uhd/features/ref_clk_calibration_iface.hpp> #include <uhd/rfnoc/mb_controller.hpp> #include <uhdlib/usrp/common/rpc.hpp> +#include <uhdlib/features/discoverable_feature_registry.hpp> +#include <uhdlib/features/fpga_load_notification_iface.hpp> #include <uhdlib/utils/rpc.hpp> #include <memory> @@ -19,7 +22,8 @@ namespace uhd { namespace rfnoc { * * This motherboard controller abstracts out a bunch of RPC calls. */ -class mpmd_mb_controller : public mb_controller +class mpmd_mb_controller : public mb_controller, + public ::uhd::features::discoverable_feature_registry { public: using sptr = std::shared_ptr<mpmd_mb_controller>; @@ -113,6 +117,42 @@ private: //! Cache of available GPIO sources std::vector<std::string> _gpio_banks; std::unordered_map<std::string, std::vector<std::string>> _gpio_srcs; + +public: + /*! When the FPGA is reloaded, pass the notification to every Radio block + * Public to allow other classes to register for notifications. + */ + class fpga_onload : public uhd::features::fpga_load_notification_iface { + public: + using sptr = std::shared_ptr<fpga_onload>; + + fpga_onload(); + + void onload() override; + + void request_cb(uhd::features::fpga_load_notification_iface::sptr handler); + + private: + std::vector<std::weak_ptr<uhd::features::fpga_load_notification_iface>> _cbs; + }; + + //! Class to expose the ref_clk_calibration discoverable feature functions. + class ref_clk_calibration : public uhd::features::ref_clk_calibration_iface { + public: + using sptr = std::shared_ptr<ref_clk_calibration>; + + ref_clk_calibration(uhd::usrp::mpmd_rpc_iface::sptr rpcc); + + void set_ref_clk_tuning_word(uint32_t tuning_word) override; + uint32_t get_ref_clk_tuning_word() override; + void store_ref_clk_tuning_word(uint32_t tuning_word) override; + + private: + uhd::usrp::mpmd_rpc_iface::sptr _rpcc; + }; + + fpga_onload::sptr _fpga_onload; + ref_clk_calibration::sptr _ref_clk_cal; }; }} // namespace uhd::rfnoc diff --git a/host/lib/include/uhdlib/usrp/common/rpc.py b/host/lib/include/uhdlib/usrp/common/rpc.py index 04a43ebce..4ca30b07d 100644 --- a/host/lib/include/uhdlib/usrp/common/rpc.py +++ b/host/lib/include/uhdlib/usrp/common/rpc.py @@ -9,7 +9,7 @@ import sys from mako.template import Template class Function: - def __init__(self, return_type, function_name, args): + def __init__(self, return_type, function_name, args, no_claim=False): self.name = function_name self.does_return = return_type != "void" self.return_type = return_type @@ -17,6 +17,7 @@ class Function: self.rpcname = f"\"{function_name}\"" self.args = [" ".join(arg) for arg in args] self.has_rpcprefix = False + self.no_claim = no_claim def enable_rpcprefix(self): self.rpcname = f"_rpc_prefix + \"{self.name}\"" @@ -31,7 +32,7 @@ class Interface: for fn in self.functions: fn.enable_rpcprefix() -def fn_from_string(function_string): +def fn_from_string(function_string, no_claim=False): m = re.match(r"^([a-zA-Z:<>,_0-9 ]+)\s+([a-zA-Z0-9_]+)\(([a-zA-Z0-9,_:&<> ]*)\)$", function_string) return_type = m.group(1) function_name = m.group(2) @@ -39,7 +40,7 @@ def fn_from_string(function_string): args = [arg.strip() for arg in args.split(",")] args = [arg.split(" ") for arg in args if len(arg) > 0] args = [(" ".join(arg[:-1]), arg[-1]) for arg in args] - return Function(return_type, function_name, args) + return Function(return_type, function_name, args, no_claim) IFACES = [ Interface("mpmd_rpc", [ @@ -66,7 +67,36 @@ IFACES = [ fn_from_string("std::map<std::string, std::string> get_mb_eeprom()"), fn_from_string("std::vector<std::string> get_gpio_src(const std::string& bank)"), fn_from_string("void set_gpio_src(const std::string& bank, const std::vector<std::string>& src)"), + + # ref_clk_calibration + fn_from_string("void set_ref_clk_tuning_word(uint32_t tuning_word)"), + fn_from_string("uint32_t get_ref_clk_tuning_word()"), + fn_from_string("void store_ref_clk_tuning_word(uint32_t tuning_word)"), + ]), + Interface("x400_rpc", [ + fn_from_string("std::vector<std::map<std::string, std::string>> get_dboard_info()", no_claim=True), + fn_from_string("void set_cal_frozen(bool state, size_t block_count, size_t chan)"), + fn_from_string("std::vector<int> get_cal_frozen(size_t block_count, size_t chan)"), + fn_from_string("double rfdc_set_nco_freq(const std::string& trx, size_t block_count, size_t chan, double freq)"), + fn_from_string("double rfdc_get_nco_freq(const std::string& trx, size_t block_count, size_t chan)"), + fn_from_string("double get_master_clock_rate()"), + fn_from_string("std::map<std::string, std::vector<uint8_t>> get_db_eeprom(size_t db_idx)"), + fn_from_string("bool get_threshold_status(size_t db_number, size_t chan, size_t threshold_block)"), + fn_from_string("void set_dac_mux_enable(size_t motherboard_channel_number, int enable)"), + fn_from_string("void set_dac_mux_data(size_t i, size_t q)"), + fn_from_string("double get_spll_freq()"), + fn_from_string("void setup_threshold(size_t db_number, size_t chan, size_t threshold_block, const std::string& mode, size_t delay, size_t under, size_t over)"), + fn_from_string("bool is_db_gpio_ifc_present(size_t db_idx)"), ]), + Interface("dboard_base_rpc", [ + fn_from_string("std::vector<std::string> get_sensors(const std::string& trx)"), + fn_from_string("sensor_value_t::sensor_map_t get_sensor(const std::string& trx, const std::string& sensor, size_t chan)"), + ], has_rpcprefix=True), + Interface("zbx_rpc", [ + fn_from_string("double get_dboard_prc_rate()"), + fn_from_string("double get_dboard_sample_rate()"), + fn_from_string("void enable_iq_swap(bool is_band_inverted, const std::string& trx, size_t chan)"), + ], has_rpcprefix=True), ] COMMON_TMPL = """<% import time %>\ @@ -117,11 +147,20 @@ namespace uhd { namespace usrp { %for function in iface.functions: ${function.return_type} ${function.name}(${",".join(function.args)}) override { - %if function.does_return: - return _rpcc->request_with_token<${function.return_type}>(${",".join([function.rpcname] + function.arg_names)}); + %if function.no_claim: + %if function.does_return: + return _rpcc->request<${function.return_type}> + %else: + _rpcc->notify + %endif %else: - _rpcc->notify_with_token(${",".join([function.rpcname] + function.arg_names)}); + %if function.does_return: + return _rpcc->request_with_token<${function.return_type}> + %else: + _rpcc->notify_with_token + %endif %endif + (${",".join([function.rpcname] + function.arg_names)}); } %endfor diff --git a/host/lib/include/uhdlib/usrp/common/x400_rfdc_control.hpp b/host/lib/include/uhdlib/usrp/common/x400_rfdc_control.hpp new file mode 100644 index 000000000..8d2923436 --- /dev/null +++ b/host/lib/include/uhdlib/usrp/common/x400_rfdc_control.hpp @@ -0,0 +1,85 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include <uhd/types/memmap_iface.hpp> +#include <uhd/types/time_spec.hpp> +#include <cstdint> +#include <memory> +#include <vector> +#include <string> + +namespace uhd { namespace rfnoc { namespace x400 { + +//! Control class for the RFDC components of a single daughterboard +// +// This class controls the NCOs and other RFDC settings. The corresponding FPGA +// module is rfdc_timing_control.v. +class rfdc_control +{ +public: + using sptr = std::shared_ptr<rfdc_control>; + + struct regmap + { + //! Address of the NCO reset register + static constexpr uint32_t NCO_RESET = 0; + //! Bit position of reset-start bit (w) + static constexpr uint32_t NCO_RESET_START_MSB = 0; + //! Bit position of reset-done bit (r) + static constexpr uint32_t NCO_RESET_DONE_MSB = 1; + //! Address of the gearbox reset register + static constexpr uint32_t GEARBOX_RESET = 4; + //! Bit position of ADC gearbox reset + static constexpr uint32_t ADC_RESET_MSB = 0; + //! Bit position of DAC gearbox reset + static constexpr uint32_t DAC_RESET_MSB = 1; + }; + + //! Identify the NCOs/ADCs/DACs available to this radio control + enum class rfdc_type { RX0, RX1, TX0, TX1 }; + + rfdc_control(uhd::memmap32_iface_timed&& iface, const std::string& log_id); + + //! Reset the listed NCOs + // + // All NCOs that are listed in \p ncos are reset synchronously. + // + // \param ncos A list of NCOs that shall be reset at the given time + // \param time The time at which the reset shall occur + void reset_ncos(const std::vector<rfdc_type>& ncos, const uhd::time_spec_t& time); + + //! Reset the listed gearboxes + // + // All gearboxes that are listed in \p gearboxes are reset synchronously. + // + // \param gearboxes A list of gearboxes that shall be reset at the given time + // \param time The time at which the reset shall occur. Note: If \p time is + // set to ASAP, the resets will still occur synchronously, but + // at a non-deterministic time. This will suffice for synchronizing + // gearboxes on a single device. + void reset_gearboxes( + const std::vector<rfdc_type>& gearboxes, const uhd::time_spec_t& time); + + //! Return true if the NCO is out of reset + bool get_nco_reset_done(); + + //! Set an NCO to a specific frequency + // + // \param nco Which NCO to re-tune + // \param freq the new frequency to tune it to (in Hz) + double set_nco_freq(const rfdc_type nco, const double freq); + +private: + //! Peek/poke interface + memmap32_iface_timed _iface; + + //! Prefix for log messages + const std::string _log_id; +}; + +}}} // namespace uhd::rfnoc::x400 diff --git a/host/lib/include/uhdlib/usrp/dboard/debug_dboard.hpp b/host/lib/include/uhdlib/usrp/dboard/debug_dboard.hpp new file mode 100644 index 000000000..72ac16e28 --- /dev/null +++ b/host/lib/include/uhdlib/usrp/dboard/debug_dboard.hpp @@ -0,0 +1,592 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include "x400_dboard_iface.hpp" +#include <uhd/exception.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhdlib/rfnoc/rf_control/dboard_iface.hpp> +#include <string> + +#define UHD_LOG_SKIP_CFG() \ + UHD_LOG_TRACE( \ + "RFNOC::DEBUG_DB", "Skipping unsupported debug db config for " << __FUNCTION__); + +namespace uhd { namespace rfnoc { + +const static uint16_t EMPTY_DB_PID = 0x0; +const static uint16_t DEBUG_DB_PID = 0x4001; +const static uint16_t IF_TEST_DBOARD_PID = 0x4006; + +/*! \brief Implementation of common dboard_iface for IF Test and Debug dboards. + */ +class debug_dboard_common_impl : public uhd::usrp::x400::x400_dboard_iface +{ +public: + using sptr = std::shared_ptr<debug_dboard_common_impl>; + + rf_control::gain_profile_iface::sptr get_tx_gain_profile_api() override + { + return rf_control::gain_profile_iface::sptr(); + } + + rf_control::gain_profile_iface::sptr get_rx_gain_profile_api() override + { + return rf_control::gain_profile_iface::sptr(); + } + + bool is_adc_self_cal_supported() override + { + return false; + } + + uhd::usrp::x400::adc_self_cal_params_t get_adc_self_cal_params(double) override + { + return { + 0.0, + 0.0, + 0.0, + 0.0, + }; + } + + size_t get_chan_from_dboard_fe(const std::string& fe, direction_t) const override + { + if (fe == "0") { + return 0; + } + if (fe == "1") { + return 1; + } + throw uhd::key_error(std::string("[X400] Invalid frontend: ") + fe); + } + + std::string get_dboard_fe_from_chan(size_t chan, direction_t) const override + { + if (chan == 0) { + return "0"; + } + if (chan == 1) { + return "1"; + } + throw uhd::lookup_error( + std::string("[X400] Invalid channel: ") + std::to_string(chan)); + } + + std::vector<usrp::pwr_cal_mgr::sptr>& get_pwr_mgr(direction_t) override + { + static std::vector<usrp::pwr_cal_mgr::sptr> empty_vtr; + return empty_vtr; + } + + eeprom_map_t get_db_eeprom() override + { + return {}; + } + + std::string get_tx_antenna(const size_t) const override + { + return ""; + } + + std::vector<std::string> get_tx_antennas(const size_t) const override + { + return {}; + } + + void set_tx_antenna(const std::string&, const size_t) override{UHD_LOG_SKIP_CFG()} + + std::string get_rx_antenna(const size_t) const override + { + return ""; + } + + std::vector<std::string> get_rx_antennas(const size_t) const override + { + return {}; + } + + void set_rx_antenna(const std::string&, const size_t) override + { + UHD_LOG_SKIP_CFG() + } + + double get_tx_frequency(const size_t) override + { + return 0; + } + + double set_tx_frequency(const double, size_t) override + { + UHD_LOG_SKIP_CFG() + return 0; + } + + void set_tx_tune_args(const device_addr_t&, const size_t) override{UHD_LOG_SKIP_CFG()} + + freq_range_t get_tx_frequency_range(const size_t) const override + { + return meta_range_t(0.0, 0.0); + } + + double get_rx_frequency(const size_t) override + { + return 0; + } + + double set_rx_frequency(const double, const size_t) override + { + UHD_LOG_SKIP_CFG() + return 0; + } + + void set_rx_tune_args(const device_addr_t&, const size_t) override{UHD_LOG_SKIP_CFG()} + + freq_range_t get_rx_frequency_range(const size_t) const override + { + return meta_range_t(0.0, 0.0); + } + + std::vector<std::string> get_tx_gain_names(const size_t) const override + { + return {}; + } + + gain_range_t get_tx_gain_range(const size_t) const override + { + return meta_range_t(0.0, 0.0); + } + + gain_range_t get_tx_gain_range(const std::string&, const size_t) const override + { + return meta_range_t(0.0, 0.0); + } + + double get_tx_gain(const size_t) override + { + return 0; + } + + double get_tx_gain(const std::string&, const size_t) override + { + UHD_LOG_SKIP_CFG() + return 0; + } + + double set_tx_gain(const double, const size_t) override + { + UHD_LOG_SKIP_CFG() + return 0; + } + + double set_tx_gain(const double, const std::string&, const size_t) override + { + UHD_LOG_SKIP_CFG() + return 0; + } + + std::vector<std::string> get_rx_gain_names(const size_t) const override + { + return {}; + } + + gain_range_t get_rx_gain_range(const size_t) const override + { + UHD_LOG_SKIP_CFG() + return meta_range_t(0.0, 0.0); + } + + gain_range_t get_rx_gain_range(const std::string&, const size_t) const override + { + UHD_LOG_SKIP_CFG() + return meta_range_t(0.0, 0.0); + } + + double get_rx_gain(const size_t) override + { + return 0; + } + + double get_rx_gain(const std::string&, const size_t) override + { + return 0; + } + + double set_rx_gain(const double, const size_t) override + { + UHD_LOG_SKIP_CFG() + return 0; + } + + double set_rx_gain(const double, const std::string&, const size_t) override + { + UHD_LOG_SKIP_CFG() + return 0; + } + + void set_rx_agc(const bool, const size_t) override{UHD_LOG_SKIP_CFG()} + + meta_range_t get_tx_bandwidth_range(size_t) const override + { + return meta_range_t(0.0, 0.0); + } + + double get_tx_bandwidth(const size_t) override + { + return 0; + } + + double set_tx_bandwidth(const double, const size_t) override + { + UHD_LOG_SKIP_CFG() + return 0; + } + + meta_range_t get_rx_bandwidth_range(size_t) const override + { + return meta_range_t(0.0, 0.0); + } + + double get_rx_bandwidth(const size_t) override + { + return 0; + } + + double set_rx_bandwidth(const double, const size_t) override + { + UHD_LOG_SKIP_CFG() + return 0; + } + + std::vector<std::string> get_rx_lo_names(const size_t) const override + { + return {}; + } + + std::vector<std::string> get_rx_lo_sources( + const std::string&, const size_t) const override + { + UHD_LOG_SKIP_CFG() + return {}; + } + + freq_range_t get_rx_lo_freq_range(const std::string&, const size_t) const override + { + return meta_range_t(0.0, 0.0); + } + + void set_rx_lo_source(const std::string&, const std::string&, const size_t) override + { + UHD_LOG_SKIP_CFG() + } + + const std::string get_rx_lo_source(const std::string&, const size_t) override + { + return ""; + } + + void set_rx_lo_export_enabled(bool, const std::string&, const size_t) override + { + UHD_LOG_SKIP_CFG() + } + + bool get_rx_lo_export_enabled(const std::string&, const size_t) override + { + return false; + } + + double set_rx_lo_freq(double, const std::string&, const size_t) override + { + UHD_LOG_SKIP_CFG() + return 0; + } + + double get_rx_lo_freq(const std::string&, const size_t) override + { + return 0; + } + + std::vector<std::string> get_tx_lo_names(const size_t) const override + { + return {}; + } + + std::vector<std::string> get_tx_lo_sources(const std::string&, const size_t) const override + { + return {}; + } + + freq_range_t get_tx_lo_freq_range(const std::string&, const size_t) override + { + return meta_range_t(0.0, 0.0); + } + + void set_tx_lo_source(const std::string&, const std::string&, const size_t) override + { + UHD_LOG_SKIP_CFG() + } + + const std::string get_tx_lo_source(const std::string&, const size_t) override + { + return ""; + } + + void set_tx_lo_export_enabled(const bool, const std::string&, const size_t) override + { + UHD_LOG_SKIP_CFG() + } + + bool get_tx_lo_export_enabled(const std::string&, const size_t) override + { + return false; + } + + double set_tx_lo_freq(const double, const std::string&, const size_t) override + { + UHD_LOG_SKIP_CFG() + return 0; + } + + double get_tx_lo_freq(const std::string&, const size_t) override + { + return 0; + } + + void set_command_time(uhd::time_spec_t, const size_t) override + { + // nop + } +}; + +/*! \brief Implementation of dboard_iface for debug_db. + */ +class debug_dboard_impl : public debug_dboard_common_impl +{ + // Just an empty class for conveniently organizing class hierarchy. +}; + +/*! \brief Fake dboard implementation for an empty slot + */ +class empty_slot_dboard_impl : public debug_dboard_common_impl +{ + // Just an empty class for conveniently organizing class hierarchy. +}; + +/*! \brief Implementation of dboard_iface for IF Test dboard. + */ +class if_test_dboard_impl : public debug_dboard_common_impl +{ +public: + /****************************************************************************** + * Structors + *****************************************************************************/ + if_test_dboard_impl(const size_t db_idx, + const std::string& rpc_prefix, + const std::string& unique_id, + std::shared_ptr<mpmd_mb_controller> mb_controller, + uhd::property_tree::sptr tree) + : _unique_id(unique_id) + , _db_idx(db_idx) + , _rpc_prefix(rpc_prefix) + , _mb_control(mb_controller) + , _tree(tree) + { + RFNOC_LOG_TRACE("Entering " << __FUNCTION__); + RFNOC_LOG_TRACE("DB ID: " << _db_idx); + UHD_ASSERT_THROW(_mb_control); + _rpcc = _mb_control->get_rpc_client(); + UHD_ASSERT_THROW(_rpcc); + _init_frontend_subtree(); + } + + ~if_test_dboard_impl() + { + RFNOC_LOG_TRACE(__FUNCTION__); + } + + // The IF Test dboard muxes a single SMA port (for each of RX and TX) like so: + // /---> dac0 + // /----> dac1 + // TX SMA port -- [mux] -----> dac2 + // \----> dac3 + // + // (and similarly with the RX SMA port and the adcs) + + std::vector<std::string> get_tx_muxes(void) + { + return {"DAC0", "DAC1", "DAC2", "DAC3"}; + } + + void set_tx_mux(const std::string& mux) + { + RFNOC_LOG_TRACE("Setting TX mux to " << mux); + _rpcc->notify_with_token( + _rpc_prefix + "config_tx_path", _get_tx_path_from_mux(mux)); + } + + std::string get_tx_mux(void) + { + return _rpcc->request_with_token<std::string>(_rpc_prefix + "get_tx_path"); + } + + std::vector<std::string> get_rx_muxes(void) + { + return {"ADC0", "ADC1", "ADC2", "ADC3"}; + } + + void set_rx_mux(const std::string& mux) + { + RFNOC_LOG_TRACE("Setting RX mux to " << mux); + _rpcc->notify_with_token( + _rpc_prefix + "config_rx_path", _get_rx_path_from_mux(mux)); + } + + std::string get_rx_mux(void) + { + return _rpcc->request_with_token<std::string>(_rpc_prefix + "get_rx_path"); + } + + eeprom_map_t get_db_eeprom() override + { + return _rpcc->request_with_token<eeprom_map_t>("get_db_eeprom", _db_idx); + } + + +private: + //! Used by the RFNOC_LOG_* macros. + const std::string _unique_id; + std::string get_unique_id() const + { + return _unique_id; + } + + //! Index of this daughterboard + const size_t _db_idx; + + //! Prepended for all dboard RPC calls + const std::string _rpc_prefix; + + //! Reference to the MB controller + uhd::rfnoc::mpmd_mb_controller::sptr _mb_control; + + //! Reference to the RPC client + uhd::rpc_client::sptr _rpcc; + + //! Reference to this block's subtree + // + // It is mutable because _tree->access<>(..).get() is not const, but we + // need to do just that in some const contexts + mutable uhd::property_tree::sptr _tree; + + std::string _get_tx_path_from_mux(const std::string mux) + { + if (mux == "DAC0") { + return "dac0"; + } else if (mux == "DAC1") { + return "dac1"; + } else if (mux == "DAC2") { + return "dac2"; + } else if (mux == "DAC3") { + return "dac3"; + } else { + throw uhd::value_error( + std::string("[RFNOC::IF_TEST_DBOARD] Invalid TX Mux Name: ") + mux); + } + } + + std::string _get_rx_path_from_mux(const std::string mux) + { + if (mux == "ADC0") { + return "adc0"; + } else if (mux == "ADC1") { + return "adc1"; + } else if (mux == "ADC2") { + return "adc2"; + } else if (mux == "ADC3") { + return "adc3"; + } else { + throw uhd::value_error( + std::string("[RFNOC::IF_TEST_DBOARD] Invalid RX Mux Name: ") + mux); + } + } + + void _init_frontend_subtree() + { + auto subtree = _tree->subtree(fs_path("dboard")); + + // DB EEPROM + subtree->create<eeprom_map_t>("eeprom") + .add_coerced_subscriber([this](const eeprom_map_t&) { + throw uhd::runtime_error("Attempting to update daughterboard eeprom!"); + }) + .set_publisher([this]() { return get_db_eeprom(); }); + + static const char IF_TEST_FE_NAME[] = "IF_TEST"; + + const fs_path tx_fe_path = fs_path("tx_frontends/0"); + const fs_path rx_fe_path = fs_path("rx_frontends/0"); + RFNOC_LOG_TRACE("Adding non-RFNoC block properties" + << " to prop tree path " << tx_fe_path << " and " << rx_fe_path); + + subtree->create<std::string>(tx_fe_path / "name").set(IF_TEST_FE_NAME); + subtree->create<std::string>(rx_fe_path / "name").set(IF_TEST_FE_NAME); + + // TX Mux + subtree->create<std::string>(tx_fe_path / "mux" / "value") + .add_coerced_subscriber( + [this](const std::string& mux) { this->set_tx_mux(mux); }) + .set_publisher([this]() { return this->get_tx_mux(); }); + subtree->create<std::vector<std::string>>(tx_fe_path / "mux" / "options") + .set(get_tx_muxes()) + .add_coerced_subscriber([](const std::vector<std::string>&) { + throw uhd::runtime_error("Attempting to update mux options!"); + }); + + // RX Mux + subtree->create<std::string>(rx_fe_path / "mux" / "value") + .add_coerced_subscriber( + [this](const std::string& mux) { this->set_rx_mux(mux); }) + .set_publisher([this]() { return this->get_rx_mux(); }); + subtree->create<std::vector<std::string>>(rx_fe_path / "mux" / "options") + .set(get_rx_muxes()) + .add_coerced_subscriber([](const std::vector<std::string>&) { + throw uhd::runtime_error("Attempting to update mux options!"); + }); + + for (auto fe_path : {tx_fe_path, rx_fe_path}) { + // Antennas + const std::vector<std::string> antenna_options = {"SMA"}; + subtree->create<std::vector<std::string>>(fe_path / "antenna" / "options") + .set(antenna_options) + .add_coerced_subscriber([](const std::vector<std::string>&) { + throw uhd::runtime_error("Attempting to update antenna options!"); + }); + + // Frequency range + const uhd::freq_range_t freq_range(0.0, 0.0); + subtree->create<meta_range_t>(fe_path / "freq" / "range") + .set(freq_range) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update freq range!"); + }); + + // Gains + const uhd::gain_range_t gain_range(0.0, 0.0, 1.0); + subtree->create<meta_range_t>(fe_path / "gains" / "all" / "range") + .set(gain_range) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update gain range!"); + }); + + // Connection + subtree->create<std::string>(fe_path / "connection").set("IQ"); + } + } +}; + +}} // namespace uhd::rfnoc diff --git a/host/lib/include/uhdlib/usrp/dboard/null_dboard.hpp b/host/lib/include/uhdlib/usrp/dboard/null_dboard.hpp new file mode 100644 index 000000000..107ccdeb0 --- /dev/null +++ b/host/lib/include/uhdlib/usrp/dboard/null_dboard.hpp @@ -0,0 +1,361 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include "x400_dboard_iface.hpp" +#include <uhd/exception.hpp> +#include <uhdlib/rfnoc/rf_control/dboard_iface.hpp> +#include <string> + +namespace uhd { namespace rfnoc { + +/*! \brief Implementation of dboard_iface for unpopulated or unsupported daughterboards + */ +class null_dboard_impl : public uhd::usrp::x400::x400_dboard_iface +{ +public: + using sptr = std::shared_ptr<null_dboard_impl>; + + rf_control::gain_profile_iface::sptr get_tx_gain_profile_api() override + { + return rf_control::gain_profile_iface::sptr(); + } + + rf_control::gain_profile_iface::sptr get_rx_gain_profile_api() override + { + return rf_control::gain_profile_iface::sptr(); + } + + bool is_adc_self_cal_supported() override + { + return false; + } + + uhd::usrp::x400::adc_self_cal_params_t get_adc_self_cal_params(double) override + { + return { + 0.0, + 0.0, + 0.0, + 0.0, + }; + } + + size_t get_chan_from_dboard_fe(const std::string& fe, direction_t) const override + { + if (fe == "0") { + return 0; + } + if (fe == "1") { + return 1; + } + throw uhd::key_error(std::string("[X400] Invalid frontend: ") + fe); + } + + std::string get_dboard_fe_from_chan(size_t chan, direction_t) const override + { + if (chan == 0) { + return "0"; + } + if (chan == 1) { + return "1"; + } + throw uhd::lookup_error( + std::string("[X400] Invalid channel: ") + std::to_string(chan)); + } + + std::vector<usrp::pwr_cal_mgr::sptr>& get_pwr_mgr(direction_t) override + { + static std::vector<usrp::pwr_cal_mgr::sptr> empty_vtr; + return empty_vtr; + } + + eeprom_map_t get_db_eeprom() override + { + return {}; + } + + std::string get_tx_antenna(const size_t) const override + { + return ""; + } + + std::vector<std::string> get_tx_antennas(const size_t) const override + { + return {}; + } + + void set_tx_antenna(const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + std::string get_rx_antenna(const size_t) const override + { + return ""; + } + + std::vector<std::string> get_rx_antennas(const size_t) const override + { + return {}; + } + + void set_rx_antenna(const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + double get_tx_frequency(const size_t) override + { + return 0; + } + + double set_tx_frequency(const double, size_t) override + { + throw _no_dboard_exception(); + } + + void set_tx_tune_args(const device_addr_t&, const size_t) override + { + throw _no_dboard_exception(); + } + + freq_range_t get_tx_frequency_range(const size_t) const override + { + return meta_range_t(0.0, 0.0); + } + + double get_rx_frequency(const size_t) override + { + return 0; + } + + double set_rx_frequency(const double, const size_t) override + { + throw _no_dboard_exception(); + } + + void set_rx_tune_args(const device_addr_t&, const size_t) override + { + throw _no_dboard_exception(); + } + + freq_range_t get_rx_frequency_range(const size_t) const override + { + return meta_range_t(0.0, 0.0); + } + + std::vector<std::string> get_tx_gain_names(const size_t) const override + { + return {}; + } + + gain_range_t get_tx_gain_range(const size_t) const override + { + return meta_range_t(0.0, 0.0); + } + + gain_range_t get_tx_gain_range(const std::string&, const size_t) const override + { + return meta_range_t(0.0, 0.0); + } + + double get_tx_gain(const size_t) override + { + return 0; + } + + double get_tx_gain(const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + double set_tx_gain(const double, const size_t) override + { + throw _no_dboard_exception(); + } + + double set_tx_gain(const double, const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + std::vector<std::string> get_rx_gain_names(const size_t) const override + { + return {}; + } + + gain_range_t get_rx_gain_range(const size_t) const override + { + throw _no_dboard_exception(); + } + + gain_range_t get_rx_gain_range(const std::string&, const size_t) const override + { + throw _no_dboard_exception(); + } + + double get_rx_gain(const size_t) override + { + return 0; + } + + double get_rx_gain(const std::string&, const size_t) override + { + return 0; + } + + double set_rx_gain(const double, const size_t) override + { + throw _no_dboard_exception(); + } + + double set_rx_gain(const double, const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + void set_rx_agc(const bool, const size_t) override + { + throw _no_dboard_exception(); + } + + meta_range_t get_tx_bandwidth_range(size_t) const override + { + return meta_range_t(0.0, 0.0); + } + + double get_tx_bandwidth(const size_t) override + { + return 0; + } + + double set_tx_bandwidth(const double, const size_t) override + { + throw _no_dboard_exception(); + } + + meta_range_t get_rx_bandwidth_range(size_t) const override + { + return meta_range_t(0.0, 0.0); + } + + double get_rx_bandwidth(const size_t) override + { + return 0; + } + + double set_rx_bandwidth(const double, const size_t) override + { + throw _no_dboard_exception(); + } + + std::vector<std::string> get_rx_lo_names(const size_t) const override + { + return {}; + } + + std::vector<std::string> get_rx_lo_sources( + const std::string&, const size_t) const override + { + throw _no_dboard_exception(); + } + + freq_range_t get_rx_lo_freq_range(const std::string&, const size_t) const override + { + throw _no_dboard_exception(); + } + + void set_rx_lo_source(const std::string&, const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + const std::string get_rx_lo_source(const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + void set_rx_lo_export_enabled(bool, const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + bool get_rx_lo_export_enabled(const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + double set_rx_lo_freq(double, const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + double get_rx_lo_freq(const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + std::vector<std::string> get_tx_lo_names(const size_t) const override + { + return {}; + } + + std::vector<std::string> get_tx_lo_sources(const std::string&, const size_t) const override + { + throw _no_dboard_exception(); + } + + freq_range_t get_tx_lo_freq_range(const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + void set_tx_lo_source(const std::string&, const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + const std::string get_tx_lo_source(const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + void set_tx_lo_export_enabled(const bool, const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + bool get_tx_lo_export_enabled(const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + double set_tx_lo_freq(const double, const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + double get_tx_lo_freq(const std::string&, const size_t) override + { + throw _no_dboard_exception(); + } + + void set_command_time(uhd::time_spec_t, const size_t) override + { + // nop + } + +private: + uhd::runtime_error _no_dboard_exception() const + { + const std::string msg("No daughterboard or daughterboard with unrecognized PID."); + return uhd::runtime_error(msg); + } +}; + +}} // namespace uhd::rfnoc diff --git a/host/lib/include/uhdlib/usrp/dboard/x400_dboard_iface.hpp b/host/lib/include/uhdlib/usrp/dboard/x400_dboard_iface.hpp new file mode 100644 index 000000000..a6d52cbc0 --- /dev/null +++ b/host/lib/include/uhdlib/usrp/dboard/x400_dboard_iface.hpp @@ -0,0 +1,42 @@ +// +// Copyright 2021 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include <uhdlib/rfnoc/rf_control/dboard_iface.hpp> +#include <memory> + +namespace uhd { namespace usrp { namespace x400 { + +/*! Parameters used for ADC self cal on the X400. + * + * If the daughterboard supports ADC self-cal, min_gain and max_gain will be the + * gains used in the gain auto detection algorithm. + */ +struct adc_self_cal_params_t +{ + double min_gain; + double max_gain; + double rx_freq; + double tx_freq; +}; + +/*! Interface for daughterboards which support being plugged into a X400 motherboard. + */ +class x400_dboard_iface : public uhd::rfnoc::rf_control::dboard_iface +{ +public: + using sptr = std::shared_ptr<x400_dboard_iface>; + + //! Returns whether this dboard supports ADC self cal + virtual bool is_adc_self_cal_supported() = 0; + + //! Returns the parameters required to generate a suitable loopback tone at + //! tone_freq to perform ADC self cal. + virtual adc_self_cal_params_t get_adc_self_cal_params(double tone_freq) = 0; +}; + +}}} // namespace uhd::usrp::x400 diff --git a/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_constants.hpp b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_constants.hpp new file mode 100644 index 000000000..0d1d7af7c --- /dev/null +++ b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_constants.hpp @@ -0,0 +1,269 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// +#pragma once + +#include <uhd/exception.hpp> +#include <uhd/types/ranges.hpp> +#include <unordered_map> +#include <array> +#include <cstddef> +#include <list> +#include <map> +#include <string> +#include <vector> + +namespace uhd { namespace usrp { namespace zbx { + +//! Which LO to address when peeking/poking +// This must match the LO_SELECT values in gen_zbx_cpld_regs.py +enum class zbx_lo_t { + TX0_LO1 = 0, + TX0_LO2 = 1, + TX1_LO1 = 2, + TX1_LO2 = 3, + RX0_LO1 = 4, + RX0_LO2 = 5, + RX1_LO1 = 6, + RX1_LO2 = 7 +}; + +static const std::map<zbx_lo_t, std::string> ZBX_LO_LOG_ID = { + {zbx_lo_t::TX0_LO1, "ZBX TX0 LO1"}, + {zbx_lo_t::TX0_LO2, "ZBX TX0 LO2"}, + {zbx_lo_t::TX1_LO1, "ZBX TX1 LO1"}, + {zbx_lo_t::TX1_LO2, "ZBX TX1 LO2"}, + {zbx_lo_t::RX0_LO1, "ZBX RX0 LO1"}, + {zbx_lo_t::RX0_LO2, "ZBX RX0 LO2"}, + {zbx_lo_t::RX1_LO1, "ZBX RX1 LO1"}, + {zbx_lo_t::RX1_LO2, "ZBX RX1 LO2"}}; + +static constexpr std::array<zbx_lo_t, 8> ZBX_ALL_LO = {zbx_lo_t::TX0_LO1, + zbx_lo_t::TX0_LO2, + zbx_lo_t::TX1_LO1, + zbx_lo_t::TX1_LO2, + zbx_lo_t::RX0_LO1, + zbx_lo_t::RX0_LO2, + zbx_lo_t::RX1_LO1, + zbx_lo_t::RX1_LO2}; + + +/****************************************************************************** + * Important: When changing values here, check if that also requires updating + * the manual (host/docs/zbx.dox). If it also requires changing the website or + * other sales/marketing material, make sure to let the appropriate people know! + *****************************************************************************/ + +enum class zbx_lo_source_t { internal, external }; +static constexpr zbx_lo_source_t ZBX_DEFAULT_LO_SOURCE = zbx_lo_source_t::internal; + +// The ZBX has a non-configurable analog bandwidth of 400 MHz. At lower frequency, +// the usable bandwidth may be smaller though. For those smaller bandwidths, see +// the tune maps. +static constexpr double ZBX_DEFAULT_BANDWIDTH = 400e6; // Hz + +static constexpr double LMX2572_MAX_FREQ = 6.4e9; // Hz +// LMX2572 can go lower, but on the ZBX, the analog paths limit frequencies down +// to 3.2 GHz +static constexpr double LMX2572_MIN_FREQ = 3.2e9; // Hz +static constexpr double LMX2572_DEFAULT_FREQ = 4e9; // Hz +static constexpr uint32_t ZBX_LO_LOCK_TIMEOUT_MS = 20; // milliseconds +// This is the step size for the LO tuning relative to the PRC rate: +static constexpr int ZBX_RELATIVE_LO_STEP_SIZE = 6; + +static constexpr double ZBX_MIN_FREQ = 1e6; // Hz +static constexpr double ZBX_MAX_FREQ = 8e9; // Hz +static constexpr double ZBX_DEFAULT_FREQ = 1e9; // Hz +static const uhd::freq_range_t ZBX_FREQ_RANGE(ZBX_MIN_FREQ, ZBX_MAX_FREQ); +static constexpr double ZBX_LOWBAND_FREQ = 3e9; // Hz + +constexpr char HW_GAIN_STAGE[] = "hw"; + +static constexpr double RX_MIN_GAIN = 0; +static constexpr double RX_MAX_GAIN = 60; +static constexpr double RX_GAIN_STEP = 1; +static constexpr double ZBX_DEFAULT_RX_GAIN = RX_MIN_GAIN; +static const uhd::gain_range_t ZBX_RX_GAIN_RANGE(RX_MIN_GAIN, RX_MAX_GAIN, RX_GAIN_STEP); +// Rx gain is limited to [0, 38] for frequency <= 500 MHz +static constexpr double RX_LOW_FREQ_MAX_GAIN = 38; +static constexpr double RX_LOW_FREQ_MAX_GAIN_CUTOFF = 500e6; // Hz +static const uhd::gain_range_t ZBX_RX_LOW_FREQ_GAIN_RANGE( + RX_MIN_GAIN, RX_LOW_FREQ_MAX_GAIN, RX_GAIN_STEP); +static constexpr double TX_MIN_GAIN = 0; +static constexpr double TX_MAX_GAIN = 60; +static constexpr double TX_GAIN_STEP = 1; +static constexpr double ZBX_DEFAULT_TX_GAIN = TX_MIN_GAIN; +static const uhd::gain_range_t ZBX_TX_GAIN_RANGE(TX_MIN_GAIN, TX_MAX_GAIN, TX_GAIN_STEP); + +static constexpr char ZBX_GAIN_PROFILE_DEFAULT[] = "default"; +static constexpr char ZBX_GAIN_PROFILE_MANUAL[] = "manual"; +static constexpr char ZBX_GAIN_PROFILE_CPLD[] = "table"; +static constexpr char ZBX_GAIN_PROFILE_CPLD_NOATR[] = "table_noatr"; +static const std::vector<std::string> ZBX_GAIN_PROFILES = {ZBX_GAIN_PROFILE_DEFAULT, + ZBX_GAIN_PROFILE_MANUAL, + ZBX_GAIN_PROFILE_CPLD, + ZBX_GAIN_PROFILE_CPLD_NOATR}; + +// Maximum attenuation of the TX DSAs +static constexpr uint8_t ZBX_TX_DSA_MAX_ATT = 31; +// Maximum attenuation of the RX DSAs +static constexpr uint8_t ZBX_RX_DSA_MAX_ATT = 15; + +static constexpr char ZBX_GAIN_STAGE_DSA1[] = "DSA1"; +static constexpr char ZBX_GAIN_STAGE_DSA2[] = "DSA2"; +static constexpr char ZBX_GAIN_STAGE_DSA3A[] = "DSA3A"; +static constexpr char ZBX_GAIN_STAGE_DSA3B[] = "DSA3B"; +static constexpr char ZBX_GAIN_STAGE_AMP[] = "AMP"; +static constexpr char ZBX_GAIN_STAGE_ALL[] = "all"; +// Not technically a gain stage, but we'll keep it +static constexpr char ZBX_GAIN_STAGE_TABLE[] = "TABLE"; + +static const std::vector<std::string> ZBX_RX_GAIN_STAGES = { + ZBX_GAIN_STAGE_DSA1, ZBX_GAIN_STAGE_DSA2, ZBX_GAIN_STAGE_DSA3A, ZBX_GAIN_STAGE_DSA3B}; + +static const std::vector<std::string> ZBX_TX_GAIN_STAGES = { + ZBX_GAIN_STAGE_DSA1, ZBX_GAIN_STAGE_DSA2, ZBX_GAIN_STAGE_AMP}; + +enum class tx_amp { BYPASS = 0, LOWBAND = 1, HIGHBAND = 2 }; + +static constexpr double ZBX_TX_BYPASS_GAIN = 0.0; +static constexpr double ZBX_TX_LOWBAND_GAIN = 14.0; +static constexpr double ZBX_TX_HIGHBAND_GAIN = 21.0; + +// The amplifier gain varies wildly across frequency, temperature.... but we +// need some kind of mapping for querying/setting individual gain stages by +// dB value. +static const std::map<tx_amp, double> ZBX_TX_AMP_GAIN_MAP = { + {tx_amp::BYPASS, ZBX_TX_BYPASS_GAIN}, + {tx_amp::LOWBAND, ZBX_TX_LOWBAND_GAIN}, + {tx_amp::HIGHBAND, ZBX_TX_HIGHBAND_GAIN}}; +static const std::map<double, tx_amp> ZBX_TX_GAIN_AMP_MAP = { + {ZBX_TX_BYPASS_GAIN, tx_amp::BYPASS}, + {ZBX_TX_LOWBAND_GAIN, tx_amp::LOWBAND}, + {ZBX_TX_HIGHBAND_GAIN, tx_amp::HIGHBAND}}; + + +/*** Antenna-related constants ***********************************************/ +// TX and RX SMA connectors on the front panel +constexpr char ANTENNA_TXRX[] = "TX/RX0"; +constexpr char ANTENNA_RX[] = "RX1"; +// Internal "antenna" ports +constexpr char ANTENNA_CAL_LOOPBACK[] = "CAL_LOOPBACK"; +constexpr char ANTENNA_TERMINATION[] = "TERMINATION"; // Only RX path +// Default antennas (which are selected at init) +constexpr auto DEFAULT_TX_ANTENNA = ANTENNA_TXRX; +constexpr auto DEFAULT_RX_ANTENNA = ANTENNA_RX; +// Helper lists +static const std::vector<std::string> RX_ANTENNAS = { + ANTENNA_TXRX, ANTENNA_RX, ANTENNA_CAL_LOOPBACK, ANTENNA_TERMINATION}; +static const std::vector<std::string> TX_ANTENNAS = {ANTENNA_TXRX, ANTENNA_CAL_LOOPBACK}; +// For branding purposes, ZBX changed the antenna names around. For existing +// software, we still accept the old antenna names, but map them to the new ones +static const std::unordered_map<std::string, std::string> TX_ANTENNA_NAME_COMPAT_MAP{ + {"TX/RX", ANTENNA_TXRX}}; +static const std::unordered_map<std::string, std::string> RX_ANTENNA_NAME_COMPAT_MAP{ + {"TX/RX", ANTENNA_TXRX}, {"RX2", ANTENNA_RX}}; + +/*** LO-related constants ****************************************************/ +//! Low-band LO +static constexpr char ZBX_LO1[] = "LO1"; +//! LO at 2nd mixer +static constexpr char ZBX_LO2[] = "LO2"; + +static constexpr char RFDC_NCO[] = "rfdc"; + +static const std::vector<std::string> ZBX_LOS = {ZBX_LO1, ZBX_LO2, RFDC_NCO}; + +static constexpr size_t ZBX_NUM_CHANS = 2; +static constexpr std::array<size_t, 2> ZBX_CHANNELS{0, 1}; + +static constexpr double ZBX_MIX1_MN_THRESHOLD = 4e9; + +// Struct for holding band information, used by zbx_radio_control_impl. +// This information should be selected base on requested tune frequency, and should not be +// changed once initialized. +struct tune_map_item_t +{ + double min_band_freq; + double max_band_freq; + uint8_t rf_fir; + uint8_t if1_fir; + uint8_t if2_fir; + int mix1_m; + int mix1_n; + int mix2_m; + int mix2_n; + double if1_freq_min; + double if1_freq_max; + double if2_freq_min; + double if2_freq_max; +}; + +// These are addresses for the various table-based registers +static constexpr uint32_t ATR_ADDR_0X = 0; +static constexpr uint32_t ATR_ADDR_RX = 1; +static constexpr uint32_t ATR_ADDR_TX = 2; +static constexpr uint32_t ATR_ADDR_XX = 3; // Full-duplex +// Helper for looping +static constexpr std::array<uint32_t, 4> ATR_ADDRS{0, 1, 2, 3}; + +// Turn clang-formatting off so it doesn't compress these tables into a mess. +// clang-format off +static const std::vector<tune_map_item_t> rx_tune_map = { +// | min_band_freq | max_band_freq | rf_fir | if1_fir | if2_fir | mix1 m, n | mix2 m, n | if1_freq_min | if1_freq_max | if2_freq_min | if2_freq_max | + { 1e6, 200e6, 1, 1, 2, -1, 1, -1, 1, 4100e6, 4100e6, 1850e6, 1850e6 }, + { 200e6, 400e6, 1, 1, 2, -1, 1, -1, 1, 4100e6, 4100e6, 1850e6, 1850e6 }, + { 400e6, 500e6, 1, 1, 2, -1, 1, -1, 1, 4100e6, 4100e6, 1850e6, 1850e6 }, + { 500e6, 900e6, 1, 1, 2, -1, 1, -1, 1, 4100e6, 4100e6, 1850e6, 1850e6 }, + { 900e6, 1800e6, 1, 1, 2, -1, 1, -1, 1, 4100e6, 4100e6, 2150e6, 2150e6 }, + { 1800e6, 2300e6, 2, 1, 1, -1, 1, -1, 1, 4100e6, 4100e6, 1060e6, 1060e6 }, + { 2300e6, 2700e6, 3, 1, 1, -1, 1, -1, 1, 4100e6, 3700e6, 1060e6, 1060e6 }, + { 2700e6, 3000e6, 3, 4, 2, 1, -1, 1, -1, 7000e6, 7100e6, 2050e6, 2080e6 }, + { 3000e6, 4200e6, 0, 1, 2, 0, 0, -1, 1, 0, 0, 1850e6, 1850e6 }, + { 4200e6, 4500e6, 0, 2, 2, 0, 0, -1, 1, 0, 0, 1850e6, 1850e6 }, + { 4500e6, 4700e6, 0, 2, 1, 0, 0, -1, 1, 0, 0, 1060e6, 1060e6 }, + { 4700e6, 5300e6, 0, 2, 1, 0, 0, -1, 1, 0, 0, 1060e6, 1060e6 }, + { 5300e6, 5600e6, 0, 2, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 }, + { 5600e6, 6800e6, 0, 3, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 }, + { 6800e6, 7400e6, 0, 4, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 }, + { 7400e6, 8000e6, 0, 4, 2, 0, 0, 1, -1, 0, 0, 1850e6, 1850e6 }, +}; + +static const std::vector<tune_map_item_t> tx_tune_map = { +// | min_band_freq | max_band_freq | rf_fir | if1_fir | if2_fir | mix1 m, n | mix2 m, n | if1_freq_min | if1_freq_max | if2_freq_min | if2_freq_max | + { 1e6, 200e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 }, + { 200e6, 300e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 }, + { 300e6, 400e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 }, + { 400e6, 600e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 }, + { 600e6, 800e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 }, + { 800e6, 1300e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 }, + { 1300e6, 1800e6, 1, 2, 1, -1, 1, 1, -1, 4600e6, 4600e6, 1060e6, 1060e6 }, + { 1800e6, 2300e6, 2, 1, 1, -1, 1, -1, 1, 4100e6, 4100e6, 1060e6, 1060e6 }, + { 2300e6, 2700e6, 3, 1, 2, -1, 1, -1, 1, 3700e6, 3700e6, 2070e6, 2200e6 }, + { 2700e6, 3000e6, 3, 5, 2, 1, -1, 1, -1, 6800e6, 7100e6, 2000e6, 2000e6 }, + { 3000e6, 4030e6, 0, 1, 2, 0, 0, -1, 1, 0, 0, 2050e6, 2370e6 }, + { 4030e6, 4500e6, 0, 1, 1, 0, 0, -1, 1, 0, 0, 1060e6, 1060e6 }, + { 4500e6, 4900e6, 0, 2, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 }, + { 4900e6, 5100e6, 0, 2, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 }, + { 5100e6, 5700e6, 0, 3, 2, 0, 0, 1, -1, 0, 0, 1900e6, 2300e6 }, + { 5700e6, 6100e6, 0, 4, 2, 0, 0, 1, -1, 0, 0, 2300e6, 2500e6 }, + { 6100e6, 6400e6, 0, 4, 2, 0, 0, 1, -1, 0, 0, 2400e6, 2500e6 }, + { 6400e6, 7000e6, 0, 5, 2, 0, 0, 1, -1, 0, 0, 1900e6, 1950e6 }, + { 7000e6, 7400e6, 0, 6, 1, 0, 0, 1, -1, 0, 0, 1060e6, 1060e6 }, + { 7400e6, 8000e6, 0, 6, 2, 0, 0, 1, -1, 0, 0, 1950e6, 2050e6 }, +}; + +// Turn clang-format back on just for posterity +// clang-format on + +}}} // namespace uhd::usrp::zbx + + +namespace uhd { namespace experts { +// << Operator overload for expert's node printing (zbx_lo_source_t property) +// Any added expert nodes of type enum class will have to define this +std::ostream& operator<<( + std::ostream& os, const ::uhd::usrp::zbx::zbx_lo_source_t& lo_source); +}} // namespace uhd::experts diff --git a/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp new file mode 100644 index 000000000..03f0fa5b7 --- /dev/null +++ b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp @@ -0,0 +1,487 @@ +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include "zbx_constants.hpp" +#include "zbx_lo_ctrl.hpp" +#include <uhd/types/direction.hpp> +#include <uhd/types/serial.hpp> +#include <uhd/types/time_spec.hpp> +#include <unordered_map> +#include <zbx_cpld_regs.hpp> +#include <array> +#include <functional> +#include <mutex> + +namespace uhd { namespace usrp { namespace zbx { + +/*! ZBX CPLD Control Class + * + * A note on table indexing: Many settings take an index paramater, usually + * called 'idx'. These settings can be configured in 256 different ways. The + * configuration that is chosen out of those 256 different ones depends on the + * current config register (see get_current_config()). This register itself + * depends on the ATR mode the CPLD is in. + * When the ATR mode is atr_mode::CLASSIC_ATR, then only the first four indexes + * are used, and depend on the current ATR state of the radio (RX, TX, full duplex, + * or idle). If the mode is atr_mode::FPGA_STATE, then only the first 16 indexes + * are used, and the setting that is applied follows the four ATR pins with no + * specific mapping to RX or TX states. If the mode is atr_mode::SW_DEFINED, + * then the ATR pins are ignored, and the state is configured by set_sw_config(). + */ +class zbx_cpld_ctrl +{ +public: + enum chan_t { CHAN0, CHAN1, BOTH_CHANS, NO_CHAN }; + enum spi_xact_t { READ, WRITE }; + // Note: The values in this enum must match the values in the CPLD regmaps. + enum class atr_mode { SW_DEFINED = 0, CLASSIC_ATR = 1, FPGA_STATE = 2 }; + enum class dsa_type { DSA1, DSA2, DSA3A, DSA3B }; + enum class atr_mode_target { DSA, PATH_LED }; + + // The RX gain settings have four DSAs + using rx_dsa_type = std::array<uint32_t, 4>; + // The TX gain settings have two DSAs and one amp-path with 3 possible + // settings. + using tx_dsa_type = std::array<uint32_t, 3>; + + using poke_fn_type = + std::function<void(const uint32_t, const uint32_t, const chan_t)>; + using peek_fn_type = std::function<uint32_t(const uint32_t)>; + using sleep_fn_type = std::function<void(const uhd::time_spec_t&)>; + + //! Maps a DSA name ("DSA1", "DSA2", etc.) to its equivalent dsa_type + static const std::unordered_map<std::string, dsa_type> dsa_map; + + zbx_cpld_ctrl(poke_fn_type&& poke_fn, + peek_fn_type&& peek_fn, + sleep_fn_type&& sleep_fn, + const std::string& log_id); + + ~zbx_cpld_ctrl(void) = default; + + //! Write a value to the scratch register + void set_scratch(const uint32_t value); + + //! Read back the value from the scratch register + uint32_t get_scratch(); + + /*! Configure the ATR mode of a channel + * + * This configures how the DSAs, LEDs, and switches are controlled by the + * ATR pins going into the CPLD. + * + * See the CPLD register map for more information. In a nutshell, this will + * define how the current config register is populated (ATR pins or + * set_sw_config()). + * + * \param channel The channel for which this setting applies (either 0 or 1) + * \param target The target for this setting. With atr_mode_target::DSA, it + * will change the mode for the attenuators. With PATH_LED, it + * will change the mode for the RF path and LED controls. + * \param mode The ATR mode for this channel and target. + */ + void set_atr_mode( + const size_t channel, const atr_mode_target target, const atr_mode mode); + + /*! Choose the SW configuration of the CPLD + * + * When the ATR mode is anything other than SW_DEFINED, this has no effect. + * When the ATR mode is SW_DEFINED, this will choose which DSA/LED/switch + * configuration to apply to hardware. + * + * \param channel The RF channel for which this applies (0 or 1) + * \param target The target for this setting. With atr_mode_target::DSA, it + * will change the mode for the attenuators. With PATH_LED, it + * will change the mode for the RF path and LED controls. + * \param rf_config The selected RF configuration + */ + void set_sw_config( + const size_t channel, const atr_mode_target target, const uint8_t rf_config); + + /*! Read back the current config register + * + * \param channel The RF channel for which this applies (0 or 1) + * \param target The target for this setting. With atr_mode_target::DSA, it + * will change the mode for the attenuators. With PATH_LED, it + * will change the mode for the RF path and LED controls. + */ + uint8_t get_current_config(const size_t channel, const atr_mode_target target); + + /*! Set all RX DSAs directly + * + * This will directly update the DSA tables at the given index. In other + * words, this setting will directly be applied to hardware. + * + * \param channel daughterboard channel to program + * \param idx Table index + * \param dsa_steps DSA step values + */ + void set_tx_gain_switches( + const size_t channel, const uint8_t idx, const tx_dsa_type& dsa_steps); + + /*! Set all TX DSAs directly + * + * This will directly update the DSA tables at the given index. In other + * words, this setting will directly be applied to hardware. + * + * \param channel daughterboard channel to program + * \param idx Table index + * \param dsa_steps DSA step values + */ + void set_rx_gain_switches( + const size_t channel, const uint8_t idx, const rx_dsa_type& dsa_steps); + + /*! Set all RX DSAs using the CPLD table + * + * This will read DSA settings from the lookup table at position \p table_idx + * and write them to the DSAs at position \p idx. + * + * \param channel daughterboard channel to program + * \param idx DSA table index + * \param table_idx Lookup table index + */ + void set_rx_gain_switches( + const size_t channel, const uint8_t idx, const uint8_t table_idx); + + /*! Set all TX DSAs using the CPLD table + * + * This will read DSA settings from the lookup table at position \p table_idx + * and write them to the DSAs at position \p idx. + * + * \param channel daughterboard channel to program + * \param idx DSA table index + * \param table_idx Lookup table index + */ + void set_tx_gain_switches( + const size_t channel, const uint8_t idx, const uint8_t table_idx); + + /*! Set a specific TX DSA + * + * \returns the coerced value that's written to the DSA + */ + uint8_t set_tx_dsa(const size_t channel, + const uint8_t idx, + const dsa_type tx_dsa, + const uint8_t att); + + /*! Set a specific RX DSA + * + * \returns the coerced value that's written to the DSA + */ + uint8_t set_rx_dsa(const size_t channel, + const uint8_t idx, + const dsa_type rx_dsa, + const uint8_t att); + + /*! Set a specific TX DSA + * + * \returns the coerced value that's written to the DSA + */ + uint8_t get_tx_dsa(const size_t channel, + const uint8_t idx, + const dsa_type tx_dsa, + const bool update_cache = false); + + /*! Set a specific RX DSA + * + * \returns the coerced value that's written to the DSA + */ + uint8_t get_rx_dsa(const size_t channel, + const uint8_t idx, + const dsa_type rx_dsa, + const bool update_cache = false); + + /*! Setting switches required for antenna mode switching, transmitting side + * + * Note: If the antenna is set to TX/RX, this also configures the TX + * amplifier. This unfortunate API coupling is due to the fact that the + * same switch that chooses the antenna path also switches the amplifier in + * and out. + * + * \param channel daughterboard channel to program + * \param idx Table index + * \param amp The amplifier configuration + * \param antenna desired antenna mode + */ + void set_tx_antenna_switches(const size_t channel, + const uint8_t idx, + const std::string& antenna, + const tx_amp amp); + + /*! Setting switches required for antenna mode switching, receiving side + * + * \param channel daughterboard channel to program + * \param idx Table index + * \param gain desired antenna mode + * \param is_highband highband or lowband settings + */ + void set_rx_antenna_switches( + const size_t channel, const uint8_t idx, const std::string& antenna); + + /*! Return the current amp settings + */ + tx_amp get_tx_amp_settings( + const size_t channel, const uint8_t idx, const bool update_cache); + + /*! Setting switches required for rf filter changes, receiving side + * + * \param channel daughterboard channel to program + * \param idx Table index + * \param rf_fir rf filter value + */ + void set_rx_rf_filter( + const size_t channel, const uint8_t idx, const uint8_t rf_fir); + + /*! Setting switches required for if1 filter changes, receiving side + * + * \param channel daughterboard channel to program + * \param idx Table index + * \param if1_fir if1 filter value + */ + void set_rx_if1_filter( + const size_t channel, const uint8_t idx, const uint8_t if1_fir); + + /*! Setting switches required for if2 filter changes, receiving side + * + * \param channel daughterboard channel to program + * \param idx Table index + * \param if2_fir if2 filter value + */ + void set_rx_if2_filter( + const size_t channel, const uint8_t idx, const uint8_t if2_fir); + + /*! Setting switches required for rf filter changes, transmitting side + * + * \param channel daughterboard channel to program + * \param idx Table index + * \param rf_fir rf filter value + */ + void set_tx_rf_filter( + const size_t channel, const uint8_t idx, const uint8_t rf_fir); + + /*! Setting switches required for if1 filter changes, transmitting side + * + * \param channel daughterboard channel to program + * \param idx Table index + * \param if1_fir if1 filter value + */ + void set_tx_if1_filter( + const size_t channel, const uint8_t idx, const uint8_t if1_fir); + + /*! Setting switches required for if2 filter changes, transmitting side + * + * \param channel daughterboard channel to program + * \param idx Table index + * \param if2_fir if2 filter value + */ + void set_tx_if2_filter( + const size_t channel, const uint8_t idx, const uint8_t if2_fir); + + /************************************************************************** + * LED controls + *************************************************************************/ + /*! Turn the LEDs on or off + * + * There are two LEDs on ZBX, the TRX LED has a red and green component. + * + * Note that toggling any of the LED settings to 'true' won't necessarily + * turn on the LED. The current CPLD config register for this channel must + * also match \p idx in order for this state to be applied. + * + * \param channel The channel for which these settings apply + * \param idx The LED table index that is configured + * \param rx On-state of the green RX2 LED + * \param trx_rx On-state of the green TX/RX LED + * \param trx_tx On-state of the red TX/RX LED + */ + void set_leds(const size_t channel, + const uint8_t idx, + const bool rx, + const bool trx_rx, + const bool trx_tx); + + /************************************************************************** + * LO controls + *************************************************************************/ + //! Write to a register on an LO + // + // Note: All eight LOs are accessed through the same CPLD register. For + // timed commands to the LOs, it is up to the call site to ensure that SPI + // writes/reads do not get interleaved. + // + // Note: This will not poll the ready bit of the CPLD. To ensure valid + // transactions, either manually call lo_spi_ready(), or make sure that SPI + // commands are timed appropriately, i.e., new SPI transaction requests reach + // the CPLD only after the previous transaction is comppleted. + // + // \param lo Which LO to write to. + // \param addr The address of the LO register (see the LMX2572 datasheet) + // \param data The data to write to the LO register (see the LMX2572 datasheet) + void lo_poke16(const zbx_lo_t lo, const uint8_t addr, const uint16_t data); + + //! Read back from the LO + // + // Note: The LMX2572 has a MUXout pin, not just an SDO pin. This means the + // call site needs to ensure that MUXout configuration is in the correct + // state before calling this function (to either read back the lock status, + // or the SPI read return value). + // + // Note: This will not poll the ready bit of the CPLD. To ensure valid + // transactions, either manually call lo_spi_ready(), or make sure that SPI + // commands are timed appropriately, i.e., new SPI transaction requests reach + // the CPLD only after the previous transaction is comppleted. + // + // \param lo Which LO to read from + // \param addr Which address on the LO to read from (see LMX2572 datasheet) + // \param valid_timeout_ms After triggering the transaction, the function will + // wait for this many ms before throwing an exception. A zero timeout + // is possible, which means the first read to the LO_SPI_STATUS + // register must already have the ready bit high. + uint16_t lo_peek16(const zbx_lo_t lo, const uint8_t addr); + + //! Returns true if the LO_SPI_READY bit is high, i.e., the LO SPI is ready + // for a transaction + bool lo_spi_ready(); + + //! LO's incoming source control (external/internal) is actually found in + // the CPLD path control register spaces + // + // \param idx Table index + // \param lo Which LO to read from + // \param lo_source Set LO source to internal/external + void set_lo_source( + const size_t idx, const zbx_lo_t lo, const zbx_lo_source_t lo_source); + + //! Retrieve lo source + // \param idx Table index to read from + // \param lo Which LO to read from + zbx_lo_source_t get_lo_source(const size_t idx, zbx_lo_t lo); + + //! Synchronize LOs + // + // This will assert a SYNC pulse on all the LOs listed in \p los. + // + // Note: This function will throw an exception if LO sync bypass is enabled + // (see set_lo_sync_bypass()). + // + // A note on timing: Like most CPLD controls, the time is inherited from the + // underlying register interface. That is to say, the APIs don't take a time + // as an argument, but assume the command time is correctly applied. + // The different channels of the ZBX (channel 0/1) may have different command + // times. Because this API potentially affects both channels at once, the + // channel index must be provided to determine which channel's time should + // be used. + // + // \param ref_chan The channel that is used as a timing reference. + // \param los A list of LOs to synchronize + // \throws uhd::runtime_error if LO sync bypass is enabled. + void pulse_lo_sync(const size_t ref_chan, const std::vector<zbx_lo_t>& los); + + //! Enable/disable LO sync bypass + // + // This is a ZBX-specific option, which will allow synchronizing the LOs via + // the MB_SYNTH_SYNC pin instead of using a register. Enabling this will + // disable the ability to call pulse_lo_sync(). + // + // \param enable If true, enables the bypass. When false, disables the bypass + // and pulse_lo_sync() can be called. + void set_lo_sync_bypass(const bool enable); + + /*! Write DSA table for TX frequency to DB CPLD + */ + void update_tx_dsa_settings( + const std::vector<uint32_t>& dsa1_table, const std::vector<uint32_t>& dsa2_table); + + /*! Write DSA table for RX frequency to DB CPLD + */ + void update_rx_dsa_settings(const std::vector<uint32_t>& dsa1_table, + const std::vector<uint32_t>& dsa2_table, + const std::vector<uint32_t>& dsa3a_table, + const std::vector<uint32_t>& dsa3b_table); + +private: + /*! Dump the state of the registers into the CPLD + * + * \param chan Which channel does this change pertain to? This is forwarded + * to _poke32(). + * \param save_all If true, save all registers. If false, only change those + * that changed since last save_state() call. + * Note that if save_all is true, the chan parameter does not + * really apply, because all registers (for all channels) are + * written to. Therefore, only use save_all==true in + * combination with NO_CHAN. + */ + void commit(const chan_t chan = NO_CHAN, const bool save_all = false); + + /*! Update a register field by peeking the corresponding CPLD register + * + * This will synchronize the state of the internal register cache with the + * actual value from the CPLD. This will incur a single peek (non-timed) to + * the chip before returning. + */ + void update_field(const zbx_cpld_regs_t::zbx_cpld_field_t field, const size_t idx); + + /*! Perform an LO SPI transaction (interact with the LO_SPI_STATUS register) + * + * Note: This has the ability to throttle the SPI transactions. The reason + * is that the peek/poke interface from UHD to the CPLD is faster than the + * SPI interface from the CPLD to the LO. If two SPI writes were to be + * sent without a throttle, the second one would clobber the first. Never + * call this with throttle == false if another SPI transaction is following! + * + * \param lo Which LO to address + * \param addr 7-bit address of the LO's register + * \param data 16-bit data to write (can be empty for reads) + * \param write If true, write, else read + * \param throttle If true, wait after writing, so that a following SPI + * transaction won't clobber the previous one + */ + void _lo_spi_transact(const zbx_lo_t lo, + const uint8_t addr, + const uint16_t data, + const spi_xact_t xact_type, + const bool throttle = true); + + /*! Write a list of values to a register. + * The list start address is searched using reg_addr_name. + * The method will raise an exception if values is longer than + * the register size. + * The caller is responsible to commit the data once values are written. + * This allows multiple vector writes with a single commit. + */ + void write_register_vector( + const std::string& reg_addr_name, const std::vector<uint32_t>& values); + + //! Poker object + poke_fn_type _poke32; + + //! Peeker object + peek_fn_type _peek32; + + //! Hardware-timed sleep, used to throttle pokes + sleep_fn_type _sleep; + + // Address offset (on top of _db_cpld_offset) where the LO SPI register is + const uint32_t _lo_spi_offset; + + // infos about the daughtherboard revision + std::string _db_rev_info; + + // Cached register state + zbx_cpld_regs_t _regs = zbx_cpld_regs_t(); + + const std::string _log_id; +}; + +}}} // namespace uhd::usrp::zbx + +namespace uhd { namespace experts { +// << Operator overload for expert's node printing (zbx_lo_source_t property) +// Any added expert nodes of type enum class will have to define this +std::ostream& operator<<( + std::ostream& os, const ::uhd::usrp::zbx::zbx_cpld_ctrl::atr_mode& lo_source); +}} // namespace uhd::experts diff --git a/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_dboard.hpp b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_dboard.hpp new file mode 100644 index 000000000..619c4a05f --- /dev/null +++ b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_dboard.hpp @@ -0,0 +1,416 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include "zbx_constants.hpp" +#include "zbx_cpld_ctrl.hpp" +#include "zbx_expert.hpp" +#include "zbx_lo_ctrl.hpp" +#include <uhd/cal/dsa_cal.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/rfnoc/register_iface.hpp> +#include <uhd/rfnoc/registry.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/wb_iface.hpp> +#include <uhdlib/experts/expert_factory.hpp> +#include <uhdlib/rfnoc/rf_control/dboard_iface.hpp> +#include <uhdlib/usrp/common/mpmd_mb_controller.hpp> +#include <uhdlib/usrp/common/pwr_cal_mgr.hpp> +#include <uhdlib/usrp/common/rpc.hpp> +#include <uhdlib/usrp/common/x400_rfdc_control.hpp> +#include <uhdlib/usrp/dboard/x400_dboard_iface.hpp> +#include <uhdlib/utils/rpc.hpp> +#include <stddef.h> +#include <memory> +#include <string> +#include <vector> + +using namespace uhd::rfnoc; + +namespace uhd { namespace usrp { namespace zbx { + +const static uint16_t ZBX_PID = 0x4002; + +/*! Provide access to a ZBX radio. + */ +class zbx_dboard_impl : public uhd::usrp::x400::x400_dboard_iface +{ +public: + using sptr = std::shared_ptr<zbx_dboard_impl>; + using time_accessor_fn_type = std::function<uhd::time_spec_t(size_t)>; + + /************************************************************************ + * Structors + ***********************************************************************/ + zbx_dboard_impl(register_iface& reg_iface, + const size_t reg_base_address, + time_accessor_fn_type&& time_accessor, + const size_t db_idx, + const std::string& radio_slot, + const std::string& rpc_prefix, + const std::string& unique_id, + uhd::usrp::x400_rpc_iface::sptr mb_rpcc, + uhd::usrp::zbx_rpc_iface::sptr rpcc, + uhd::rfnoc::x400::rfdc_control::sptr rfdcc, + uhd::property_tree::sptr tree); + virtual ~zbx_dboard_impl(); + + size_t get_chan_from_dboard_fe( + const std::string& fe, const uhd::direction_t) const override; + std::string get_dboard_fe_from_chan( + const size_t chan, const uhd::direction_t) const override; + + /************************************************************************ + * node_t && noc_block_base API calls + ***********************************************************************/ + void deinit(); + + void set_command_time(uhd::time_spec_t time, const size_t chan) override; + + /************************************************************************ + * API calls + ***********************************************************************/ + + bool is_adc_self_cal_supported() override + { + return true; + } + + uhd::usrp::x400::adc_self_cal_params_t get_adc_self_cal_params(const double tone_freq) override + { + // This is chosen such that the IF2 frequency is 1.06G + const double rx_freq = 4.7e9 - 5.12e6; + const double if2_freq = 1.06e9; + const double offset = tone_freq - if2_freq; + + // Minus because this zone is inverted + const double tx_freq = rx_freq - offset; + return { + 10.0, // min_gain + 50.0, // max_gain + rx_freq, // rx_freq + tx_freq, // tx_freq + }; + } + + rf_control::gain_profile_iface::sptr get_tx_gain_profile_api() override + { + return _tx_gain_profile_api; + } + rf_control::gain_profile_iface::sptr get_rx_gain_profile_api() override + { + return _rx_gain_profile_api; + } + + void set_tx_antenna(const std::string& ant, const size_t chan) override; + void set_rx_antenna(const std::string& ant, const size_t chan) override; + std::vector<std::string> get_tx_antennas(const size_t /*chan*/) const override + { + return TX_ANTENNAS; + } + std::vector<std::string> get_rx_antennas(const size_t /*chan*/) const override + { + return RX_ANTENNAS; + } + + double set_tx_frequency(const double freq, const size_t chan) override; + double set_rx_frequency(const double freq, const size_t chan) override; + uhd::freq_range_t get_tx_frequency_range(const size_t /*chan*/) const override + { + return ZBX_FREQ_RANGE; + } + uhd::freq_range_t get_rx_frequency_range(const size_t /*chan*/) const override + { + return ZBX_FREQ_RANGE; + } + + double set_tx_bandwidth(const double bandwidth, const size_t chan) override; + double set_rx_bandwidth(const double bandwidth, const size_t chan) override; + uhd::meta_range_t get_tx_bandwidth_range(size_t chan) const override + { + return _tree + ->access<uhd::meta_range_t>( + _get_frontend_path(TX_DIRECTION, chan) / "bandwidth" / "range") + .get(); + } + uhd::meta_range_t get_rx_bandwidth_range(size_t chan) const override + { + return _tree + ->access<uhd::meta_range_t>( + _get_frontend_path(RX_DIRECTION, chan) / "bandwidth" / "range") + .get(); + } + + double set_tx_gain(const double gain, const size_t chan) override; + double set_tx_gain( + const double gain, const std::string& name, const size_t chan) override; + double set_rx_gain(const double gain, const size_t chan) override; + double set_rx_gain( + const double gain, const std::string& name, const size_t chan) override; + double get_rx_gain(const size_t chan) override; + double get_tx_gain(const size_t chan) override; + double get_rx_gain(const std::string& name, const size_t chan) override; + double get_tx_gain(const std::string& name, const size_t chan) override; + + uhd::gain_range_t get_tx_gain_range(const size_t /*chan*/) const override + { + return ZBX_TX_GAIN_RANGE; + } + uhd::gain_range_t get_rx_gain_range(const size_t /*chan*/) const override + { + // FIXME This should return a ZBX_RX_LOW_FREQ_GAIN_RANGE when freq is + // low, but this function is const + return ZBX_RX_GAIN_RANGE; + } + + // LO Property Getters + std::vector<std::string> get_tx_lo_names(const size_t /*chan*/) const + { + return ZBX_LOS; + } + std::vector<std::string> get_rx_lo_names(const size_t /*chan*/) const + { + return ZBX_LOS; + } + std::vector<std::string> get_tx_lo_sources( + const std::string& /*name*/, const size_t /*chan*/) const + { + return std::vector<std::string>{"internal", "external"}; + } + std::vector<std::string> get_rx_lo_sources( + const std::string& /*name*/, const size_t /*chan*/) const + { + return std::vector<std::string>{"internal", "external"}; + } + + // LO Frequency Control + double set_tx_lo_freq( + const double freq, const std::string& name, const size_t chan) override; + double set_rx_lo_freq( + const double freq, const std::string& name, const size_t chan) override; + double get_tx_lo_freq(const std::string& name, const size_t chan) override; + double get_rx_lo_freq(const std::string& name, size_t chan) override; + + // LO Source Control + void set_tx_lo_source( + const std::string& src, const std::string& name, const size_t chan) override; + void set_rx_lo_source( + const std::string& src, const std::string& name, const size_t chan) override; + const std::string get_tx_lo_source( + const std::string& name, const size_t chan) override; + const std::string get_rx_lo_source( + const std::string& name, const size_t chan) override; + + uhd::freq_range_t get_rx_lo_freq_range( + const std::string& name, const size_t chan) const override + { + return _get_lo_freq_range(name, chan); + } + + // TODO: Why is this not const? + uhd::freq_range_t get_tx_lo_freq_range( + const std::string& name, const size_t chan) override + { + return _get_lo_freq_range(name, chan); + } + + void set_rx_lo_export_enabled( + const bool enabled, const std::string& name, const size_t chan) override; + bool get_rx_lo_export_enabled( + const std::string& name, const size_t chan) override; + void set_tx_lo_export_enabled( + const bool enabled, const std::string& name, const size_t chan) override; + bool get_tx_lo_export_enabled(const std::string& name, const size_t chan) override; + + + /****************************************************************************** + * EEPROM API + *****************************************************************************/ + eeprom_map_t get_db_eeprom() override; + + /************************************************************************** + * Radio Identification API Calls + *************************************************************************/ + + std::string get_tx_antenna(size_t chan) const override; + std::string get_rx_antenna(size_t chan) const override; + double get_tx_frequency(size_t chan) override; + double get_rx_frequency(size_t chan) override; + double get_rx_bandwidth(size_t chan) override; + double get_tx_bandwidth(size_t chan) override; + void set_tx_tune_args(const uhd::device_addr_t&, const size_t) override; + void set_rx_tune_args(const uhd::device_addr_t&, const size_t) override; + std::vector<std::string> get_tx_gain_names(size_t) const override; + std::vector<std::string> get_rx_gain_names(size_t) const override; + + uhd::gain_range_t get_tx_gain_range( + const std::string& name, const size_t chan) const override; + + uhd::gain_range_t get_rx_gain_range( + const std::string& name, const size_t chan) const override; + + void set_rx_agc(const bool, const size_t) override; + + std::vector<uhd::usrp::pwr_cal_mgr::sptr>& get_pwr_mgr(uhd::direction_t trx) override; + +private: + uhd::property_tree::sptr get_tree() + { + return _tree; + } + + // Expert map, keyed by the pair of tx/rx and channel + uhd::experts::expert_container::sptr _expert_container; + + /************************************************************************** + * Helpers + *************************************************************************/ + //! Initialize DB-CPLD + void _init_cpld(); + + //! Initialize all the peripherals connected to this block + void _init_peripherals(); + + //! Init a subtree for the RF frontends + void _init_frontend_subtree(uhd::property_tree::sptr subtree, + const uhd::direction_t trx, + const size_t chan_idx, + const fs_path fe_path); + //! Initializing the expert properties + void _init_frequency_prop_tree(uhd::property_tree::sptr subtree, + uhd::experts::expert_container::sptr expert, + const fs_path fe_path); + void _init_gain_prop_tree(uhd::property_tree::sptr subtree, + uhd::experts::expert_container::sptr, + const uhd::direction_t trx, + const size_t chan_idx, + const fs_path fe_path); + void _init_antenna_prop_tree(uhd::property_tree::sptr subtree, + uhd::experts::expert_container::sptr, + const uhd::direction_t trx, + const size_t chan_idx, + const fs_path fe_path); + void _init_programming_prop_tree(uhd::property_tree::sptr subtree, + uhd::experts::expert_container::sptr, + const fs_path fe_path); + void _init_lo_prop_tree(uhd::property_tree::sptr subtree, + uhd::experts::expert_container::sptr, + const uhd::direction_t trx, + const size_t chan_idx, + const fs_path fe_path); + //! Init all experts, bind to properties created above + void _init_experts(uhd::property_tree::sptr subtree, + uhd::experts::expert_container::sptr, + const uhd::direction_t trx, + const size_t chan_idx, + const fs_path fe_path); + + uhd::usrp::pwr_cal_mgr::sptr _init_power_cal(uhd::property_tree::sptr subtree, + const uhd::direction_t trx, + const size_t chan_idx, + const fs_path fe_path); + + //! Initialize property tree + void _init_prop_tree(); + //! Init RPC interaction + void _init_mpm(); + + //! Set up sensor property nodes + void _init_mpm_sensors(const direction_t dir, const size_t chan_idx); + + //! Get subtree path for a given direction/channel + fs_path _get_frontend_path(const direction_t dir, const size_t chan_idx) const; + + // Get all los "lock status", per enabled && locked individual LOs + bool _get_all_los_locked(const direction_t dir, const size_t chan); + + const std::string _unique_id; + std::string get_unique_id() const; + + freq_range_t _get_lo_freq_range(const std::string& name, const size_t chan) const; + + /************************************************************************** + * Private attributes + *************************************************************************/ + + static constexpr size_t _num_rx_chans = 2; + static constexpr size_t _num_tx_chans = 2; + + //! Interface to the registers + uhd::rfnoc::register_iface& _regs; + const size_t _reg_base_address; + + //! Interface to get the command time + time_accessor_fn_type _time_accessor; + + //! Letter representation of the radio we're currently running + const std::string _radio_slot; + + //! Index of this daughterboard + const size_t _db_idx; + + // infos about the daughtherboard + std::vector<std::map<std::string, std::string>> _all_dboard_info; + + //! Prepended for all dboard RPC calls + const std::string _rpc_prefix; + + //! Reference to the MB controller + uhd::rfnoc::mpmd_mb_controller::sptr _mb_control; + + //! Reference to wb_iface adapters + std::vector<uhd::timed_wb_iface::sptr> _wb_ifaces; + + //! Reference to the RPC client + uhd::usrp::x400_rpc_iface::sptr _mb_rpcc; + uhd::usrp::zbx_rpc_iface::sptr _rpcc; + + //! Reference to the RFDC controller + uhd::rfnoc::x400::rfdc_control::sptr _rfdcc; + + //! Reference to the CPLD controls + std::shared_ptr<zbx_cpld_ctrl> _cpld; + + //! Reference to all LO controls + std::map<zbx_lo_t, std::shared_ptr<zbx_lo_ctrl>> _lo_ctrl_map; + + //! Reference to the TX Cal data + std::shared_ptr<uhd::usrp::cal::zbx_tx_dsa_cal> _tx_dsa_cal; + + //! Reference to the RX Cal data + std::shared_ptr<uhd::usrp::cal::zbx_rx_dsa_cal> _rx_dsa_cal; + + //! Reference to this block's subtree + // + // It is mutable because _tree->access<>(..).get() is not const, but we + // need to do just that in some const contexts + mutable uhd::property_tree::sptr _tree; + + std::vector<uhd::usrp::pwr_cal_mgr::sptr> _rx_pwr_mgr; + std::vector<uhd::usrp::pwr_cal_mgr::sptr> _tx_pwr_mgr; + + rf_control::gain_profile_iface::sptr _tx_gain_profile_api; + rf_control::gain_profile_iface::sptr _rx_gain_profile_api; + + //! Store the current RX gain profile + std::vector<std::string> _rx_gain_profile = { + ZBX_GAIN_PROFILE_DEFAULT, ZBX_GAIN_PROFILE_DEFAULT}; + //! Store the current TX gain profile + std::vector<std::string> _tx_gain_profile = { + ZBX_GAIN_PROFILE_DEFAULT, ZBX_GAIN_PROFILE_DEFAULT}; + + //! The sampling rate of the RFdc, typically something close to 3 GHz + const double _rfdc_rate; + + //! The PLL reference rate, typically something in the 50 - 64 MHz range + const double _prc_rate; +}; + +}}} // namespace uhd::usrp::zbx diff --git a/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_expert.hpp b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_expert.hpp new file mode 100644 index 000000000..f386a4fdb --- /dev/null +++ b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_expert.hpp @@ -0,0 +1,837 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include "zbx_constants.hpp" +#include "zbx_cpld_ctrl.hpp" +#include "zbx_lo_ctrl.hpp" +#include <uhd/cal/container.hpp> +#include <uhd/cal/dsa_cal.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/types/ranges.hpp> +#include <uhdlib/experts/expert_nodes.hpp> +#include <uhdlib/rfnoc/rf_control/gain_profile_iface.hpp> +#include <uhdlib/usrp/common/pwr_cal_mgr.hpp> +#include <uhdlib/usrp/common/rpc.hpp> +#include <uhdlib/usrp/common/x400_rfdc_control.hpp> +#include <uhdlib/utils/rpc.hpp> +#include <cmath> +#include <memory> + +namespace uhd { namespace usrp { namespace zbx { + +namespace { + +//! Depending on the given \p lo_step_size, this will return a valid frequency +// range on a quantized grid for the the LOs. The lower limit of this range will +// never be smaller than LMX2572_MIN_FREQ and the upper frequency will never be +// larger than LMX2572_MAX_FREQ. All frequencies will be integer multiples of +// the given \p lo_step_size. +uhd::freq_range_t _get_quantized_lo_range(const double lo_step_size) +{ + const double start = std::ceil(LMX2572_MIN_FREQ / lo_step_size) * lo_step_size; + const double stop = std::floor(LMX2572_MAX_FREQ / lo_step_size) * lo_step_size; + UHD_ASSERT_THROW(start >= LMX2572_MIN_FREQ); + UHD_ASSERT_THROW(stop <= LMX2572_MAX_FREQ); + return uhd::freq_range_t(start, stop, lo_step_size); +} + +} // namespace + +/*!--------------------------------------------------------- + * zbx_scheduling_expert + * + * This expert is responsible for scheduling time sensitive actions + * in other experts. It responds to changes in the command time and + * selectively causes experts to run in order to ensure a synchronized + * system. + * + * There is one scheduling expert per channel, they are shared between RX and TX. + * So, 2 scheduling experts total per radio block. + * --------------------------------------------------------- + */ +class zbx_scheduling_expert : public experts::worker_node_t +{ +public: + zbx_scheduling_expert(const experts::node_retriever_t& db, const uhd::fs_path fe_path) + : experts::worker_node_t(fe_path / "zbx_scheduling_expert") + , _command_time(db, fe_path / "time/cmd") + , _frontend_time(db, fe_path / "time/fe") + { + bind_accessor(_command_time); + bind_accessor(_frontend_time); + } + +private: + virtual void resolve(); + + // Inputs + experts::data_reader_t<time_spec_t> _command_time; + + // Outputs + experts::data_writer_t<time_spec_t> _frontend_time; +}; + +/*!--------------------------------------------------------- + * zbx_freq_fe_expert (Frequency Front-end Expert) + * + * This expert is responsible for responding to user requests for center frequency tuning + * + * This should trigger: + * - relevant LO experts + * - adjacent MPM expert + * - adjacent CPLD (tx/rx) Programming expert + * After all of the above, the Frequency Backend expert should be triggered to returned + * the coerced center frequency + * + * One instance of this expert is required for each combination of Direction (TX/RX) and + * Channel (0,1); four total + * -------------------------------------------------------- + */ +class zbx_freq_fe_expert : public uhd::experts::worker_node_t +{ +public: + zbx_freq_fe_expert(const uhd::experts::node_retriever_t& db, + const uhd::fs_path fe_path, + const uhd::direction_t trx, + const size_t chan, + const double rfdc_rate, + const double lo_step_size) + : experts::worker_node_t(fe_path / "zbx_freq_fe_expert") + , _desired_frequency(db, fe_path / "freq" / "desired") + , _desired_lo1_frequency(db, fe_path / "los" / ZBX_LO1 / "freq" / "value" / "desired") + , _desired_lo2_frequency(db, fe_path / "los" / ZBX_LO2 / "freq" / "value" / "desired") + , _lo1_enabled(db, fe_path / ZBX_LO1 / "enabled") + , _lo2_enabled(db, fe_path / ZBX_LO2 / "enabled") + , _desired_if2_frequency(db, fe_path / "if_freq" / "desired") + , _band_inverted(db, fe_path / "band_inverted") + , _is_highband(db, fe_path / "is_highband") + , _mixer1_m(db, fe_path / "mixer1_m") + , _mixer1_n(db, fe_path / "mixer1_n") + , _mixer2_m(db, fe_path / "mixer2_m") + , _mixer2_n(db, fe_path / "mixer2_n") + , _rf_filter(db, fe_path / "rf" / "filter") + , _if1_filter(db, fe_path / "if1" / "filter") + , _if2_filter(db, fe_path / "if2" / "filter") + , _rfdc_rate(rfdc_rate) + , _lo_freq_range(_get_quantized_lo_range(lo_step_size)) + , _trx(trx) + , _chan(chan) + { + // Inputs + bind_accessor(_desired_frequency); + + // Outputs + bind_accessor(_desired_lo1_frequency); + bind_accessor(_desired_lo2_frequency); + bind_accessor(_lo1_enabled); + bind_accessor(_lo2_enabled); + bind_accessor(_desired_if2_frequency); + bind_accessor(_band_inverted); + bind_accessor(_is_highband); + bind_accessor(_mixer1_m); + bind_accessor(_mixer1_n); + bind_accessor(_mixer2_m); + bind_accessor(_mixer2_n); + bind_accessor(_rf_filter); + bind_accessor(_if1_filter); + bind_accessor(_if2_filter); + } + +private: + void resolve() override; + + // Inputs from user/API + uhd::experts::data_reader_t<double> _desired_frequency; + + // Outputs + // From calculation, to LO expert + uhd::experts::data_writer_t<double> _desired_lo1_frequency; + uhd::experts::data_writer_t<double> _desired_lo2_frequency; + uhd::experts::data_writer_t<bool> _lo1_enabled; + uhd::experts::data_writer_t<bool> _lo2_enabled; + // From calculation, to MPM/RPC expert + uhd::experts::data_writer_t<double> _desired_if2_frequency; + uhd::experts::data_writer_t<bool> _band_inverted; + // From calculation, to Frequency Backend expert + uhd::experts::data_writer_t<bool> _is_highband; + uhd::experts::data_writer_t<int> _mixer1_m; + uhd::experts::data_writer_t<int> _mixer1_n; + uhd::experts::data_writer_t<int> _mixer2_m; + uhd::experts::data_writer_t<int> _mixer2_n; + // From calculation, to CPLD Programming expert + uhd::experts::data_writer_t<int> _rf_filter; + uhd::experts::data_writer_t<int> _if1_filter; + uhd::experts::data_writer_t<int> _if2_filter; + + const double _rfdc_rate; + const uhd::freq_range_t _lo_freq_range; + tune_map_item_t _tune_settings; + // Channel properties + const uhd::direction_t _trx; + const size_t _chan; +}; + +/*!--------------------------------------------------------- + * zbx_freq_be_expert (Frequency Back-end Expert) + * + * This expert is responsible for calculating the final coerced frequency and returning it + * to the user + * + * This should trigger: + * - adjacent gain expert + * + * One instance of this expert is required for each combination of Direction (TX/RX) and + * Channel (0,1); four total + * -------------------------------------------------------- + */ +class zbx_freq_be_expert : public uhd::experts::worker_node_t +{ +public: + zbx_freq_be_expert(const uhd::experts::node_retriever_t& db, + const uhd::fs_path fe_path, + const uhd::direction_t trx, + const size_t chan) + : uhd::experts::worker_node_t(fe_path / "zbx_freq_be_expert") + , _coerced_lo1_frequency(db, fe_path / "los" / ZBX_LO1 / "freq" / "value" / "coerced") + , _coerced_lo2_frequency(db, fe_path / "los" / ZBX_LO2 / "freq" / "value" / "coerced") + , _coerced_if2_frequency(db, fe_path / "if_freq" / "coerced") + , _is_highband(db, fe_path / "is_highband") + , _mixer1_m(db, fe_path / "mixer1_m") + , _mixer1_n(db, fe_path / "mixer1_n") + , _mixer2_m(db, fe_path / "mixer2_m") + , _mixer2_n(db, fe_path / "mixer2_n") + , _coerced_frequency(db, fe_path / "freq" / "coerced") + , _trx(trx) + , _chan(chan) + { + // Inputs + bind_accessor(_coerced_lo1_frequency); + bind_accessor(_coerced_lo2_frequency); + bind_accessor(_coerced_if2_frequency); + bind_accessor(_is_highband); + bind_accessor(_mixer1_m); + bind_accessor(_mixer1_n); + bind_accessor(_mixer2_m); + bind_accessor(_mixer2_n); + + // Outputs + bind_accessor(_coerced_frequency); + } + +private: + void resolve() override; + + // Inputs from LO expert(s) + uhd::experts::data_reader_t<double> _coerced_lo1_frequency; + uhd::experts::data_reader_t<double> _coerced_lo2_frequency; + // Input from MPM/RPC expert + uhd::experts::data_reader_t<double> _coerced_if2_frequency; + uhd::experts::data_reader_t<bool> _is_highband; + // Input from Frequency FE + uhd::experts::data_reader_t<int> _mixer1_m; + uhd::experts::data_reader_t<int> _mixer1_n; + uhd::experts::data_reader_t<int> _mixer2_m; + uhd::experts::data_reader_t<int> _mixer2_n; + + // Output to user/API + uhd::experts::data_writer_t<double> _coerced_frequency; + + // Channel properties + const uhd::direction_t _trx; + const size_t _chan; +}; + +/*!--------------------------------------------------------- + * zbx_lo_expert + * + * This expert is responsible for controlling one LO on the zbx + * note: LO source control is handled by the CPLD Programming Expert + * + * This should trigger: + * - Relevant (tx/rx, channel) Frequency Back-end Expert + * + * One instance of this expert is required for each LO (lo1, lo2) per Direction (TX/RX) + * and Channel (0,1); eight total + * -------------------------------------------------------- + */ +class zbx_lo_expert : public uhd::experts::worker_node_t +{ +public: + zbx_lo_expert(const uhd::experts::node_retriever_t& db, + const uhd::fs_path fe_path, + const uhd::direction_t trx, + const size_t chan, + const std::string lo, + std::shared_ptr<zbx_lo_ctrl> zbx_lo_ctrl) + : uhd::experts::worker_node_t(fe_path / "zbx_" + lo + "_expert") + , _desired_lo_frequency(db, fe_path / "los" / lo / "freq" / "value" / "desired") + , _set_is_enabled(db, fe_path / lo / "enabled") + , _test_mode_enabled(db, fe_path / lo / "test_mode") + , _coerced_lo_frequency(db, fe_path / "los" / lo / "freq" / "value" / "coerced") + , _lo_ctrl(zbx_lo_ctrl) + , _trx(trx) + , _chan(chan) + { + bind_accessor(_desired_lo_frequency); + bind_accessor(_test_mode_enabled); + bind_accessor(_set_is_enabled); + bind_accessor(_coerced_lo_frequency); + } + +private: + void resolve() override; + + // Inputs from Frequency FE expert or user/API + uhd::experts::data_reader_t<double> _desired_lo_frequency; + uhd::experts::data_reader_t<bool> _set_is_enabled; + // Inputs from user/API + uhd::experts::data_reader_t<bool> _test_mode_enabled; + + // Outputs to Frequency BE expert or user/API + uhd::experts::data_writer_t<double> _coerced_lo_frequency; + + std::shared_ptr<zbx_lo_ctrl> _lo_ctrl; + const uhd::direction_t _trx; + const size_t _chan; +}; + + +/*! DSA coercer expert + * + * Knows how to coerce a DSA value. + */ +class zbx_gain_coercer_expert : public uhd::experts::worker_node_t +{ +public: + zbx_gain_coercer_expert(const uhd::experts::node_retriever_t& db, + const uhd::fs_path gain_path, + const uhd::meta_range_t valid_range) + : uhd::experts::worker_node_t(gain_path / "zbx_gain_coercer_expert") + , _gain_desired(db, gain_path / "desired") + , _gain_coerced(db, gain_path / "coerced") + , _valid_range(valid_range) + { + bind_accessor(_gain_desired); + bind_accessor(_gain_coerced); + } + +private: + void resolve() override; + // Input + uhd::experts::data_reader_t<double> _gain_desired; + // Output + uhd::experts::data_writer_t<double> _gain_coerced; + // Attributes + const uhd::meta_range_t _valid_range; +}; + +/*!--------------------------------------------------------- + * zbx_tx_gain_expert (TX Gain Expert) + * + * This expert is responsible for controlling the gain of each TX channel. + * If the gain profile is set to default, then it will look up the corresponding + * amp and DSA values and write them to those nodes. + * + * This should trigger: + * - Adjacent CPLD TX Programming Expert + * + * One instance of this expert is required for each TX Channel (0,1); two total + * -------------------------------------------------------- + */ +class zbx_tx_gain_expert : public uhd::experts::worker_node_t +{ +public: + zbx_tx_gain_expert(const uhd::experts::node_retriever_t& db, + const uhd::fs_path fe_path, + const size_t chan, + uhd::usrp::pwr_cal_mgr::sptr power_mgr, + uhd::usrp::cal::zbx_tx_dsa_cal::sptr dsa_cal) + : uhd::experts::worker_node_t(fe_path / "zbx_gain_expert") + , _gain_in(db, fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value" / "desired") + , _profile(db, fe_path / "gains" / "all" / "profile") + , _frequency(db, fe_path / "freq" / "coerced") + , _gain_out(db, fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value" / "coerced") + , _dsa1(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA1 / "value" / "desired") + , _dsa2(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA2 / "value" / "desired") + , _amp_gain(db, fe_path / "gains" / ZBX_GAIN_STAGE_AMP / "value" / "desired") + , _power_mgr(power_mgr) + , _dsa_cal(dsa_cal) + , _chan(chan) + { + bind_accessor(_gain_in); + bind_accessor(_profile); + bind_accessor(_frequency); + bind_accessor(_gain_out); + bind_accessor(_dsa1); + bind_accessor(_dsa2); + bind_accessor(_amp_gain); + } + +private: + void resolve() override; + void _set_tx_dsa(const std::string, const uint8_t desired_gain); + double _set_tx_amp_by_gain(const double gain); + // Inputs from user/API + uhd::experts::data_reader_t<double> _gain_in; + // Inputs for DSA calibration + uhd::experts::data_reader_t<std::string> _profile; + uhd::experts::data_reader_t<double> _frequency; + + // Output to user/API + uhd::experts::data_writer_t<double> _gain_out; + // Outputs to CPLD programming expert + uhd::experts::data_writer_t<double> _dsa1; + uhd::experts::data_writer_t<double> _dsa2; + uhd::experts::data_writer_t<double> _amp_gain; + + uhd::usrp::pwr_cal_mgr::sptr _power_mgr; + uhd::usrp::cal::zbx_tx_dsa_cal::sptr _dsa_cal; + const size_t _chan; +}; + +/*!--------------------------------------------------------- + * zbx_rx_gain_expert (RX Gain Expert) + * + * This expert is responsible for controlling the gain of each RX channel + * + * This should trigger: + * - Adjacent CPLD RX Programming Expert + * + * One instance of this expert is required for each RX Channel (0,1); two total + * -------------------------------------------------------- + */ +class zbx_rx_gain_expert : public uhd::experts::worker_node_t +{ +public: + zbx_rx_gain_expert(const uhd::experts::node_retriever_t& db, + const uhd::fs_path fe_path, + const size_t chan, + uhd::usrp::pwr_cal_mgr::sptr power_mgr, + uhd::usrp::cal::zbx_rx_dsa_cal::sptr dsa_cal) + : uhd::experts::worker_node_t(fe_path / "zbx_gain_expert") + , _gain_in(db, fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value" / "desired") + , _profile(db, fe_path / "gains" / "all" / "profile") + , _frequency(db, fe_path / "freq" / "coerced") + , _gain_out(db, fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value" / "coerced") + , _dsa1(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA1 / "value" / "desired") + , _dsa2(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA2 / "value" / "desired") + , _dsa3a(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA3A / "value" / "desired") + , _dsa3b(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA3B / "value" / "desired") + , _power_mgr(power_mgr) + , _dsa_cal(dsa_cal) + , _chan(chan) + { + bind_accessor(_gain_in); + bind_accessor(_profile); + bind_accessor(_frequency); + bind_accessor(_gain_out); + bind_accessor(_dsa1); + bind_accessor(_dsa2); + bind_accessor(_dsa3a); + bind_accessor(_dsa3b); + } + +private: + void resolve() override; + + // Inputs from user/API + uhd::experts::data_reader_t<double> _gain_in; + uhd::experts::data_reader_t<std::string> _profile; + // Inputs for dsa calibration + uhd::experts::data_reader_t<double> _frequency; + + // Output to user/API + uhd::experts::data_writer_t<double> _gain_out; + // Outputs to CPLD programming expert + uhd::experts::data_writer_t<double> _dsa1; + uhd::experts::data_writer_t<double> _dsa2; + uhd::experts::data_writer_t<double> _dsa3a; + uhd::experts::data_writer_t<double> _dsa3b; + + uhd::usrp::pwr_cal_mgr::sptr _power_mgr; + uhd::usrp::cal::zbx_rx_dsa_cal::sptr _dsa_cal; + const size_t _chan; +}; + +/*!--------------------------------------------------------- + * zbx_tx_programming_expert (TX CPLD Programming Expert) + * + * This expert is responsible for programming the ZBX CPLD with parameters determined by + * user input or other experts This includes antenna setting, gain/dsa steps, lo source + * control, rf filter settings + * + * This expert should not trigger any other experts, these are all blind parameters + * + * One instance of this expert is required for each TX Channel (0,1); two total + * -------------------------------------------------------- + */ +class zbx_tx_programming_expert : public uhd::experts::worker_node_t +{ +public: + zbx_tx_programming_expert(const uhd::experts::node_retriever_t& db, + const uhd::fs_path tx_fe_path, + const uhd::fs_path rx_fe_path, /*needed for shared command time*/ + const size_t chan, + uhd::usrp::cal::zbx_tx_dsa_cal::sptr dsa_cal, + std::shared_ptr<zbx_cpld_ctrl> cpld) + : experts::worker_node_t(tx_fe_path / "zbx_tx_programming_expert") + , _antenna(db, tx_fe_path / "antenna" / "value") + , _atr_mode(db, tx_fe_path / "atr_mode") + , _profile(db, tx_fe_path / "gains" / "all" / "profile") + , _command_time(db, rx_fe_path / "time" / "cmd") + , _frequency(db, tx_fe_path / "freq" / "coerced") + , _dsa1(db, tx_fe_path / "gains" / ZBX_GAIN_STAGE_DSA1 / "value" / "coerced") + , _dsa2(db, tx_fe_path / "gains" / ZBX_GAIN_STAGE_DSA2 / "value" / "coerced") + , _amp_gain(db, tx_fe_path / "gains" / ZBX_GAIN_STAGE_AMP / "value" / "coerced") + , _rf_filter(db, tx_fe_path / "rf" / "filter") + , _if1_filter(db, tx_fe_path / "if1" / "filter") + , _if2_filter(db, tx_fe_path / "if2" / "filter") + , _is_highband(db, tx_fe_path / "is_highband") + , _lo1_source(db, tx_fe_path / "ch" / ZBX_LO1 / "source") + , _lo2_source(db, tx_fe_path / "ch" / ZBX_LO2 / "source") + , _dsa_cal(dsa_cal) + , _cpld(cpld) + , _chan(chan) + { + bind_accessor(_antenna); + bind_accessor(_atr_mode); + bind_accessor(_profile); + bind_accessor(_command_time); + bind_accessor(_frequency); + bind_accessor(_dsa1); + bind_accessor(_dsa2); + bind_accessor(_amp_gain); + bind_accessor(_rf_filter); + bind_accessor(_if1_filter); + bind_accessor(_if2_filter); + bind_accessor(_is_highband); + bind_accessor(_lo1_source); + bind_accessor(_lo2_source); + } + +private: + void resolve() override; + + // Inputs from user/API + uhd::experts::data_reader_t<std::string> _antenna; + uhd::experts::data_reader_t<zbx_cpld_ctrl::atr_mode> _atr_mode; + uhd::experts::data_reader_t<std::string> _profile; + + // Inputs from the Frequency FE expert + // Note: this is just for node dependencies, we want to be notified if just the tune + // frequency has been changed. + uhd::experts::data_reader_t<time_spec_t> _command_time; + uhd::experts::data_reader_t<double> _frequency; + + // Inputs from Gain TX expert + uhd::experts::data_reader_t<double> _dsa1; + uhd::experts::data_reader_t<double> _dsa2; + uhd::experts::data_reader_t<double> _amp_gain; + + // Inputs from Frequency FE expert + uhd::experts::data_reader_t<int> _rf_filter; + uhd::experts::data_reader_t<int> _if1_filter; + uhd::experts::data_reader_t<int> _if2_filter; + uhd::experts::data_reader_t<bool> _is_highband; + // Inputs from LO expert(s) + uhd::experts::data_reader_t<zbx_lo_source_t> _lo1_source; + uhd::experts::data_reader_t<zbx_lo_source_t> _lo2_source; + + uhd::usrp::cal::zbx_tx_dsa_cal::sptr _dsa_cal; + // Expects constructed cpld control objects + std::shared_ptr<zbx_cpld_ctrl> _cpld; + const size_t _chan; +}; + +/*!--------------------------------------------------------- + * zbx_rx_programming_expert (RX CPLD Programming Expert) + * + * This expert is responsible for programming the ZBX CPLD with parameters determined by + * user input or other experts. + * This includes antenna setting, gain/dsa steps, lo source control, rf filter settings + * + * This expert should not trigger any other experts, these are all blind parameters + * + * One instance of this expert is required for each RX Channel (0,1); two total + * -------------------------------------------------------- + */ +class zbx_rx_programming_expert : public uhd::experts::worker_node_t +{ +public: + zbx_rx_programming_expert(const uhd::experts::node_retriever_t& db, + const uhd::fs_path fe_path, + const size_t chan, + uhd::usrp::cal::zbx_rx_dsa_cal::sptr dsa_cal, + std::shared_ptr<zbx_cpld_ctrl> cpld) + : experts::worker_node_t(fe_path / "zbx_rx_programming_expert") + , _antenna(db, fe_path / "antenna" / "value") + , _atr_mode(db, fe_path / "atr_mode") + , _profile(db, fe_path / "gains" / "all" / "profile") + , _command_time(db, fe_path / "time" / "cmd") + , _frequency(db, fe_path / "freq" / "coerced") + , _dsa1(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA1 / "value" / "coerced") + , _dsa2(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA2 / "value" / "coerced") + , _dsa3a(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA3A / "value" / "coerced") + , _dsa3b(db, fe_path / "gains" / ZBX_GAIN_STAGE_DSA3B / "value" / "coerced") + , _rf_filter(db, fe_path / "rf" / "filter") + , _if1_filter(db, fe_path / "if1" / "filter") + , _if2_filter(db, fe_path / "if2" / "filter") + , _is_highband(db, fe_path / "is_highband") + , _lo1_source(db, fe_path / "ch" / ZBX_LO1 / "source") + , _lo2_source(db, fe_path / "ch" / ZBX_LO2 / "source") + , _dsa_cal(dsa_cal) + , _cpld(cpld) + , _chan(chan) + { + bind_accessor(_antenna); + bind_accessor(_atr_mode); + bind_accessor(_profile); + bind_accessor(_command_time); + bind_accessor(_frequency); + bind_accessor(_dsa1); + bind_accessor(_dsa2); + bind_accessor(_dsa3a); + bind_accessor(_dsa3b); + bind_accessor(_rf_filter); + bind_accessor(_if1_filter); + bind_accessor(_if2_filter); + bind_accessor(_is_highband); + bind_accessor(_lo1_source); + bind_accessor(_lo2_source); + } + +private: + void resolve() override; + void _update_leds(); + + // Inputs from user/API + uhd::experts::data_reader_t<std::string> _antenna; + uhd::experts::data_reader_t<zbx_cpld_ctrl::atr_mode> _atr_mode; + uhd::experts::data_reader_t<std::string> _profile; + + // Inputs from the Frequency FE expert + // Note: this is just for node dependencies, we want to be notified if just the tune + // frequency has been changed. + uhd::experts::data_reader_t<time_spec_t> _command_time; + uhd::experts::data_reader_t<double> _frequency; + + // Inputs from Gain expert + uhd::experts::data_reader_t<double> _dsa1; + uhd::experts::data_reader_t<double> _dsa2; + uhd::experts::data_reader_t<double> _dsa3a; + uhd::experts::data_reader_t<double> _dsa3b; + + // Inputs from Frequency FE expert + uhd::experts::data_reader_t<int> _rf_filter; + uhd::experts::data_reader_t<int> _if1_filter; + uhd::experts::data_reader_t<int> _if2_filter; + uhd::experts::data_reader_t<bool> _is_highband; + // Inputs from LO expert(s) + uhd::experts::data_reader_t<zbx_lo_source_t> _lo1_source; + uhd::experts::data_reader_t<zbx_lo_source_t> _lo2_source; + + uhd::usrp::cal::zbx_rx_dsa_cal::sptr _dsa_cal; + // Expects constructed cpld control objects + std::shared_ptr<zbx_cpld_ctrl> _cpld; + const size_t _chan; +}; + +/*!--------------------------------------------------------- + * zbx_band_inversion_expert + * + * This expert is responsible for handling the band inversion calls to MPM on the target + * device + * + * This expert should not trigger any others + * + * One instance of this expert is required for each Direction (TX/RX) and Channel (0,1); + * four total + * -------------------------------------------------------- + */ +class zbx_band_inversion_expert : public uhd::experts::worker_node_t +{ +public: + zbx_band_inversion_expert(const uhd::experts::node_retriever_t& db, + const uhd::fs_path fe_path, + const uhd::direction_t trx, + const size_t chan, + const int db_idx, + uhd::usrp::zbx_rpc_iface::sptr rpcc) + : uhd::experts::worker_node_t(fe_path / "zbx_band_inversion_expert") + , _is_band_inverted(db, fe_path / "band_inverted") + , _db_idx(db_idx) + , _rpcc(rpcc) + , _trx(trx) + , _chan(chan) + { + bind_accessor(_is_band_inverted); + } + +private: + void resolve() override; + + // Inputs from Frequency FE expert + uhd::experts::data_reader_t<bool> _is_band_inverted; + + const size_t _db_idx; + uhd::usrp::zbx_rpc_iface::sptr _rpcc; + const uhd::direction_t _trx; + const size_t _chan; +}; + +/*!--------------------------------------------------------- + * zbx_rfdc_freq_expert + * + * This expert is responsible for handling any rfdc frequency calls to MPM on the target + * device + * + * This expert should not trigger any experts + * + * One instance of this expert is required for each Direction (TX/RX) and Channel (0,1); + * four total + * -------------------------------------------------------- + */ +class zbx_rfdc_freq_expert : public uhd::experts::worker_node_t +{ +public: + zbx_rfdc_freq_expert(const uhd::experts::node_retriever_t& db, + const uhd::fs_path fe_path, + const uhd::direction_t trx, + const size_t chan, + const std::string rpc_prefix, + int db_idx, + uhd::usrp::x400_rpc_iface::sptr rpcc) + : uhd::experts::worker_node_t(fe_path / "zbx_rfdc_freq_expert") + , _rfdc_freq_desired( + db, fe_path / "los" / RFDC_NCO / "freq" / "value" / "desired") + , _rfdc_freq_coerced( + db, fe_path / "los" / RFDC_NCO / "freq" / "value" / "coerced") + , _if2_frequency_desired(db, fe_path / "if_freq" / "desired") + , _if2_frequency_coerced(db, fe_path / "if_freq" / "coerced") + , _rpc_prefix(rpc_prefix) + , _db_idx(db_idx) + , _rpcc(rpcc) + , _trx(trx) + , _chan(chan) + { + bind_accessor(_rfdc_freq_desired); + bind_accessor(_rfdc_freq_coerced); + bind_accessor(_if2_frequency_desired); + bind_accessor(_if2_frequency_coerced); + } + +private: + void resolve() override; + + // Inputs from user/API + uhd::experts::data_reader_t<double> _rfdc_freq_desired; + + // Outputs to user/API + uhd::experts::data_writer_t<double> _rfdc_freq_coerced; + + + // Inputs from Frequency FE expert + uhd::experts::data_reader_t<double> _if2_frequency_desired; + + // Outputs to Frequency BE expert + uhd::experts::data_writer_t<double> _if2_frequency_coerced; + + + const std::string _rpc_prefix; + const size_t _db_idx; + uhd::usrp::x400_rpc_iface::sptr _rpcc; + const uhd::direction_t _trx; + const size_t _chan; +}; + +using uhd::rfnoc::x400::rfdc_control; +/*!--------------------------------------------------------- + * zbx_sync_expert + * + * This expert is responsible for handling the phase alignment. + * Per channel, there are up to 4 things whose phase need syncing: The two + * LOs, the NCO, and the ADC/DAC gearboxes. However, the LOs share a sync + * register, and so do the NCOs. To minimize writes, we thus need a single sync + * expert at the end of the graph, who combines all LOs and all NCOs. + * -------------------------------------------------------- + */ +class zbx_sync_expert : public uhd::experts::worker_node_t +{ +public: + zbx_sync_expert(const uhd::experts::node_retriever_t& db, + const uhd::fs_path tx_fe_path, + const uhd::fs_path rx_fe_path, + rfdc_control::sptr rfdcc, + std::shared_ptr<zbx_cpld_ctrl> cpld) + : uhd::experts::worker_node_t("zbx_sync_expert") + , _fe_time{{db, rx_fe_path / 0 / "time/fe"}, {db, rx_fe_path / 1 / "time/fe"}} + , _lo_freqs{{zbx_lo_t::RX0_LO1, + {db, rx_fe_path / 0 / "los" / ZBX_LO1 / "freq" / "value" / "coerced"}}, + {zbx_lo_t::RX0_LO2, + {db, rx_fe_path / 0 / "los" / ZBX_LO2 / "freq" / "value" / "coerced"}}, + {zbx_lo_t::TX0_LO1, + {db, tx_fe_path / 0 / "los" / ZBX_LO1 / "freq" / "value" / "coerced"}}, + {zbx_lo_t::TX0_LO2, + {db, tx_fe_path / 0 / "los" / ZBX_LO2 / "freq" / "value" / "coerced"}}, + {zbx_lo_t::RX1_LO1, + {db, rx_fe_path / 1 / "los" / ZBX_LO1 / "freq" / "value" / "coerced"}}, + {zbx_lo_t::RX1_LO2, + {db, rx_fe_path / 1 / "los" / ZBX_LO2 / "freq" / "value" / "coerced"}}, + {zbx_lo_t::TX1_LO1, + {db, tx_fe_path / 1 / "los" / ZBX_LO1 / "freq" / "value" / "coerced"}}, + {zbx_lo_t::TX1_LO2, + {db, tx_fe_path / 1 / "los" / ZBX_LO2 / "freq" / "value" / "coerced"}}} + , _nco_freqs{{rfdc_control::rfdc_type::RX0, + {db, rx_fe_path / 0 / "if_freq" / "coerced"}}, + {rfdc_control::rfdc_type::RX1, + {db, rx_fe_path / 1 / "if_freq" / "coerced"}}, + {rfdc_control::rfdc_type::TX0, + {db, tx_fe_path / 0 / "if_freq" / "coerced"}}, + {rfdc_control::rfdc_type::TX1, + {db, tx_fe_path / 1 / "if_freq" / "coerced"}}} + , _rfdcc(rfdcc) + , _cpld(cpld) + { + for (auto& fe_time : _fe_time) { + bind_accessor(fe_time); + } + for (auto& lo_freq : _lo_freqs) { + bind_accessor(lo_freq.second); + } + for (auto& nco_freq : _nco_freqs) { + bind_accessor(nco_freq.second); + } + } + +private: + void resolve() override; + + // Inputs from user/API + // Command time: We have 2 channels, one time spec per channel + std::vector<uhd::experts::data_reader_t<time_spec_t>> _fe_time; + // We have 8 LOs: + std::map<zbx_lo_t, uhd::experts::data_reader_t<double>> _lo_freqs; + // We have 4 NCOs + std::map<rfdc_control::rfdc_type, uhd::experts::data_reader_t<double>> _nco_freqs; + + // This expert has no outputs. + + // Attributes + rfdc_control::sptr _rfdcc; + std::shared_ptr<zbx_cpld_ctrl> _cpld; + //! Store the sync state of the ADC gearboxes. If false, we assume they're + // out of sync. This could also be a vector of booleans if we want to be + // able to sync ADC gearboxes individually. + bool _adcs_synced = false; + //! Store the sync state of the DAC gearboxes. If false, we assume they're + // out of sync. This could also be a vector of booleans if we want to be + // able to sync DAC gearboxes individually. + bool _dacs_synced = false; +}; + +}}} // namespace uhd::usrp::zbx diff --git a/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_lo_ctrl.hpp b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_lo_ctrl.hpp new file mode 100644 index 000000000..add7013ef --- /dev/null +++ b/host/lib/include/uhdlib/usrp/dboard/zbx/zbx_lo_ctrl.hpp @@ -0,0 +1,86 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include "zbx_constants.hpp" +#include <uhd/types/direction.hpp> +#include <uhdlib/usrp/common/lmx2572.hpp> +#include <functional> + +namespace uhd { namespace usrp { namespace zbx { + +class zbx_lo_ctrl final +{ +public: + // Pass in our lo selection and poke/peek functions + zbx_lo_ctrl(zbx_lo_t lo, + lmx2572_iface::write_fn_t&& poke16, + lmx2572_iface::read_fn_t&& peek16, + lmx2572_iface::sleep_fn_t&& sleep, + const double default_frequency, + const double db_prc_rate, + const bool testing_mode_enabled); + + // Passes in a desired LO frequency to the LMX driver, returns the coerced frequency + double set_lo_freq(const double freq); + + // Returns cached LO frequency value + double get_lo_freq(); + + // Spins up a timeout loop to wait for the PLL's to lock + // \throws uhd::runtime_error on a failure to lock + void wait_for_lo_lock(); + + // Returns the lock status of the PLL + bool get_lock_status(); + + // Enable/disable LO port + // Targeted LO port depends on whether test mode is disabled/enabled + void set_lo_port_enabled(bool enable); + + // Returns status of LO port + // Targeted LO port depends on whether test mode is disabled/enabled + bool get_lo_port_enabled(); + + // Enable test mode of the LO + void set_lo_test_mode_enabled(bool enable); + + // Returns whether the test mode has been enabled + bool get_lo_test_mode_enabled(); + + static zbx_lo_t lo_string_to_enum( + const uhd::direction_t trx, const size_t channel, const std::string name); + + // TODO: Future implementation of spur dodging + // void set_spur_dodging(const bool enable); + // bool get_spur_dodging(); +private: + // Returns the appropriate output port for given LO + lmx2572_iface::output_t _get_output_port(bool test_port); + + // Specific LO that this class was constructed for + const zbx_lo_t _lo; + + const std::string _log_id; + + // LMX driver set up with this object specific LO + lmx2572_iface::sptr _lmx; + + // Cached overall LO output frequency. + // TODO: seperate between coerced/desired frequencies for recalculation once LO step + // quantization is introduced + double _freq; + + // Daughterboard PRC rate, used as the reference frequency + double _db_prc_rate; + + // Set LO output mode, RF output mode is considered normal use case + // Testing mode is for LMX V&V + bool _testing_mode_enabled; +}; + +}}} // namespace uhd::usrp::zbx diff --git a/host/lib/rc/CMakeLists.txt b/host/lib/rc/CMakeLists.txt index 52b6693c0..33dc8540b 100644 --- a/host/lib/rc/CMakeLists.txt +++ b/host/lib/rc/CMakeLists.txt @@ -8,6 +8,18 @@ include(CMakeRC) cmrc_add_resource_library(uhd-resources ALIAS uhd_rc NAMESPACE rc +# calibration data test file cal/test.cal +# ZBX revB/C TX calibration data + cal/x4xx_pwr_zbx_tx_0_tx+rx0.cal + cal/x4xx_pwr_zbx_tx_1_tx+rx0.cal +# ZBX revB/C RX calibration data + cal/x4xx_pwr_zbx_rx_0_tx+rx0.cal + cal/x4xx_pwr_zbx_rx_1_tx+rx0.cal + cal/x4xx_pwr_zbx_rx_0_rx1.cal + cal/x4xx_pwr_zbx_rx_1_rx1.cal +# ZBX DSA data + cal/zbx_dsa_tx.cal + cal/zbx_dsa_rx.cal ) set_property(TARGET uhd-resources PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/host/lib/rc/cal/x4xx_pwr_zbx_rx_0_rx1.cal b/host/lib/rc/cal/x4xx_pwr_zbx_rx_0_rx1.cal Binary files differnew file mode 100644 index 000000000..cc25f6ded --- /dev/null +++ b/host/lib/rc/cal/x4xx_pwr_zbx_rx_0_rx1.cal diff --git a/host/lib/rc/cal/x4xx_pwr_zbx_rx_0_tx+rx0.cal b/host/lib/rc/cal/x4xx_pwr_zbx_rx_0_tx+rx0.cal Binary files differnew file mode 100644 index 000000000..6da01d48b --- /dev/null +++ b/host/lib/rc/cal/x4xx_pwr_zbx_rx_0_tx+rx0.cal diff --git a/host/lib/rc/cal/x4xx_pwr_zbx_rx_1_rx1.cal b/host/lib/rc/cal/x4xx_pwr_zbx_rx_1_rx1.cal Binary files differnew file mode 100644 index 000000000..d56c740c2 --- /dev/null +++ b/host/lib/rc/cal/x4xx_pwr_zbx_rx_1_rx1.cal diff --git a/host/lib/rc/cal/x4xx_pwr_zbx_rx_1_tx+rx0.cal b/host/lib/rc/cal/x4xx_pwr_zbx_rx_1_tx+rx0.cal Binary files differnew file mode 100644 index 000000000..99f4eec4d --- /dev/null +++ b/host/lib/rc/cal/x4xx_pwr_zbx_rx_1_tx+rx0.cal diff --git a/host/lib/rc/cal/x4xx_pwr_zbx_tx_0_tx+rx0.cal b/host/lib/rc/cal/x4xx_pwr_zbx_tx_0_tx+rx0.cal Binary files differnew file mode 100644 index 000000000..349557bae --- /dev/null +++ b/host/lib/rc/cal/x4xx_pwr_zbx_tx_0_tx+rx0.cal diff --git a/host/lib/rc/cal/x4xx_pwr_zbx_tx_1_tx+rx0.cal b/host/lib/rc/cal/x4xx_pwr_zbx_tx_1_tx+rx0.cal Binary files differnew file mode 100644 index 000000000..4ecf1ba00 --- /dev/null +++ b/host/lib/rc/cal/x4xx_pwr_zbx_tx_1_tx+rx0.cal diff --git a/host/lib/rc/cal/zbx_dsa_rx.cal b/host/lib/rc/cal/zbx_dsa_rx.cal Binary files differnew file mode 100644 index 000000000..197a85b70 --- /dev/null +++ b/host/lib/rc/cal/zbx_dsa_rx.cal diff --git a/host/lib/rc/cal/zbx_dsa_rx.json b/host/lib/rc/cal/zbx_dsa_rx.json new file mode 100644 index 000000000..798a11987 --- /dev/null +++ b/host/lib/rc/cal/zbx_dsa_rx.json @@ -0,0 +1,1081 @@ +{ + "metadata": { + "name": "ZBX DSA Mapping (RX)", + "serial": "", + "version_major": 2 + }, + "band_dsa_map": [ + { + "name": "1M_to_200M", + "max_freq": 200000000, + "gains": [ + { "steps": [15, 15, 15, 15] }, + { "steps": [15, 15, 15, 14] }, + { "steps": [15, 15, 15, 13] }, + { "steps": [15, 15, 15, 12] }, + { "steps": [15, 15, 15, 11] }, + { "steps": [15, 15, 14, 11] }, + { "steps": [15, 15, 13, 11] }, + { "steps": [15, 14, 13, 11] }, + { "steps": [15, 13, 13, 11] }, + { "steps": [15, 12, 13, 11] }, + { "steps": [15, 11, 13, 11] }, + { "steps": [14, 11, 13, 11] }, + { "steps": [13, 11, 13, 11] }, + { "steps": [12, 11, 13, 11] }, + { "steps": [11, 11, 13, 11] }, + { "steps": [10, 11, 13, 11] }, + { "steps": [ 9, 11, 13, 11] }, + { "steps": [ 8, 11, 13, 11] }, + { "steps": [ 7, 11, 13, 11] }, + { "steps": [ 6, 11, 13, 11] }, + { "steps": [ 5, 11, 13, 11] }, + { "steps": [ 4, 11, 13, 11] }, + { "steps": [ 3, 11, 13, 11] }, + { "steps": [ 2, 11, 13, 11] }, + { "steps": [ 1, 11, 13, 11] }, + { "steps": [ 0, 11, 13, 11] }, + { "steps": [ 0, 10, 13, 11] }, + { "steps": [ 0, 9, 13, 11] }, + { "steps": [ 0, 8, 13, 11] }, + { "steps": [ 0, 7, 13, 11] }, + { "steps": [ 0, 6, 13, 11] }, + { "steps": [ 0, 5, 13, 11] }, + { "steps": [ 0, 4, 13, 11] }, + { "steps": [ 0, 3, 13, 11] }, + { "steps": [ 0, 2, 13, 11] }, + { "steps": [ 0, 1, 13, 11] }, + { "steps": [ 0, 0, 13, 11] }, + { "steps": [ 0, 0, 12, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] } + ], + }, + { + "name": "200M_to_400M", + "max_freq": 400000000, + "gains": [ + { "steps": [15, 15, 15, 15] }, + { "steps": [15, 15, 15, 14] }, + { "steps": [15, 15, 15, 13] }, + { "steps": [15, 15, 15, 12] }, + { "steps": [15, 15, 15, 11] }, + { "steps": [15, 15, 14, 11] }, + { "steps": [15, 15, 13, 11] }, + { "steps": [15, 14, 13, 11] }, + { "steps": [15, 13, 13, 11] }, + { "steps": [15, 12, 13, 11] }, + { "steps": [15, 11, 13, 11] }, + { "steps": [14, 11, 13, 11] }, + { "steps": [13, 11, 13, 11] }, + { "steps": [12, 11, 13, 11] }, + { "steps": [11, 11, 13, 11] }, + { "steps": [10, 11, 13, 11] }, + { "steps": [ 9, 11, 13, 11] }, + { "steps": [ 8, 11, 13, 11] }, + { "steps": [ 7, 11, 13, 11] }, + { "steps": [ 6, 11, 13, 11] }, + { "steps": [ 5, 11, 13, 11] }, + { "steps": [ 4, 11, 13, 11] }, + { "steps": [ 3, 11, 13, 11] }, + { "steps": [ 2, 11, 13, 11] }, + { "steps": [ 1, 11, 13, 11] }, + { "steps": [ 0, 11, 13, 11] }, + { "steps": [ 0, 10, 13, 11] }, + { "steps": [ 0, 9, 13, 11] }, + { "steps": [ 0, 8, 13, 11] }, + { "steps": [ 0, 7, 13, 11] }, + { "steps": [ 0, 6, 13, 11] }, + { "steps": [ 0, 5, 13, 11] }, + { "steps": [ 0, 4, 13, 11] }, + { "steps": [ 0, 3, 13, 11] }, + { "steps": [ 0, 2, 13, 11] }, + { "steps": [ 0, 1, 13, 11] }, + { "steps": [ 0, 0, 13, 11] }, + { "steps": [ 0, 0, 12, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] } + ], + }, + { + "name": "400M_to_500M", + "max_freq": 500000000, + "gains": [ + { "steps": [15, 15, 15, 15] }, + { "steps": [15, 15, 15, 14] }, + { "steps": [15, 15, 15, 13] }, + { "steps": [15, 15, 15, 12] }, + { "steps": [15, 15, 15, 11] }, + { "steps": [15, 15, 14, 11] }, + { "steps": [15, 15, 13, 11] }, + { "steps": [15, 14, 13, 11] }, + { "steps": [15, 13, 13, 11] }, + { "steps": [15, 12, 13, 11] }, + { "steps": [15, 11, 13, 11] }, + { "steps": [15, 10, 13, 11] }, + { "steps": [14, 10, 13, 11] }, + { "steps": [13, 10, 13, 11] }, + { "steps": [12, 10, 13, 11] }, + { "steps": [11, 10, 13, 11] }, + { "steps": [10, 10, 13, 11] }, + { "steps": [ 9, 10, 13, 11] }, + { "steps": [ 8, 10, 13, 11] }, + { "steps": [ 7, 10, 13, 11] }, + { "steps": [ 6, 10, 13, 11] }, + { "steps": [ 5, 10, 13, 11] }, + { "steps": [ 4, 10, 13, 11] }, + { "steps": [ 3, 10, 13, 11] }, + { "steps": [ 2, 10, 13, 11] }, + { "steps": [ 1, 10, 13, 11] }, + { "steps": [ 0, 10, 13, 11] }, + { "steps": [ 0, 9, 13, 11] }, + { "steps": [ 0, 8, 13, 11] }, + { "steps": [ 0, 7, 13, 11] }, + { "steps": [ 0, 6, 13, 11] }, + { "steps": [ 0, 5, 13, 11] }, + { "steps": [ 0, 4, 13, 11] }, + { "steps": [ 0, 3, 13, 11] }, + { "steps": [ 0, 2, 13, 11] }, + { "steps": [ 0, 1, 13, 11] }, + { "steps": [ 0, 0, 13, 11] }, + { "steps": [ 0, 0, 12, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 11, 11] } + ], + }, + { + "name": "500M_to_900M", + "max_freq": 900000000, + "gains": [ + { "steps": [15, 15, 15, 15] }, + { "steps": [15, 15, 15, 14] }, + { "steps": [15, 15, 15, 13] }, + { "steps": [15, 15, 15, 12] }, + { "steps": [15, 15, 15, 11] }, + { "steps": [15, 15, 15, 10] }, + { "steps": [15, 15, 15, 9] }, + { "steps": [15, 14, 15, 9] }, + { "steps": [15, 13, 15, 9] }, + { "steps": [15, 12, 15, 9] }, + { "steps": [15, 11, 15, 9] }, + { "steps": [15, 10, 15, 9] }, + { "steps": [14, 10, 15, 9] }, + { "steps": [13, 10, 15, 9] }, + { "steps": [12, 10, 15, 9] }, + { "steps": [11, 10, 15, 9] }, + { "steps": [10, 10, 15, 9] }, + { "steps": [ 9, 10, 15, 9] }, + { "steps": [ 8, 10, 15, 9] }, + { "steps": [ 7, 10, 15, 9] }, + { "steps": [ 6, 10, 15, 9] }, + { "steps": [ 5, 10, 15, 9] }, + { "steps": [ 4, 10, 15, 9] }, + { "steps": [ 3, 10, 15, 9] }, + { "steps": [ 2, 10, 15, 9] }, + { "steps": [ 1, 10, 15, 9] }, + { "steps": [ 0, 10, 15, 9] }, + { "steps": [ 0, 9, 14, 10] }, + { "steps": [ 0, 8, 14, 10] }, + { "steps": [ 0, 7, 14, 10] }, + { "steps": [ 0, 6, 14, 10] }, + { "steps": [ 0, 5, 14, 10] }, + { "steps": [ 0, 4, 14, 10] }, + { "steps": [ 0, 3, 14, 10] }, + { "steps": [ 0, 2, 14, 10] }, + { "steps": [ 0, 1, 14, 10] }, + { "steps": [ 0, 0, 14, 10] }, + { "steps": [ 0, 0, 13, 10] }, + { "steps": [ 0, 0, 12, 10] }, + { "steps": [ 0, 0, 11, 10] }, + { "steps": [ 0, 0, 10, 10] }, + { "steps": [ 0, 0, 9, 10] }, + { "steps": [ 0, 0, 8, 10] }, + { "steps": [ 0, 0, 7, 10] }, + { "steps": [ 0, 0, 6, 10] }, + { "steps": [ 0, 0, 5, 10] }, + { "steps": [ 0, 0, 4, 10] }, + { "steps": [ 0, 0, 3, 10] }, + { "steps": [ 0, 0, 2, 10] }, + { "steps": [ 0, 0, 1, 10] }, + { "steps": [ 0, 0, 0, 10] }, + { "steps": [ 0, 0, 0, 9] }, + { "steps": [ 0, 0, 0, 8] }, + { "steps": [ 0, 0, 0, 7] }, + { "steps": [ 0, 0, 0, 6] }, + { "steps": [ 0, 0, 0, 5] }, + { "steps": [ 0, 0, 0, 4] }, + { "steps": [ 0, 0, 0, 3] }, + { "steps": [ 0, 0, 0, 2] }, + { "steps": [ 0, 0, 0, 1] }, + { "steps": [ 0, 0, 0, 0] } + ], + }, + { + "name": "900M_to_1800M", + "max_freq": 1800000000, + "gains": [ + { "steps": [15, 15, 15, 15] }, + { "steps": [15, 15, 15, 14] }, + { "steps": [15, 15, 15, 13] }, + { "steps": [15, 15, 15, 12] }, + { "steps": [15, 15, 15, 11] }, + { "steps": [15, 15, 15, 10] }, + { "steps": [15, 14, 15, 10] }, + { "steps": [15, 13, 15, 10] }, + { "steps": [15, 12, 15, 10] }, + { "steps": [15, 11, 15, 10] }, + { "steps": [15, 10, 14, 11] }, + { "steps": [15, 9, 14, 11] }, + { "steps": [15, 8, 14, 11] }, + { "steps": [14, 8, 14, 11] }, + { "steps": [13, 8, 14, 11] }, + { "steps": [12, 8, 14, 11] }, + { "steps": [11, 8, 14, 11] }, + { "steps": [10, 8, 14, 11] }, + { "steps": [ 9, 8, 14, 11] }, + { "steps": [ 8, 8, 14, 11] }, + { "steps": [ 7, 8, 14, 11] }, + { "steps": [ 6, 8, 14, 11] }, + { "steps": [ 5, 8, 14, 11] }, + { "steps": [ 4, 8, 14, 11] }, + { "steps": [ 3, 8, 14, 11] }, + { "steps": [ 2, 8, 14, 11] }, + { "steps": [ 1, 8, 14, 11] }, + { "steps": [ 0, 8, 14, 11] }, + { "steps": [ 0, 7, 14, 11] }, + { "steps": [ 0, 6, 14, 11] }, + { "steps": [ 0, 5, 14, 11] }, + { "steps": [ 0, 4, 14, 11] }, + { "steps": [ 0, 3, 14, 11] }, + { "steps": [ 0, 2, 14, 11] }, + { "steps": [ 0, 1, 14, 11] }, + { "steps": [ 0, 0, 14, 11] }, + { "steps": [ 0, 0, 13, 11] }, + { "steps": [ 0, 0, 12, 11] }, + { "steps": [ 0, 0, 11, 11] }, + { "steps": [ 0, 0, 10, 11] }, + { "steps": [ 0, 0, 9, 11] }, + { "steps": [ 0, 0, 8, 11] }, + { "steps": [ 0, 0, 7, 11] }, + { "steps": [ 0, 0, 6, 11] }, + { "steps": [ 0, 0, 5, 11] }, + { "steps": [ 0, 0, 4, 11] }, + { "steps": [ 0, 0, 3, 11] }, + { "steps": [ 0, 0, 2, 11] }, + { "steps": [ 0, 0, 1, 11] }, + { "steps": [ 0, 0, 0, 11] }, + { "steps": [ 0, 0, 0, 10] }, + { "steps": [ 0, 0, 0, 9] }, + { "steps": [ 0, 0, 0, 8] }, + { "steps": [ 0, 0, 0, 7] }, + { "steps": [ 0, 0, 0, 6] }, + { "steps": [ 0, 0, 0, 5] }, + { "steps": [ 0, 0, 0, 4] }, + { "steps": [ 0, 0, 0, 3] }, + { "steps": [ 0, 0, 0, 2] }, + { "steps": [ 0, 0, 0, 1] }, + { "steps": [ 0, 0, 0, 0] } + ], + }, + { + "name": "1800M_to_2300M", + "max_freq": 2300000000, + "gains": [ + { "steps": [15, 15, 15, 15] }, + { "steps": [15, 15, 15, 14] }, + { "steps": [15, 15, 15, 13] }, + { "steps": [15, 15, 15, 12] }, + { "steps": [15, 15, 14, 12] }, + { "steps": [15, 14, 14, 12] }, + { "steps": [15, 12, 15, 12] }, + { "steps": [15, 11, 15, 12] }, + { "steps": [15, 10, 15, 12] }, + { "steps": [15, 9, 14, 13] }, + { "steps": [15, 8, 14, 13] }, + { "steps": [15, 7, 14, 13] }, + { "steps": [14, 7, 14, 13] }, + { "steps": [13, 7, 14, 13] }, + { "steps": [12, 7, 14, 13] }, + { "steps": [11, 7, 14, 13] }, + { "steps": [10, 7, 14, 13] }, + { "steps": [ 9, 7, 14, 13] }, + { "steps": [ 8, 7, 14, 13] }, + { "steps": [ 7, 7, 14, 13] }, + { "steps": [ 6, 7, 14, 13] }, + { "steps": [ 5, 7, 14, 13] }, + { "steps": [ 4, 7, 14, 13] }, + { "steps": [ 3, 7, 14, 13] }, + { "steps": [ 2, 7, 14, 13] }, + { "steps": [ 1, 7, 14, 13] }, + { "steps": [ 0, 7, 14, 13] }, + { "steps": [ 0, 6, 14, 13] }, + { "steps": [ 0, 5, 14, 13] }, + { "steps": [ 0, 4, 14, 13] }, + { "steps": [ 0, 3, 14, 13] }, + { "steps": [ 0, 2, 14, 13] }, + { "steps": [ 0, 1, 14, 13] }, + { "steps": [ 0, 0, 14, 13] }, + { "steps": [ 0, 0, 13, 13] }, + { "steps": [ 0, 0, 12, 13] }, + { "steps": [ 0, 0, 11, 13] }, + { "steps": [ 0, 0, 10, 13] }, + { "steps": [ 0, 0, 9, 13] }, + { "steps": [ 0, 0, 8, 13] }, + { "steps": [ 0, 0, 7, 13] }, + { "steps": [ 0, 0, 6, 13] }, + { "steps": [ 0, 0, 5, 13] }, + { "steps": [ 0, 0, 4, 13] }, + { "steps": [ 0, 0, 3, 13] }, + { "steps": [ 0, 0, 2, 13] }, + { "steps": [ 0, 0, 1, 13] }, + { "steps": [ 0, 0, 0, 13] }, + { "steps": [ 0, 0, 0, 12] }, + { "steps": [ 0, 0, 0, 11] }, + { "steps": [ 0, 0, 0, 10] }, + { "steps": [ 0, 0, 0, 9] }, + { "steps": [ 0, 0, 0, 8] }, + { "steps": [ 0, 0, 0, 7] }, + { "steps": [ 0, 0, 0, 6] }, + { "steps": [ 0, 0, 0, 5] }, + { "steps": [ 0, 0, 0, 4] }, + { "steps": [ 0, 0, 0, 3] }, + { "steps": [ 0, 0, 0, 2] }, + { "steps": [ 0, 0, 0, 1] }, + { "steps": [ 0, 0, 0, 0] } + ], + }, + { + "name": "2300M_to_2700M", + "max_freq": 2700000000, + "gains": [ + { "steps": [15, 15, 15, 15] }, + { "steps": [15, 15, 15, 14] }, + { "steps": [15, 15, 15, 13] }, + { "steps": [15, 15, 15, 12] }, + { "steps": [15, 15, 14, 12] }, + { "steps": [15, 14, 14, 12] }, + { "steps": [15, 13, 14, 12] }, + { "steps": [15, 11, 15, 12] }, + { "steps": [15, 10, 15, 12] }, + { "steps": [15, 9, 15, 12] }, + { "steps": [15, 8, 15, 12] }, + { "steps": [15, 7, 14, 13] }, + { "steps": [15, 6, 14, 13] }, + { "steps": [14, 6, 14, 13] }, + { "steps": [13, 6, 14, 13] }, + { "steps": [12, 6, 14, 13] }, + { "steps": [11, 6, 14, 13] }, + { "steps": [10, 6, 14, 13] }, + { "steps": [ 9, 6, 14, 13] }, + { "steps": [ 8, 6, 14, 13] }, + { "steps": [ 7, 6, 14, 13] }, + { "steps": [ 6, 6, 14, 13] }, + { "steps": [ 5, 6, 14, 13] }, + { "steps": [ 4, 6, 14, 13] }, + { "steps": [ 3, 6, 14, 13] }, + { "steps": [ 2, 6, 14, 13] }, + { "steps": [ 1, 6, 14, 13] }, + { "steps": [ 0, 6, 14, 13] }, + { "steps": [ 0, 5, 14, 13] }, + { "steps": [ 0, 4, 14, 13] }, + { "steps": [ 0, 3, 14, 13] }, + { "steps": [ 0, 2, 14, 13] }, + { "steps": [ 0, 1, 14, 13] }, + { "steps": [ 0, 0, 14, 13] }, + { "steps": [ 0, 0, 13, 13] }, + { "steps": [ 0, 0, 12, 13] }, + { "steps": [ 0, 0, 11, 13] }, + { "steps": [ 0, 0, 10, 13] }, + { "steps": [ 0, 0, 9, 13] }, + { "steps": [ 0, 0, 8, 13] }, + { "steps": [ 0, 0, 7, 13] }, + { "steps": [ 0, 0, 6, 13] }, + { "steps": [ 0, 0, 5, 13] }, + { "steps": [ 0, 0, 4, 13] }, + { "steps": [ 0, 0, 3, 13] }, + { "steps": [ 0, 0, 2, 13] }, + { "steps": [ 0, 0, 1, 13] }, + { "steps": [ 0, 0, 0, 13] }, + { "steps": [ 0, 0, 0, 12] }, + { "steps": [ 0, 0, 0, 11] }, + { "steps": [ 0, 0, 0, 10] }, + { "steps": [ 0, 0, 0, 9] }, + { "steps": [ 0, 0, 0, 8] }, + { "steps": [ 0, 0, 0, 7] }, + { "steps": [ 0, 0, 0, 6] }, + { "steps": [ 0, 0, 0, 5] }, + { "steps": [ 0, 0, 0, 4] }, + { "steps": [ 0, 0, 0, 3] }, + { "steps": [ 0, 0, 0, 2] }, + { "steps": [ 0, 0, 0, 1] }, + { "steps": [ 0, 0, 0, 0] } + ], + }, + { + "name": "2700M_to_3000M", + "max_freq": 3000000000, + "gains": [ + { "steps": [15, 15, 15, 15] }, + { "steps": [15, 15, 15, 14] }, + { "steps": [15, 15, 15, 13] }, + { "steps": [15, 15, 15, 12] }, + { "steps": [15, 15, 15, 11] }, + { "steps": [15, 15, 15, 10] }, + { "steps": [15, 15, 15, 9] }, + { "steps": [15, 15, 15, 8] }, + { "steps": [15, 15, 14, 8] }, + { "steps": [15, 15, 12, 9] }, + { "steps": [15, 14, 12, 9] }, + { "steps": [15, 12, 13, 9] }, + { "steps": [15, 11, 13, 9] }, + { "steps": [15, 10, 13, 9] }, + { "steps": [15, 9, 13, 9] }, + { "steps": [15, 8, 12, 10] }, + { "steps": [15, 7, 12, 10] }, + { "steps": [15, 6, 12, 10] }, + { "steps": [15, 5, 12, 10] }, + { "steps": [15, 4, 12, 10] }, + { "steps": [14, 4, 12, 10] }, + { "steps": [13, 4, 12, 10] }, + { "steps": [12, 4, 12, 10] }, + { "steps": [11, 4, 12, 10] }, + { "steps": [10, 4, 12, 10] }, + { "steps": [ 9, 4, 12, 10] }, + { "steps": [ 8, 4, 12, 10] }, + { "steps": [ 7, 4, 12, 10] }, + { "steps": [ 6, 4, 12, 10] }, + { "steps": [ 5, 4, 12, 10] }, + { "steps": [ 4, 4, 12, 10] }, + { "steps": [ 3, 4, 12, 10] }, + { "steps": [ 2, 4, 12, 10] }, + { "steps": [ 1, 4, 12, 10] }, + { "steps": [ 0, 4, 12, 10] }, + { "steps": [ 0, 3, 12, 10] }, + { "steps": [ 0, 2, 12, 10] }, + { "steps": [ 0, 1, 12, 10] }, + { "steps": [ 0, 0, 12, 10] }, + { "steps": [ 0, 0, 11, 10] }, + { "steps": [ 0, 0, 10, 10] }, + { "steps": [ 0, 0, 9, 10] }, + { "steps": [ 0, 0, 8, 10] }, + { "steps": [ 0, 0, 7, 10] }, + { "steps": [ 0, 0, 6, 10] }, + { "steps": [ 0, 0, 5, 10] }, + { "steps": [ 0, 0, 4, 10] }, + { "steps": [ 0, 0, 3, 10] }, + { "steps": [ 0, 0, 2, 10] }, + { "steps": [ 0, 0, 1, 10] }, + { "steps": [ 0, 0, 0, 10] }, + { "steps": [ 0, 0, 0, 9] }, + { "steps": [ 0, 0, 0, 8] }, + { "steps": [ 0, 0, 0, 7] }, + { "steps": [ 0, 0, 0, 6] }, + { "steps": [ 0, 0, 0, 5] }, + { "steps": [ 0, 0, 0, 4] }, + { "steps": [ 0, 0, 0, 3] }, + { "steps": [ 0, 0, 0, 2] }, + { "steps": [ 0, 0, 0, 1] }, + { "steps": [ 0, 0, 0, 0] } + ], + }, + { + "name": "3000M_to_4200M", + "max_freq": 4200000000, + "gains": [ + { "steps": [15, 15, 15, 15] }, + { "steps": [15, 15, 15, 14] }, + { "steps": [15, 15, 15, 13] }, + { "steps": [15, 15, 15, 12] }, + { "steps": [15, 15, 15, 11] }, + { "steps": [15, 15, 15, 10] }, + { "steps": [15, 15, 15, 9] }, + { "steps": [15, 15, 15, 8] }, + { "steps": [15, 15, 14, 8] }, + { "steps": [15, 15, 12, 9] }, + { "steps": [15, 15, 11, 9] }, + { "steps": [15, 15, 10, 9] }, + { "steps": [15, 15, 9, 9] }, + { "steps": [15, 15, 8, 9] }, + { "steps": [15, 15, 7, 9] }, + { "steps": [15, 15, 6, 9] }, + { "steps": [15, 15, 5, 9] }, + { "steps": [15, 15, 4, 9] }, + { "steps": [14, 15, 4, 9] }, + { "steps": [13, 15, 4, 9] }, + { "steps": [12, 15, 4, 9] }, + { "steps": [11, 15, 4, 9] }, + { "steps": [10, 15, 4, 9] }, + { "steps": [ 9, 15, 4, 9] }, + { "steps": [ 8, 15, 4, 9] }, + { "steps": [ 7, 15, 4, 9] }, + { "steps": [ 6, 15, 4, 9] }, + { "steps": [ 5, 15, 4, 9] }, + { "steps": [ 4, 15, 4, 9] }, + { "steps": [ 3, 15, 4, 9] }, + { "steps": [ 2, 15, 4, 9] }, + { "steps": [ 1, 15, 4, 9] }, + { "steps": [ 0, 15, 4, 9] }, + { "steps": [ 0, 15, 3, 9] }, + { "steps": [ 0, 15, 2, 9] }, + { "steps": [ 0, 15, 1, 9] }, + { "steps": [ 0, 15, 0, 9] }, + { "steps": [ 0, 14, 0, 9] }, + { "steps": [ 0, 13, 0, 9] }, + { "steps": [ 0, 12, 0, 9] }, + { "steps": [ 0, 11, 0, 9] }, + { "steps": [ 0, 10, 0, 9] }, + { "steps": [ 0, 9, 0, 9] }, + { "steps": [ 0, 7, 0, 10] }, + { "steps": [ 0, 6, 0, 10] }, + { "steps": [ 0, 5, 0, 10] }, + { "steps": [ 0, 4, 0, 10] }, + { "steps": [ 0, 3, 0, 10] }, + { "steps": [ 0, 2, 0, 10] }, + { "steps": [ 0, 1, 0, 10] }, + { "steps": [ 0, 0, 0, 10] }, + { "steps": [ 0, 0, 0, 9] }, + { "steps": [ 0, 0, 0, 8] }, + { "steps": [ 0, 0, 0, 7] }, + { "steps": [ 0, 0, 0, 6] }, + { "steps": [ 0, 0, 0, 5] }, + { "steps": [ 0, 0, 0, 4] }, + { "steps": [ 0, 0, 0, 3] }, + { "steps": [ 0, 0, 0, 2] }, + { "steps": [ 0, 0, 0, 1] }, + { "steps": [ 0, 0, 0, 0] } + ], + }, + { + "name": "4200M_to_4500M", + "max_freq": 4500000000, + "gains": [ + { "steps": [15, 15, 15, 15] }, + { "steps": [15, 15, 15, 14] }, + { "steps": [15, 15, 15, 13] }, + { "steps": [15, 15, 15, 12] }, + { "steps": [15, 15, 15, 11] }, + { "steps": [15, 15, 14, 11] }, + { "steps": [15, 15, 13, 11] }, + { "steps": [15, 15, 12, 11] }, + { "steps": [15, 15, 11, 11] }, + { "steps": [15, 15, 9, 12] }, + { "steps": [15, 15, 8, 12] }, + { "steps": [15, 15, 7, 12] }, + { "steps": [15, 15, 6, 12] }, + { "steps": [15, 15, 5, 12] }, + { "steps": [15, 15, 4, 12] }, + { "steps": [14, 15, 4, 12] }, + { "steps": [13, 15, 4, 12] }, + { "steps": [12, 15, 4, 12] }, + { "steps": [11, 15, 4, 12] }, + { "steps": [10, 15, 4, 12] }, + { "steps": [ 9, 15, 4, 12] }, + { "steps": [ 8, 15, 4, 12] }, + { "steps": [ 7, 15, 4, 12] }, + { "steps": [ 6, 15, 4, 12] }, + { "steps": [ 5, 15, 4, 12] }, + { "steps": [ 4, 15, 4, 12] }, + { "steps": [ 3, 15, 4, 12] }, + { "steps": [ 2, 15, 4, 12] }, + { "steps": [ 1, 15, 4, 12] }, + { "steps": [ 0, 15, 4, 12] }, + { "steps": [ 0, 15, 3, 12] }, + { "steps": [ 0, 15, 2, 12] }, + { "steps": [ 0, 15, 1, 12] }, + { "steps": [ 0, 15, 0, 12] }, + { "steps": [ 0, 14, 0, 12] }, + { "steps": [ 0, 13, 0, 12] }, + { "steps": [ 0, 12, 0, 12] }, + { "steps": [ 0, 11, 0, 12] }, + { "steps": [ 0, 10, 0, 12] }, + { "steps": [ 0, 9, 0, 12] }, + { "steps": [ 0, 8, 0, 12] }, + { "steps": [ 0, 7, 0, 12] }, + { "steps": [ 0, 6, 0, 12] }, + { "steps": [ 0, 5, 0, 12] }, + { "steps": [ 0, 4, 0, 12] }, + { "steps": [ 0, 3, 0, 12] }, + { "steps": [ 0, 2, 0, 12] }, + { "steps": [ 0, 1, 0, 12] }, + { "steps": [ 0, 0, 0, 12] }, + { "steps": [ 0, 0, 0, 11] }, + { "steps": [ 0, 0, 0, 10] }, + { "steps": [ 0, 0, 0, 9] }, + { "steps": [ 0, 0, 0, 8] }, + { "steps": [ 0, 0, 0, 7] }, + { "steps": [ 0, 0, 0, 6] }, + { "steps": [ 0, 0, 0, 5] }, + { "steps": [ 0, 0, 0, 4] }, + { "steps": [ 0, 0, 0, 3] }, + { "steps": [ 0, 0, 0, 2] }, + { "steps": [ 0, 0, 0, 1] }, + { "steps": [ 0, 0, 0, 0] } + ], + }, + { + "name": "4500M_to_4700M", + "max_freq": 4700000000, + "gains": [ + { "steps": [15, 15, 15, 15] }, + { "steps": [15, 15, 15, 14] }, + { "steps": [15, 15, 14, 14] }, + { "steps": [15, 15, 12, 15] }, + { "steps": [15, 15, 11, 15] }, + { "steps": [15, 15, 10, 15] }, + { "steps": [15, 15, 9, 15] }, + { "steps": [15, 15, 8, 15] }, + { "steps": [15, 15, 7, 15] }, + { "steps": [15, 15, 6, 15] }, + { "steps": [15, 15, 5, 15] }, + { "steps": [15, 15, 4, 15] }, + { "steps": [14, 15, 4, 15] }, + { "steps": [13, 15, 4, 15] }, + { "steps": [12, 15, 4, 15] }, + { "steps": [11, 15, 4, 15] }, + { "steps": [10, 15, 4, 15] }, + { "steps": [ 9, 15, 4, 15] }, + { "steps": [ 8, 15, 4, 15] }, + { "steps": [ 7, 15, 4, 15] }, + { "steps": [ 6, 15, 4, 15] }, + { "steps": [ 5, 15, 4, 15] }, + { "steps": [ 4, 15, 4, 15] }, + { "steps": [ 3, 15, 4, 15] }, + { "steps": [ 2, 15, 4, 15] }, + { "steps": [ 1, 15, 4, 15] }, + { "steps": [ 0, 15, 4, 15] }, + { "steps": [ 0, 15, 3, 15] }, + { "steps": [ 0, 15, 2, 15] }, + { "steps": [ 0, 15, 1, 15] }, + { "steps": [ 0, 15, 0, 15] }, + { "steps": [ 0, 14, 0, 15] }, + { "steps": [ 0, 13, 0, 15] }, + { "steps": [ 0, 12, 0, 15] }, + { "steps": [ 0, 11, 0, 15] }, + { "steps": [ 0, 10, 0, 15] }, + { "steps": [ 0, 9, 0, 15] }, + { "steps": [ 0, 8, 0, 15] }, + { "steps": [ 0, 7, 0, 15] }, + { "steps": [ 0, 6, 0, 15] }, + { "steps": [ 0, 5, 0, 15] }, + { "steps": [ 0, 4, 0, 15] }, + { "steps": [ 0, 3, 0, 15] }, + { "steps": [ 0, 2, 0, 15] }, + { "steps": [ 0, 1, 0, 15] }, + { "steps": [ 0, 0, 0, 15] }, + { "steps": [ 0, 0, 0, 14] }, + { "steps": [ 0, 0, 0, 13] }, + { "steps": [ 0, 0, 0, 12] }, + { "steps": [ 0, 0, 0, 11] }, + { "steps": [ 0, 0, 0, 10] }, + { "steps": [ 0, 0, 0, 9] }, + { "steps": [ 0, 0, 0, 8] }, + { "steps": [ 0, 0, 0, 7] }, + { "steps": [ 0, 0, 0, 6] }, + { "steps": [ 0, 0, 0, 5] }, + { "steps": [ 0, 0, 0, 4] }, + { "steps": [ 0, 0, 0, 3] }, + { "steps": [ 0, 0, 0, 2] }, + { "steps": [ 0, 0, 0, 1] }, + { "steps": [ 0, 0, 0, 0] } + ], + }, + { + "name": "4700M_to_5300M", + "max_freq": 5300000000, + "gains": [ + { "steps": [15, 15, 15, 15] }, + { "steps": [15, 15, 15, 14] }, + { "steps": [15, 15, 15, 13] }, + { "steps": [15, 15, 15, 12] }, + { "steps": [15, 15, 13, 13] }, + { "steps": [15, 15, 12, 13] }, + { "steps": [15, 15, 11, 13] }, + { "steps": [15, 15, 10, 13] }, + { "steps": [15, 15, 9, 13] }, + { "steps": [15, 15, 7, 14] }, + { "steps": [15, 15, 6, 14] }, + { "steps": [15, 15, 5, 14] }, + { "steps": [15, 15, 4, 14] }, + { "steps": [15, 15, 3, 14] }, + { "steps": [14, 15, 3, 14] }, + { "steps": [13, 15, 3, 14] }, + { "steps": [12, 15, 3, 14] }, + { "steps": [11, 15, 3, 14] }, + { "steps": [10, 15, 3, 14] }, + { "steps": [ 9, 15, 3, 14] }, + { "steps": [ 8, 15, 3, 14] }, + { "steps": [ 7, 15, 3, 14] }, + { "steps": [ 6, 15, 3, 14] }, + { "steps": [ 5, 15, 3, 14] }, + { "steps": [ 4, 15, 3, 14] }, + { "steps": [ 3, 15, 3, 14] }, + { "steps": [ 2, 15, 3, 14] }, + { "steps": [ 1, 15, 3, 14] }, + { "steps": [ 0, 15, 3, 14] }, + { "steps": [ 0, 15, 2, 14] }, + { "steps": [ 0, 15, 1, 14] }, + { "steps": [ 0, 15, 0, 14] }, + { "steps": [ 0, 14, 0, 14] }, + { "steps": [ 0, 13, 0, 14] }, + { "steps": [ 0, 12, 0, 14] }, + { "steps": [ 0, 11, 0, 14] }, + { "steps": [ 0, 10, 0, 14] }, + { "steps": [ 0, 9, 0, 14] }, + { "steps": [ 0, 8, 0, 14] }, + { "steps": [ 0, 7, 0, 14] }, + { "steps": [ 0, 6, 0, 14] }, + { "steps": [ 0, 5, 0, 14] }, + { "steps": [ 0, 4, 0, 14] }, + { "steps": [ 0, 3, 0, 14] }, + { "steps": [ 0, 2, 0, 14] }, + { "steps": [ 0, 1, 0, 14] }, + { "steps": [ 0, 0, 0, 14] }, + { "steps": [ 0, 0, 0, 13] }, + { "steps": [ 0, 0, 0, 12] }, + { "steps": [ 0, 0, 0, 11] }, + { "steps": [ 0, 0, 0, 10] }, + { "steps": [ 0, 0, 0, 9] }, + { "steps": [ 0, 0, 0, 8] }, + { "steps": [ 0, 0, 0, 7] }, + { "steps": [ 0, 0, 0, 6] }, + { "steps": [ 0, 0, 0, 5] }, + { "steps": [ 0, 0, 0, 4] }, + { "steps": [ 0, 0, 0, 3] }, + { "steps": [ 0, 0, 0, 2] }, + { "steps": [ 0, 0, 0, 1] }, + { "steps": [ 0, 0, 0, 0] } + ], + }, + { + "name": "5300M_to_5600M", + "max_freq": 5600000000, + "gains": [ + { "steps": [15, 15, 15, 15] }, + { "steps": [15, 15, 15, 14] }, + { "steps": [15, 15, 15, 13] }, + { "steps": [15, 15, 14, 13] }, + { "steps": [15, 15, 12, 14] }, + { "steps": [15, 15, 11, 14] }, + { "steps": [15, 15, 10, 14] }, + { "steps": [15, 15, 9, 14] }, + { "steps": [15, 15, 8, 14] }, + { "steps": [15, 15, 7, 14] }, + { "steps": [15, 15, 6, 14] }, + { "steps": [15, 15, 4, 15] }, + { "steps": [15, 15, 3, 15] }, + { "steps": [14, 15, 3, 15] }, + { "steps": [13, 15, 3, 15] }, + { "steps": [12, 15, 3, 15] }, + { "steps": [11, 15, 3, 15] }, + { "steps": [10, 15, 3, 15] }, + { "steps": [ 9, 15, 3, 15] }, + { "steps": [ 8, 15, 3, 15] }, + { "steps": [ 7, 15, 3, 15] }, + { "steps": [ 6, 15, 3, 15] }, + { "steps": [ 5, 15, 3, 15] }, + { "steps": [ 4, 15, 3, 15] }, + { "steps": [ 3, 15, 3, 15] }, + { "steps": [ 2, 15, 3, 15] }, + { "steps": [ 1, 15, 3, 15] }, + { "steps": [ 0, 15, 3, 15] }, + { "steps": [ 0, 15, 2, 15] }, + { "steps": [ 0, 15, 1, 15] }, + { "steps": [ 0, 15, 0, 15] }, + { "steps": [ 0, 14, 0, 15] }, + { "steps": [ 0, 13, 0, 15] }, + { "steps": [ 0, 12, 0, 15] }, + { "steps": [ 0, 11, 0, 15] }, + { "steps": [ 0, 10, 0, 15] }, + { "steps": [ 0, 9, 0, 15] }, + { "steps": [ 0, 8, 0, 15] }, + { "steps": [ 0, 7, 0, 15] }, + { "steps": [ 0, 6, 0, 15] }, + { "steps": [ 0, 5, 0, 15] }, + { "steps": [ 0, 4, 0, 15] }, + { "steps": [ 0, 3, 0, 15] }, + { "steps": [ 0, 2, 0, 15] }, + { "steps": [ 0, 1, 0, 15] }, + { "steps": [ 0, 0, 0, 15] }, + { "steps": [ 0, 0, 0, 14] }, + { "steps": [ 0, 0, 0, 13] }, + { "steps": [ 0, 0, 0, 12] }, + { "steps": [ 0, 0, 0, 11] }, + { "steps": [ 0, 0, 0, 10] }, + { "steps": [ 0, 0, 0, 9] }, + { "steps": [ 0, 0, 0, 8] }, + { "steps": [ 0, 0, 0, 7] }, + { "steps": [ 0, 0, 0, 6] }, + { "steps": [ 0, 0, 0, 5] }, + { "steps": [ 0, 0, 0, 4] }, + { "steps": [ 0, 0, 0, 3] }, + { "steps": [ 0, 0, 0, 2] }, + { "steps": [ 0, 0, 0, 1] }, + { "steps": [ 0, 0, 0, 0] } + ], + }, + { + "name": "5600M_to_6800M", + "max_freq": 6800000000, + "gains": [ + { "steps": [15, 15, 15, 15] }, + { "steps": [15, 15, 15, 14] }, + { "steps": [15, 15, 15, 13] }, + { "steps": [15, 15, 15, 12] }, + { "steps": [15, 15, 13, 13] }, + { "steps": [15, 15, 12, 13] }, + { "steps": [15, 15, 11, 13] }, + { "steps": [15, 15, 10, 13] }, + { "steps": [15, 15, 9, 13] }, + { "steps": [15, 15, 7, 14] }, + { "steps": [15, 15, 6, 14] }, + { "steps": [15, 15, 5, 14] }, + { "steps": [15, 15, 4, 14] }, + { "steps": [15, 15, 3, 14] }, + { "steps": [14, 15, 3, 14] }, + { "steps": [13, 15, 3, 14] }, + { "steps": [12, 15, 3, 14] }, + { "steps": [11, 15, 3, 14] }, + { "steps": [10, 15, 3, 14] }, + { "steps": [ 9, 15, 3, 14] }, + { "steps": [ 8, 15, 3, 14] }, + { "steps": [ 7, 15, 3, 14] }, + { "steps": [ 6, 15, 3, 14] }, + { "steps": [ 5, 15, 3, 14] }, + { "steps": [ 4, 15, 3, 14] }, + { "steps": [ 3, 15, 3, 14] }, + { "steps": [ 2, 15, 3, 14] }, + { "steps": [ 1, 15, 3, 14] }, + { "steps": [ 0, 15, 3, 14] }, + { "steps": [ 0, 15, 2, 14] }, + { "steps": [ 0, 15, 1, 14] }, + { "steps": [ 0, 15, 0, 14] }, + { "steps": [ 0, 14, 0, 14] }, + { "steps": [ 0, 13, 0, 14] }, + { "steps": [ 0, 12, 0, 14] }, + { "steps": [ 0, 11, 0, 14] }, + { "steps": [ 0, 10, 0, 14] }, + { "steps": [ 0, 9, 0, 14] }, + { "steps": [ 0, 8, 0, 14] }, + { "steps": [ 0, 7, 0, 14] }, + { "steps": [ 0, 6, 0, 14] }, + { "steps": [ 0, 5, 0, 14] }, + { "steps": [ 0, 4, 0, 14] }, + { "steps": [ 0, 3, 0, 14] }, + { "steps": [ 0, 2, 0, 14] }, + { "steps": [ 0, 1, 0, 14] }, + { "steps": [ 0, 0, 0, 14] }, + { "steps": [ 0, 0, 0, 13] }, + { "steps": [ 0, 0, 0, 12] }, + { "steps": [ 0, 0, 0, 11] }, + { "steps": [ 0, 0, 0, 10] }, + { "steps": [ 0, 0, 0, 9] }, + { "steps": [ 0, 0, 0, 8] }, + { "steps": [ 0, 0, 0, 7] }, + { "steps": [ 0, 0, 0, 6] }, + { "steps": [ 0, 0, 0, 5] }, + { "steps": [ 0, 0, 0, 4] }, + { "steps": [ 0, 0, 0, 3] }, + { "steps": [ 0, 0, 0, 2] }, + { "steps": [ 0, 0, 0, 1] }, + { "steps": [ 0, 0, 0, 0] } + ], + }, + { + "name": "6800M_to_7400M", + "max_freq": 7400000000, + "gains": [ + { "steps": [15, 15, 15, 15] }, + { "steps": [15, 15, 15, 14] }, + { "steps": [15, 15, 15, 13] }, + { "steps": [15, 15, 15, 12] }, + { "steps": [15, 15, 15, 11] }, + { "steps": [15, 15, 15, 10] }, + { "steps": [15, 15, 14, 10] }, + { "steps": [15, 15, 13, 10] }, + { "steps": [15, 15, 12, 10] }, + { "steps": [15, 15, 11, 10] }, + { "steps": [15, 15, 9, 11] }, + { "steps": [15, 15, 8, 11] }, + { "steps": [15, 15, 7, 11] }, + { "steps": [15, 15, 6, 11] }, + { "steps": [15, 15, 5, 11] }, + { "steps": [15, 15, 3, 12] }, + { "steps": [15, 15, 2, 12] }, + { "steps": [15, 15, 1, 12] }, + { "steps": [14, 15, 1, 12] }, + { "steps": [13, 15, 1, 12] }, + { "steps": [12, 15, 1, 12] }, + { "steps": [11, 15, 1, 12] }, + { "steps": [10, 15, 1, 12] }, + { "steps": [ 9, 15, 1, 12] }, + { "steps": [ 8, 15, 1, 12] }, + { "steps": [ 7, 15, 1, 12] }, + { "steps": [ 6, 15, 1, 12] }, + { "steps": [ 5, 15, 1, 12] }, + { "steps": [ 4, 15, 1, 12] }, + { "steps": [ 3, 15, 1, 12] }, + { "steps": [ 2, 15, 1, 12] }, + { "steps": [ 1, 15, 1, 12] }, + { "steps": [ 0, 15, 1, 12] }, + { "steps": [ 0, 15, 0, 12] }, + { "steps": [ 0, 14, 0, 12] }, + { "steps": [ 0, 13, 0, 12] }, + { "steps": [ 0, 12, 0, 12] }, + { "steps": [ 0, 11, 0, 12] }, + { "steps": [ 0, 10, 0, 12] }, + { "steps": [ 0, 9, 0, 12] }, + { "steps": [ 0, 8, 0, 12] }, + { "steps": [ 0, 7, 0, 12] }, + { "steps": [ 0, 6, 0, 12] }, + { "steps": [ 0, 5, 0, 12] }, + { "steps": [ 0, 4, 0, 12] }, + { "steps": [ 0, 3, 0, 12] }, + { "steps": [ 0, 2, 0, 12] }, + { "steps": [ 0, 1, 0, 12] }, + { "steps": [ 0, 0, 0, 12] }, + { "steps": [ 0, 0, 0, 11] }, + { "steps": [ 0, 0, 0, 10] }, + { "steps": [ 0, 0, 0, 9] }, + { "steps": [ 0, 0, 0, 8] }, + { "steps": [ 0, 0, 0, 7] }, + { "steps": [ 0, 0, 0, 6] }, + { "steps": [ 0, 0, 0, 5] }, + { "steps": [ 0, 0, 0, 4] }, + { "steps": [ 0, 0, 0, 3] }, + { "steps": [ 0, 0, 0, 2] }, + { "steps": [ 0, 0, 0, 1] }, + { "steps": [ 0, 0, 0, 0] } + ], + }, + { + "name": "7400M_to_8000M", + "max_freq": 8000000000, + "gains": [ + { "steps": [15, 15, 15, 15] }, + { "steps": [15, 15, 15, 14] }, + { "steps": [15, 15, 15, 13] }, + { "steps": [15, 15, 15, 12] }, + { "steps": [15, 15, 15, 11] }, + { "steps": [15, 15, 15, 10] }, + { "steps": [15, 15, 15, 9] }, + { "steps": [15, 15, 15, 8] }, + { "steps": [15, 15, 15, 7] }, + { "steps": [15, 15, 15, 6] }, + { "steps": [15, 15, 15, 5] }, + { "steps": [15, 15, 14, 5] }, + { "steps": [15, 15, 12, 6] }, + { "steps": [15, 15, 11, 6] }, + { "steps": [15, 15, 10, 6] }, + { "steps": [15, 15, 9, 6] }, + { "steps": [15, 15, 7, 7] }, + { "steps": [15, 15, 6, 7] }, + { "steps": [15, 15, 5, 7] }, + { "steps": [15, 15, 4, 7] }, + { "steps": [15, 15, 3, 7] }, + { "steps": [15, 15, 2, 7] }, + { "steps": [15, 15, 1, 7] }, + { "steps": [14, 15, 1, 7] }, + { "steps": [13, 15, 1, 7] }, + { "steps": [12, 15, 1, 7] }, + { "steps": [11, 15, 1, 7] }, + { "steps": [10, 15, 1, 7] }, + { "steps": [ 9, 15, 1, 7] }, + { "steps": [ 8, 15, 1, 7] }, + { "steps": [ 7, 15, 1, 7] }, + { "steps": [ 6, 15, 1, 7] }, + { "steps": [ 5, 15, 1, 7] }, + { "steps": [ 4, 15, 1, 7] }, + { "steps": [ 3, 15, 1, 7] }, + { "steps": [ 2, 15, 1, 7] }, + { "steps": [ 1, 15, 1, 7] }, + { "steps": [ 0, 15, 1, 7] }, + { "steps": [ 0, 15, 0, 7] }, + { "steps": [ 0, 14, 0, 7] }, + { "steps": [ 0, 13, 0, 7] }, + { "steps": [ 0, 12, 0, 7] }, + { "steps": [ 0, 10, 0, 8] }, + { "steps": [ 0, 9, 0, 8] }, + { "steps": [ 0, 8, 0, 8] }, + { "steps": [ 0, 7, 0, 8] }, + { "steps": [ 0, 6, 0, 8] }, + { "steps": [ 0, 5, 0, 8] }, + { "steps": [ 0, 4, 0, 8] }, + { "steps": [ 0, 3, 0, 8] }, + { "steps": [ 0, 2, 0, 8] }, + { "steps": [ 0, 1, 0, 8] }, + { "steps": [ 0, 0, 0, 8] }, + { "steps": [ 0, 0, 0, 7] }, + { "steps": [ 0, 0, 0, 6] }, + { "steps": [ 0, 0, 0, 5] }, + { "steps": [ 0, 0, 0, 4] }, + { "steps": [ 0, 0, 0, 3] }, + { "steps": [ 0, 0, 0, 2] }, + { "steps": [ 0, 0, 0, 1] }, + { "steps": [ 0, 0, 0, 0] } + ] + } + ] +} diff --git a/host/lib/rc/cal/zbx_dsa_tx.cal b/host/lib/rc/cal/zbx_dsa_tx.cal Binary files differnew file mode 100644 index 000000000..16c17ac2c --- /dev/null +++ b/host/lib/rc/cal/zbx_dsa_tx.cal diff --git a/host/lib/rc/cal/zbx_dsa_tx.json b/host/lib/rc/cal/zbx_dsa_tx.json new file mode 100644 index 000000000..67199d9f7 --- /dev/null +++ b/host/lib/rc/cal/zbx_dsa_tx.json @@ -0,0 +1,1349 @@ +{ + "metadata": { + "name": "ZBX (Revision B) DSA Mapping (TX)", + "serial": "", + "version_major": 2 + }, + "band_dsa_map": [ + { + "name": "1M_to_200M", + "max_freq": 200000000, + "gains": [ + { "steps": [29, 31, 1] }, + { "steps": [28, 31, 1] }, + { "steps": [27, 31, 1] }, + { "steps": [26, 31, 1] }, + { "steps": [25, 31, 1] }, + { "steps": [24, 31, 1] }, + { "steps": [23, 31, 1] }, + { "steps": [22, 31, 1] }, + { "steps": [21, 31, 1] }, + { "steps": [20, 31, 1] }, + { "steps": [19, 31, 1] }, + { "steps": [18, 31, 1] }, + { "steps": [17, 31, 1] }, + { "steps": [16, 31, 1] }, + { "steps": [15, 31, 1] }, + { "steps": [14, 31, 1] }, + { "steps": [13, 31, 1] }, + { "steps": [12, 31, 1] }, + { "steps": [11, 31, 1] }, + { "steps": [10, 31, 1] }, + { "steps": [ 9, 31, 1] }, + { "steps": [ 9, 30, 1] }, + { "steps": [ 9, 29, 1] }, + { "steps": [ 9, 28, 1] }, + { "steps": [ 9, 27, 1] }, + { "steps": [ 9, 26, 1] }, + { "steps": [ 9, 25, 1] }, + { "steps": [ 9, 24, 1] }, + { "steps": [ 9, 23, 1] }, + { "steps": [ 9, 22, 1] }, + { "steps": [ 9, 21, 1] }, + { "steps": [ 9, 20, 1] }, + { "steps": [ 9, 19, 1] }, + { "steps": [ 9, 18, 1] }, + { "steps": [ 9, 17, 1] }, + { "steps": [ 9, 16, 1] }, + { "steps": [ 9, 15, 1] }, + { "steps": [ 9, 14, 1] }, + { "steps": [ 9, 13, 1] }, + { "steps": [ 9, 12, 1] }, + { "steps": [ 9, 11, 1] }, + { "steps": [ 9, 10, 1] }, + { "steps": [ 9, 9, 1] }, + { "steps": [ 9, 8, 1] }, + { "steps": [ 9, 7, 1] }, + { "steps": [ 9, 6, 1] }, + { "steps": [ 9, 5, 1] }, + { "steps": [ 9, 4, 1] }, + { "steps": [ 9, 3, 1] }, + { "steps": [10, 1, 1] }, + { "steps": [10, 0, 1] }, + { "steps": [ 9, 0, 1] }, + { "steps": [ 8, 0, 1] }, + { "steps": [ 7, 0, 1] }, + { "steps": [ 6, 0, 1] }, + { "steps": [ 5, 0, 1] }, + { "steps": [ 4, 0, 1] }, + { "steps": [ 3, 0, 1] }, + { "steps": [ 2, 0, 1] }, + { "steps": [ 1, 0, 1] }, + { "steps": [ 0, 0, 1] } + ], + }, + { + "name": "200M_to_300M", + "max_freq": 300000000, + "gains": [ + { "steps": [29, 31, 1] }, + { "steps": [28, 31, 1] }, + { "steps": [27, 31, 1] }, + { "steps": [26, 31, 1] }, + { "steps": [25, 31, 1] }, + { "steps": [24, 31, 1] }, + { "steps": [23, 31, 1] }, + { "steps": [22, 31, 1] }, + { "steps": [21, 31, 1] }, + { "steps": [20, 31, 1] }, + { "steps": [19, 31, 1] }, + { "steps": [18, 31, 1] }, + { "steps": [17, 31, 1] }, + { "steps": [16, 31, 1] }, + { "steps": [15, 31, 1] }, + { "steps": [14, 31, 1] }, + { "steps": [13, 31, 1] }, + { "steps": [12, 31, 1] }, + { "steps": [11, 31, 1] }, + { "steps": [10, 31, 1] }, + { "steps": [ 9, 31, 1] }, + { "steps": [ 9, 30, 1] }, + { "steps": [ 9, 29, 1] }, + { "steps": [ 9, 28, 1] }, + { "steps": [ 9, 27, 1] }, + { "steps": [ 9, 26, 1] }, + { "steps": [ 9, 25, 1] }, + { "steps": [ 9, 24, 1] }, + { "steps": [ 9, 23, 1] }, + { "steps": [ 9, 22, 1] }, + { "steps": [ 9, 21, 1] }, + { "steps": [ 9, 20, 1] }, + { "steps": [ 9, 19, 1] }, + { "steps": [ 9, 18, 1] }, + { "steps": [ 9, 17, 1] }, + { "steps": [ 9, 16, 1] }, + { "steps": [ 9, 15, 1] }, + { "steps": [ 9, 14, 1] }, + { "steps": [ 9, 13, 1] }, + { "steps": [ 9, 12, 1] }, + { "steps": [ 9, 11, 1] }, + { "steps": [ 9, 10, 1] }, + { "steps": [ 9, 9, 1] }, + { "steps": [ 9, 8, 1] }, + { "steps": [ 9, 7, 1] }, + { "steps": [ 9, 6, 1] }, + { "steps": [ 9, 5, 1] }, + { "steps": [ 9, 4, 1] }, + { "steps": [ 9, 3, 1] }, + { "steps": [10, 1, 1] }, + { "steps": [10, 0, 1] }, + { "steps": [ 9, 0, 1] }, + { "steps": [ 8, 0, 1] }, + { "steps": [ 7, 0, 1] }, + { "steps": [ 6, 0, 1] }, + { "steps": [ 5, 0, 1] }, + { "steps": [ 4, 0, 1] }, + { "steps": [ 3, 0, 1] }, + { "steps": [ 2, 0, 1] }, + { "steps": [ 1, 0, 1] }, + { "steps": [ 0, 0, 1] } + ], + }, + { + "name": "300M_to_400M", + "max_freq": 400000000, + "gains": [ + { "steps": [29, 31, 1] }, + { "steps": [28, 31, 1] }, + { "steps": [27, 31, 1] }, + { "steps": [26, 31, 1] }, + { "steps": [25, 31, 1] }, + { "steps": [24, 31, 1] }, + { "steps": [23, 31, 1] }, + { "steps": [22, 31, 1] }, + { "steps": [21, 31, 1] }, + { "steps": [20, 31, 1] }, + { "steps": [19, 31, 1] }, + { "steps": [18, 31, 1] }, + { "steps": [17, 31, 1] }, + { "steps": [16, 31, 1] }, + { "steps": [15, 31, 1] }, + { "steps": [14, 31, 1] }, + { "steps": [13, 31, 1] }, + { "steps": [12, 31, 1] }, + { "steps": [11, 31, 1] }, + { "steps": [10, 31, 1] }, + { "steps": [ 9, 31, 1] }, + { "steps": [ 9, 30, 1] }, + { "steps": [ 9, 29, 1] }, + { "steps": [ 9, 28, 1] }, + { "steps": [ 9, 27, 1] }, + { "steps": [ 9, 26, 1] }, + { "steps": [ 9, 25, 1] }, + { "steps": [ 9, 24, 1] }, + { "steps": [ 9, 23, 1] }, + { "steps": [ 9, 22, 1] }, + { "steps": [ 9, 21, 1] }, + { "steps": [ 9, 20, 1] }, + { "steps": [ 9, 19, 1] }, + { "steps": [ 9, 18, 1] }, + { "steps": [ 9, 17, 1] }, + { "steps": [ 9, 16, 1] }, + { "steps": [ 9, 15, 1] }, + { "steps": [ 9, 14, 1] }, + { "steps": [ 9, 13, 1] }, + { "steps": [ 9, 12, 1] }, + { "steps": [ 9, 11, 1] }, + { "steps": [ 9, 10, 1] }, + { "steps": [ 9, 9, 1] }, + { "steps": [ 9, 8, 1] }, + { "steps": [ 9, 7, 1] }, + { "steps": [ 9, 6, 1] }, + { "steps": [ 9, 5, 1] }, + { "steps": [ 9, 4, 1] }, + { "steps": [10, 2, 1] }, + { "steps": [10, 1, 1] }, + { "steps": [10, 0, 1] }, + { "steps": [ 9, 0, 1] }, + { "steps": [ 8, 0, 1] }, + { "steps": [ 7, 0, 1] }, + { "steps": [ 6, 0, 1] }, + { "steps": [ 5, 0, 1] }, + { "steps": [ 4, 0, 1] }, + { "steps": [ 3, 0, 1] }, + { "steps": [ 2, 0, 1] }, + { "steps": [ 1, 0, 1] }, + { "steps": [ 0, 0, 1] } + ], + }, + { + "name": "400M_to_600M", + "max_freq": 600000000, + "gains": [ + { "steps": [29, 31, 1] }, + { "steps": [28, 31, 1] }, + { "steps": [27, 31, 1] }, + { "steps": [26, 31, 1] }, + { "steps": [25, 31, 1] }, + { "steps": [24, 31, 1] }, + { "steps": [23, 31, 1] }, + { "steps": [22, 31, 1] }, + { "steps": [21, 31, 1] }, + { "steps": [20, 31, 1] }, + { "steps": [19, 31, 1] }, + { "steps": [18, 31, 1] }, + { "steps": [17, 31, 1] }, + { "steps": [16, 31, 1] }, + { "steps": [15, 31, 1] }, + { "steps": [14, 31, 1] }, + { "steps": [13, 31, 1] }, + { "steps": [12, 31, 1] }, + { "steps": [11, 31, 1] }, + { "steps": [10, 31, 1] }, + { "steps": [ 9, 31, 1] }, + { "steps": [ 9, 30, 1] }, + { "steps": [ 9, 29, 1] }, + { "steps": [ 9, 28, 1] }, + { "steps": [ 9, 27, 1] }, + { "steps": [ 9, 26, 1] }, + { "steps": [ 9, 25, 1] }, + { "steps": [ 9, 24, 1] }, + { "steps": [ 9, 23, 1] }, + { "steps": [ 9, 22, 1] }, + { "steps": [ 9, 21, 1] }, + { "steps": [ 9, 20, 1] }, + { "steps": [ 9, 19, 1] }, + { "steps": [ 9, 18, 1] }, + { "steps": [ 9, 17, 1] }, + { "steps": [ 9, 16, 1] }, + { "steps": [ 9, 15, 1] }, + { "steps": [ 9, 14, 1] }, + { "steps": [ 9, 13, 1] }, + { "steps": [ 9, 12, 1] }, + { "steps": [ 9, 11, 1] }, + { "steps": [ 9, 10, 1] }, + { "steps": [ 9, 9, 1] }, + { "steps": [ 9, 8, 1] }, + { "steps": [ 9, 7, 1] }, + { "steps": [ 9, 6, 1] }, + { "steps": [ 9, 5, 1] }, + { "steps": [ 9, 4, 1] }, + { "steps": [ 9, 3, 1] }, + { "steps": [10, 1, 1] }, + { "steps": [10, 0, 1] }, + { "steps": [ 9, 0, 1] }, + { "steps": [ 8, 0, 1] }, + { "steps": [ 7, 0, 1] }, + { "steps": [ 6, 0, 1] }, + { "steps": [ 5, 0, 1] }, + { "steps": [ 4, 0, 1] }, + { "steps": [ 3, 0, 1] }, + { "steps": [ 2, 0, 1] }, + { "steps": [ 1, 0, 1] }, + { "steps": [ 0, 0, 1] } + ], + }, + { + "name": "600M_to_800M", + "max_freq": 800000000, + "gains": [ + { "steps": [29, 31, 2] }, + { "steps": [28, 31, 2] }, + { "steps": [27, 31, 2] }, + { "steps": [26, 31, 2] }, + { "steps": [25, 31, 2] }, + { "steps": [24, 31, 2] }, + { "steps": [23, 31, 2] }, + { "steps": [22, 31, 2] }, + { "steps": [21, 31, 2] }, + { "steps": [20, 31, 2] }, + { "steps": [19, 31, 2] }, + { "steps": [18, 31, 2] }, + { "steps": [17, 31, 2] }, + { "steps": [16, 31, 2] }, + { "steps": [15, 31, 2] }, + { "steps": [14, 31, 2] }, + { "steps": [13, 31, 2] }, + { "steps": [12, 31, 2] }, + { "steps": [11, 31, 2] }, + { "steps": [10, 31, 2] }, + { "steps": [ 9, 31, 2] }, + { "steps": [ 9, 30, 2] }, + { "steps": [ 9, 29, 2] }, + { "steps": [ 9, 28, 2] }, + { "steps": [ 9, 27, 2] }, + { "steps": [ 9, 26, 2] }, + { "steps": [ 9, 25, 2] }, + { "steps": [ 9, 24, 2] }, + { "steps": [ 9, 23, 2] }, + { "steps": [ 9, 22, 2] }, + { "steps": [ 9, 21, 2] }, + { "steps": [ 9, 20, 2] }, + { "steps": [ 9, 19, 2] }, + { "steps": [ 9, 18, 2] }, + { "steps": [ 9, 17, 2] }, + { "steps": [ 9, 16, 2] }, + { "steps": [ 9, 15, 2] }, + { "steps": [ 9, 14, 2] }, + { "steps": [ 9, 13, 2] }, + { "steps": [ 9, 12, 2] }, + { "steps": [ 9, 11, 2] }, + { "steps": [10, 9, 2] }, + { "steps": [10, 8, 2] }, + { "steps": [10, 7, 2] }, + { "steps": [10, 6, 2] }, + { "steps": [10, 5, 2] }, + { "steps": [10, 4, 2] }, + { "steps": [10, 3, 2] }, + { "steps": [10, 2, 2] }, + { "steps": [11, 0, 2] }, + { "steps": [10, 0, 2] }, + { "steps": [ 9, 0, 2] }, + { "steps": [ 8, 0, 2] }, + { "steps": [ 7, 0, 2] }, + { "steps": [ 6, 0, 2] }, + { "steps": [ 5, 0, 2] }, + { "steps": [ 4, 0, 2] }, + { "steps": [ 3, 0, 2] }, + { "steps": [ 2, 0, 2] }, + { "steps": [ 1, 0, 2] }, + { "steps": [ 0, 0, 2] } + ], + }, + { + "name": "800M_to_1300M", + "max_freq": 1300000000, + "gains": [ + { "steps": [29, 31, 2] }, + { "steps": [28, 31, 2] }, + { "steps": [27, 31, 2] }, + { "steps": [26, 31, 2] }, + { "steps": [25, 31, 2] }, + { "steps": [24, 31, 2] }, + { "steps": [23, 31, 2] }, + { "steps": [22, 31, 2] }, + { "steps": [21, 31, 2] }, + { "steps": [20, 31, 2] }, + { "steps": [19, 31, 2] }, + { "steps": [18, 31, 2] }, + { "steps": [17, 31, 2] }, + { "steps": [16, 31, 2] }, + { "steps": [15, 31, 2] }, + { "steps": [14, 31, 2] }, + { "steps": [13, 31, 2] }, + { "steps": [12, 31, 2] }, + { "steps": [11, 31, 2] }, + { "steps": [10, 31, 2] }, + { "steps": [ 9, 31, 2] }, + { "steps": [ 9, 30, 2] }, + { "steps": [ 9, 29, 2] }, + { "steps": [ 9, 28, 2] }, + { "steps": [ 9, 27, 2] }, + { "steps": [ 9, 26, 2] }, + { "steps": [ 9, 25, 2] }, + { "steps": [ 9, 24, 2] }, + { "steps": [ 9, 23, 2] }, + { "steps": [ 9, 22, 2] }, + { "steps": [ 9, 21, 2] }, + { "steps": [ 9, 20, 2] }, + { "steps": [ 9, 19, 2] }, + { "steps": [ 9, 18, 2] }, + { "steps": [ 9, 17, 2] }, + { "steps": [ 9, 16, 2] }, + { "steps": [ 9, 15, 2] }, + { "steps": [ 9, 14, 2] }, + { "steps": [ 9, 13, 2] }, + { "steps": [ 9, 12, 2] }, + { "steps": [ 9, 11, 2] }, + { "steps": [ 9, 10, 2] }, + { "steps": [ 9, 9, 2] }, + { "steps": [ 9, 8, 2] }, + { "steps": [10, 6, 2] }, + { "steps": [10, 5, 2] }, + { "steps": [10, 4, 2] }, + { "steps": [10, 3, 2] }, + { "steps": [10, 2, 2] }, + { "steps": [10, 1, 2] }, + { "steps": [10, 0, 2] }, + { "steps": [ 9, 0, 2] }, + { "steps": [ 8, 0, 2] }, + { "steps": [ 7, 0, 2] }, + { "steps": [ 6, 0, 2] }, + { "steps": [ 5, 0, 2] }, + { "steps": [ 4, 0, 2] }, + { "steps": [ 3, 0, 2] }, + { "steps": [ 2, 0, 2] }, + { "steps": [ 1, 0, 2] }, + { "steps": [ 0, 0, 2] } + ], + }, + { + "name": "1300M_to_1800M", + "max_freq": 1800000000, + "gains": [ + { "steps": [29, 31, 2] }, + { "steps": [28, 31, 2] }, + { "steps": [27, 31, 2] }, + { "steps": [26, 31, 2] }, + { "steps": [25, 31, 2] }, + { "steps": [24, 31, 2] }, + { "steps": [23, 31, 2] }, + { "steps": [22, 31, 2] }, + { "steps": [21, 31, 2] }, + { "steps": [20, 31, 2] }, + { "steps": [19, 31, 2] }, + { "steps": [18, 31, 2] }, + { "steps": [17, 31, 2] }, + { "steps": [16, 31, 2] }, + { "steps": [15, 31, 2] }, + { "steps": [14, 31, 2] }, + { "steps": [13, 31, 2] }, + { "steps": [12, 31, 2] }, + { "steps": [11, 31, 2] }, + { "steps": [10, 31, 2] }, + { "steps": [ 9, 31, 2] }, + { "steps": [ 9, 30, 2] }, + { "steps": [ 9, 29, 2] }, + { "steps": [ 9, 28, 2] }, + { "steps": [ 9, 27, 2] }, + { "steps": [ 9, 26, 2] }, + { "steps": [ 9, 25, 2] }, + { "steps": [ 9, 24, 2] }, + { "steps": [ 9, 23, 2] }, + { "steps": [ 9, 22, 2] }, + { "steps": [ 9, 21, 2] }, + { "steps": [ 9, 20, 2] }, + { "steps": [ 9, 19, 2] }, + { "steps": [ 9, 18, 2] }, + { "steps": [ 9, 17, 2] }, + { "steps": [ 9, 16, 2] }, + { "steps": [ 9, 15, 2] }, + { "steps": [ 9, 14, 2] }, + { "steps": [ 9, 13, 2] }, + { "steps": [ 9, 12, 2] }, + { "steps": [ 9, 11, 2] }, + { "steps": [ 9, 10, 2] }, + { "steps": [ 9, 9, 2] }, + { "steps": [ 9, 8, 2] }, + { "steps": [ 9, 7, 2] }, + { "steps": [10, 5, 2] }, + { "steps": [10, 4, 2] }, + { "steps": [10, 3, 2] }, + { "steps": [10, 2, 2] }, + { "steps": [10, 1, 2] }, + { "steps": [10, 0, 2] }, + { "steps": [ 9, 0, 2] }, + { "steps": [ 8, 0, 2] }, + { "steps": [ 7, 0, 2] }, + { "steps": [ 6, 0, 2] }, + { "steps": [ 5, 0, 2] }, + { "steps": [ 4, 0, 2] }, + { "steps": [ 3, 0, 2] }, + { "steps": [ 2, 0, 2] }, + { "steps": [ 1, 0, 2] }, + { "steps": [ 0, 0, 2] } + ], + }, + { + "name": "1800M_to_2300M", + "max_freq": 2300000000, + "gains": [ + { "steps": [29, 31, 2] }, + { "steps": [28, 31, 2] }, + { "steps": [27, 31, 2] }, + { "steps": [26, 31, 2] }, + { "steps": [25, 31, 2] }, + { "steps": [24, 31, 2] }, + { "steps": [23, 31, 2] }, + { "steps": [22, 31, 2] }, + { "steps": [21, 31, 2] }, + { "steps": [20, 31, 2] }, + { "steps": [19, 31, 2] }, + { "steps": [18, 31, 2] }, + { "steps": [17, 31, 2] }, + { "steps": [16, 31, 2] }, + { "steps": [15, 31, 2] }, + { "steps": [14, 31, 2] }, + { "steps": [13, 31, 2] }, + { "steps": [12, 31, 2] }, + { "steps": [11, 31, 2] }, + { "steps": [11, 30, 2] }, + { "steps": [11, 29, 2] }, + { "steps": [11, 28, 2] }, + { "steps": [11, 27, 2] }, + { "steps": [11, 26, 2] }, + { "steps": [11, 25, 2] }, + { "steps": [11, 24, 2] }, + { "steps": [11, 23, 2] }, + { "steps": [11, 22, 2] }, + { "steps": [11, 21, 2] }, + { "steps": [11, 20, 2] }, + { "steps": [11, 19, 2] }, + { "steps": [11, 18, 2] }, + { "steps": [11, 17, 2] }, + { "steps": [11, 16, 2] }, + { "steps": [11, 15, 2] }, + { "steps": [12, 13, 2] }, + { "steps": [12, 12, 2] }, + { "steps": [12, 11, 2] }, + { "steps": [12, 10, 2] }, + { "steps": [12, 9, 2] }, + { "steps": [12, 8, 2] }, + { "steps": [12, 7, 2] }, + { "steps": [12, 6, 2] }, + { "steps": [12, 5, 2] }, + { "steps": [12, 4, 2] }, + { "steps": [12, 3, 2] }, + { "steps": [12, 2, 2] }, + { "steps": [12, 1, 2] }, + { "steps": [12, 0, 2] }, + { "steps": [11, 0, 2] }, + { "steps": [10, 0, 2] }, + { "steps": [ 9, 0, 2] }, + { "steps": [ 8, 0, 2] }, + { "steps": [ 7, 0, 2] }, + { "steps": [ 6, 0, 2] }, + { "steps": [ 5, 0, 2] }, + { "steps": [ 4, 0, 2] }, + { "steps": [ 3, 0, 2] }, + { "steps": [ 2, 0, 2] }, + { "steps": [ 1, 0, 2] }, + { "steps": [ 0, 0, 2] } + ], + }, + { + "name": "2300M_to_2700M", + "max_freq": 2700000000, + "gains": [ + { "steps": [29, 31, 2] }, + { "steps": [28, 31, 2] }, + { "steps": [27, 31, 2] }, + { "steps": [26, 31, 2] }, + { "steps": [25, 31, 2] }, + { "steps": [24, 31, 2] }, + { "steps": [23, 31, 2] }, + { "steps": [22, 31, 2] }, + { "steps": [21, 31, 2] }, + { "steps": [20, 31, 2] }, + { "steps": [19, 31, 2] }, + { "steps": [18, 31, 2] }, + { "steps": [17, 31, 2] }, + { "steps": [16, 31, 2] }, + { "steps": [15, 31, 2] }, + { "steps": [14, 31, 2] }, + { "steps": [13, 31, 2] }, + { "steps": [12, 31, 2] }, + { "steps": [11, 31, 2] }, + { "steps": [11, 30, 2] }, + { "steps": [11, 29, 2] }, + { "steps": [11, 28, 2] }, + { "steps": [11, 27, 2] }, + { "steps": [11, 26, 2] }, + { "steps": [11, 25, 2] }, + { "steps": [11, 24, 2] }, + { "steps": [11, 23, 2] }, + { "steps": [11, 22, 2] }, + { "steps": [11, 21, 2] }, + { "steps": [11, 20, 2] }, + { "steps": [11, 19, 2] }, + { "steps": [11, 18, 2] }, + { "steps": [11, 17, 2] }, + { "steps": [11, 16, 2] }, + { "steps": [11, 15, 2] }, + { "steps": [11, 14, 2] }, + { "steps": [11, 13, 2] }, + { "steps": [11, 12, 2] }, + { "steps": [11, 11, 2] }, + { "steps": [11, 10, 2] }, + { "steps": [11, 9, 2] }, + { "steps": [11, 8, 2] }, + { "steps": [11, 7, 2] }, + { "steps": [11, 6, 2] }, + { "steps": [11, 5, 2] }, + { "steps": [11, 4, 2] }, + { "steps": [11, 3, 2] }, + { "steps": [12, 1, 2] }, + { "steps": [12, 0, 2] }, + { "steps": [11, 0, 2] }, + { "steps": [10, 0, 2] }, + { "steps": [ 9, 0, 2] }, + { "steps": [ 8, 0, 2] }, + { "steps": [ 7, 0, 2] }, + { "steps": [ 6, 0, 2] }, + { "steps": [ 5, 0, 2] }, + { "steps": [ 4, 0, 2] }, + { "steps": [ 3, 0, 2] }, + { "steps": [ 2, 0, 2] }, + { "steps": [ 1, 0, 2] }, + { "steps": [ 0, 0, 2] } + ], + }, + { + "name": "2700M_to_3000M", + "max_freq": 3000000000, + "gains": [ + { "steps": [29, 31, 2] }, + { "steps": [28, 31, 2] }, + { "steps": [27, 31, 2] }, + { "steps": [26, 31, 2] }, + { "steps": [25, 31, 2] }, + { "steps": [24, 31, 2] }, + { "steps": [23, 31, 2] }, + { "steps": [22, 31, 2] }, + { "steps": [21, 31, 2] }, + { "steps": [20, 31, 2] }, + { "steps": [19, 31, 2] }, + { "steps": [18, 31, 2] }, + { "steps": [17, 31, 2] }, + { "steps": [16, 31, 2] }, + { "steps": [15, 31, 2] }, + { "steps": [14, 31, 2] }, + { "steps": [13, 31, 2] }, + { "steps": [12, 31, 2] }, + { "steps": [11, 31, 2] }, + { "steps": [10, 31, 2] }, + { "steps": [ 9, 31, 2] }, + { "steps": [ 8, 31, 2] }, + { "steps": [ 7, 31, 2] }, + { "steps": [ 7, 30, 2] }, + { "steps": [ 7, 29, 2] }, + { "steps": [ 7, 28, 2] }, + { "steps": [ 7, 27, 2] }, + { "steps": [ 7, 26, 2] }, + { "steps": [ 7, 25, 2] }, + { "steps": [ 7, 24, 2] }, + { "steps": [ 7, 23, 2] }, + { "steps": [ 7, 22, 2] }, + { "steps": [ 7, 21, 2] }, + { "steps": [ 7, 20, 2] }, + { "steps": [ 7, 19, 2] }, + { "steps": [ 7, 18, 2] }, + { "steps": [ 7, 17, 2] }, + { "steps": [ 7, 16, 2] }, + { "steps": [ 7, 15, 2] }, + { "steps": [ 7, 14, 2] }, + { "steps": [ 7, 13, 2] }, + { "steps": [ 7, 12, 2] }, + { "steps": [ 7, 11, 2] }, + { "steps": [ 7, 10, 2] }, + { "steps": [ 7, 9, 2] }, + { "steps": [ 7, 8, 2] }, + { "steps": [ 7, 7, 2] }, + { "steps": [ 7, 6, 2] }, + { "steps": [ 7, 5, 2] }, + { "steps": [ 8, 3, 2] }, + { "steps": [ 8, 2, 2] }, + { "steps": [ 8, 1, 2] }, + { "steps": [ 8, 0, 2] }, + { "steps": [ 7, 0, 2] }, + { "steps": [ 6, 0, 2] }, + { "steps": [ 5, 0, 2] }, + { "steps": [ 4, 0, 2] }, + { "steps": [ 3, 0, 2] }, + { "steps": [ 2, 0, 2] }, + { "steps": [ 1, 0, 2] }, + { "steps": [ 0, 0, 2] } + ], + }, + { + "name": "3000M_to_4100M", + "max_freq": 4100000000, + "gains": [ + { "steps": [29, 31, 2] }, + { "steps": [28, 31, 2] }, + { "steps": [27, 31, 2] }, + { "steps": [26, 31, 2] }, + { "steps": [25, 31, 2] }, + { "steps": [24, 31, 2] }, + { "steps": [23, 31, 2] }, + { "steps": [22, 31, 2] }, + { "steps": [21, 31, 2] }, + { "steps": [20, 31, 2] }, + { "steps": [19, 31, 2] }, + { "steps": [18, 31, 2] }, + { "steps": [17, 31, 2] }, + { "steps": [16, 31, 2] }, + { "steps": [15, 31, 2] }, + { "steps": [14, 31, 2] }, + { "steps": [13, 31, 2] }, + { "steps": [12, 31, 2] }, + { "steps": [11, 31, 2] }, + { "steps": [10, 31, 2] }, + { "steps": [ 9, 31, 2] }, + { "steps": [ 8, 31, 2] }, + { "steps": [ 7, 31, 2] }, + { "steps": [ 6, 31, 2] }, + { "steps": [ 6, 30, 2] }, + { "steps": [ 6, 29, 2] }, + { "steps": [ 6, 28, 2] }, + { "steps": [ 6, 27, 2] }, + { "steps": [ 6, 26, 2] }, + { "steps": [ 6, 25, 2] }, + { "steps": [ 6, 24, 2] }, + { "steps": [ 6, 23, 2] }, + { "steps": [ 6, 22, 2] }, + { "steps": [ 6, 21, 2] }, + { "steps": [ 6, 20, 2] }, + { "steps": [ 6, 19, 2] }, + { "steps": [ 6, 18, 2] }, + { "steps": [ 6, 17, 2] }, + { "steps": [ 6, 16, 2] }, + { "steps": [ 7, 14, 2] }, + { "steps": [ 7, 13, 2] }, + { "steps": [ 7, 12, 2] }, + { "steps": [ 7, 11, 2] }, + { "steps": [ 8, 9, 2] }, + { "steps": [ 8, 8, 2] }, + { "steps": [ 8, 7, 2] }, + { "steps": [ 9, 5, 2] }, + { "steps": [ 9, 4, 2] }, + { "steps": [10, 2, 2] }, + { "steps": [10, 1, 2] }, + { "steps": [10, 0, 2] }, + { "steps": [ 9, 0, 2] }, + { "steps": [ 8, 0, 2] }, + { "steps": [ 7, 0, 2] }, + { "steps": [ 6, 0, 2] }, + { "steps": [ 5, 0, 2] }, + { "steps": [ 4, 0, 2] }, + { "steps": [ 3, 0, 2] }, + { "steps": [ 2, 0, 2] }, + { "steps": [ 1, 0, 2] }, + { "steps": [ 0, 0, 2] } + ], + }, + { + "name": "4100M_to_45000M", + "max_freq": 4500000000, + "gains": [ + { "steps": [29, 31, 2] }, + { "steps": [28, 31, 2] }, + { "steps": [27, 31, 2] }, + { "steps": [26, 31, 2] }, + { "steps": [25, 31, 2] }, + { "steps": [24, 31, 2] }, + { "steps": [23, 31, 2] }, + { "steps": [22, 31, 2] }, + { "steps": [21, 31, 2] }, + { "steps": [20, 31, 2] }, + { "steps": [19, 31, 2] }, + { "steps": [18, 31, 2] }, + { "steps": [17, 31, 2] }, + { "steps": [16, 31, 2] }, + { "steps": [15, 31, 2] }, + { "steps": [14, 31, 2] }, + { "steps": [13, 31, 2] }, + { "steps": [12, 31, 2] }, + { "steps": [11, 31, 2] }, + { "steps": [10, 31, 2] }, + { "steps": [ 9, 31, 2] }, + { "steps": [ 8, 31, 2] }, + { "steps": [ 7, 31, 2] }, + { "steps": [ 6, 31, 2] }, + { "steps": [ 5, 31, 2] }, + { "steps": [ 4, 31, 2] }, + { "steps": [ 3, 31, 2] }, + { "steps": [ 3, 30, 2] }, + { "steps": [ 3, 29, 2] }, + { "steps": [ 3, 28, 2] }, + { "steps": [ 3, 27, 2] }, + { "steps": [ 3, 26, 2] }, + { "steps": [ 3, 25, 2] }, + { "steps": [ 3, 24, 2] }, + { "steps": [ 3, 23, 2] }, + { "steps": [ 3, 22, 2] }, + { "steps": [ 3, 21, 2] }, + { "steps": [ 3, 20, 2] }, + { "steps": [ 3, 19, 2] }, + { "steps": [ 4, 17, 2] }, + { "steps": [ 4, 16, 2] }, + { "steps": [ 4, 15, 2] }, + { "steps": [ 4, 14, 2] }, + { "steps": [ 4, 13, 2] }, + { "steps": [ 5, 11, 2] }, + { "steps": [ 5, 10, 2] }, + { "steps": [ 5, 9, 2] }, + { "steps": [ 5, 8, 2] }, + { "steps": [ 6, 6, 2] }, + { "steps": [ 6, 5, 2] }, + { "steps": [ 7, 3, 2] }, + { "steps": [ 7, 2, 2] }, + { "steps": [ 8, 0, 2] }, + { "steps": [ 7, 0, 2] }, + { "steps": [ 6, 0, 2] }, + { "steps": [ 5, 0, 2] }, + { "steps": [ 4, 0, 2] }, + { "steps": [ 3, 0, 2] }, + { "steps": [ 2, 0, 2] }, + { "steps": [ 1, 0, 2] }, + { "steps": [ 0, 0, 2] } + ], + }, + { + "name": "4500M_to_4900M", + "max_freq": 4900000000, + "gains": [ + { "steps": [29, 31, 2] }, + { "steps": [28, 31, 2] }, + { "steps": [27, 31, 2] }, + { "steps": [26, 31, 2] }, + { "steps": [25, 31, 2] }, + { "steps": [24, 31, 2] }, + { "steps": [23, 31, 2] }, + { "steps": [22, 31, 2] }, + { "steps": [21, 31, 2] }, + { "steps": [20, 31, 2] }, + { "steps": [19, 31, 2] }, + { "steps": [18, 31, 2] }, + { "steps": [17, 31, 2] }, + { "steps": [16, 31, 2] }, + { "steps": [15, 31, 2] }, + { "steps": [14, 31, 2] }, + { "steps": [13, 31, 2] }, + { "steps": [12, 31, 2] }, + { "steps": [11, 31, 2] }, + { "steps": [10, 31, 2] }, + { "steps": [ 9, 31, 2] }, + { "steps": [ 8, 31, 2] }, + { "steps": [ 7, 31, 2] }, + { "steps": [ 6, 31, 2] }, + { "steps": [ 5, 31, 2] }, + { "steps": [ 5, 30, 2] }, + { "steps": [ 5, 29, 2] }, + { "steps": [ 5, 28, 2] }, + { "steps": [ 5, 27, 2] }, + { "steps": [ 5, 26, 2] }, + { "steps": [ 5, 25, 2] }, + { "steps": [ 5, 24, 2] }, + { "steps": [ 5, 23, 2] }, + { "steps": [ 5, 22, 2] }, + { "steps": [ 5, 21, 2] }, + { "steps": [ 5, 20, 2] }, + { "steps": [ 5, 19, 2] }, + { "steps": [ 5, 18, 2] }, + { "steps": [ 5, 17, 2] }, + { "steps": [ 6, 15, 2] }, + { "steps": [ 6, 14, 2] }, + { "steps": [ 6, 13, 2] }, + { "steps": [ 6, 12, 2] }, + { "steps": [ 6, 11, 2] }, + { "steps": [ 7, 9, 2] }, + { "steps": [ 7, 8, 2] }, + { "steps": [ 7, 7, 2] }, + { "steps": [ 8, 5, 2] }, + { "steps": [ 8, 4, 2] }, + { "steps": [ 9, 2, 2] }, + { "steps": [ 9, 1, 2] }, + { "steps": [ 9, 0, 2] }, + { "steps": [ 8, 0, 2] }, + { "steps": [ 7, 0, 2] }, + { "steps": [ 6, 0, 2] }, + { "steps": [ 5, 0, 2] }, + { "steps": [ 4, 0, 2] }, + { "steps": [ 3, 0, 2] }, + { "steps": [ 2, 0, 2] }, + { "steps": [ 1, 0, 2] }, + { "steps": [ 0, 0, 2] } + ], + }, + { + "name": "4900M_to_5100M", + "max_freq": 5100000000, + "gains": [ + { "steps": [29, 31, 2] }, + { "steps": [28, 31, 2] }, + { "steps": [27, 31, 2] }, + { "steps": [26, 31, 2] }, + { "steps": [25, 31, 2] }, + { "steps": [24, 31, 2] }, + { "steps": [23, 31, 2] }, + { "steps": [22, 31, 2] }, + { "steps": [21, 31, 2] }, + { "steps": [20, 31, 2] }, + { "steps": [19, 31, 2] }, + { "steps": [18, 31, 2] }, + { "steps": [17, 31, 2] }, + { "steps": [16, 31, 2] }, + { "steps": [15, 31, 2] }, + { "steps": [14, 31, 2] }, + { "steps": [13, 31, 2] }, + { "steps": [12, 31, 2] }, + { "steps": [11, 31, 2] }, + { "steps": [10, 31, 2] }, + { "steps": [ 9, 31, 2] }, + { "steps": [ 8, 31, 2] }, + { "steps": [ 7, 31, 2] }, + { "steps": [ 6, 31, 2] }, + { "steps": [ 6, 30, 2] }, + { "steps": [ 6, 29, 2] }, + { "steps": [ 6, 28, 2] }, + { "steps": [ 6, 27, 2] }, + { "steps": [ 6, 26, 2] }, + { "steps": [ 6, 25, 2] }, + { "steps": [ 6, 24, 2] }, + { "steps": [ 6, 23, 2] }, + { "steps": [ 6, 22, 2] }, + { "steps": [ 6, 21, 2] }, + { "steps": [ 6, 20, 2] }, + { "steps": [ 6, 19, 2] }, + { "steps": [ 6, 18, 2] }, + { "steps": [ 6, 17, 2] }, + { "steps": [ 7, 15, 2] }, + { "steps": [ 7, 14, 2] }, + { "steps": [ 7, 13, 2] }, + { "steps": [ 7, 12, 2] }, + { "steps": [ 7, 11, 2] }, + { "steps": [ 8, 9, 2] }, + { "steps": [ 8, 8, 2] }, + { "steps": [ 8, 7, 2] }, + { "steps": [ 9, 5, 2] }, + { "steps": [ 9, 4, 2] }, + { "steps": [ 9, 3, 2] }, + { "steps": [10, 1, 2] }, + { "steps": [10, 0, 2] }, + { "steps": [ 9, 0, 2] }, + { "steps": [ 8, 0, 2] }, + { "steps": [ 7, 0, 2] }, + { "steps": [ 6, 0, 2] }, + { "steps": [ 5, 0, 2] }, + { "steps": [ 4, 0, 2] }, + { "steps": [ 3, 0, 2] }, + { "steps": [ 2, 0, 2] }, + { "steps": [ 1, 0, 2] }, + { "steps": [ 0, 0, 2] } + ], + }, + { + "name": "5100M_to_5700M", + "max_freq": 5700000000, + "gains": [ + { "steps": [29, 31, 2] }, + { "steps": [28, 31, 2] }, + { "steps": [27, 31, 2] }, + { "steps": [26, 31, 2] }, + { "steps": [25, 31, 2] }, + { "steps": [24, 31, 2] }, + { "steps": [23, 31, 2] }, + { "steps": [22, 31, 2] }, + { "steps": [21, 31, 2] }, + { "steps": [20, 31, 2] }, + { "steps": [19, 31, 2] }, + { "steps": [18, 31, 2] }, + { "steps": [17, 31, 2] }, + { "steps": [16, 31, 2] }, + { "steps": [15, 31, 2] }, + { "steps": [14, 31, 2] }, + { "steps": [13, 31, 2] }, + { "steps": [12, 31, 2] }, + { "steps": [11, 31, 2] }, + { "steps": [10, 31, 2] }, + { "steps": [ 9, 31, 2] }, + { "steps": [ 8, 31, 2] }, + { "steps": [ 7, 31, 2] }, + { "steps": [ 6, 31, 2] }, + { "steps": [ 5, 31, 2] }, + { "steps": [ 4, 31, 2] }, + { "steps": [ 4, 30, 2] }, + { "steps": [ 4, 29, 2] }, + { "steps": [ 4, 28, 2] }, + { "steps": [ 4, 27, 2] }, + { "steps": [ 4, 26, 2] }, + { "steps": [ 4, 25, 2] }, + { "steps": [ 4, 24, 2] }, + { "steps": [ 4, 23, 2] }, + { "steps": [ 4, 22, 2] }, + { "steps": [ 4, 21, 2] }, + { "steps": [ 4, 20, 2] }, + { "steps": [ 4, 19, 2] }, + { "steps": [ 4, 18, 2] }, + { "steps": [ 5, 16, 2] }, + { "steps": [ 5, 15, 2] }, + { "steps": [ 5, 14, 2] }, + { "steps": [ 5, 13, 2] }, + { "steps": [ 5, 12, 2] }, + { "steps": [ 5, 11, 2] }, + { "steps": [ 6, 9, 2] }, + { "steps": [ 6, 8, 2] }, + { "steps": [ 6, 7, 2] }, + { "steps": [ 7, 5, 2] }, + { "steps": [ 7, 4, 2] }, + { "steps": [ 8, 2, 2] }, + { "steps": [ 8, 1, 2] }, + { "steps": [ 8, 0, 2] }, + { "steps": [ 7, 0, 2] }, + { "steps": [ 6, 0, 2] }, + { "steps": [ 5, 0, 2] }, + { "steps": [ 4, 0, 2] }, + { "steps": [ 3, 0, 2] }, + { "steps": [ 2, 0, 2] }, + { "steps": [ 1, 0, 2] }, + { "steps": [ 0, 0, 2] } + ], + }, + { + "name": "5700M_to_6100M", + "max_freq": 6100000000, + "gains": [ + { "steps": [29, 31, 2] }, + { "steps": [28, 31, 2] }, + { "steps": [27, 31, 2] }, + { "steps": [26, 31, 2] }, + { "steps": [25, 31, 2] }, + { "steps": [24, 31, 2] }, + { "steps": [23, 31, 2] }, + { "steps": [22, 31, 2] }, + { "steps": [21, 31, 2] }, + { "steps": [20, 31, 2] }, + { "steps": [19, 31, 2] }, + { "steps": [18, 31, 2] }, + { "steps": [17, 31, 2] }, + { "steps": [16, 31, 2] }, + { "steps": [15, 31, 2] }, + { "steps": [14, 31, 2] }, + { "steps": [13, 31, 2] }, + { "steps": [12, 31, 2] }, + { "steps": [11, 31, 2] }, + { "steps": [10, 31, 2] }, + { "steps": [ 9, 31, 2] }, + { "steps": [ 8, 31, 2] }, + { "steps": [ 7, 31, 2] }, + { "steps": [ 6, 31, 2] }, + { "steps": [ 5, 31, 2] }, + { "steps": [ 4, 31, 2] }, + { "steps": [ 4, 30, 2] }, + { "steps": [ 4, 29, 2] }, + { "steps": [ 4, 28, 2] }, + { "steps": [ 4, 27, 2] }, + { "steps": [ 4, 26, 2] }, + { "steps": [ 4, 25, 2] }, + { "steps": [ 4, 24, 2] }, + { "steps": [ 4, 23, 2] }, + { "steps": [ 4, 22, 2] }, + { "steps": [ 5, 20, 2] }, + { "steps": [ 5, 19, 2] }, + { "steps": [ 5, 18, 2] }, + { "steps": [ 5, 17, 2] }, + { "steps": [ 5, 16, 2] }, + { "steps": [ 5, 15, 2] }, + { "steps": [ 5, 14, 2] }, + { "steps": [ 5, 13, 2] }, + { "steps": [ 6, 11, 2] }, + { "steps": [ 6, 10, 2] }, + { "steps": [ 6, 9, 2] }, + { "steps": [ 6, 8, 2] }, + { "steps": [ 7, 6, 2] }, + { "steps": [ 7, 5, 2] }, + { "steps": [ 8, 3, 2] }, + { "steps": [ 8, 2, 2] }, + { "steps": [ 9, 0, 2] }, + { "steps": [ 8, 0, 2] }, + { "steps": [ 7, 0, 2] }, + { "steps": [ 6, 0, 2] }, + { "steps": [ 5, 0, 2] }, + { "steps": [ 4, 0, 2] }, + { "steps": [ 3, 0, 2] }, + { "steps": [ 2, 0, 2] }, + { "steps": [ 1, 0, 2] }, + { "steps": [ 0, 0, 2] } + ], + }, + { + "name": "6100M_to_6400M", + "max_freq": 6400000000, + "gains": [ + { "steps": [29, 31, 2] }, + { "steps": [28, 31, 2] }, + { "steps": [27, 31, 2] }, + { "steps": [26, 31, 2] }, + { "steps": [25, 31, 2] }, + { "steps": [24, 31, 2] }, + { "steps": [23, 31, 2] }, + { "steps": [22, 31, 2] }, + { "steps": [21, 31, 2] }, + { "steps": [20, 31, 2] }, + { "steps": [19, 31, 2] }, + { "steps": [18, 31, 2] }, + { "steps": [17, 31, 2] }, + { "steps": [16, 31, 2] }, + { "steps": [15, 31, 2] }, + { "steps": [14, 31, 2] }, + { "steps": [13, 31, 2] }, + { "steps": [12, 31, 2] }, + { "steps": [11, 31, 2] }, + { "steps": [10, 31, 2] }, + { "steps": [ 9, 31, 2] }, + { "steps": [ 8, 31, 2] }, + { "steps": [ 7, 31, 2] }, + { "steps": [ 6, 31, 2] }, + { "steps": [ 5, 31, 2] }, + { "steps": [ 4, 31, 2] }, + { "steps": [ 3, 31, 2] }, + { "steps": [ 2, 31, 2] }, + { "steps": [ 2, 30, 2] }, + { "steps": [ 2, 29, 2] }, + { "steps": [ 2, 28, 2] }, + { "steps": [ 2, 27, 2] }, + { "steps": [ 2, 26, 2] }, + { "steps": [ 3, 24, 2] }, + { "steps": [ 3, 23, 2] }, + { "steps": [ 3, 22, 2] }, + { "steps": [ 3, 21, 2] }, + { "steps": [ 3, 20, 2] }, + { "steps": [ 3, 19, 2] }, + { "steps": [ 3, 18, 2] }, + { "steps": [ 3, 17, 2] }, + { "steps": [ 3, 16, 2] }, + { "steps": [ 3, 15, 2] }, + { "steps": [ 3, 14, 2] }, + { "steps": [ 3, 13, 2] }, + { "steps": [ 4, 11, 2] }, + { "steps": [ 4, 10, 2] }, + { "steps": [ 4, 9, 2] }, + { "steps": [ 5, 7, 2] }, + { "steps": [ 5, 6, 2] }, + { "steps": [ 5, 5, 2] }, + { "steps": [ 6, 3, 2] }, + { "steps": [ 6, 2, 2] }, + { "steps": [ 7, 0, 2] }, + { "steps": [ 6, 0, 2] }, + { "steps": [ 5, 0, 2] }, + { "steps": [ 4, 0, 2] }, + { "steps": [ 3, 0, 2] }, + { "steps": [ 2, 0, 2] }, + { "steps": [ 1, 0, 2] }, + { "steps": [ 0, 0, 2] } + ], + }, + { + "name": "6400M_to_7000M", + "max_freq": 7000000000, + "gains": [ + { "steps": [29, 31, 2] }, + { "steps": [28, 31, 2] }, + { "steps": [27, 31, 2] }, + { "steps": [26, 31, 2] }, + { "steps": [25, 31, 2] }, + { "steps": [24, 31, 2] }, + { "steps": [23, 31, 2] }, + { "steps": [22, 31, 2] }, + { "steps": [21, 31, 2] }, + { "steps": [20, 31, 2] }, + { "steps": [19, 31, 2] }, + { "steps": [18, 31, 2] }, + { "steps": [17, 31, 2] }, + { "steps": [16, 31, 2] }, + { "steps": [15, 31, 2] }, + { "steps": [14, 31, 2] }, + { "steps": [13, 31, 2] }, + { "steps": [12, 31, 2] }, + { "steps": [11, 31, 2] }, + { "steps": [10, 31, 2] }, + { "steps": [ 9, 31, 2] }, + { "steps": [ 8, 31, 2] }, + { "steps": [ 7, 31, 2] }, + { "steps": [ 6, 31, 2] }, + { "steps": [ 5, 31, 2] }, + { "steps": [ 4, 31, 2] }, + { "steps": [ 4, 30, 2] }, + { "steps": [ 4, 29, 2] }, + { "steps": [ 4, 28, 2] }, + { "steps": [ 4, 27, 2] }, + { "steps": [ 4, 26, 2] }, + { "steps": [ 4, 25, 2] }, + { "steps": [ 4, 24, 2] }, + { "steps": [ 4, 23, 2] }, + { "steps": [ 4, 22, 2] }, + { "steps": [ 4, 21, 2] }, + { "steps": [ 4, 20, 2] }, + { "steps": [ 4, 19, 2] }, + { "steps": [ 4, 18, 2] }, + { "steps": [ 4, 17, 2] }, + { "steps": [ 5, 15, 2] }, + { "steps": [ 5, 14, 2] }, + { "steps": [ 5, 13, 2] }, + { "steps": [ 5, 12, 2] }, + { "steps": [ 5, 11, 2] }, + { "steps": [ 5, 10, 2] }, + { "steps": [ 6, 8, 2] }, + { "steps": [ 6, 7, 2] }, + { "steps": [ 6, 6, 2] }, + { "steps": [ 7, 4, 2] }, + { "steps": [ 7, 3, 2] }, + { "steps": [ 8, 1, 2] }, + { "steps": [ 8, 0, 2] }, + { "steps": [ 7, 0, 2] }, + { "steps": [ 6, 0, 2] }, + { "steps": [ 5, 0, 2] }, + { "steps": [ 4, 0, 2] }, + { "steps": [ 3, 0, 2] }, + { "steps": [ 2, 0, 2] }, + { "steps": [ 1, 0, 2] }, + { "steps": [ 0, 0, 2] } + ], + }, + { + "name": "7000M_to_7400M", + "max_freq": 7400000000, + "gains": [ + { "steps": [29, 31, 2] }, + { "steps": [28, 31, 2] }, + { "steps": [27, 31, 2] }, + { "steps": [26, 31, 2] }, + { "steps": [25, 31, 2] }, + { "steps": [24, 31, 2] }, + { "steps": [23, 31, 2] }, + { "steps": [22, 31, 2] }, + { "steps": [21, 31, 2] }, + { "steps": [20, 31, 2] }, + { "steps": [19, 31, 2] }, + { "steps": [18, 31, 2] }, + { "steps": [17, 31, 2] }, + { "steps": [16, 31, 2] }, + { "steps": [15, 31, 2] }, + { "steps": [14, 31, 2] }, + { "steps": [13, 31, 2] }, + { "steps": [12, 31, 2] }, + { "steps": [11, 31, 2] }, + { "steps": [10, 31, 2] }, + { "steps": [ 9, 31, 2] }, + { "steps": [ 8, 31, 2] }, + { "steps": [ 7, 31, 2] }, + { "steps": [ 6, 31, 2] }, + { "steps": [ 6, 30, 2] }, + { "steps": [ 6, 29, 2] }, + { "steps": [ 6, 28, 2] }, + { "steps": [ 6, 27, 2] }, + { "steps": [ 6, 26, 2] }, + { "steps": [ 6, 25, 2] }, + { "steps": [ 6, 24, 2] }, + { "steps": [ 6, 23, 2] }, + { "steps": [ 6, 22, 2] }, + { "steps": [ 6, 21, 2] }, + { "steps": [ 6, 20, 2] }, + { "steps": [ 6, 19, 2] }, + { "steps": [ 6, 18, 2] }, + { "steps": [ 6, 17, 2] }, + { "steps": [ 6, 16, 2] }, + { "steps": [ 6, 15, 2] }, + { "steps": [ 6, 14, 2] }, + { "steps": [ 7, 12, 2] }, + { "steps": [ 7, 11, 2] }, + { "steps": [ 7, 10, 2] }, + { "steps": [ 7, 9, 2] }, + { "steps": [ 7, 8, 2] }, + { "steps": [ 8, 6, 2] }, + { "steps": [ 8, 5, 2] }, + { "steps": [ 8, 4, 2] }, + { "steps": [ 9, 2, 2] }, + { "steps": [ 9, 1, 2] }, + { "steps": [ 9, 0, 2] }, + { "steps": [ 8, 0, 2] }, + { "steps": [ 7, 0, 2] }, + { "steps": [ 6, 0, 2] }, + { "steps": [ 5, 0, 2] }, + { "steps": [ 4, 0, 2] }, + { "steps": [ 3, 0, 2] }, + { "steps": [ 2, 0, 2] }, + { "steps": [ 1, 0, 2] }, + { "steps": [ 0, 0, 2] } + ], + }, + { + "name": "7400M_to_8000M", + "max_freq": 8000000000, + "gains": [ + { "steps": [29, 31, 2] }, + { "steps": [28, 31, 2] }, + { "steps": [27, 31, 2] }, + { "steps": [26, 31, 2] }, + { "steps": [25, 31, 2] }, + { "steps": [24, 31, 2] }, + { "steps": [23, 31, 2] }, + { "steps": [22, 31, 2] }, + { "steps": [21, 31, 2] }, + { "steps": [20, 31, 2] }, + { "steps": [19, 31, 2] }, + { "steps": [18, 31, 2] }, + { "steps": [17, 31, 2] }, + { "steps": [16, 31, 2] }, + { "steps": [15, 31, 2] }, + { "steps": [14, 31, 2] }, + { "steps": [13, 31, 2] }, + { "steps": [12, 31, 2] }, + { "steps": [11, 31, 2] }, + { "steps": [10, 31, 2] }, + { "steps": [ 9, 31, 2] }, + { "steps": [ 8, 31, 2] }, + { "steps": [ 7, 31, 2] }, + { "steps": [ 6, 31, 2] }, + { "steps": [ 5, 31, 2] }, + { "steps": [ 4, 31, 2] }, + { "steps": [ 3, 31, 2] }, + { "steps": [ 3, 30, 2] }, + { "steps": [ 3, 29, 2] }, + { "steps": [ 3, 28, 2] }, + { "steps": [ 3, 27, 2] }, + { "steps": [ 3, 26, 2] }, + { "steps": [ 3, 25, 2] }, + { "steps": [ 3, 24, 2] }, + { "steps": [ 3, 23, 2] }, + { "steps": [ 4, 21, 2] }, + { "steps": [ 4, 20, 2] }, + { "steps": [ 4, 19, 2] }, + { "steps": [ 4, 18, 2] }, + { "steps": [ 4, 17, 2] }, + { "steps": [ 4, 16, 2] }, + { "steps": [ 4, 15, 2] }, + { "steps": [ 4, 14, 2] }, + { "steps": [ 4, 13, 2] }, + { "steps": [ 4, 12, 2] }, + { "steps": [ 4, 11, 2] }, + { "steps": [ 4, 10, 2] }, + { "steps": [ 5, 8, 2] }, + { "steps": [ 5, 7, 2] }, + { "steps": [ 5, 6, 2] }, + { "steps": [ 6, 4, 2] }, + { "steps": [ 6, 3, 2] }, + { "steps": [ 6, 2, 2] }, + { "steps": [ 7, 0, 2] }, + { "steps": [ 6, 0, 2] }, + { "steps": [ 5, 0, 2] }, + { "steps": [ 4, 0, 2] }, + { "steps": [ 3, 0, 2] }, + { "steps": [ 2, 0, 2] }, + { "steps": [ 1, 0, 2] }, + { "steps": [ 0, 0, 2] } + ] + } + ] +} diff --git a/host/lib/rfnoc/rf_control/gain_profile.cpp b/host/lib/rfnoc/rf_control/gain_profile.cpp index 54ccb9006..9d415cb45 100644 --- a/host/lib/rfnoc/rf_control/gain_profile.cpp +++ b/host/lib/rfnoc/rf_control/gain_profile.cpp @@ -21,13 +21,16 @@ std::vector<std::string> default_gain_profile::get_gain_profile_names(const size return {DEFAULT_GAIN_PROFILE}; } -void default_gain_profile::set_gain_profile(const std::string& profile, const size_t) +void default_gain_profile::set_gain_profile(const std::string& profile, const size_t chan) { if (profile != DEFAULT_GAIN_PROFILE) { throw uhd::value_error( std::string("set_tx_gain_profile(): Unknown gain profile: `") + profile + "'"); } + if (_sub) { + _sub(profile, chan); + } } std::string default_gain_profile::get_gain_profile(const size_t) const @@ -35,6 +38,12 @@ std::string default_gain_profile::get_gain_profile(const size_t) const return DEFAULT_GAIN_PROFILE; } + +void default_gain_profile::add_subscriber(subscriber_type&& sub) +{ + _sub = std::move(sub); +} + enumerated_gain_profile::enumerated_gain_profile( const std::vector<std::string>& possible_profiles, const std::string& default_profile, @@ -52,6 +61,9 @@ void enumerated_gain_profile::set_gain_profile( throw uhd::key_error(err_msg); } _gain_profile.at(chan) = profile; + if (_sub) { + _sub(profile, chan); + } } std::string enumerated_gain_profile::get_gain_profile(const size_t chan) const @@ -65,4 +77,9 @@ std::vector<std::string> enumerated_gain_profile::get_gain_profile_names( return _possible_profiles; } +void enumerated_gain_profile::add_subscriber(subscriber_type&& sub) +{ + _sub = std::move(sub); +} + }}} // namespace uhd::rfnoc::rf_control diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt index f15324608..570972f16 100644 --- a/host/lib/usrp/CMakeLists.txt +++ b/host/lib/usrp/CMakeLists.txt @@ -41,3 +41,4 @@ INCLUDE_SUBDIRECTORY(usrp2) INCLUDE_SUBDIRECTORY(b100) INCLUDE_SUBDIRECTORY(x300) INCLUDE_SUBDIRECTORY(b200) +INCLUDE_SUBDIRECTORY(x400) diff --git a/host/lib/usrp/common/CMakeLists.txt b/host/lib/usrp/common/CMakeLists.txt index a61738743..e37a38110 100644 --- a/host/lib/usrp/common/CMakeLists.txt +++ b/host/lib/usrp/common/CMakeLists.txt @@ -30,6 +30,7 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/adf435x.cpp ${CMAKE_CURRENT_SOURCE_DIR}/adf535x.cpp ${CMAKE_CURRENT_SOURCE_DIR}/lmx2592.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/lmx2572.cpp ${CMAKE_CURRENT_SOURCE_DIR}/apply_corrections.cpp ${CMAKE_CURRENT_SOURCE_DIR}/validate_subdev_spec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/recv_packet_demuxer.cpp diff --git a/host/lib/usrp/common/lmx2572.cpp b/host/lib/usrp/common/lmx2572.cpp new file mode 100644 index 000000000..c56478b1d --- /dev/null +++ b/host/lib/usrp/common/lmx2572.cpp @@ -0,0 +1,1030 @@ +// +// Copyright 2020 Ettus Research, A National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + + +#include "lmx2572_regs.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/math.hpp> +#include <uhdlib/usrp/common/lmx2572.hpp> +#include <uhdlib/utils/interpolation.hpp> +#include <uhdlib/utils/narrow.hpp> +#include <cmath> +#include <limits> +#include <map> + +namespace { +// LOG ID +constexpr char LOG_ID[] = "LMX2572"; +// Highest LO / output frequency +constexpr double MAX_OUT_FREQ = 6.4e9; // Hz +// Lowest LO / output frequency +constexpr double MIN_OUT_FREQ = 12.5e6; // Hz +// Target loop bandwidth +constexpr double TARGET_LOOP_BANDWIDTH = 75e3; // Hz +// Loop Filter gain setting resistor +constexpr double LOOP_GAIN_SETTING_RESISTANCE = 150; // ohm +// Delay after powerup. TI recommends a 10 ms delay after clearing powerdown +// (not documented in the datasheet). +const uhd::time_spec_t POWERUP_DELAY = uhd::time_spec_t(10e-3); +// Conservative estimate for PLL to lock which includes VCO calibration and PLL settling +constexpr double PLL_LOCK_TIME = 200e-6; // s + +// Valid input/reference frequencies (fOSC) +// +// NOTE: These frequencies are valid for X400/ZBX. If we need to use this +// driver elsewhere, this part needs to be refactored. +const std::set<double> VALID_FOSC{61.44e6, 64e6, 62.5e6, 50e6}; + + +}; // namespace + +//! Control interface for an LMX2572 synthesizer +class lmx2572_impl : public lmx2572_iface +{ +public: + enum class muxout_state_t { LOCKDETECT, SDO }; + + explicit lmx2572_impl( + write_fn_t&& poke_fn, read_fn_t&& peek_fn, sleep_fn_t&& sleep_fn) + : _poke16(std::move(poke_fn)) + , _peek16(std::move(peek_fn)) + , _sleep(std::move(sleep_fn)) + , _regs() + { + _regs.save_state(); + } + + void commit() override + { + UHD_LOG_TRACE(LOG_ID, "Storing register cache to LMX2572..."); + const auto changed_addrs = _regs.get_changed_addrs<uint8_t>(); + for (const auto addr : changed_addrs) { + // We write R0 last, for double-buffering + if (addr == 0) { + continue; + } + _poke16(addr, _regs.get_reg(addr)); + } + _poke16(0, _regs.get_reg(0)); + _regs.save_state(); + UHD_LOG_TRACE(LOG_ID, + "Storing cache complete: Updated " << changed_addrs.size() << " registers."); + } + + bool get_enabled() override + { + // Chip is either in normal operation mode or power down mode + return _regs.powerdown == lmx2572_regs_t::powerdown_t::POWERDOWN_NORMAL_OPERATION; + } + + void set_enabled(const bool enabled) override + { + const bool prev_enabled = get_enabled(); + + _regs.powerdown = enabled + ? lmx2572_regs_t::powerdown_t::POWERDOWN_NORMAL_OPERATION + : lmx2572_regs_t::powerdown_t::POWERDOWN_POWER_DOWN; + _poke16(0, _regs.get_reg(0)); + + if (enabled && !prev_enabled) { + _sleep(POWERUP_DELAY); + } + } + + void reset() override + { + // Power-on Programming Sequence described in the datasheet, + // Section 7.5.1.1 + _regs = lmx2572_regs_t{}; + _regs.reset = lmx2572_regs_t::reset_t::RESET_RESET; + _poke16(0, _regs.get_reg(0)); + // Reset bit is self-clearing, it does not need to be poked twice. We + // manually reset it in the SW cache so we don't accidentally reset again + _regs.reset = lmx2572_regs_t::reset_t::RESET_NORMAL_OPERATION; + // Also enable register readback so we can read back the magic number + // register + _enable_register_readback(true); + // If the LO was previously powered down, the reset above will power it up. On + // power-up, we need to wait for the power up delay recommended by TI. + _sleep(POWERUP_DELAY); + // Check we can read back the last register, which always returns a + // magic constant: + const auto magic125 = _peek16(125); + if (magic125 != 0x2288) { + UHD_LOG_ERROR(LOG_ID, + "Unable to communicate with LMX2572! Expected R125==0x2288, got: " + << std::hex << magic125 << std::dec); + throw uhd::runtime_error("Unable to communicate to LMX2572!"); + } + UHD_LOG_TRACE(LOG_ID, "Communication with LMX2572 successful."); + // Now set _regs into a sensible state + _set_defaults(); + // Now write the regs in reverse order, skipping RO regs + const auto ro_regs = _regs.get_ro_regs(); + // Write R0 last for the double buffering + for (int addr = _regs.get_num_regs() - 2; addr >= 0; addr--) { + if (ro_regs.count(addr)) { + continue; + } + _poke16(uhd::narrow_cast<uint8_t>(addr), _regs.get_reg(addr)); + } + _regs.save_state(); + } + + bool get_lock_status() override + { + // Disable register readback which implicitly enables lock detect mode + _enable_register_readback(false); + // If the PLL is locked we expect to read 0xFFFF from any read + return _peek16(0) == 0xFFFF; + } + + uint16_t peek16(const uint8_t addr) + { + _enable_register_readback(true); + return _peek16(addr); + } + + void set_sync_mode(const bool enable) override + { + _sync_mode = enable; + } + + //! Returns the enabled/disabled state of the phase synchronization + bool get_sync_mode() override + { + return _sync_mode; + } + + void set_output_enable_all(const bool enable) override + { + set_output_enable(RF_OUTPUT_A, enable); + set_output_enable(RF_OUTPUT_B, enable); + } + + void set_output_enable(const output_t output, const bool enable) override + { + if (output == RF_OUTPUT_A) { + _regs.outa_pd = enable ? lmx2572_regs_t::outa_pd_t::OUTA_PD_NORMAL_OPERATION + : lmx2572_regs_t::outa_pd_t::OUTA_PD_POWER_DOWN; + return; + } + if (output == RF_OUTPUT_B) { + _regs.outb_pd = enable ? lmx2572_regs_t::outb_pd_t::OUTB_PD_NORMAL_OPERATION + : lmx2572_regs_t::outb_pd_t::OUTB_PD_POWER_DOWN; + return; + } + UHD_THROW_INVALID_CODE_PATH(); + } + + void set_output_power(const output_t output, const uint8_t power) override + { + if (output == RF_OUTPUT_A) { + _regs.outa_pwr = power; + return; + } + if (output == RF_OUTPUT_B) { + _regs.outb_pwr = power; + return; + } + UHD_THROW_INVALID_CODE_PATH(); + } + + void set_mux_input(const output_t output, const mux_in_t input) override + { + switch (output) { + case RF_OUTPUT_A: { + switch (input) { + case mux_in_t::DIVIDER: + _regs.outa_mux = + lmx2572_regs_t::outa_mux_t::OUTA_MUX_CHANNEL_DIVIDER; + return; + case mux_in_t::VCO: + _regs.outa_mux = lmx2572_regs_t::outa_mux_t::OUTA_MUX_VCO; + return; + case mux_in_t::HIGH_IMPEDANCE: + _regs.outa_mux = + lmx2572_regs_t::outa_mux_t::OUTA_MUX_HIGH_IMPEDANCE; + return; + default: + break; + } + break; + } + case RF_OUTPUT_B: { + switch (input) { + case mux_in_t::DIVIDER: + _regs.outb_mux = + lmx2572_regs_t::outb_mux_t::OUTB_MUX_CHANNEL_DIVIDER; + return; + case mux_in_t::VCO: + _regs.outb_mux = lmx2572_regs_t::outb_mux_t::OUTB_MUX_VCO; + return; + case mux_in_t::HIGH_IMPEDANCE: + _regs.outb_mux = + lmx2572_regs_t::outb_mux_t::OUTB_MUX_HIGH_IMPEDANCE; + return; + case mux_in_t::SYSREF: + _regs.outb_mux = lmx2572_regs_t::outb_mux_t::OUTB_MUX_SYSREF; + return; + default: + break; + } + break; + } + default: + break; + } + UHD_THROW_INVALID_CODE_PATH(); + } + + double set_frequency(const double target_freq, + const double fOSC, + const bool spur_dodging) override + { + // Sanity check + if (target_freq > MAX_OUT_FREQ || target_freq < MIN_OUT_FREQ) { + UHD_LOG_ERROR(LOG_ID, + "Invalid LMX2572 target frequency! Must be in [" + << (MIN_OUT_FREQ / 1e6) << " MHz, " << (MAX_OUT_FREQ / 1e6) + << " MHz]!"); + throw uhd::value_error("Invalid LMX2572 target frequency!"); + } + UHD_ASSERT_THROW(VALID_FOSC.count(fOSC)); + // Create an integer version of fOSC for some of the following + // calculations + const uint64_t fOSC_int = static_cast<uint64_t>(fOSC); + + // 1. Set up output/channel divider value and the output mux + const uint16_t out_D = _set_output_divider(target_freq); + const double fVCO = target_freq * out_D; + UHD_ASSERT_THROW(3200e6 <= fVCO && fVCO <= 6400e6); + + // 2. Configure the reference dividers/multipliers + _set_pll_div_and_mult(target_freq, fVCO, fOSC_int); + + // Calculate phase detector frequency + // See datasheet (Section 7.3.2): + // Equation (1): fPD = fOSC × OSC_2X × MULT / (PLL_R_PRE × PLL_R) + const double fPD = + fOSC * (_regs.osc_2x + 1) * _regs.mult / (_regs.pll_r_pre * _regs.pll_r); + + // pre-3. Identify SYNC category. + // Based on the category, we need to set VCO_PHASE_SYNC_EN appropriately + // and update our p-multiplier. + // Note: In the line below, we use target_freq and not actual_freq. That + // is OK, because we know that _get_sync_cat() only does a check to see + // if target_freq is an integer multiple of fOSC. If that's the case, + // then rounding/coercion errors won't happen between target_freq and + // actual_freq because we can always exactly produce frequencies that + // are integer multiples of fOSC. This way, we don't have a circular + // dependency (because actual_freq depends on p indirectly). + const int p = + _set_phase_sync(_get_sync_cat(_regs.mult, fOSC, target_freq, out_D)); + // P is introduced in Section 7.3.12 - calculate P with adaptation of + // Equation (3). It also comes up again in 8.1.6, although it's not + // called P there any more. There, it is the factor between N' and N. + // P == 2 whenever we're in a sync category where we need to program the + // N-divider with half the "normal" values. In TICS PRO, this value is + // described as "Calculated Included Channel Divide". + + // 3. Calculate N, PLL_NUM and PLL_DEN + const double delta_fVCO = spur_dodging ? 2e6 : 1.0; + // In the next statement, we: + // - Estimate PLL_DEN by PLL_DEN = ceil(fPD * p / delta_fVCO) + // - This value can exceed the limits of uint32_t, so we clamp it between + // 1 and 0xFFFFFFFF (the denominator can also not be zero, so we need + // to catch rounding errors) + // - Finally, convert to uint32_t + const uint32_t PLL_DEN = static_cast<uint32_t>(std::max(1.0, + std::min(std::ceil(fPD * p / delta_fVCO), + double(std::numeric_limits<uint32_t>::max())))); + UHD_ASSERT_THROW(PLL_DEN > 0); + // This is where we do the N=N'/2 division from Section 8.1.6: + const double N_real = fVCO / (fPD * p); + const uint32_t N = static_cast<uint32_t>(std::floor(N_real)); + const uint32_t PLL_NUM = std::round((N_real - double(N)) * PLL_DEN); + + // See datasheet (Section 7.3.4): + // Equation (2): fVCO = fPD * [PLL_N + (PLL_NUM / PLL_DEN)] * p + // Note that p here is the "extra divider in SYNC mode" that is in the + // text, but not listed in Eq. (2) in this section. + const double fVCO_actual = fPD * p * (N + (static_cast<double>(PLL_NUM) / PLL_DEN)); + UHD_ASSERT_THROW(3200e6 <= fVCO_actual && fVCO_actual <= 6400e6); + const double actual_freq = fVCO_actual / out_D; + // clang-format off + UHD_LOG_TRACE(LOG_ID, + "Calculating settings for fTARGET=" << (target_freq / 1e6) + << " MHz, fOSC=" << (fOSC / 1e6) + << " MHz: Target fVCO=" << (fVCO / 1e6) + << " MHz, actual fVCO=" << (fVCO_actual / 1e6) + << " MHz. R_pre=" << _regs.pll_r_pre + << " OSC2X=" << _regs.osc_2x + << " MULT=" << std::to_string(_regs.mult) + << " PLL_R=" << std::to_string(_regs.pll_r) + << " P=" << p + << " N=" << N + << " PLL_DEN=" << PLL_DEN + << " PLL_NUM=" << PLL_NUM + << " CHDIV=" << out_D); + // clang-format on + + // 4. Set frequency dependent registers + _compute_and_set_mult_hi(fOSC); + _set_pll_n(N); // Note: N-divider values already account for + _set_pll_num(PLL_NUM); // N-divider adaptations at this point. No more + _set_pll_den(PLL_DEN); // divide-by-2 necessary. + _set_fcal_hpfd_adj(fPD); + _set_fcal_lpfd_adj(fPD); + _set_pfd_dly(fVCO_actual); + _set_mash_seed(spur_dodging, PLL_NUM, fPD); + + if (get_sync_mode()) { + // From R69 register field description (Table 77): + // The delay should be at least 4 times the PLL lock time. The + // delay is expressed in state machine clock periods where + // state_machine_clock_period = 2^(CAL_CLK_DIV) / fOSC and + // CAL_CLK_DIV is one of {0, 1} + const double period = ((_regs.cal_clk_div == 0) ? 1 : 2) / fOSC; + const uint32_t mash_rst_count = + static_cast<uint32_t>(std::ceil(4 * PLL_LOCK_TIME / period)); + _set_mash_rst_count(mash_rst_count); + } + + // 5. Calculate charge pump gain + _compute_and_set_charge_pump_gain(fVCO_actual, N_real); + + // 6. Calculate VCO calibration values + _compute_and_set_vco_cal(fVCO_actual); + + // 7. Set amplitude on enabled outputs + if (_get_output_enabled(RF_OUTPUT_A)) { + _find_and_set_lo_power(actual_freq, RF_OUTPUT_A); + } + if (_get_output_enabled(RF_OUTPUT_B)) { + _find_and_set_lo_power(actual_freq, RF_OUTPUT_B); + } + + return actual_freq; + } + +private: + /************************************************************************** + * Attributes + *************************************************************************/ + write_fn_t _poke16; + read_fn_t _peek16; + sleep_fn_t _sleep; + lmx2572_regs_t _regs = lmx2572_regs_t(); + muxout_state_t _mux_state = muxout_state_t::SDO; + bool _sync_mode = false; + + /************************************************************************** + * Private Methods + *************************************************************************/ + //! Identify sync category according to Section 8.1.6 of the datasheet. This + // function implements the flowchart (Fig. 170). + sync_cat _get_sync_cat( + const uint8_t M, const double fOSC, const double fOUT, const uint16_t CHDIV) + { + if (!get_sync_mode()) { + return NONE; + } + // Right-hand path of the flowchart: + if (M > 1) { + if (CHDIV > 2) { + return CAT4; + } + if (std::fmod(fOUT, fOSC * M) != 0) { + return CAT4; + } + // In the flow chart, there's a third condition (PLL_NUM == 0) but + // that is implied in the previous condition. Here's the proof: + // 1) Because of the previous condition, we know that + // f_OUT = f_OSC * M * K, where K is an integer. + // We also know that that the doubler is disabled, so we can + // ignore it here. + // 2) PLL_NUM must be zero when f_VCO / f_PD is an integer value: + // + // f_VCO + // N = ----- + // f_PD + // + // 3) We can insert + // f_VCO = f_OUT * D + // and + // f_PD = f_OSC * M / R where R is an integer (R-divider) + // which yields: + // + // f_OUT * D * R + // N = ------------- + // f_OSC * M + // + // 4) Now we can insert 1), which yields + // + // N = K * D * R + // + // D is either 1 or 2, and K and R are integers. Therefore, N + // is an integer too, and PLL_NUM == 0. _ + // |_| + // + // Note: We could simply calculate N here, but that would require + // knowing K and R, which we can avoid with this simple comment. + } + // Left-hand path of the flowchart: + if (M == 1 && std::fmod(fOUT, fOSC) != 0) { + return CAT3; + } + if (M == 1 && CHDIV > 2) { + return CAT2; + } + if (CHDIV == 2) { + return CAT1B; + } + return CAT1A; + } + + //! Enable/disable register readback mode enabled + // SPI MISO is multiplexed to lock detect and register readback. Reading + // any register when the mux is set to lock detect will return just the + // lock detect signal, so ensure we're in readback mode if reads desired + void _enable_register_readback(const bool enable) + { + auto desired_state = enable + ? lmx2572_regs_t::muxout_ld_sel_t::MUXOUT_LD_SEL_REGISTER_READBACK + : lmx2572_regs_t::muxout_ld_sel_t::MUXOUT_LD_SEL_LOCK_DETECT; + if (_regs.muxout_ld_sel != desired_state) { + _regs.muxout_ld_sel = desired_state; + _poke16(0, _regs.get_reg(0)); + } + } + + //! Sets the output divider registers + // + // Configures both the output divider and the output mux. If the divider is + // used, the mux input is set to CHDIV, otherwise, it's set to VCO. + uint16_t _set_output_divider(const double freq) + { + // clang-format off + // Map the desired output / LO frequency to output divider settings + const std::map< + double, + std::tuple<uint16_t, lmx2572_regs_t::chdiv_t> + > out_div_map { + // freq outD chdiv + {25e6, {256, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_256}}, + {50e6, {128, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_128}}, + {100e6, {64, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_64 }}, + {200e6, {32, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_32 }}, + {400e6, {16, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_16 }}, + {800e6, {8, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_8 }}, + {1.6e9, {4, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_4 }}, + {3.2e9, {2, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_2 }}, + // CHDIV isn't used for out_divider == 1 so use DIVIDE_BY_2 + // We use +1 as an epsilon value here. Upon entering this function + // we already know that that freq <= 6.4e9. We increase the + // boundary here so that upper_bound() will not fail on the + // corner case freq == 6.4e9. + {6.4e9+1, {1, lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_2 }} + }; + // clang-format on + + uint16_t out_D; + lmx2572_regs_t::chdiv_t chdiv; + auto out_div_it = out_div_map.upper_bound(freq); + UHD_ASSERT_THROW(out_div_it != out_div_map.end()); + std::tie(out_D, chdiv) = out_div_it->second; + _regs.chdiv = lmx2572_regs_t::chdiv_t(chdiv); + // If we're using the output divider, map it to the corresponding output + // mux. Otherwise, connect the VCO directly to the mux. + const mux_in_t input = (out_D > 1) ? mux_in_t::DIVIDER : mux_in_t::VCO; + if (_get_output_enabled(RF_OUTPUT_A)) { + set_mux_input(RF_OUTPUT_A, input); + } + if (_get_output_enabled(RF_OUTPUT_B)) { + set_mux_input(RF_OUTPUT_B, input); + } + + return out_D; + } + + //! Returns the output enabled status of output + bool _get_output_enabled(const output_t output) + { + if (output == RF_OUTPUT_A) { + return _regs.outa_pd == lmx2572_regs_t::outa_pd_t::OUTA_PD_NORMAL_OPERATION; + } else { + return _regs.outb_pd == lmx2572_regs_t::outb_pd_t::OUTB_PD_NORMAL_OPERATION; + } + } + + //! Sets the MASH_RST_COUNT registers + void _set_mash_rst_count(const uint32_t mash_rst_count) + { + _regs.mash_rst_count_upper = uhd::narrow_cast<uint16_t>(mash_rst_count >> 16); + _regs.mash_rst_count_lower = uhd::narrow_cast<uint16_t>(mash_rst_count); + } + + //! Calculate and set the mult_hi register + // + // Sets the MULT_HI bit (needs to be high if the multiplier output frequency + // is larger than 100 MHz). + // + // \param ref_frequency The OSCin signal's frequency. + void _compute_and_set_mult_hi(const double fOSC) + { + const double fMULTout = + (fOSC * (int(_regs.osc_2x) + 1) * _regs.mult) / _regs.pll_r_pre; + _regs.mult_hi = (_regs.mult > 1 && fMULTout > 100e6) + ? lmx2572_regs_t::mult_hi_t::MULT_HI_GREATER_THAN_100M + : lmx2572_regs_t::mult_hi_t::MULT_HI_LESS_THAN_EQUAL_TO_100M; + } + + //! Sets the mash seed value based on fPD and whether spur dodging is enabled + void _set_mash_seed(const bool spur_dodging, const uint32_t PLL_NUM, const double pfd) + { + uint32_t mash_seed = 0; + if (spur_dodging || PLL_NUM == 0) { + // Leave mash_seed set to 0 + } + else { + const std::map<double, uint32_t> seed_map = { + {25e6, 4999}, + {30.72e6, 5531}, + {31.25e6, 5591}, + {32e6, 5657}, + {50e6, 7096}, + {61.44e6, 7841}, + {62.5e6, 7907}, + {64e6, 7993} + }; + mash_seed = seed_map.lower_bound(pfd)->second; + } + _regs.mash_seed_upper = uhd::narrow_cast<uint16_t>(mash_seed >> 16); + _regs.mash_seed_lower = uhd::narrow_cast<uint16_t>(mash_seed); + } + + void _find_and_set_lo_power(const double freq, const output_t output) + { + if (freq < 3e9) { + set_output_power(output, 25); + } else if (3e9 <= freq && freq < 4e9) { + constexpr double slope = 5.0; + constexpr double segment_range = 1e9; + constexpr int power_base = 25; + const double offset = freq - 3e9; + const uint8_t power = + std::round<uint8_t>(power_base + ((offset / segment_range) * slope)); + set_output_power(output, power); + } else if (4e9 <= freq && freq < 5e9) { + constexpr double slope = 10.0; + constexpr double segment_range = 1e9; + constexpr int power_base = 30; + const double offset = freq - 4e9; + const uint8_t power = + std::round<uint8_t>(power_base + ((offset / segment_range) * slope)); + set_output_power(output, power); + } else if (5e9 <= freq && freq < 6.4e9) { + constexpr double slope = 5 / 1.4; + constexpr double segment_range = 1.4e9; + constexpr int power_base = 40; + const double offset = freq - 5e9; + const uint8_t power = + std::round<uint8_t>(power_base + ((offset / segment_range) * slope)); + set_output_power(output, power); + } else if (freq >= 6.4e9) { + set_output_power(output, 45); + } else { + UHD_THROW_INVALID_CODE_PATH(); + } + } + + //! Sets the FCAL_HPFD_ADJ value based on fPD + void _set_fcal_hpfd_adj(const double pfd) + { + // These frequency constants are from the data sheet (Section 7.6.1) + if (pfd <= 37.5e6) { + _regs.fcal_hpfd_adj = 0x0; + } else if (37.5e6 < pfd && pfd <= 75e6) { + _regs.fcal_hpfd_adj = 0x1; + } else if (75e6 < pfd && pfd <= 100e6) { + _regs.fcal_hpfd_adj = 0x2; + } else { // 100 MHz > pfd + _regs.fcal_hpfd_adj = 0x3; + } + } + + //! Sets the FCAL_LPFD_ADJ value based on the fPD (Section 7.6.1) + void _set_fcal_lpfd_adj(const double pfd) + { + // These frequency constants are from the data sheet (Section 7.6.1) + if (pfd >= 10e6) { + _regs.fcal_lpfd_adj = 0x0; + } else if (10e6 > pfd && pfd >= 5e6) { + _regs.fcal_lpfd_adj = 0x1; + } else if (5e6 > pfd && pfd >= 2.5e6) { + _regs.fcal_lpfd_adj = 0x2; + } else { // pfd > 2.5MHz + _regs.fcal_lpfd_adj = 0x3; + } + } + + //! Sets the PFD Delay value based on fVCO (Section 7.3.4) + void _set_pfd_dly(const double fVCO) + { + UHD_ASSERT_THROW(_regs.mash_order == lmx2572_regs_t::MASH_ORDER_THIRD_ORDER); + // Thse constants / values come from the data sheet (Table 3) + if (3.2e9 <= fVCO && fVCO < 4e9) { + _regs.pfd_dly_sel = 2; + } else if (4e9 <= fVCO && fVCO < 4.9e9) { + _regs.pfd_dly_sel = 2; + } else if (4.9e9 <= fVCO && fVCO <= 6.4e9) { + _regs.pfd_dly_sel = 3; + } else { + UHD_THROW_INVALID_CODE_PATH(); + } + } + + //! Sets the PLL divider and multiplier values + void _set_pll_div_and_mult( + const double fTARGET, const double fVCO, const uint64_t fOSC_int) + { + // We want to avoid SYNC category 4 (device unreliable in SYNC mode) so + // fix the pre-divider and multiplier to 1 + // See datasheet (Section 8.1.6) + _regs.pll_r_pre = 1; + _regs.mult = 1; + // Doubler fixed to disabled + _regs.osc_2x = lmx2572_regs_t::osc_2x_t::OSC_2X_DISABLED; + // Post-divider + uint8_t pll_r = 0; + // NOTE: This calculation is designed for the ZBX daughterboard. Should + // we want to reuse this driver elsewhere, we need to factor this out + // and make it a bit nicer. + if (get_sync_mode()) { + if (fTARGET < 3200e6) { + switch (fOSC_int) { + case 61440000: { + if (3200e6 <= fVCO && fVCO < 3950e6) { + pll_r = 2; + } else if (3950e6 <= fVCO && fVCO <= 6400e6) { + pll_r = 1; + } + break; + } + case 64000000: { + if (3200e6 <= fVCO && fVCO < 4100e6) { + pll_r = 2; + } else if (4150e6 < fVCO && fVCO <= 6400e6) { + pll_r = 1; + } + break; + } + case 62500000: { + if (3200e6 <= fVCO && fVCO < 4000e6) { + pll_r = 2; + } else if (4050e6 <= fVCO && fVCO <= 6400e6) { + pll_r = 1; + } + break; + } + case 50000000: { + pll_r = 1; + break; + } + default: + UHD_THROW_INVALID_CODE_PATH(); + } // end switch + } // end if (fTARGET < 3200e6) + else { + pll_r = 1; + } + } else { + pll_r = 1; + } + UHD_ASSERT_THROW(pll_r > 0); + _regs.pll_r = pll_r; + // Section 7.3.2 states to not use both the double and the multiplier + // (M), so let's check we're doing that + UHD_ASSERT_THROW( + _regs.mult == 1 || _regs.osc_2x == lmx2572_regs_t::osc_2x_t::OSC_2X_DISABLED); + } + + //! Set the value of VCO_PHASE_SYNC_EN according to our sync category + // + // Assumption: outa_mux and outb_mux have already been appropriately + // programmed for this use case. + // + // Also calculates the P-value (see set_frequency() for more discussion on + // that value). + int _set_phase_sync(const sync_cat cat) + { + int P = 1; + // We always set the default value for VCO_PHASE_SYNC_EN here. Some + // sync categories do not, in fact, require this bit to be asserted. + // By resetting it here, we can exactly follow the datasheet in the + // following switch statement. + _regs.vco_phase_sync_en = + lmx2572_regs_t::vco_phase_sync_en_t::VCO_PHASE_SYNC_EN_NORMAL_OPERATION; + // This switch statement implements Table 137 from Section 8.1.6 of the + // datasheet. + switch (cat) { + case CAT1A: + UHD_LOG_TRACE(LOG_ID, "Sync Category: 1A"); + // Nothing required in this mode, input and output are always + // at a deterministic phase relationship. + break; + case CAT1B: + UHD_LOG_TRACE(LOG_ID, "Sync Category: 1B"); + // Set VCO_PHASE_SYNC_EN = 1 + _regs.vco_phase_sync_en = lmx2572_regs_t::vco_phase_sync_en_t:: + VCO_PHASE_SYNC_EN_PHASE_SYNC_MODE; + P = 2; + break; + case CAT2: + UHD_LOG_TRACE(LOG_ID, "Sync Category: 2"); + // Note: We assume the existence and usage of the SYNC pin here. + // This means there are no more steps required (Steps 3-6 are + // skipped). + break; + case CAT3: + UHD_LOG_TRACE(LOG_ID, "Sync Category: 3"); + // In this category, we assume that the SYNC signal will be + // applied afterwards, and that timing requirements are met. + if (_regs.outa_mux == lmx2572_regs_t::outa_mux_t::OUTA_MUX_CHANNEL_DIVIDER + || _regs.outb_mux + == lmx2572_regs_t::outb_mux_t::OUTB_MUX_CHANNEL_DIVIDER) { + P = 2; + } + _regs.vco_phase_sync_en = lmx2572_regs_t::vco_phase_sync_en_t:: + VCO_PHASE_SYNC_EN_PHASE_SYNC_MODE; + break; + case CAT4: + UHD_LOG_TRACE(LOG_ID, "Sync Category: 4"); + UHD_LOG_WARNING(LOG_ID, + "PLL programming does not allow reliable phase synchronization!"); + break; + case NONE: + // No phase sync, we're done + break; + default: + UHD_THROW_INVALID_CODE_PATH(); + } + return P; + } + + //! Compute and set charge pump gain register + // TODO: Charge pump settings will eventually come from a + // lookup table in the Cal EEPROM for Charge Pump setting vs. F_CORE VCO_. + void _compute_and_set_charge_pump_gain(const double fVCO_actual, const double N_real) + { + // clang-format off + // Table 135 (VCO Gain) + const std::map< + double, + std::tuple<double, double, uint8_t, uint8_t, uint8_t> + > vco_gain_map { + // fmax fmin fmax vco kmin kmax + {3.65e9, {3.2e9, 3.65e9, 1, 32, 47}}, + {4.2e9, {3.65e9, 4.2e9, 2, 35, 54}}, + {4.65e9, {4.2e9, 4.65e9, 3, 47, 64}}, + {5.2e9, {4.65e9, 5.2e9, 4, 50, 73}}, + {5.75e9, {5.2e9, 5.75e9, 5, 61, 82}}, + {6.4e9, {5.75e9, 6.4e9, 6, 57, 79}} + }; + // clang-format on + double fmin, fmax; + int VCO_CORE; + int KvcoMin, KvcoMax; + auto vco_gain_it = vco_gain_map.lower_bound(fVCO_actual); + UHD_ASSERT_THROW(vco_gain_it != vco_gain_map.end()); + std::tie(fmin, fmax, VCO_CORE, KvcoMin, KvcoMax) = vco_gain_it->second; + double Kvco = uhd::math::linear_interp<double>(fVCO_actual, fmin, KvcoMin, fmax, KvcoMax); + + // Calculate the optimal charge pump current (uA) + const double icp = 2 * uhd::math::PI * TARGET_LOOP_BANDWIDTH * N_real / (Kvco * LOOP_GAIN_SETTING_RESISTANCE); + + // clang-format off + // Table 2 (Charge Pump Gain) + const std::map<double, uint8_t> cpg_map = { + // gain cpg + { 0, 0}, + { 625, 1}, + {1250, 2}, + {1875, 3}, + {2500, 4}, + {3125, 5}, + {3750, 6}, + {4375, 7}, + {5000, 12}, + {5625, 13}, + {6250, 14}, + {6875, 15} + }; + // clang-format on + const uint8_t cpg = uhd::math::at_nearest(cpg_map, icp); + _regs.cpg = cpg; + } + + //! Compute and set VCO calibration values + // This method implements VCO partial assist calibration + // See datasheet (Section 8.1.4.1) + void _compute_and_set_vco_cal(const double fVCO_actual) + { + // clang-format off + // Table 136 + const std::map< + double, + std::tuple<double, double, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t> + > vco_partial_assist_map{ + // fmax fmin fmax vco Cmin Cmax Amin Amax + {3.65e9, {3.2e9, 3.65e9, 1, 131, 19, 138, 137}}, + {4.2e9, {3.65e9, 4.2e9, 2, 143, 25, 162, 142}}, + {4.65e9, {4.2e9, 4.65e9, 3, 135, 34, 126, 114}}, + {5.2e9, {4.65e9, 5.2e9, 4, 136, 25, 195, 172}}, + {5.75e9, {5.2e9, 5.75e9, 5, 133, 20, 190, 163}}, + {6.4e9, {5.75e9, 6.4e9, 6, 151, 27, 256, 204}} + }; + // clang-format on + double fmin, fmax; + uint8_t VCO_CORE, Cmin, Cmax; + uint16_t Amin, Amax; + auto vco_cal_it = vco_partial_assist_map.lower_bound(fVCO_actual); + UHD_ASSERT_THROW(vco_cal_it != vco_partial_assist_map.end()); + std::tie(fmin, fmax, VCO_CORE, Cmin, Cmax, Amin, Amax) = vco_cal_it->second; + + uint16_t VCO_CAPCTRL_STRT = + std::round(Cmin - (fVCO_actual - fmin) * (Cmin - Cmax) / (fmax - fmin)); + // From R78 register field description (Table 86) + const uint16_t VCO_CAPCTRL_STRT_MAX = 183; + VCO_CAPCTRL_STRT = std::min(VCO_CAPCTRL_STRT_MAX, VCO_CAPCTRL_STRT); + + uint16_t VCO_DACISET_STRT = + std::round(Amin - ((fVCO_actual - fmin) * (Amin - Amax) / (fmax - fmin))); + // From R17 register field description (Table 25), 9-bit register + const uint16_t VCO_DACISET_STRT_MAX = 511; // 0x1FF + VCO_DACISET_STRT = std::min(VCO_DACISET_STRT, VCO_DACISET_STRT_MAX); + + _regs.vco_sel = VCO_CORE; + _regs.vco_capctrl_strt = VCO_CAPCTRL_STRT; + _regs.vco_daciset_strt = VCO_DACISET_STRT; + } + + void _set_pll_n(const uint32_t n) + { + UHD_ASSERT_THROW((n & 0x7FFFF) == n); + // The regs object masks internally, this 0x7 is just for the sake of + // reading + _regs.pll_n_upper_3_bits = uhd::narrow_cast<uint16_t>((n >> 16) & 0x7); + _regs.pll_n_lower_16_bits = uhd::narrow_cast<uint16_t>(n); + } + + void _set_pll_num(const uint32_t num) + { + _regs.pll_num_upper = uhd::narrow_cast<uint16_t>(num >> 16); + _regs.pll_num_lower = uhd::narrow_cast<uint16_t>(num); + } + + void _set_pll_den(const uint32_t den) + { + _regs.pll_den_upper = uhd::narrow_cast<uint16_t>(den >> 16); + _regs.pll_den_lower = uhd::narrow_cast<uint16_t>(den); + } + + + // NOTE: Some of these defaults are just sensible defaults, and get + // overwritten as soon as anything interesting happens. Other defaults are + // specific to X400/ZBX. If we want to use this driver for other dboards, + // we should add APIs to set those other things in order not to have a + // leaky abstraction (we'd like to contain lmx2572_regs_t within this file). + void _set_defaults() + { + _regs.ramp_en = lmx2572_regs_t::ramp_en_t::RAMP_EN_NORMAL_OPERATION; + _regs.vco_phase_sync_en = + lmx2572_regs_t::vco_phase_sync_en_t::VCO_PHASE_SYNC_EN_NORMAL_OPERATION; + _regs.add_hold = 0; + _regs.out_mute = lmx2572_regs_t::out_mute_t::OUT_MUTE_MUTED; + _regs.fcal_hpfd_adj = 1; + _regs.fcal_lpfd_adj = 0; + _regs.fcal_en = lmx2572_regs_t::fcal_en_t::FCAL_EN_ENABLE; + _regs.muxout_ld_sel = lmx2572_regs_t::muxout_ld_sel_t::MUXOUT_LD_SEL_LOCK_DETECT; + _regs.reset = lmx2572_regs_t::reset_t::RESET_NORMAL_OPERATION; + _regs.powerdown = lmx2572_regs_t::powerdown_t::POWERDOWN_NORMAL_OPERATION; + + _regs.cal_clk_div = 0; + + _regs.ipbuf_type = lmx2572_regs_t::ipbuf_type_t::IPBUF_TYPE_DIFFERENTIAL; + _regs.ipbuf_term = lmx2572_regs_t::ipbuf_term_t::IPBUF_TERM_INTERNALLY_TERMINATED; + + _regs.out_force = lmx2572_regs_t::out_force_t::OUT_FORCE_USE_OUT_MUTE; + + // set_frequency() implements VCO Partial assist so set the correct modes of + // operation and some defaults (defaults will get overwritten) + // See datasheet (Section 8.1.4) + _regs.vco_daciset_force = + lmx2572_regs_t::vco_daciset_force_t::VCO_DACISET_FORCE_NORMAL_OPERATION; + _regs.vco_capctrl_force = + lmx2572_regs_t::vco_capctrl_force_t::VCO_CAPCTRL_FORCE_NORMAL_OPERATION; + _regs.vco_sel_force = lmx2572_regs_t::vco_sel_force_t::VCO_SEL_FORCE_DISABLED; + _regs.vco_daciset_strt = 0x096; + _regs.vco_sel = 0x6; + _regs.vco_capctrl_strt = 0; + + _regs.mult_hi = lmx2572_regs_t::mult_hi_t::MULT_HI_LESS_THAN_EQUAL_TO_100M; + _regs.osc_2x = lmx2572_regs_t::osc_2x_t::OSC_2X_DISABLED; + + _regs.mult = 1; + + _regs.pll_r = 1; + + _regs.pll_r_pre = 1; + + _regs.cpg = 7; + + _regs.pll_n_upper_3_bits = 0; + _regs.pll_n_lower_16_bits = 0x28; + + _regs.mash_seed_en = lmx2572_regs_t::mash_seed_en_t::MASH_SEED_EN_ENABLED; + _regs.pfd_dly_sel = 0x2; + + _regs.pll_den_upper = 0; + _regs.pll_den_lower = 0; + + _regs.mash_seed_upper = 0; + _regs.mash_seed_lower = 0; + + _regs.pll_num_upper = 0; + _regs.pll_num_lower = 0; + + _regs.outa_pwr = 0; + _regs.outb_pd = lmx2572_regs_t::outb_pd_t::OUTB_PD_POWER_DOWN; + _regs.outa_pd = lmx2572_regs_t::outa_pd_t::OUTA_PD_POWER_DOWN; + _regs.mash_reset_n = + lmx2572_regs_t::mash_reset_n_t::MASH_RESET_N_NORMAL_OPERATION; + _regs.mash_order = lmx2572_regs_t::mash_order_t::MASH_ORDER_THIRD_ORDER; + + _regs.outa_mux = lmx2572_regs_t::outa_mux_t::OUTA_MUX_VCO; + _regs.outb_pwr = 0; + + _regs.outb_mux = lmx2572_regs_t::outb_mux_t::OUTB_MUX_VCO; + + _regs.inpin_ignore = 0; + _regs.inpin_hyst = lmx2572_regs_t::inpin_hyst_t::INPIN_HYST_DISABLED; + _regs.inpin_lvl = lmx2572_regs_t::inpin_lvl_t::INPIN_LVL_INVALID; + _regs.inpin_fmt = + lmx2572_regs_t::inpin_fmt_t::INPIN_FMT_SYNC_EQUALS_SYSREFREQ_EQUALS_CMOS2; + + _regs.ld_type = lmx2572_regs_t::ld_type_t::LD_TYPE_VTUNE_AND_VCOCAL; + + _regs.ld_dly = 100; + // See Table 144 (LDO_DLY Setting) + _regs.ldo_dly = 3; + + _regs.dblbuf_en_0 = lmx2572_regs_t::dblbuf_en_0_t::DBLBUF_EN_0_ENABLED; + _regs.dblbuf_en_1 = lmx2572_regs_t::dblbuf_en_1_t::DBLBUF_EN_1_ENABLED; + _regs.dblbuf_en_2 = lmx2572_regs_t::dblbuf_en_2_t::DBLBUF_EN_2_ENABLED; + _regs.dblbuf_en_3 = lmx2572_regs_t::dblbuf_en_3_t::DBLBUF_EN_3_ENABLED; + _regs.dblbuf_en_4 = lmx2572_regs_t::dblbuf_en_4_t::DBLBUF_EN_4_ENABLED; + _regs.dblbuf_en_5 = lmx2572_regs_t::dblbuf_en_5_t::DBLBUF_EN_5_ENABLED; + + _regs.mash_rst_count_upper = 0; + _regs.mash_rst_count_lower = 0; + + // Always gets set by set_frequency() + _regs.chdiv = lmx2572_regs_t::chdiv_t::CHDIV_DIVIDE_BY_2; + + _regs.ramp_thresh_33rd = 0; + _regs.quick_recal_en = 0; + + _regs.ramp_thresh_upper = 0; + _regs.ramp_thresh_lower = 0; + + _regs.ramp_limit_high_33rd = 0; + _regs.ramp_limit_high_upper = 0; + _regs.ramp_limit_high_lower = 0; + + _regs.ramp_limit_low_33rd = 0; + _regs.ramp_limit_low_upper = 0; + _regs.ramp_limit_low_lower = 0; + + // Per the datasheet, the following fields need to be programmed to specific + // constants which differ from the defaults after a reset occurs + _regs.reg29_reserved0 = 0; + _regs.reg30_reserved0 = 0x18A6; + _regs.reg52_reserved0 = 0x421; + _regs.reg57_reserved0 = 0x20; + _regs.reg78_reserved0 = 1; + } +}; + +lmx2572_iface::sptr lmx2572_iface::make(lmx2572_iface::write_fn_t&& poke_fn, + lmx2572_iface::read_fn_t&& peek_fn, + lmx2572_iface::sleep_fn_t&& sleep_fn) +{ + return std::make_shared<lmx2572_impl>( + std::move(poke_fn), std::move(peek_fn), std::move(sleep_fn)); +} diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt index 2dd4e7e26..1d31d6930 100644 --- a/host/lib/usrp/dboard/CMakeLists.txt +++ b/host/lib/usrp/dboard/CMakeLists.txt @@ -49,3 +49,10 @@ endif(ENABLE_N300) if(ENABLE_N320) INCLUDE_SUBDIRECTORY(rhodium) endif(ENABLE_N320) +if(ENABLE_MPMD AND ENABLE_EISCAT) + INCLUDE_SUBDIRECTORY(eiscat) +endif(ENABLE_MPMD AND ENABLE_EISCAT) + +if(ENABLE_X400) + INCLUDE_SUBDIRECTORY(zbx) +endif(ENABLE_X400) diff --git a/host/lib/usrp/dboard/zbx/CMakeLists.txt b/host/lib/usrp/dboard/zbx/CMakeLists.txt new file mode 100644 index 000000000..4a4a39d56 --- /dev/null +++ b/host/lib/usrp/dboard/zbx/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright 2019 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +IF(ENABLE_X400) + LIST(APPEND X410_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/zbx_dboard.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/zbx_dboard_init.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/zbx_cpld_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/zbx_lo_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/zbx_expert.cpp + ) + LIBUHD_APPEND_SOURCES(${X410_SOURCES}) +ENDIF(ENABLE_X400) + diff --git a/host/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp b/host/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp new file mode 100644 index 000000000..8899f2a18 --- /dev/null +++ b/host/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp @@ -0,0 +1,931 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/utils/log.hpp> +#include <uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp> +#include <chrono> +#include <map> +#include <thread> + +namespace { +//! The time we need to wait after sending a SPI command +const uhd::time_spec_t SPI_THROTTLE_TIME = uhd::time_spec_t(2e-6); +} // namespace + +namespace uhd { namespace usrp { namespace zbx { + +// clang-format off +const std::unordered_map<size_t, std::unordered_map<zbx_cpld_ctrl::dsa_type, zbx_cpld_regs_t::zbx_cpld_field_t>> + RX_DSA_CPLD_MAP +{ + {0, { + {zbx_cpld_ctrl::dsa_type::DSA1, zbx_cpld_regs_t::zbx_cpld_field_t::RX0_DSA1}, + {zbx_cpld_ctrl::dsa_type::DSA2, zbx_cpld_regs_t::zbx_cpld_field_t::RX0_DSA2}, + {zbx_cpld_ctrl::dsa_type::DSA3A, zbx_cpld_regs_t::zbx_cpld_field_t::RX0_DSA3_A}, + {zbx_cpld_ctrl::dsa_type::DSA3B, zbx_cpld_regs_t::zbx_cpld_field_t::RX0_DSA3_B} + }}, + {1, { + {zbx_cpld_ctrl::dsa_type::DSA1, zbx_cpld_regs_t::zbx_cpld_field_t::RX1_DSA1}, + {zbx_cpld_ctrl::dsa_type::DSA2, zbx_cpld_regs_t::zbx_cpld_field_t::RX1_DSA2}, + {zbx_cpld_ctrl::dsa_type::DSA3A, zbx_cpld_regs_t::zbx_cpld_field_t::RX1_DSA3_A}, + {zbx_cpld_ctrl::dsa_type::DSA3B, zbx_cpld_regs_t::zbx_cpld_field_t::RX1_DSA3_B} + }} +}; + +const std::unordered_map<size_t, std::unordered_map<zbx_cpld_ctrl::dsa_type, zbx_cpld_regs_t::zbx_cpld_field_t>> + TX_DSA_CPLD_MAP +{ + {0, { + {zbx_cpld_ctrl::dsa_type::DSA1, zbx_cpld_regs_t::zbx_cpld_field_t::TX0_DSA1}, + {zbx_cpld_ctrl::dsa_type::DSA2, zbx_cpld_regs_t::zbx_cpld_field_t::TX0_DSA2} + }}, + {1, { + {zbx_cpld_ctrl::dsa_type::DSA1, zbx_cpld_regs_t::zbx_cpld_field_t::TX1_DSA1}, + {zbx_cpld_ctrl::dsa_type::DSA2, zbx_cpld_regs_t::zbx_cpld_field_t::TX1_DSA2} + }} +}; +// clang-format on + + +const std::unordered_map<std::string, zbx_cpld_ctrl::dsa_type> zbx_cpld_ctrl::dsa_map{ + {ZBX_GAIN_STAGE_DSA1, zbx_cpld_ctrl::dsa_type::DSA1}, + {ZBX_GAIN_STAGE_DSA2, zbx_cpld_ctrl::dsa_type::DSA2}, + {ZBX_GAIN_STAGE_DSA3A, zbx_cpld_ctrl::dsa_type::DSA3A}, + {ZBX_GAIN_STAGE_DSA3B, zbx_cpld_ctrl::dsa_type::DSA3B}}; + +zbx_cpld_ctrl::zbx_cpld_ctrl(poke_fn_type&& poke_fn, + peek_fn_type&& peek_fn, + sleep_fn_type&& sleep_fn, + const std::string& log_id) + : _poke32(std::move(poke_fn)) + , _peek32(std::move(peek_fn)) + , _sleep(std::move(sleep_fn)) + , _lo_spi_offset(_regs.get_addr("SPI_READY")) + , _log_id(log_id) +{ + UHD_LOG_TRACE(_log_id, "Entering CPLD ctor..."); + // Reset and stash the regs state. We can't assume the defaults in + // gen_zbx_cpld_regs.py match what's on the hardware. + commit(NO_CHAN, true); + _regs.save_state(); +} + +void zbx_cpld_ctrl::set_scratch(const uint32_t value) +{ + _regs.SCRATCH = value; + commit(NO_CHAN); +} + +uint32_t zbx_cpld_ctrl::get_scratch() +{ + return _peek32(_regs.get_addr("SCRATCH")); +} + +void zbx_cpld_ctrl::set_atr_mode( + const size_t channel, const atr_mode_target target, const atr_mode mode) +{ + UHD_ASSERT_THROW(channel == 0 || channel == 1); + if (target == atr_mode_target::DSA) { + if (channel == 0) { + _regs.RF0_DSA_OPTION = static_cast<zbx_cpld_regs_t::RF0_DSA_OPTION_t>(mode); + } else { + _regs.RF1_DSA_OPTION = static_cast<zbx_cpld_regs_t::RF1_DSA_OPTION_t>(mode); + } + } else { + if (channel == 0) { + _regs.RF0_OPTION = static_cast<zbx_cpld_regs_t::RF0_OPTION_t>(mode); + } else { + _regs.RF1_OPTION = static_cast<zbx_cpld_regs_t::RF1_OPTION_t>(mode); + } + } + commit(channel == 0 ? CHAN0 : CHAN1); +} + +void zbx_cpld_ctrl::set_sw_config( + const size_t channel, const atr_mode_target target, const uint8_t rf_config) +{ + UHD_ASSERT_THROW(channel == 0 || channel == 1); + // clang-format off + static const std::map<std::pair<size_t, atr_mode_target>, zbx_cpld_regs_t::zbx_cpld_field_t> + mode_map{ + {{0, atr_mode_target::PATH_LED}, zbx_cpld_regs_t::zbx_cpld_field_t::SW_RF0_CONFIG }, + {{1, atr_mode_target::PATH_LED}, zbx_cpld_regs_t::zbx_cpld_field_t::SW_RF1_CONFIG }, + {{0, atr_mode_target::DSA }, zbx_cpld_regs_t::zbx_cpld_field_t::SW_RF0_DSA_CONFIG}, + {{1, atr_mode_target::DSA }, zbx_cpld_regs_t::zbx_cpld_field_t::SW_RF1_DSA_CONFIG} + }; + // clang-format on + _regs.set_field(mode_map.at({channel, target}), rf_config); + commit(channel == 0 ? CHAN0 : CHAN1); +} + +uint8_t zbx_cpld_ctrl::get_current_config( + const size_t channel, const atr_mode_target target) +{ + UHD_ASSERT_THROW(channel == 0 || channel == 1); + const uint16_t addr = _regs.get_addr("CURRENT_RF0_CONFIG"); + const uint32_t config_reg = _peek32(addr); + _regs.set_reg(addr, config_reg); + _regs.save_state(); + // clang-format off + static const std::map<std::pair<size_t, atr_mode_target>, zbx_cpld_regs_t::zbx_cpld_field_t> + mode_map{ + {{0, atr_mode_target::PATH_LED}, zbx_cpld_regs_t::zbx_cpld_field_t::CURRENT_RF0_CONFIG }, + {{1, atr_mode_target::PATH_LED}, zbx_cpld_regs_t::zbx_cpld_field_t::CURRENT_RF1_CONFIG }, + {{0, atr_mode_target::DSA}, zbx_cpld_regs_t::zbx_cpld_field_t::CURRENT_RF0_DSA_CONFIG}, + {{1, atr_mode_target::DSA}, zbx_cpld_regs_t::zbx_cpld_field_t::CURRENT_RF1_DSA_CONFIG} + }; + // clang-format on + return _regs.get_field(mode_map.at({channel, target})); +} + +void zbx_cpld_ctrl::set_tx_gain_switches( + const size_t channel, const uint8_t idx, const tx_dsa_type& dsa_steps) +{ + UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS); + + UHD_LOG_TRACE(_log_id, + "Set TX DSA for channel " << channel << ": DSA1=" << dsa_steps[0] << ", DSA2=" + << dsa_steps[1] << ", AMP=" << dsa_steps[2]); + if (channel == 0) { + _regs.TX0_DSA1[idx] = dsa_steps[0]; + _regs.TX0_DSA2[idx] = dsa_steps[1]; + } else if (channel == 1) { + _regs.TX1_DSA1[idx] = dsa_steps[0]; + _regs.TX1_DSA2[idx] = dsa_steps[1]; + } + // Correct amp path gets configured by switch_tx_antenna_switches() + commit(channel == 0 ? CHAN0 : CHAN1); +} + +void zbx_cpld_ctrl::set_rx_gain_switches( + const size_t channel, const uint8_t idx, const rx_dsa_type& dsa_steps) +{ + UHD_LOG_TRACE(_log_id, + "Setting RX DSA for channel " + << channel << ": DSA1=" << dsa_steps[0] << ", DSA2=" << dsa_steps[1] + << ", DSA3A=" << dsa_steps[2] << ", DSA3B=" << dsa_steps[3]); + if (channel == 0) { + _regs.RX0_DSA1[idx] = dsa_steps[0]; + _regs.RX0_DSA2[idx] = dsa_steps[1]; + _regs.RX0_DSA3_A[idx] = dsa_steps[2]; + _regs.RX0_DSA3_B[idx] = dsa_steps[3]; + } else if (channel == 1) { + _regs.RX1_DSA1[idx] = dsa_steps[0]; + _regs.RX1_DSA2[idx] = dsa_steps[1]; + _regs.RX1_DSA3_A[idx] = dsa_steps[2]; + _regs.RX1_DSA3_B[idx] = dsa_steps[3]; + } + commit(channel == 0 ? CHAN0 : CHAN1); +} + +void zbx_cpld_ctrl::set_rx_gain_switches( + const size_t channel, const uint8_t idx, const uint8_t table_idx) +{ + UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS); + UHD_LOG_TRACE(_log_id, + "Setting RX DSA for channel " << channel << " from table index " << table_idx); + if (channel == 0) { + _regs.RX0_TABLE_SELECT[idx] = table_idx; + } else { + _regs.RX1_TABLE_SELECT[idx] = table_idx; + } + commit(channel == 0 ? CHAN0 : CHAN1); +} + +void zbx_cpld_ctrl::set_tx_gain_switches( + const size_t channel, const uint8_t idx, const uint8_t table_idx) +{ + UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS); + UHD_LOG_TRACE(_log_id, + "Setting TX DSA for channel " << channel << " from table index " << table_idx); + if (channel == 0) { + _regs.TX0_TABLE_SELECT[idx] = table_idx; + } else { + _regs.TX1_TABLE_SELECT[idx] = table_idx; + } + commit(channel == 0 ? CHAN0 : CHAN1); +} + +uint8_t zbx_cpld_ctrl::set_tx_dsa( + const size_t channel, const uint8_t idx, const dsa_type tx_dsa, const uint8_t att) +{ + UHD_ASSERT_THROW(channel == 0 || channel == 1); + UHD_ASSERT_THROW(tx_dsa == dsa_type::DSA1 || tx_dsa == dsa_type::DSA2); + const uint8_t att_coerced = std::min(att, ZBX_TX_DSA_MAX_ATT); + _regs.set_field(TX_DSA_CPLD_MAP.at(channel).at(tx_dsa), att_coerced, idx); + commit(channel == 0 ? CHAN0 : CHAN1); + return att_coerced; +} + +uint8_t zbx_cpld_ctrl::set_rx_dsa( + const size_t channel, const uint8_t idx, const dsa_type rx_dsa, const uint8_t att) +{ + UHD_ASSERT_THROW(channel == 0 || channel == 1); + const uint8_t att_coerced = std::min(att, ZBX_RX_DSA_MAX_ATT); + _regs.set_field(RX_DSA_CPLD_MAP.at(channel).at(rx_dsa), att_coerced, idx); + commit(channel == 0 ? CHAN0 : CHAN1); + return att_coerced; +} + +uint8_t zbx_cpld_ctrl::get_tx_dsa(const size_t channel, + const uint8_t idx, + const dsa_type tx_dsa, + const bool update_cache) +{ + UHD_ASSERT_THROW(channel == 0 || channel == 1); + UHD_ASSERT_THROW(tx_dsa == dsa_type::DSA1 || tx_dsa == dsa_type::DSA2); + if (update_cache) { + update_field(TX_DSA_CPLD_MAP.at(channel).at(tx_dsa), idx); + } + return _regs.get_field(TX_DSA_CPLD_MAP.at(channel).at(tx_dsa), idx); +} + +uint8_t zbx_cpld_ctrl::get_rx_dsa(const size_t channel, + const uint8_t idx, + const dsa_type rx_dsa, + const bool update_cache) +{ + UHD_ASSERT_THROW(channel == 0 || channel == 1); + if (update_cache) { + update_field(RX_DSA_CPLD_MAP.at(channel).at(rx_dsa), idx); + } + return _regs.get_field(RX_DSA_CPLD_MAP.at(channel).at(rx_dsa), idx); +} + +void zbx_cpld_ctrl::set_tx_antenna_switches( + const size_t channel, const uint8_t idx, const std::string& antenna, const tx_amp amp) +{ + UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS); + UHD_ASSERT_THROW( + amp == tx_amp::BYPASS || amp == tx_amp::LOWBAND || amp == tx_amp::HIGHBAND); + + // Antenna settings: TX/RX, CAL_LOOPBACK + if (channel == 0) { + if (antenna == ANTENNA_TXRX) { + // clang-format off + static const std::map<tx_amp, + std::pair<zbx_cpld_regs_t::TX0_ANT_11_t, zbx_cpld_regs_t::TX0_ANT_10_t>> amp_map{ + {tx_amp::BYPASS, {zbx_cpld_regs_t::TX0_ANT_11_BYPASS_AMP, zbx_cpld_regs_t::TX0_ANT_10_BYPASS_AMP}}, + {tx_amp::LOWBAND, {zbx_cpld_regs_t::TX0_ANT_11_LOWBAND_AMP, zbx_cpld_regs_t::TX0_ANT_10_LOWBAND_AMP}}, + {tx_amp::HIGHBAND, {zbx_cpld_regs_t::TX0_ANT_11_HIGHBAND_AMP, zbx_cpld_regs_t::TX0_ANT_10_HIGHBAND_AMP}} + }; + // clang-format on + std::tie(_regs.TX0_ANT_11[idx], _regs.TX0_ANT_10[idx]) = amp_map.at(amp); + } else if (antenna == ANTENNA_CAL_LOOPBACK) { + _regs.TX0_ANT_10[idx] = zbx_cpld_regs_t::TX0_ANT_10_CAL_LOOPBACK; + _regs.RX0_ANT_1[idx] = zbx_cpld_regs_t::RX0_ANT_1_CAL_LOOPBACK; + _regs.TX0_ANT_11[idx] = zbx_cpld_regs_t::TX0_ANT_11_BYPASS_AMP; + } else { + UHD_LOG_WARNING(_log_id, + "ZBX Radio: TX Antenna setting not recognized: \"" << antenna.c_str() + << "\""); + } + } else { + // Antenna settings: TX/RX, CAL_LOOPBACK + if (antenna == ANTENNA_TXRX) { + // clang-format off + static const std::map<tx_amp, + std::pair<zbx_cpld_regs_t::TX1_ANT_11_t, zbx_cpld_regs_t::TX1_ANT_10_t>> amp_map{ + {tx_amp::BYPASS, {zbx_cpld_regs_t::TX1_ANT_11_BYPASS_AMP, zbx_cpld_regs_t::TX1_ANT_10_BYPASS_AMP}}, + {tx_amp::LOWBAND, {zbx_cpld_regs_t::TX1_ANT_11_LOWBAND_AMP, zbx_cpld_regs_t::TX1_ANT_10_LOWBAND_AMP}}, + {tx_amp::HIGHBAND, {zbx_cpld_regs_t::TX1_ANT_11_HIGHBAND_AMP, zbx_cpld_regs_t::TX1_ANT_10_HIGHBAND_AMP}} + }; + // clang-format on + std::tie(_regs.TX1_ANT_11[idx], _regs.TX1_ANT_10[idx]) = amp_map.at(amp); + } else if (antenna == ANTENNA_CAL_LOOPBACK) { + _regs.TX1_ANT_10[idx] = zbx_cpld_regs_t::TX1_ANT_10_CAL_LOOPBACK; + _regs.RX1_ANT_1[idx] = zbx_cpld_regs_t::RX1_ANT_1_CAL_LOOPBACK; + _regs.TX1_ANT_11[idx] = zbx_cpld_regs_t::TX1_ANT_11_BYPASS_AMP; + } else { + UHD_LOG_WARNING(_log_id, + "ZBX Radio: TX Antenna setting not recognized: \"" << antenna << "\""); + } + } + commit(channel == 0 ? CHAN0 : CHAN1); +} + +void zbx_cpld_ctrl::set_rx_antenna_switches( + const size_t channel, const uint8_t idx, const std::string& antenna) +{ + UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS); + + // Antenna settings: RX2, TX/RX, CAL_LOOPBACK, TERMINATION + if (channel == 0) { + if (antenna == ANTENNA_TXRX) { + _regs.RX0_ANT_1[idx] = zbx_cpld_regs_t::RX0_ANT_1_TX_RX; + _regs.TX0_ANT_11[idx] = zbx_cpld_regs_t::TX0_ANT_11_TX_RX; + } else if (antenna == ANTENNA_CAL_LOOPBACK) { + _regs.RX0_ANT_1[idx] = zbx_cpld_regs_t::RX0_ANT_1_CAL_LOOPBACK; + _regs.TX0_ANT_10[idx] = zbx_cpld_regs_t::TX0_ANT_10_CAL_LOOPBACK; + _regs.TX0_ANT_11[idx] = zbx_cpld_regs_t::TX0_ANT_11_BYPASS_AMP; + } else if (antenna == ANTENNA_TERMINATION) { + _regs.RX0_ANT_1[idx] = zbx_cpld_regs_t::RX0_ANT_1_TERMINATION; + } else if (antenna == ANTENNA_RX) { + _regs.RX0_ANT_1[idx] = zbx_cpld_regs_t::RX0_ANT_1_RX2; + } else { + UHD_LOG_WARNING(_log_id, + "ZBX Radio: RX Antenna setting not recognized: \"" << antenna << "\""); + } + } else { + if (antenna == ANTENNA_TXRX) { + _regs.RX1_ANT_1[idx] = zbx_cpld_regs_t::RX1_ANT_1_TX_RX; + _regs.TX1_ANT_11[idx] = zbx_cpld_regs_t::TX1_ANT_11_TX_RX; + } else if (antenna == ANTENNA_CAL_LOOPBACK) { + _regs.RX1_ANT_1[idx] = zbx_cpld_regs_t::RX1_ANT_1_CAL_LOOPBACK; + _regs.TX1_ANT_10[idx] = zbx_cpld_regs_t::TX1_ANT_10_CAL_LOOPBACK; + _regs.TX1_ANT_11[idx] = zbx_cpld_regs_t::TX1_ANT_11_BYPASS_AMP; + } else if (antenna == ANTENNA_TERMINATION) { + _regs.RX1_ANT_1[idx] = zbx_cpld_regs_t::RX1_ANT_1_TERMINATION; + } else if (antenna == ANTENNA_RX) { + _regs.RX1_ANT_1[idx] = zbx_cpld_regs_t::RX1_ANT_1_RX2; + } else { + UHD_LOG_WARNING(_log_id, + "ZBX Radio: RX Antenna setting not recognized: \"" << antenna << "\""); + } + } + commit(channel == 0 ? CHAN0 : CHAN1); +} + +tx_amp zbx_cpld_ctrl::get_tx_amp_settings( + const size_t channel, const uint8_t idx, const bool update_cache) +{ + if (channel == 0) { + if (update_cache) { + update_field(zbx_cpld_regs_t::zbx_cpld_field_t::TX0_ANT_10, idx); + update_field(zbx_cpld_regs_t::zbx_cpld_field_t::TX0_ANT_11, idx); + } + if ((_regs.TX0_ANT_11[idx] == zbx_cpld_regs_t::TX0_ANT_11_BYPASS_AMP + && _regs.TX0_ANT_10[idx] != zbx_cpld_regs_t::TX0_ANT_10_BYPASS_AMP) + || (_regs.TX0_ANT_11[idx] == zbx_cpld_regs_t::TX0_ANT_11_HIGHBAND_AMP + && _regs.TX0_ANT_10[idx] != zbx_cpld_regs_t::TX0_ANT_10_HIGHBAND_AMP) + || (_regs.TX0_ANT_11[idx] == zbx_cpld_regs_t::TX0_ANT_11_LOWBAND_AMP + && _regs.TX0_ANT_10[idx] != zbx_cpld_regs_t::TX0_ANT_10_LOWBAND_AMP)) { + UHD_LOG_WARNING( + _log_id, "Detected inconsistency in the TX amp switch settings."); + } + // clang-format off + static const std::map<zbx_cpld_regs_t::TX0_ANT_10_t, tx_amp> amp_map{ + {zbx_cpld_regs_t::TX0_ANT_10_BYPASS_AMP , tx_amp::BYPASS }, + {zbx_cpld_regs_t::TX0_ANT_10_CAL_LOOPBACK, tx_amp::BYPASS }, + {zbx_cpld_regs_t::TX0_ANT_10_LOWBAND_AMP , tx_amp::LOWBAND }, + {zbx_cpld_regs_t::TX0_ANT_10_HIGHBAND_AMP, tx_amp::HIGHBAND} + }; + // clang-format on + return amp_map.at(_regs.TX0_ANT_10[idx]); + } + if (channel == 1) { + if (update_cache) { + update_field(zbx_cpld_regs_t::zbx_cpld_field_t::TX0_ANT_10, idx); + update_field(zbx_cpld_regs_t::zbx_cpld_field_t::TX0_ANT_11, idx); + } + if ((_regs.TX1_ANT_11[idx] == zbx_cpld_regs_t::TX1_ANT_11_BYPASS_AMP + && _regs.TX1_ANT_10[idx] != zbx_cpld_regs_t::TX1_ANT_10_BYPASS_AMP) + || (_regs.TX1_ANT_11[idx] == zbx_cpld_regs_t::TX1_ANT_11_HIGHBAND_AMP + && _regs.TX1_ANT_10[idx] != zbx_cpld_regs_t::TX1_ANT_10_HIGHBAND_AMP) + || (_regs.TX1_ANT_11[idx] == zbx_cpld_regs_t::TX1_ANT_11_LOWBAND_AMP + && _regs.TX1_ANT_10[idx] != zbx_cpld_regs_t::TX1_ANT_10_LOWBAND_AMP)) { + UHD_LOG_WARNING( + _log_id, "Detected inconsistency in the TX amp switch settings."); + } + // clang-format off + static const std::map<zbx_cpld_regs_t::TX1_ANT_10_t, tx_amp> amp_map{ + {zbx_cpld_regs_t::TX1_ANT_10_BYPASS_AMP , tx_amp::BYPASS }, + {zbx_cpld_regs_t::TX1_ANT_10_CAL_LOOPBACK, tx_amp::BYPASS }, + {zbx_cpld_regs_t::TX1_ANT_10_LOWBAND_AMP , tx_amp::LOWBAND }, + {zbx_cpld_regs_t::TX1_ANT_10_HIGHBAND_AMP, tx_amp::HIGHBAND} + }; + // clang-format on + return amp_map.at(_regs.TX1_ANT_10[idx]); + } + UHD_THROW_INVALID_CODE_PATH(); +} + +void zbx_cpld_ctrl::set_rx_rf_filter( + const size_t channel, const uint8_t idx, const uint8_t rf_fir) +{ + UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && rf_fir < 4); + + if (rf_fir == 0) { + if (channel == 0) { + _regs.RX0_4[idx] = zbx_cpld_regs_t::RX0_4_HIGHBAND; + _regs.RX0_2[idx] = zbx_cpld_regs_t::RX0_2_HIGHBAND; + } else { + _regs.RX1_4[idx] = zbx_cpld_regs_t::RX1_4_HIGHBAND; + _regs.RX1_2[idx] = zbx_cpld_regs_t::RX1_2_HIGHBAND; + } + } else { + // Clang-format likes to "staircase" multiple tertiary statements, it's much + // easier to read lined up + // clang-format off + if (channel == 0) { + _regs.RX0_4[idx] = zbx_cpld_regs_t::RX0_4_LOWBAND; + _regs.RX0_2[idx] = zbx_cpld_regs_t::RX0_2_LOWBAND; + _regs.RX0_RF_11[idx] = rf_fir == 1 ? zbx_cpld_regs_t::RX0_RF_11_RF_1 + : rf_fir == 2 ? zbx_cpld_regs_t::RX0_RF_11_RF_2 + : zbx_cpld_regs_t::RX0_RF_11_RF_3; + _regs.RX0_RF_3[idx] = rf_fir == 1 ? zbx_cpld_regs_t::RX0_RF_3_RF_1 + : rf_fir == 2 ? zbx_cpld_regs_t::RX0_RF_3_RF_2 + : zbx_cpld_regs_t::RX0_RF_3_RF_3; + } else { + _regs.RX1_4[idx] = zbx_cpld_regs_t::RX1_4_LOWBAND; + _regs.RX1_2[idx] = zbx_cpld_regs_t::RX1_2_LOWBAND; + _regs.RX1_RF_11[idx] = rf_fir == 1 ? zbx_cpld_regs_t::RX1_RF_11_RF_1 + : rf_fir == 2 ? zbx_cpld_regs_t::RX1_RF_11_RF_2 + : zbx_cpld_regs_t::RX1_RF_11_RF_3; + _regs.RX1_RF_3[idx] = rf_fir == 1 ? zbx_cpld_regs_t::RX1_RF_3_RF_1 + : rf_fir == 2 ? zbx_cpld_regs_t::RX1_RF_3_RF_2 + : zbx_cpld_regs_t::RX1_RF_3_RF_3; + } + // clang-format on + } + commit(channel == 0 ? CHAN0 : CHAN1); +} + +void zbx_cpld_ctrl::set_rx_if1_filter( + const size_t channel, const uint8_t idx, const uint8_t if1_fir) +{ + UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && if1_fir != 0 && if1_fir < 5); + + // Clang-format likes to "staircase" multiple tertiary statements, it's much + // easier to read lined up + // clang-format off + if (channel == 0) { + _regs.RX0_IF1_5[idx] = if1_fir == 1 ? zbx_cpld_regs_t::RX0_IF1_5_FILTER_1 + : if1_fir == 2 ? zbx_cpld_regs_t::RX0_IF1_5_FILTER_2 + : if1_fir == 3 ? zbx_cpld_regs_t::RX0_IF1_5_FILTER_3 + : zbx_cpld_regs_t::RX0_IF1_5_FILTER_4; + + _regs.RX0_IF1_6[idx] = if1_fir == 1 ? zbx_cpld_regs_t::RX0_IF1_6_FILTER_1 + : if1_fir == 2 ? zbx_cpld_regs_t::RX0_IF1_6_FILTER_2 + : if1_fir == 3 ? zbx_cpld_regs_t::RX0_IF1_6_FILTER_3 + : zbx_cpld_regs_t::RX0_IF1_6_FILTER_4; + } else { + _regs.RX1_IF1_5[idx] = if1_fir == 1 ? zbx_cpld_regs_t::RX1_IF1_5_FILTER_1 + : if1_fir == 2 ? zbx_cpld_regs_t::RX1_IF1_5_FILTER_2 + : if1_fir == 3 ? zbx_cpld_regs_t::RX1_IF1_5_FILTER_3 + : zbx_cpld_regs_t::RX1_IF1_5_FILTER_4; + + _regs.RX1_IF1_6[idx] = if1_fir == 1 ? zbx_cpld_regs_t::RX1_IF1_6_FILTER_1 + : if1_fir == 2 ? zbx_cpld_regs_t::RX1_IF1_6_FILTER_2 + : if1_fir == 3 ? zbx_cpld_regs_t::RX1_IF1_6_FILTER_3 + : zbx_cpld_regs_t::RX1_IF1_6_FILTER_4; + } + // clang-format on + commit(channel == 0 ? CHAN0 : CHAN1); +} + +void zbx_cpld_ctrl::set_rx_if2_filter( + const size_t channel, const uint8_t idx, const uint8_t if2_fir) +{ + UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && if2_fir != 0 && if2_fir < 3); + + if (channel == 0) { + _regs.RX0_IF2_7_8[idx] = if2_fir == 1 ? zbx_cpld_regs_t::RX0_IF2_7_8_FILTER_1 + : zbx_cpld_regs_t::RX0_IF2_7_8_FILTER_2; + } else { + _regs.RX1_IF2_7_8[idx] = if2_fir == 1 ? zbx_cpld_regs_t::RX1_IF2_7_8_FILTER_1 + : zbx_cpld_regs_t::RX1_IF2_7_8_FILTER_2; + } + commit(channel == 0 ? CHAN0 : CHAN1); +} + +void zbx_cpld_ctrl::set_tx_rf_filter( + const size_t channel, const uint8_t idx, const uint8_t rf_fir) +{ + UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && rf_fir < 4); + + if (rf_fir == 0) { + if (channel == 0) { + _regs.TX0_RF_9[idx] = zbx_cpld_regs_t::TX0_RF_9_HIGHBAND; + _regs.TX0_7[idx] = zbx_cpld_regs_t::TX0_7_HIGHBAND; + } else { + _regs.TX1_RF_9[idx] = zbx_cpld_regs_t::TX1_RF_9_HIGHBAND; + _regs.TX1_7[idx] = zbx_cpld_regs_t::TX1_7_HIGHBAND; + } + } else { + // Clang-format likes to "staircase" multiple tertiary statements, it's much + // easier to read lined up + // clang-format off + if (channel == 0) { + _regs.TX0_RF_9[idx] = rf_fir == 1 ? zbx_cpld_regs_t::TX0_RF_9_RF_1 + : rf_fir == 2 ? zbx_cpld_regs_t::TX0_RF_9_RF_2 + : zbx_cpld_regs_t::TX0_RF_9_RF_3; + + _regs.TX0_RF_8[idx] = rf_fir == 1 ? zbx_cpld_regs_t::TX0_RF_8_RF_1 + : rf_fir == 2 ? zbx_cpld_regs_t::TX0_RF_8_RF_2 + : zbx_cpld_regs_t::TX0_RF_8_RF_3; + _regs.TX0_7[idx] = zbx_cpld_regs_t::TX0_7_LOWBAND; + } else { + _regs.TX1_RF_9[idx] = rf_fir == 1 ? zbx_cpld_regs_t::TX1_RF_9_RF_1 + : rf_fir == 2 ? zbx_cpld_regs_t::TX1_RF_9_RF_2 + : zbx_cpld_regs_t::TX1_RF_9_RF_3; + + _regs.TX1_RF_8[idx] = rf_fir == 1 ? zbx_cpld_regs_t::TX1_RF_8_RF_1 + : rf_fir == 2 ? zbx_cpld_regs_t::TX1_RF_8_RF_2 + : zbx_cpld_regs_t::TX1_RF_8_RF_3; + _regs.TX1_7[idx] = zbx_cpld_regs_t::TX1_7_LOWBAND; + } + // clang-format on + } + commit(channel == 0 ? CHAN0 : CHAN1); +} + +void zbx_cpld_ctrl::set_tx_if1_filter( + const size_t channel, const uint8_t idx, const uint8_t if1_fir) +{ + UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && if1_fir != 0 && if1_fir < 7); + + if (if1_fir < 4) { + // Clang-format likes to "staircase" multiple tertiary statements, it's much + // easier to read lined up + // clang-format off + if (channel == 0) { + _regs.TX0_IF1_6[idx] = zbx_cpld_regs_t::TX0_IF1_6_FILTER_0_3; + _regs.TX0_IF1_3[idx] = zbx_cpld_regs_t::TX0_IF1_3_FILTER_0_3; + _regs.TX0_IF1_4[idx] = if1_fir == 1 ? zbx_cpld_regs_t::TX0_IF1_4_FILTER_1 + : if1_fir == 2 ? zbx_cpld_regs_t::TX0_IF1_4_FILTER_2 + : zbx_cpld_regs_t::TX0_IF1_4_FILTER_3; + + _regs.TX0_IF1_5[idx] = if1_fir == 1 ? zbx_cpld_regs_t::TX0_IF1_5_FILTER_1 + : if1_fir == 2 ? zbx_cpld_regs_t::TX0_IF1_5_FILTER_2 + : zbx_cpld_regs_t::TX0_IF1_5_FILTER_3; + } else { + _regs.TX1_IF1_6[idx] = zbx_cpld_regs_t::TX1_IF1_6_FILTER_0_3; + _regs.TX1_IF1_3[idx] = zbx_cpld_regs_t::TX1_IF1_3_FILTER_0_3; + _regs.TX1_IF1_4[idx] = if1_fir == 1 ? zbx_cpld_regs_t::TX1_IF1_4_FILTER_1 + : if1_fir == 2 ? zbx_cpld_regs_t::TX1_IF1_4_FILTER_2 + : zbx_cpld_regs_t::TX1_IF1_4_FILTER_3; + + _regs.TX1_IF1_5[idx] = if1_fir == 1 ? zbx_cpld_regs_t::TX1_IF1_5_FILTER_1 + : if1_fir == 2 ? zbx_cpld_regs_t::TX1_IF1_5_FILTER_2 + : zbx_cpld_regs_t::TX1_IF1_5_FILTER_3; + } + } else { + if (channel == 0) { + _regs.TX0_IF1_4[idx] = zbx_cpld_regs_t::TX0_IF1_4_TERMINATION; + _regs.TX0_IF1_5[idx] = zbx_cpld_regs_t::TX0_IF1_5_TERMINATION; + _regs.TX0_IF1_3[idx] = if1_fir == 4 ? zbx_cpld_regs_t::TX0_IF1_3_FILTER_4 + : if1_fir == 5 ? zbx_cpld_regs_t::TX0_IF1_3_FILTER_5 + : zbx_cpld_regs_t::TX0_IF1_3_FILTER_6; + + _regs.TX0_IF1_6[idx] = if1_fir == 4 ? zbx_cpld_regs_t::TX0_IF1_6_FILTER_4 + : if1_fir == 5 ? zbx_cpld_regs_t::TX0_IF1_6_FILTER_5 + : zbx_cpld_regs_t::TX0_IF1_6_FILTER_6; + } else { + _regs.TX1_IF1_4[idx] = zbx_cpld_regs_t::TX1_IF1_4_TERMINATION; + _regs.TX1_IF1_5[idx] = zbx_cpld_regs_t::TX1_IF1_5_TERMINATION; + _regs.TX1_IF1_3[idx] = if1_fir == 4 ? zbx_cpld_regs_t::TX1_IF1_3_FILTER_4 + : if1_fir == 5 ? zbx_cpld_regs_t::TX1_IF1_3_FILTER_5 + : zbx_cpld_regs_t::TX1_IF1_3_FILTER_6; + + _regs.TX1_IF1_6[idx] = if1_fir == 4 ? zbx_cpld_regs_t::TX1_IF1_6_FILTER_4 + : if1_fir == 5 ? zbx_cpld_regs_t::TX1_IF1_6_FILTER_5 + : zbx_cpld_regs_t::TX1_IF1_6_FILTER_6; + } + // clang-format on + } + commit(channel == 0 ? CHAN0 : CHAN1); +} + +void zbx_cpld_ctrl::set_tx_if2_filter( + const size_t channel, const uint8_t idx, const uint8_t if2_fir) +{ + UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS && if2_fir != 0 && if2_fir < 3); + + if (channel == 0) { + _regs.TX0_IF2_1_2[idx] = if2_fir == 1 ? zbx_cpld_regs_t::TX0_IF2_1_2_FILTER_1 + : zbx_cpld_regs_t::TX0_IF2_1_2_FILTER_2; + } else { + _regs.TX1_IF2_1_2[idx] = if2_fir == 1 ? zbx_cpld_regs_t::TX1_IF2_1_2_FILTER_1 + : zbx_cpld_regs_t::TX1_IF2_1_2_FILTER_2; + } + commit(channel == 0 ? CHAN0 : CHAN1); +} + +/****************************************************************************** + * LED control + *****************************************************************************/ +void zbx_cpld_ctrl::set_leds(const size_t channel, + const uint8_t idx, + const bool rx, + const bool trx_rx, + const bool trx_tx) +{ + UHD_ASSERT_THROW(channel < ZBX_NUM_CHANS); + if (channel == 0) { + _regs.RX0_RX_LED[idx] = rx ? zbx_cpld_regs_t::RX0_RX_LED_ENABLE + : zbx_cpld_regs_t::RX0_RX_LED_DISABLE; + _regs.RX0_TRX_LED[idx] = trx_rx ? zbx_cpld_regs_t::RX0_TRX_LED_ENABLE + : zbx_cpld_regs_t::RX0_TRX_LED_DISABLE; + _regs.TX0_TRX_LED[idx] = trx_tx ? zbx_cpld_regs_t::TX0_TRX_LED_ENABLE + : zbx_cpld_regs_t::TX0_TRX_LED_DISABLE; + } else { + _regs.RX1_RX_LED[idx] = rx ? zbx_cpld_regs_t::RX1_RX_LED_ENABLE + : zbx_cpld_regs_t::RX1_RX_LED_DISABLE; + _regs.RX1_TRX_LED[idx] = trx_rx ? zbx_cpld_regs_t::RX1_TRX_LED_ENABLE + : zbx_cpld_regs_t::RX1_TRX_LED_DISABLE; + _regs.TX1_TRX_LED[idx] = trx_tx ? zbx_cpld_regs_t::TX1_TRX_LED_ENABLE + : zbx_cpld_regs_t::TX1_TRX_LED_DISABLE; + } + commit(channel == 0 ? CHAN0 : CHAN1); +} + +/****************************************************************************** + * LO control + *****************************************************************************/ +void zbx_cpld_ctrl::lo_poke16(const zbx_lo_t lo, const uint8_t addr, const uint16_t data) +{ + _lo_spi_transact(lo, addr, data, spi_xact_t::WRITE, true); + // We always sleep here, in the assumption that the next poke to the CPLD is + // also a + // SPI transaction. + // Note that this causes minor inefficiencies when stacking SPI writes with + // other, non-SPI pokes (because the last SPI poke will still be followed by + // a sleep, which isn't even necessary). If this becomes an issue, this + // function can be changed to include a flag as an argument whether or not + // to throttle. +} + +uint16_t zbx_cpld_ctrl::lo_peek16(const zbx_lo_t lo, const uint8_t addr) +{ + _lo_spi_transact(lo, addr, 0, spi_xact_t::READ, true); + // Now poll the LO_SPI_READY register until we have good return value + const auto timeout = std::chrono::steady_clock::now() + + std::chrono::milliseconds(ZBX_LO_LOCK_TIMEOUT_MS); + while (std::chrono::steady_clock::now() < timeout) { + _regs.set_reg(_lo_spi_offset, _peek32(_lo_spi_offset)); + if (_regs.DATA_VALID) { + break; + } + } + + // Mark this register clean again + _regs.save_state(); + if (!_regs.DATA_VALID) { + const std::string err_msg = + "Unable to read back from LO SPI! Transaction timed out after " + + std::to_string(ZBX_LO_LOCK_TIMEOUT_MS) + " ms."; + UHD_LOG_ERROR(_log_id, err_msg); + throw uhd::io_error(err_msg); + } + // The read worked. Now we run some sanity checks to make sure we got what + // we expected + UHD_ASSERT_THROW(_regs.ADDRESS == addr); + UHD_ASSERT_THROW(_regs.LO_SELECT == zbx_cpld_regs_t::LO_SELECT_t(lo)); + // All good, return the read value + return _regs.DATA; +} + +bool zbx_cpld_ctrl::lo_spi_ready() +{ + return _peek32(_lo_spi_offset) & (1 << 30); +} + +void zbx_cpld_ctrl::set_lo_source( + const size_t idx, const zbx_lo_t lo, const zbx_lo_source_t lo_source) +{ + // LO source is either internal or external + const bool internal = lo_source == zbx_lo_source_t::internal; + switch (lo) { + case zbx_lo_t::TX0_LO1: + _regs.TX0_LO_14[idx] = internal ? zbx_cpld_regs_t::TX0_LO_14_INTERNAL + : zbx_cpld_regs_t::TX0_LO_14_EXTERNAL; + break; + case zbx_lo_t::TX0_LO2: + _regs.TX0_LO_13[idx] = internal ? zbx_cpld_regs_t::TX0_LO_13_INTERNAL + : zbx_cpld_regs_t::TX0_LO_13_EXTERNAL; + break; + case zbx_lo_t::TX1_LO1: + _regs.TX1_LO_14[idx] = internal ? zbx_cpld_regs_t::TX1_LO_14_INTERNAL + : zbx_cpld_regs_t::TX1_LO_14_EXTERNAL; + break; + case zbx_lo_t::TX1_LO2: + _regs.TX1_LO_13[idx] = internal ? zbx_cpld_regs_t::TX1_LO_13_INTERNAL + : zbx_cpld_regs_t::TX1_LO_13_EXTERNAL; + break; + case zbx_lo_t::RX0_LO1: + _regs.RX0_LO_9[idx] = internal ? zbx_cpld_regs_t::RX0_LO_9_INTERNAL + : zbx_cpld_regs_t::RX0_LO_9_EXTERNAL; + break; + case zbx_lo_t::RX0_LO2: + _regs.RX0_LO_10[idx] = internal ? zbx_cpld_regs_t::RX0_LO_10_INTERNAL + : zbx_cpld_regs_t::RX0_LO_10_EXTERNAL; + break; + case zbx_lo_t::RX1_LO1: + _regs.RX1_LO_9[idx] = internal ? zbx_cpld_regs_t::RX1_LO_9_INTERNAL + : zbx_cpld_regs_t::RX1_LO_9_EXTERNAL; + break; + case zbx_lo_t::RX1_LO2: + _regs.RX1_LO_10[idx] = internal ? zbx_cpld_regs_t::RX1_LO_10_INTERNAL + : zbx_cpld_regs_t::RX1_LO_10_EXTERNAL; + break; + default: + UHD_THROW_INVALID_CODE_PATH(); + } + if (lo == zbx_lo_t::TX0_LO1 || lo == zbx_lo_t::TX0_LO2 || lo == zbx_lo_t::RX0_LO1 + || lo == zbx_lo_t::RX0_LO2) { + commit(CHAN0); + } else { + commit(CHAN1); + } +} + +zbx_lo_source_t zbx_cpld_ctrl::get_lo_source(const size_t idx, zbx_lo_t lo) +{ + switch (lo) { + case zbx_lo_t::TX0_LO1: + return _regs.TX0_LO_14[idx] == zbx_cpld_regs_t::TX0_LO_14_INTERNAL + ? zbx_lo_source_t::internal + : zbx_lo_source_t::external; + case zbx_lo_t::TX0_LO2: + return _regs.TX0_LO_13[idx] == zbx_cpld_regs_t::TX0_LO_13_INTERNAL + ? zbx_lo_source_t::internal + : zbx_lo_source_t::external; + case zbx_lo_t::TX1_LO1: + return _regs.TX1_LO_14[idx] == zbx_cpld_regs_t::TX1_LO_14_INTERNAL + ? zbx_lo_source_t::internal + : zbx_lo_source_t::external; + case zbx_lo_t::TX1_LO2: + return _regs.TX1_LO_13[idx] == zbx_cpld_regs_t::TX1_LO_13_INTERNAL + ? zbx_lo_source_t::internal + : zbx_lo_source_t::external; + case zbx_lo_t::RX0_LO1: + return _regs.RX0_LO_9[idx] == zbx_cpld_regs_t::RX0_LO_9_INTERNAL + ? zbx_lo_source_t::internal + : zbx_lo_source_t::external; + case zbx_lo_t::RX0_LO2: + return _regs.RX0_LO_10[idx] == zbx_cpld_regs_t::RX0_LO_10_INTERNAL + ? zbx_lo_source_t::internal + : zbx_lo_source_t::external; + case zbx_lo_t::RX1_LO1: + return _regs.RX1_LO_9[idx] == zbx_cpld_regs_t::RX1_LO_9_INTERNAL + ? zbx_lo_source_t::internal + : zbx_lo_source_t::external; + case zbx_lo_t::RX1_LO2: + return _regs.RX1_LO_10[idx] == zbx_cpld_regs_t::RX1_LO_10_INTERNAL + ? zbx_lo_source_t::internal + : zbx_lo_source_t::external; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +void zbx_cpld_ctrl::pulse_lo_sync(const size_t ref_chan, const std::vector<zbx_lo_t>& los) +{ + if (_regs.BYPASS_SYNC_REGISTER == zbx_cpld_regs_t::BYPASS_SYNC_REGISTER_ENABLE) { + const std::string err_msg = "Cannot pulse LO SYNC when bypass is enabled!"; + UHD_LOG_ERROR(_log_id, err_msg); + throw uhd::runtime_error(_log_id + err_msg); + } + // Assert a 1 for all LOs to be sync'd + static const std::unordered_map<zbx_lo_t, zbx_cpld_regs_t::zbx_cpld_field_t> + lo_pulse_map{{ + {zbx_lo_t::TX0_LO1, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_TX0_LO1_SYNC}, + {zbx_lo_t::TX0_LO2, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_TX0_LO2_SYNC}, + {zbx_lo_t::TX1_LO1, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_TX1_LO1_SYNC}, + {zbx_lo_t::TX1_LO2, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_TX1_LO2_SYNC}, + {zbx_lo_t::RX0_LO1, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_RX0_LO1_SYNC}, + {zbx_lo_t::RX0_LO2, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_RX0_LO2_SYNC}, + {zbx_lo_t::RX1_LO1, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_RX1_LO1_SYNC}, + {zbx_lo_t::RX1_LO2, zbx_cpld_regs_t::zbx_cpld_field_t::PULSE_RX1_LO2_SYNC}, + }}; + for (const auto lo : los) { + _regs.set_field(lo_pulse_map.at(lo), 1); + } + commit(ref_chan == 0 ? CHAN0 : CHAN1); + // The bits are strobed, they self-clear. We reflect that here by resetting + // them without another commit: + for (const auto lo_it : lo_pulse_map) { + _regs.set_field(lo_it.second, 0); + } + _regs.save_state(); +} + +void zbx_cpld_ctrl::set_lo_sync_bypass(const bool enable) +{ + _regs.BYPASS_SYNC_REGISTER = enable ? zbx_cpld_regs_t::BYPASS_SYNC_REGISTER_ENABLE + : zbx_cpld_regs_t::BYPASS_SYNC_REGISTER_DISABLE; + commit(NO_CHAN); +} + +void zbx_cpld_ctrl::update_tx_dsa_settings( + const std::vector<uint32_t>& dsa1_table, const std::vector<uint32_t>& dsa2_table) +{ + write_register_vector("TX0_TABLE_DSA1", dsa1_table); + write_register_vector("TX0_TABLE_DSA2", dsa2_table); + write_register_vector("TX1_TABLE_DSA1", dsa1_table); + write_register_vector("TX1_TABLE_DSA2", dsa2_table); + commit(NO_CHAN); +} + +void zbx_cpld_ctrl::update_rx_dsa_settings(const std::vector<uint32_t>& dsa1_table, + const std::vector<uint32_t>& dsa2_table, + const std::vector<uint32_t>& dsa3a_table, + const std::vector<uint32_t>& dsa3b_table) +{ + write_register_vector("RX0_TABLE_DSA1", dsa1_table); + write_register_vector("RX0_TABLE_DSA2", dsa2_table); + write_register_vector("RX0_TABLE_DSA3_A", dsa3a_table); + write_register_vector("RX0_TABLE_DSA3_B", dsa3b_table); + write_register_vector("RX1_TABLE_DSA1", dsa1_table); + write_register_vector("RX1_TABLE_DSA2", dsa2_table); + write_register_vector("RX1_TABLE_DSA3_A", dsa3a_table); + write_register_vector("RX1_TABLE_DSA3_B", dsa3b_table); + commit(NO_CHAN); +} + +/****************************************************************************** + * Private methods + *****************************************************************************/ +void zbx_cpld_ctrl::_lo_spi_transact(const zbx_lo_t lo, + const uint8_t addr, + const uint16_t data, + const spi_xact_t xact_type, + const bool throttle) +{ + // Look up the channel based on the LO, so we can load the correct command + // time for the poke + const chan_t chan = (lo == zbx_lo_t::TX0_LO1 || lo == zbx_lo_t::TX0_LO2 + || lo == zbx_lo_t::RX0_LO1 || lo == zbx_lo_t::RX0_LO2) + ? CHAN0 + : CHAN1; + // Note: For SPI transactions, we can't also be lugging around other + // registers. This means that we assume that the state of _regs is clean. + _regs.ADDRESS = addr; + _regs.DATA = data; + _regs.READ_FLAG = (xact_type == spi_xact_t::WRITE) ? zbx_cpld_regs_t::READ_FLAG_WRITE + : zbx_cpld_regs_t::READ_FLAG_READ; + _regs.LO_SELECT = zbx_cpld_regs_t::LO_SELECT_t(lo); + _regs.START_TRANSACTION = zbx_cpld_regs_t::START_TRANSACTION_ENABLE; + _poke32(_lo_spi_offset, _regs.get_reg(_lo_spi_offset), chan); + _regs.START_TRANSACTION = zbx_cpld_regs_t::START_TRANSACTION_DISABLE; + _regs.save_state(); + // Write complete. Now we need to send a sleep to throttle the SPI + // transactions: + if (throttle) { + _sleep(SPI_THROTTLE_TIME); + } +} + +void zbx_cpld_ctrl::write_register_vector( + const std::string& reg_addr_name, const std::vector<uint32_t>& values) +{ + UHD_LOG_DEBUG( + _log_id, "Write " << values.size() << " values to register " << reg_addr_name); + zbx_cpld_regs_t::zbx_cpld_field_t type = _regs.get_field_type(reg_addr_name); + if (values.size() > _regs.get_array_size(type)) { + const std::string err_msg = "Number of values passed for register vector(" + + std::to_string(values.size()) + + ") exceeds size of register (" + + std::to_string(_regs.get_array_size(type)) + ")"; + UHD_LOG_ERROR(_log_id, err_msg); + throw uhd::runtime_error(err_msg); + } + for (size_t i = 0; i < values.size(); i++) { + _regs.set_field(type, values[i], i); + } +} + +void zbx_cpld_ctrl::commit(const chan_t chan, const bool save_all) +{ + UHD_LOG_TRACE(_log_id, + "Storing register cache " << (save_all ? "completely" : "selectively") + << " to CPLD..."); + const auto changed_addrs = save_all ? _regs.get_all_addrs() + : _regs.get_changed_addrs<size_t>(); + for (const auto addr : changed_addrs) { + _poke32(addr, _regs.get_reg(addr), save_all ? NO_CHAN : chan); + } + _regs.save_state(); + UHD_LOG_TRACE(_log_id, + "Storing cache complete: " + "Updated " + << changed_addrs.size() << " registers."); +} + +void zbx_cpld_ctrl::update_field( + const zbx_cpld_regs_t::zbx_cpld_field_t field, const size_t idx) +{ + const uint16_t addr = _regs.get_addr(field) + 4 * idx; + const uint32_t chip_val = _peek32(addr); + _regs.set_reg(addr, chip_val); + const auto changed_addrs = _regs.get_changed_addrs<size_t>(); + // If this is the only change in our register stack, then we call save_state() + // because we don't want to write this value we just read from the CPLD back + // to it. However, if there are other changes queued up, we'll have to wait + // until the next commit() call. If this is not desired, we need to update + // the regmap code to selectively save state. + if (changed_addrs.empty() + || (changed_addrs.size() == 1 && changed_addrs.count(addr))) { + _regs.save_state(); + } else { + UHD_LOG_DEBUG(_log_id, + "Not saving register state after calling update_field(). This may " + "cause unnecessary writes in the future."); + } +} + +}}} // namespace uhd::usrp::zbx diff --git a/host/lib/usrp/dboard/zbx/zbx_dboard.cpp b/host/lib/usrp/dboard/zbx/zbx_dboard.cpp new file mode 100644 index 000000000..d41302c8f --- /dev/null +++ b/host/lib/usrp/dboard/zbx/zbx_dboard.cpp @@ -0,0 +1,758 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/types/direction.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/math.hpp> +#include <uhdlib/usrp/dboard/zbx/zbx_dboard.hpp> +#include <uhdlib/utils/narrow.hpp> +#include <cstdlib> +#include <sstream> + +namespace uhd { namespace usrp { namespace zbx { + +/****************************************************************************** + * Structors + *****************************************************************************/ +zbx_dboard_impl::zbx_dboard_impl(register_iface& reg_iface, + const size_t reg_base_address, + time_accessor_fn_type&& time_accessor, + const size_t db_idx, + const std::string& radio_slot, + const std::string& rpc_prefix, + const std::string& unique_id, + uhd::usrp::x400_rpc_iface::sptr mb_rpcc, + uhd::usrp::zbx_rpc_iface::sptr rpcc, + uhd::rfnoc::x400::rfdc_control::sptr rfdcc, + uhd::property_tree::sptr tree) + : _unique_id(unique_id) + , _regs(reg_iface) + , _reg_base_address(reg_base_address) + , _time_accessor(time_accessor) + , _radio_slot(radio_slot) + , _db_idx(db_idx) + , _rpc_prefix(rpc_prefix) + , _mb_rpcc(mb_rpcc) + , _rpcc(rpcc) + , _rfdcc(rfdcc) + , _tree(tree) + , _rfdc_rate(_rpcc->get_dboard_sample_rate()) + , _prc_rate(_rpcc->get_dboard_prc_rate()) +{ + RFNOC_LOG_TRACE("Entering zbx_dboard_impl ctor..."); + RFNOC_LOG_TRACE("Radio slot: " << _radio_slot); + + _tx_gain_profile_api = std::make_shared<rf_control::enumerated_gain_profile>( + ZBX_GAIN_PROFILES, ZBX_GAIN_PROFILE_DEFAULT, ZBX_NUM_CHANS); + _rx_gain_profile_api = std::make_shared<rf_control::enumerated_gain_profile>( + ZBX_GAIN_PROFILES, ZBX_GAIN_PROFILE_DEFAULT, ZBX_NUM_CHANS); + + _expert_container = + uhd::experts::expert_factory::create_container("zbx_radio_" + _radio_slot); + _init_cpld(); + _init_peripherals(); + // Prop tree requires the initialization of certain peripherals + _init_prop_tree(); + _expert_container->resolve_all(); +} + +zbx_dboard_impl::~zbx_dboard_impl() +{ + RFNOC_LOG_TRACE("zbx_dboard::dtor() "); +} + +void zbx_dboard_impl::deinit() +{ + _wb_ifaces.clear(); +} + +void zbx_dboard_impl::set_command_time(uhd::time_spec_t time, const size_t chan) +{ + // When the command time gets updated, import it into the expert graph + get_tree() + ->access<time_spec_t>(fs_path("dboard") / "rx_frontends" / chan / "time/cmd") + .set(time); +} + +std::string zbx_dboard_impl::get_unique_id() const +{ + return _unique_id; +} + + +/****************************************************************************** + * API Calls + *****************************************************************************/ +void zbx_dboard_impl::set_tx_antenna(const std::string& ant, const size_t chan) +{ + RFNOC_LOG_TRACE("Setting TX antenna to " << ant << " for chan " << chan); + if (!TX_ANTENNA_NAME_COMPAT_MAP.count(ant)) { + assert_has(TX_ANTENNAS, ant, "tx antenna"); + } + const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan); + + _tree->access<std::string>(fe_path / "antenna" / "value").set(ant); +} + +void zbx_dboard_impl::set_rx_antenna(const std::string& ant, const size_t chan) +{ + RFNOC_LOG_TRACE("Setting RX antenna to " << ant << " for chan " << chan); + if (!RX_ANTENNA_NAME_COMPAT_MAP.count(ant)) { + assert_has(RX_ANTENNAS, ant, "rx antenna"); + } + + const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan); + + _tree->access<std::string>(fe_path / "antenna" / "value").set(ant); +} + +double zbx_dboard_impl::set_tx_frequency(const double req_freq, const size_t chan) +{ + const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan); + + _tree->access<double>(fe_path / "freq").set(req_freq); + + // Our power manager sets a new gain value via the API, based on its new calculations. + // Since the expert nodes are protected by a mutex, it will hang if we try to call + // update_power() from inside the expert resolve methods (resolve() -> update_power() + // -> set_tx_gain -> resolve()) + _tx_pwr_mgr.at(chan)->update_power(); + + return _tree->access<double>(fe_path / "freq").get(); +} + +double zbx_dboard_impl::set_rx_frequency(const double req_freq, const size_t chan) +{ + const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan); + + _tree->access<double>(fe_path / "freq").set(req_freq); + + // Our power manager sets a new gain value via the API, based on its new calculations. + // Since the expert nodes are protected by a mutex, it will hang if we try to call + // update_power() from inside the expert resolve methods (resolve() -> update_power() + // -> set_rx_gain -> resolve()) + _rx_pwr_mgr.at(chan)->update_power(); + + return _tree->access<double>(fe_path / "freq").get(); +} + +double zbx_dboard_impl::set_tx_bandwidth(const double bandwidth, const size_t chan) +{ + const double bw = get_tx_bandwidth(chan); + if (!uhd::math::frequencies_are_equal(bandwidth, bw)) { + RFNOC_LOG_WARNING("Invalid analog bandwidth: " << (bandwidth / 1e6) << " MHz."); + } + return bw; +} + +double zbx_dboard_impl::get_tx_bandwidth(size_t chan) +{ + return _tree + ->access<double>(_get_frontend_path(TX_DIRECTION, chan) / "bandwidth/value") + .get(); +} + +double zbx_dboard_impl::set_rx_bandwidth(const double bandwidth, const size_t chan) +{ + const double bw = get_rx_bandwidth(chan); + if (!uhd::math::frequencies_are_equal(bandwidth, bw)) { + RFNOC_LOG_WARNING("Invalid analog bandwidth: " << (bandwidth / 1e6) << " MHz."); + } + return bw; +} + +double zbx_dboard_impl::get_rx_bandwidth(size_t chan) +{ + return _tree + ->access<double>(_get_frontend_path(RX_DIRECTION, chan) / "bandwidth/value") + .get(); +} + +double zbx_dboard_impl::set_tx_gain( + const double gain, const std::string& name_, const size_t chan) +{ + // We have to accept the empty string for "all", because that's widely used + // (e.g. by multi_usrp) + const std::string name = name_.empty() ? ZBX_GAIN_STAGE_ALL : name_; + const fs_path gains_path = _get_frontend_path(TX_DIRECTION, chan) / "gains"; + const auto gain_profile = _tx_gain_profile_api->get_gain_profile(chan); + // Default gain profile: Setting anything other than 'all' is forbidden + if (gain_profile == ZBX_GAIN_PROFILE_DEFAULT && name != ZBX_GAIN_STAGE_ALL) { + throw uhd::key_error("Invalid gain name for gain profile 'default': " + name); + } + // Also, when the gain name is all, we have to be in default mode. + if (gain_profile != ZBX_GAIN_PROFILE_DEFAULT && name == ZBX_GAIN_STAGE_ALL) { + throw uhd::key_error( + "Setting overall gain is only valid in gain profile 'default'!"); + } + // The combination of the no-ATR profile, and any gain name other than 'table' + // is not valid. + if (gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR && name != ZBX_GAIN_STAGE_TABLE) { + throw uhd::key_error("set_tx_gain(): Invalid combination of gain profile " + + gain_profile + " and gain name " + name); + } + // First, we handle the 'table' gain name. It's handled a bit differently + // than the rest. + if (name == ZBX_GAIN_STAGE_TABLE) { + static const uhd::meta_range_t table_range(0, 255, 1); + const uint8_t table_idx = uhd::narrow<uint8_t>(table_range.clip(gain, true)); + if (gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) { + _cpld->set_sw_config(chan, zbx_cpld_ctrl::atr_mode_target::DSA, table_idx); + return static_cast<double>(table_idx); + } + if (gain_profile == ZBX_GAIN_PROFILE_MANUAL + || gain_profile == ZBX_GAIN_PROFILE_CPLD) { + _cpld->set_tx_gain_switches(chan, ATR_ADDR_TX, table_idx); + _cpld->set_tx_gain_switches(chan, ATR_ADDR_XX, table_idx); + return static_cast<double>(table_idx); + } + // That covers all the gain profiles for gain name 'table'. + UHD_THROW_INVALID_CODE_PATH(); + } + // Sanity check key. Note we do this after the previous gain stage, because + // it's not a property node. + if (!_tree->exists(gains_path / name)) { + throw uhd::key_error("Invalid TX gain stage: " + name); + } + // This leaves directly setting either the DSAs or the amplifier. This is + // possible in both the manual and CPLD gain profiles. + return _tree->access<double>(gains_path / name / "value").set(gain).get(); +} + +double zbx_dboard_impl::set_rx_gain( + const double gain, const std::string& name_, const size_t chan) +{ + // We have to accept the empty string for "all", because that's widely used + // (e.g. by multi_usrp). + const std::string name = name_.empty() ? ZBX_GAIN_STAGE_ALL : name_; + const fs_path gains_path = _get_frontend_path(RX_DIRECTION, chan) / "gains"; + const auto gain_profile = _rx_gain_profile_api->get_gain_profile(chan); + + // Default gain profile: Setting anything other than ZBX_GAIN_STAGE_ALL is forbidden + if (gain_profile == ZBX_GAIN_PROFILE_DEFAULT && name != ZBX_GAIN_STAGE_ALL) { + throw uhd::key_error("Invalid gain name for gain profile 'default': " + name); + } + // Also, when the gain name is all, we have to be in default mode. + if (gain_profile != ZBX_GAIN_PROFILE_DEFAULT && name == ZBX_GAIN_STAGE_ALL) { + throw uhd::key_error( + "Setting overall gain is only valid in gain profile 'default'!"); + } + // The combination of the no-ATR profile, and any gain name other than 'table' + // is not valid. + if (gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR && name != ZBX_GAIN_STAGE_TABLE) { + throw uhd::key_error("set_rx_gain(): Invalid combination of gain profile " + + gain_profile + " and gain name " + name); + } + // First, we handle the 'table' gain name. It's a bit different from the + // rest. + if (name == ZBX_GAIN_STAGE_TABLE) { + static const uhd::meta_range_t table_range(0, 255, 1); + const uint8_t table_idx = uhd::narrow<uint8_t>(table_range.clip(gain, true)); + if (gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) { + _cpld->set_sw_config(chan, zbx_cpld_ctrl::atr_mode_target::DSA, table_idx); + return static_cast<double>(table_idx); + } + if (gain_profile == ZBX_GAIN_PROFILE_MANUAL + || gain_profile == ZBX_GAIN_PROFILE_CPLD) { + _cpld->set_rx_gain_switches(chan, ATR_ADDR_RX, table_idx); + _cpld->set_rx_gain_switches(chan, ATR_ADDR_XX, table_idx); + return static_cast<double>(table_idx); + } + // That covers all the gain profiles for gain name 'table'. + UHD_THROW_INVALID_CODE_PATH(); + } + // Sanity check key. Note we do this after the previous gain stage, because + // it's not a property node. + if (!_tree->exists(gains_path / name / "value")) { + throw uhd::key_error("Invalid RX gain stage: " + name); + } + return _tree->access<double>(gains_path / name / "value").set(gain).get(); +} + +double zbx_dboard_impl::set_tx_gain(const double gain, const size_t chan) +{ + const auto gain_profile = _tx_gain_profile_api->get_gain_profile(chan); + if (gain_profile == ZBX_GAIN_PROFILE_MANUAL) { + const std::string err_msg = "When using 'manual' gain mode, calling " + "set_tx_gain() without a gain name is not allowed!"; + RFNOC_LOG_ERROR(err_msg); + throw uhd::runtime_error(err_msg); + } + if (gain_profile == ZBX_GAIN_PROFILE_CPLD + || gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) { + return set_tx_gain(gain, ZBX_GAIN_STAGE_TABLE, chan); + } + return set_tx_gain(gain, ZBX_GAIN_STAGE_ALL, chan); +} + +double zbx_dboard_impl::set_rx_gain(const double gain, const size_t chan) +{ + const auto gain_profile = _rx_gain_profile_api->get_gain_profile(chan); + if (gain_profile == ZBX_GAIN_PROFILE_MANUAL) { + const std::string err_msg = "When using 'manual' gain mode, calling " + "set_rx_gain() without a gain name is not allowed!"; + RFNOC_LOG_ERROR(err_msg); + throw uhd::runtime_error(err_msg); + } + if (gain_profile == ZBX_GAIN_PROFILE_CPLD + || gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) { + return set_rx_gain(gain, ZBX_GAIN_STAGE_TABLE, chan); + } + return set_rx_gain(gain, ZBX_GAIN_STAGE_ALL, chan); +} + +double zbx_dboard_impl::get_tx_gain(const size_t chan) +{ + const auto gain_profile = _tx_gain_profile_api->get_gain_profile(chan); + if (gain_profile == ZBX_GAIN_PROFILE_CPLD + || gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) { + return get_tx_gain(ZBX_GAIN_STAGE_TABLE, chan); + } + if (gain_profile == ZBX_GAIN_PROFILE_DEFAULT) { + return get_tx_gain(ZBX_GAIN_STAGE_ALL, chan); + } + throw uhd::runtime_error( + "get_tx_gain(): When in 'manual' gain profile, a gain name is required!"); +} + +double zbx_dboard_impl::get_rx_gain(const size_t chan) +{ + const auto gain_profile = _rx_gain_profile_api->get_gain_profile(chan); + if (gain_profile == ZBX_GAIN_PROFILE_CPLD + || gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) { + return get_rx_gain(ZBX_GAIN_STAGE_TABLE, chan); + } + if (gain_profile == ZBX_GAIN_PROFILE_DEFAULT) { + return get_rx_gain(ZBX_GAIN_STAGE_ALL, chan); + } + throw uhd::runtime_error( + "get_rx_gain(): When in 'manual' gain profile, a gain name is required!"); +} + +double zbx_dboard_impl::get_tx_gain(const std::string& name_, const size_t chan) +{ + // We have to accept the empty string for "all", because that's widely used + // (e.g. by multi_usrp) + const std::string name = name_.empty() ? ZBX_GAIN_STAGE_ALL : name_; + const fs_path gains_path = _get_frontend_path(TX_DIRECTION, chan) / "gains"; + const auto gain_profile = _tx_gain_profile_api->get_gain_profile(chan); + // Overall gain: Only reliable in 'default' mode. We warn, not throw, in + // the other modes. That's because reading back the overall gain is common + // diagnostic for many existing applications. + if (name == ZBX_GAIN_STAGE_ALL && gain_profile != ZBX_GAIN_PROFILE_DEFAULT) { + RFNOC_LOG_WARNING("get_tx_gain(): Trying to read back overall gain in " + "non-default gain profile is undefined."); + } + // Table gain: Returns the current DSA table index. + if (name == ZBX_GAIN_STAGE_TABLE) { + return static_cast<double>( + _cpld->get_current_config(chan, zbx_cpld_ctrl::atr_mode_target::DSA)); + } + // Otherwise: DSA or amp. Sanity check key is valid. Because the table gain + // is not a property tree node, this check comes after the previous if-clause. + if (!_tree->exists(gains_path / name / "value")) { + RFNOC_LOG_ERROR("get_tx_gain(): Invalid gain name `" << name << "'"); + throw uhd::key_error(std::string("get_tx_gain(): Invalid gain name: ") + name); + } + // We're not yet done: If we're in CPLD/table profiles, we peek the current + // DSA settings and apply them to the local cache. + // Note: This means we have a different behaviour between directly accessing + // the prop tree, or accessing the C++ API. + if ((name == ZBX_GAIN_STAGE_DSA1 || name == ZBX_GAIN_STAGE_DSA2) + && (gain_profile == ZBX_GAIN_PROFILE_CPLD + || gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR)) { + const uint8_t idx = + (gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) + ? _cpld->get_current_config(chan, zbx_cpld_ctrl::atr_mode_target::DSA) + : ATR_ADDR_TX; + constexpr bool update_cache = true; // Make sure to peek the actual value + const auto dsa = (name == ZBX_GAIN_STAGE_DSA1) ? zbx_cpld_ctrl::dsa_type::DSA1 + : zbx_cpld_ctrl::dsa_type::DSA2; + const uint8_t dsa_val = _cpld->get_tx_dsa(chan, idx, dsa, update_cache); + // Update the tree because we're good citizens, and if we switch the + // gain profile from 'table' to 'manual', we want everything to be + // consistent. This will not cause a poke to the CPLD, b/c the experts + // won't write gains in this gain profile. + // Note that the other DSA values in the tree are not updated automatically, + // which is why we can't write DSA values to the CPLD in this mode. If + // we want to allow writing DSA values in this mode, we need to update + // everything here, or put some more cleverness into the programming + // expert. + _tree->access<double>(gains_path / name / "value") + .set(ZBX_TX_DSA_MAX_ATT - dsa_val); + } + // Now return the value from the tree + return _tree->access<double>(gains_path / name / "value").get(); +} + +double zbx_dboard_impl::get_rx_gain(const std::string& name_, const size_t chan) +{ + // We have to accept the empty string for "all", because that's widely used + // (e.g. by multi_usrp) + const std::string name = name_.empty() ? ZBX_GAIN_STAGE_ALL : name_; + const fs_path gains_path = _get_frontend_path(RX_DIRECTION, chan) / "gains"; + const auto gain_profile = _rx_gain_profile_api->get_gain_profile(chan); + // Overall gain: Only reliable in 'default' mode. We warn, not throw, in + // the other modes. That's because reading back the overall gain is common + // diagnostic for many existing applications. + if (name == ZBX_GAIN_STAGE_ALL && gain_profile != ZBX_GAIN_PROFILE_DEFAULT) { + RFNOC_LOG_WARNING("get_rx_gain(): Trying to read back overall gain in " + "non-default gain profile is undefined."); + } + // Table gain: Returns the current DSA table index. + if (name == ZBX_GAIN_STAGE_TABLE) { + return static_cast<double>( + _cpld->get_current_config(chan, zbx_cpld_ctrl::atr_mode_target::DSA)); + } + // Otherwise: DSA. Sanity check key is valid. Because the table gain is not + // a property tree node, this check comes after the previous if-clause. + if (!_tree->exists(gains_path / name / "value")) { + RFNOC_LOG_ERROR("get_rx_gain(): Invalid gain name `" << name << "'"); + throw uhd::key_error(std::string("get_rx_gain(): Invalid gain name: ") + name); + } + // We're not yet done: If we're in CPLD/table profiles, we peek the current + // DSA settings and apply them to the local cache. + // Note: This means we have a different behaviour between directly accessing + // the prop tree, or accessing the C++ API. + if (gain_profile == ZBX_GAIN_PROFILE_CPLD + || gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) { + const uint8_t idx = + (gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) + ? _cpld->get_current_config(chan, zbx_cpld_ctrl::atr_mode_target::DSA) + : ATR_ADDR_RX; + constexpr bool update_cache = true; // Make sure to peek the actual value + static const std::map<std::string, zbx_cpld_ctrl::dsa_type> dsa_map{ + {ZBX_GAIN_STAGE_DSA1, zbx_cpld_ctrl::dsa_type::DSA1}, + {ZBX_GAIN_STAGE_DSA2, zbx_cpld_ctrl::dsa_type::DSA2}, + {ZBX_GAIN_STAGE_DSA3A, zbx_cpld_ctrl::dsa_type::DSA3A}, + {ZBX_GAIN_STAGE_DSA3B, zbx_cpld_ctrl::dsa_type::DSA3B}, + }; + const auto dsa = dsa_map.at(name); + const uint8_t dsa_val = _cpld->get_rx_dsa(chan, idx, dsa, update_cache); + // Update the tree because we're good citizens, and if we switch the + // gain profile from 'table' to 'manual', we want everything to be + // consistent. This will not cause a poke to the CPLD, b/c the experts + // won't write gains in this gain profile. + // Note that the other DSA values in the tree are not updated automatically, + // which is why we can't write DSA values to the CPLD in this profile. If + // we want to allow writing DSA values in this profile, we need to update + // everything here, or put some more cleverness into the programming + // expert. + _tree->access<double>(gains_path / name / "value") + .set(static_cast<double>(ZBX_RX_DSA_MAX_ATT - dsa_val)); + } + return _tree->access<double>(gains_path / name / "value").get(); +} + +std::vector<std::string> zbx_dboard_impl::get_tx_gain_names(const size_t chan) const +{ + UHD_ASSERT_THROW(chan < ZBX_NUM_CHANS); + const std::string gain_profile = _tx_gain_profile_api->get_gain_profile(chan); + + if (gain_profile == ZBX_GAIN_PROFILE_DEFAULT) { + return {ZBX_GAIN_STAGE_ALL}; + } + if (gain_profile == ZBX_GAIN_PROFILE_CPLD + || gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) { + return {ZBX_GAIN_STAGE_TABLE}; + } + return ZBX_TX_GAIN_STAGES; +} + +std::vector<std::string> zbx_dboard_impl::get_rx_gain_names(const size_t chan) const +{ + UHD_ASSERT_THROW(chan < ZBX_NUM_CHANS); + const std::string gain_profile = _rx_gain_profile_api->get_gain_profile(chan); + + if (gain_profile == ZBX_GAIN_PROFILE_DEFAULT) { + return {ZBX_GAIN_STAGE_ALL}; + } + if (gain_profile == ZBX_GAIN_PROFILE_CPLD + || gain_profile == ZBX_GAIN_PROFILE_CPLD_NOATR) { + return {ZBX_GAIN_STAGE_TABLE}; + } + return ZBX_RX_GAIN_STAGES; +} + +const std::string zbx_dboard_impl::get_tx_lo_source( + const std::string& name, const size_t chan) +{ + const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan); + if (!_tree->exists(fe_path / "ch" / name)) { + throw uhd::value_error("get_tx_lo_source(): Invalid LO name: " + name); + } + + const zbx_lo_source_t lo_source = + _tree->access<zbx_lo_source_t>(fe_path / "ch" / name / "source").get(); + return lo_source == zbx_lo_source_t::internal ? "internal" : "external"; +} + +const std::string zbx_dboard_impl::get_rx_lo_source( + const std::string& name, const size_t chan) +{ + const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan); + if (!_tree->exists(fe_path / "ch" / name)) { + throw uhd::value_error("get_rx_lo_source(): Invalid LO name: " + name); + } + + const zbx_lo_source_t lo_source = + _tree->access<zbx_lo_source_t>(fe_path / "ch" / name / "source").get(); + return lo_source == zbx_lo_source_t::internal ? "internal" : "external"; +} + +void zbx_dboard_impl::set_rx_lo_source( + const std::string& src, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_rx_lo_source(name=" << name << ", src=" << src << ")"); + const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan); + if (!_tree->exists(fe_path / "ch" / name)) { + throw uhd::value_error("set_rx_lo_source(): Invalid LO name: " + name); + } + + _tree->access<zbx_lo_source_t>(fe_path / "ch" / name / "source") + .set(src == "internal" ? zbx_lo_source_t::internal : zbx_lo_source_t::external); +} + +void zbx_dboard_impl::set_tx_lo_source( + const std::string& src, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_lo_source(name=" << name << ", src=" << src << ")"); + const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan); + if (!_tree->exists(fe_path / "ch" / name)) { + throw uhd::value_error("set_tx_lo_source(): Invalid LO name: " + name); + } + + _tree->access<zbx_lo_source_t>(fe_path / "ch" / name / "source") + .set(src == "internal" ? zbx_lo_source_t::internal : zbx_lo_source_t::external); +} + +double zbx_dboard_impl::set_tx_lo_freq( + double freq, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_tx_lo_freq(freq=" << freq << ", name=" << name << ")"); + const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan); + assert_has(ZBX_LOS, name); + + return _tree->access<double>(fe_path / "los" / name / "freq" / "value").set(freq).get(); +} + +double zbx_dboard_impl::get_tx_lo_freq(const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("get_tx_lo_freq(name=" << name << ")"); + const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan); + assert_has(ZBX_LOS, name); + + return _tree->access<double>(fe_path / "los" / name / "freq" / "value").get(); +} + +freq_range_t zbx_dboard_impl::_get_lo_freq_range( + const std::string& name, const size_t /*chan*/) const +{ + if (name == ZBX_LO1 || name == ZBX_LO2) { + // Note this doesn't include the LO step size. The LO step size is only + // used when the LO frequencies are automatically calculated (which is + // the normal use case). When setting LO frequencies manually, it is + // possible to set LOs to values outside of the step size. + return freq_range_t{LMX2572_MIN_FREQ, LMX2572_MAX_FREQ}; + } + if (name == RFDC_NCO) { + // It might make sense to constrain the possible NCO values more, since + // the bandpass filters for IF2 only allow a certain range. Note that LO1 + // and LO2 freq ranges are also constrained by their analog filters. + // But in principle, this is the range for the NCO... so why not. + return freq_range_t{0.0, _rfdc_rate}; + } + throw uhd::value_error("Invalid LO name: " + name); +} + +double zbx_dboard_impl::set_rx_lo_freq( + double freq, const std::string& name, const size_t chan) +{ + RFNOC_LOG_TRACE("set_rx_lo_freq(freq=" << freq << ", name=" << name << ")"); + const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan); + assert_has(ZBX_LOS, name); + + return _tree->access<double>(fe_path / "los" / name / "freq" / "value") + .set(freq) + .get(); +} + +double zbx_dboard_impl::get_rx_lo_freq(const std::string& name, size_t chan) +{ + RFNOC_LOG_TRACE("get_rx_lo_freq(name=" << name << ")"); + const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan); + assert_has(ZBX_LOS, name); + + return _tree->access<double>(fe_path / "los" / name / "freq" / "value").get(); +} + +std::string zbx_dboard_impl::get_tx_antenna(size_t chan) const +{ + const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan); + return _tree->access<std::string>(fe_path / "antenna" / "value").get(); +} + +std::string zbx_dboard_impl::get_rx_antenna(size_t chan) const +{ + const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan); + return _tree->access<std::string>(fe_path / "antenna" / "value").get(); +} + +double zbx_dboard_impl::get_tx_frequency(size_t chan) +{ + const fs_path fe_path = _get_frontend_path(TX_DIRECTION, chan); + return _tree->access<double>(fe_path / "freq").get(); +} + +double zbx_dboard_impl::get_rx_frequency(size_t chan) +{ + const fs_path fe_path = _get_frontend_path(RX_DIRECTION, chan); + return _tree->access<double>(fe_path / "freq").get(); +} + +void zbx_dboard_impl::set_tx_tune_args(const uhd::device_addr_t&, const size_t) +{ + RFNOC_LOG_TRACE("tune_args not supported by this radio."); +} + +void zbx_dboard_impl::set_rx_tune_args(const uhd::device_addr_t&, const size_t) +{ + RFNOC_LOG_TRACE("tune_args not supported by this radio."); +} + +void zbx_dboard_impl::set_rx_agc(const bool, const size_t) +{ + throw uhd::not_implemented_error("set_rx_agc() is not supported on this radio!"); +} + +uhd::gain_range_t zbx_dboard_impl::get_tx_gain_range( + const std::string& name, const size_t chan) const +{ + // We have to accept the empty string for "all", because that's widely used + // (e.g. by multi_usrp) + if (!name.empty() && name != ZBX_GAIN_STAGE_ALL) { + throw uhd::value_error( + std::string("get_tx_gain_range(): Unknown gain name '") + name + "'!"); + } + return get_tx_gain_range(chan); +} + +uhd::gain_range_t zbx_dboard_impl::get_rx_gain_range( + const std::string& name, const size_t chan) const +{ + // We have to accept the empty string for "all", because that's widely used + // (e.g. by multi_usrp) + if (!name.empty() && name != ZBX_GAIN_STAGE_ALL) { + throw uhd::value_error( + std::string("get_rx_gain_range(): Unknown gain name '") + name + "'!"); + } + return get_rx_gain_range(chan); +} + +void zbx_dboard_impl::set_rx_lo_export_enabled(bool, const std::string&, const size_t) +{ + throw uhd::not_implemented_error( + "set_rx_lo_export_enabled is not supported on this radio"); +} + +bool zbx_dboard_impl::get_rx_lo_export_enabled(const std::string&, const size_t) +{ + return false; +} + +void zbx_dboard_impl::set_tx_lo_export_enabled(bool, const std::string&, const size_t) +{ + throw uhd::not_implemented_error( + "set_rx_lo_export_enabled is not supported on this radio"); +} + +bool zbx_dboard_impl::get_tx_lo_export_enabled(const std::string&, const size_t) +{ + return false; +} + +/****************************************************************************** + * EEPROM API + *****************************************************************************/ +eeprom_map_t zbx_dboard_impl::get_db_eeprom() +{ + return _mb_rpcc->get_db_eeprom(_db_idx); +} + +size_t zbx_dboard_impl::get_chan_from_dboard_fe( + const std::string& fe, const uhd::direction_t) const +{ + if (fe == "0") { + return 0; + } + if (fe == "1") { + return 1; + } + throw uhd::key_error(std::string("[X400] Invalid frontend: ") + fe); +} + +std::string zbx_dboard_impl::get_dboard_fe_from_chan( + const size_t chan, const uhd::direction_t) const +{ + if (chan == 0) { + return "0"; + } + if (chan == 1) { + return "1"; + } + throw uhd::lookup_error( + std::string("[X400] Invalid channel: ") + std::to_string(chan)); +} + +/********************************************************************* + * Private misc/calculative helper functions + **********************************************************************/ + +bool zbx_dboard_impl::_get_all_los_locked(const direction_t dir, const size_t chan) +{ + const fs_path fe_path = _get_frontend_path(dir, chan); + + const bool is_lo1_enabled = _tree->access<bool>(fe_path / ZBX_LO1 / "enabled").get(); + const bool is_lo1_locked = + _lo_ctrl_map.at(zbx_lo_ctrl::lo_string_to_enum(dir, chan, ZBX_LO1)) + ->get_lock_status(); + // LO2 is always enabled via center frequency tuning, but users may manually disable + // it + const bool is_lo2_enabled = _tree->access<bool>(fe_path / ZBX_LO2 / "enabled").get(); + const bool is_lo2_locked = + _lo_ctrl_map.at(zbx_lo_ctrl::lo_string_to_enum(dir, chan, ZBX_LO2)) + ->get_lock_status(); + // We only care about the lock status if it's enabled (lowband center frequency) + // That means we have set it to true if is_lo[1,2]_enabled is *false*, but check for + // the lock if is_lo[1,2]_enabled is *true* + return (!is_lo1_enabled || is_lo1_locked) && (!is_lo2_enabled || is_lo2_locked); +} + +fs_path zbx_dboard_impl::_get_frontend_path( + const direction_t dir, const size_t chan_idx) const +{ + UHD_ASSERT_THROW(chan_idx < ZBX_NUM_CHANS); + const std::string frontend = dir == TX_DIRECTION ? "tx_frontends" : "rx_frontends"; + return fs_path("dboard") / frontend / chan_idx; +} + +std::vector<uhd::usrp::pwr_cal_mgr::sptr>& zbx_dboard_impl::get_pwr_mgr( + uhd::direction_t trx) +{ + switch (trx) { + case uhd::RX_DIRECTION: + return _rx_pwr_mgr; + case uhd::TX_DIRECTION: + return _tx_pwr_mgr; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +}}} // namespace uhd::usrp::zbx diff --git a/host/lib/usrp/dboard/zbx/zbx_dboard_init.cpp b/host/lib/usrp/dboard/zbx/zbx_dboard_init.cpp new file mode 100644 index 000000000..e6bbf2798 --- /dev/null +++ b/host/lib/usrp/dboard/zbx/zbx_dboard_init.cpp @@ -0,0 +1,685 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/cal/database.hpp> +#include <uhd/exception.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/property_tree.ipp> +#include <uhd/rfnoc/register_iface.hpp> +#include <uhd/transport/chdr.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/types/eeprom.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/log.hpp> +#include <uhdlib/experts/expert_container.hpp> +#include <uhdlib/experts/expert_factory.hpp> +#include <uhdlib/rfnoc/reg_iface_adapter.hpp> +#include <uhdlib/usrp/dboard/zbx/zbx_constants.hpp> +#include <uhdlib/usrp/dboard/zbx/zbx_dboard.hpp> +#include <uhdlib/usrp/dboard/zbx/zbx_expert.hpp> +#include <boost/algorithm/string.hpp> +#include <sstream> +#include <vector> + +using namespace uhd; +using namespace uhd::experts; +using namespace uhd::rfnoc; + +// ostream << operator overloads for our enum classes, so that property nodes of that type +// can be added to our expert graph +namespace uhd { namespace experts { + +std::ostream& operator<<( + std::ostream& os, const ::uhd::usrp::zbx::zbx_lo_source_t& lo_source) +{ + switch (lo_source) { + case ::uhd::usrp::zbx::zbx_lo_source_t::internal: + os << "internal"; + return os; + case ::uhd::usrp::zbx::zbx_lo_source_t::external: + os << "external"; + return os; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +std::ostream& operator<<( + std::ostream& os, const ::uhd::usrp::zbx::zbx_cpld_ctrl::atr_mode& atr) +{ + switch (atr) { + case ::uhd::usrp::zbx::zbx_cpld_ctrl::atr_mode::SW_DEFINED: + os << "SW_DEFINED"; + return os; + case ::uhd::usrp::zbx::zbx_cpld_ctrl::atr_mode::CLASSIC_ATR: + os << "CLASSIC ATR"; + return os; + case ::uhd::usrp::zbx::zbx_cpld_ctrl::atr_mode::FPGA_STATE: + os << "FPGA_STATE"; + return os; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} +}} // namespace uhd::experts + +namespace uhd { namespace usrp { namespace zbx { + +void zbx_dboard_impl::_init_cpld() +{ + // CPLD + RFNOC_LOG_TRACE("Initializing CPLD..."); + _cpld = std::make_shared<zbx_cpld_ctrl>( + [this]( + const uint32_t addr, const uint32_t data, const zbx_cpld_ctrl::chan_t chan) { + const auto time_spec = (chan == zbx_cpld_ctrl::NO_CHAN) + ? time_spec_t::ASAP + : (chan == zbx_cpld_ctrl::CHAN1) + ? _time_accessor(1) + : _time_accessor(0); + _regs.poke32(_reg_base_address + addr, data, time_spec); + }, + [this](const uint32_t addr) { + // We don't do timed peeks, so no chan parameter here. + return _regs.peek32(_reg_base_address + addr); + }, + [this](const uhd::time_spec_t& sleep_time) { _regs.sleep(sleep_time); }, + get_unique_id() + "::CPLD"); + UHD_ASSERT_THROW(_cpld); + // We don't have access to the scratch register, so we use the config + // registers to test communication. This also does some basic sanity check + // of the CPLDs logic. + RFNOC_LOG_TRACE("Testing CPLD communication..."); + const uint32_t random_value = static_cast<uint32_t>(time(NULL)); + _cpld->set_scratch(random_value); + UHD_ASSERT_THROW(_cpld->get_scratch() == random_value); + // Now go to classic ATR mode + RFNOC_LOG_TRACE("CPLD communication good. Switching to classic ATR mode."); + for (size_t i = 0; i < ZBX_NUM_CHANS; ++i) { + _cpld->set_atr_mode( + i, zbx_cpld_ctrl::atr_mode_target::DSA, zbx_cpld_ctrl::atr_mode::CLASSIC_ATR); + _cpld->set_atr_mode(i, + zbx_cpld_ctrl::atr_mode_target::PATH_LED, + zbx_cpld_ctrl::atr_mode::CLASSIC_ATR); + } +} + +void zbx_dboard_impl::_init_peripherals() +{ + RFNOC_LOG_TRACE("Initializing peripherals..."); + // Load DSA cal data (rx and tx) + constexpr char dsa_step_filename_tx[] = "zbx_dsa_tx"; + constexpr char dsa_step_filename_rx[] = "zbx_dsa_rx"; + uhd::eeprom_map_t eeprom_map = get_db_eeprom(); + const std::string db_serial(eeprom_map["serial"].begin(), eeprom_map["serial"].end()); + if (uhd::usrp::cal::database::has_cal_data( + dsa_step_filename_tx, db_serial, uhd::usrp::cal::source::ANY)) { + RFNOC_LOG_TRACE("load binary TX DSA steps from database..."); + const auto tx_dsa_data = uhd::usrp::cal::database::read_cal_data( + dsa_step_filename_tx, db_serial, uhd::usrp::cal::source::ANY); + RFNOC_LOG_TRACE("create TX DSA object..."); + _tx_dsa_cal = uhd::usrp::cal::zbx_tx_dsa_cal::make(); + RFNOC_LOG_TRACE("store deserialized TX DSA data into object..."); + _tx_dsa_cal->deserialize(tx_dsa_data); + } else { + RFNOC_LOG_ERROR("Could not find TX DSA cal data!"); + throw uhd::runtime_error("Could not find TX DSA cal data!"); + } + if (uhd::usrp::cal::database::has_cal_data( + dsa_step_filename_rx, db_serial, uhd::usrp::cal::source::ANY)) { + // read binary blob without knowledge about content + RFNOC_LOG_TRACE("load binary RX DSA steps from database..."); + const auto rx_dsa_data = uhd::usrp::cal::database::read_cal_data( + dsa_step_filename_rx, db_serial, uhd::usrp::cal::source::ANY); + + RFNOC_LOG_TRACE("create RX DSA object..."); + _rx_dsa_cal = uhd::usrp::cal::zbx_rx_dsa_cal::make(); + + RFNOC_LOG_TRACE("store deserialized RX DSA data into object..."); + _rx_dsa_cal->deserialize(rx_dsa_data); + } else { + RFNOC_LOG_ERROR("Could not find RX DSA cal data!"); + throw uhd::runtime_error("Could not find RX DSA cal data!"); + } +} + +void zbx_dboard_impl::_init_prop_tree() +{ + auto subtree = get_tree()->subtree(fs_path("dboard")); + + // Construct RX frontend + for (size_t chan_idx = 0; chan_idx < ZBX_NUM_CHANS; chan_idx++) { + const fs_path fe_path = fs_path("rx_frontends") / chan_idx; + + // Command time needs to be shadowed into the property tree so we can use + // it in the expert graph. TX and RX share the command time, so we could + // put it onto its own sub-tree, or copy the property between TX and RX. + // With respect to TwinRX and trying to keep the tree lean and browsable, + // we compromise and put the command time onto the RX frontend path, even + // though it's also valid for TX. + // This data node will be used for scheduling the other experts: + expert_factory::add_data_node<time_spec_t>( + _expert_container, fe_path / "time/fe", time_spec_t(0.0)); + // This prop node will be used to import the command time into the + // graph: + expert_factory::add_prop_node<time_spec_t>( + _expert_container, subtree, fe_path / "time/cmd", time_spec_t(0.0)); + + _init_frontend_subtree(subtree, RX_DIRECTION, chan_idx, fe_path); + + // The time nodes get connected with one scheduling expert per channel: + expert_factory::add_worker_node<zbx_scheduling_expert>( + _expert_container, _expert_container->node_retriever(), fe_path); + } + + // Construct TX frontend + // Note: the TX frontend uses the RX property tree, this must + // be constructed after the RX frontend + for (size_t chan_idx = 0; chan_idx < ZBX_NUM_CHANS; chan_idx++) { + const fs_path fe_path = fs_path("tx_frontends") / chan_idx; + _init_frontend_subtree(subtree, TX_DIRECTION, chan_idx, fe_path); + } + + // Now add the sync worker: + expert_factory::add_worker_node<zbx_sync_expert>(_expert_container, + _expert_container->node_retriever(), + fs_path("tx_frontends"), + fs_path("rx_frontends"), + _rfdcc, + _cpld); + + subtree->create<eeprom_map_t>("eeprom") + .add_coerced_subscriber([this](const eeprom_map_t&) { + throw uhd::runtime_error("Attempting to update daughterboard eeprom!"); + }) + .set_publisher([this]() { return get_db_eeprom(); }); +} + +void zbx_dboard_impl::_init_frontend_subtree(uhd::property_tree::sptr subtree, + const uhd::direction_t trx, + const size_t chan_idx, + const fs_path fe_path) +{ + static constexpr char ZBX_FE_NAME[] = "ZBX"; + + RFNOC_LOG_TRACE("Adding non-RFNoC block properties for channel " + << chan_idx << " to prop tree path " << fe_path); + // Standard attributes + subtree->create<std::string>(fe_path / "name").set(ZBX_FE_NAME); + subtree->create<std::string>(fe_path / "connection").set("IQ"); + + _init_frequency_prop_tree(subtree, _expert_container, fe_path); + _init_gain_prop_tree(subtree, _expert_container, trx, chan_idx, fe_path); + _init_antenna_prop_tree(subtree, _expert_container, trx, chan_idx, fe_path); + _init_lo_prop_tree(subtree, _expert_container, trx, chan_idx, fe_path); + _init_programming_prop_tree(subtree, _expert_container, fe_path); + _init_experts(subtree, _expert_container, trx, chan_idx, fe_path); +} + + +uhd::usrp::pwr_cal_mgr::sptr zbx_dboard_impl::_init_power_cal( + uhd::property_tree::sptr subtree, + const uhd::direction_t trx, + const size_t chan_idx, + const fs_path fe_path) +{ + const std::string DIR = (trx == TX_DIRECTION) ? "TX" : "RX"; + + uhd::eeprom_map_t eeprom_map = get_db_eeprom(); + /* The cal serial is the DB serial plus the FE name */ + const std::string db_serial(eeprom_map["serial"].begin(), eeprom_map["serial"].end()); + const std::string cal_serial = + db_serial + "#" + subtree->access<std::string>(fe_path / "name").get(); + /* Now create a gain group for this. */ + /* _?x_gain_groups won't work, because it doesn't group the */ + /* gains the way we want them to be grouped. */ + auto ggroup = uhd::gain_group::make(); + ggroup->register_fcns(HW_GAIN_STAGE, + {[this, trx, chan_idx]() { + return trx == TX_DIRECTION ? get_tx_gain_range(chan_idx) + : get_rx_gain_range(chan_idx); + }, + [this, trx, chan_idx]() { + return trx == TX_DIRECTION ? get_tx_gain(ZBX_GAIN_STAGE_ALL, chan_idx) + : get_rx_gain(ZBX_GAIN_STAGE_ALL, chan_idx); + }, + [this, trx, chan_idx](const double gain) { + trx == TX_DIRECTION ? set_tx_gain(gain, chan_idx) + : set_rx_gain(gain, chan_idx); + }}, + 10 /* High priority */); + /* If we had a digital (baseband) gain, we would register it here,*/ + /* so that the power manager would know to use it as a */ + /* backup gain stage. */ + /* Note that such a baseband gain might not be available */ + /* on the LV version. */ + return uhd::usrp::pwr_cal_mgr::make( + cal_serial, + "X400-CAL-" + DIR, + [this, trx, chan_idx]() { + return trx == TX_DIRECTION ? get_tx_frequency(chan_idx) + : get_rx_frequency(chan_idx); + }, + [this, + trx_str = (trx == TX_DIRECTION ? "tx" : "rx"), + fe_path, + subtree, + chan_str = std::to_string(chan_idx)]() -> std::string { + const std::string antenna = pwr_cal_mgr::sanitize_antenna_name( + subtree->access<std::string>(fe_path / "antenna/value").get()); + // The lookup key for X410 + ZBX shall start with x4xx_pwr_zbx. + // Should we rev the ZBX in a way that would make generic cal data + // unsuitable between revs, then we need to check the rev (or PID) + // here and generate a different key prefix (e.g. x4xx_pwr_zbxD_ or + // something like that). + return std::string("x4xx_pwr_zbx_") + trx_str + "_" + chan_str + "_" + + antenna; + }, + ggroup); +} + +void zbx_dboard_impl::_init_experts(uhd::property_tree::sptr subtree, + expert_container::sptr expert, + const uhd::direction_t trx, + const size_t chan_idx, + const fs_path fe_path) +{ + RFNOC_LOG_TRACE(fe_path + ", Creating experts..."); + + get_pwr_mgr(trx).insert(get_pwr_mgr(trx).begin() + chan_idx, + _init_power_cal(subtree, trx, chan_idx, fe_path)); + + // NOTE: THE ORDER OF EXPERT INITIALIZATION MATTERS + // After construction, all nodes (properties and experts) are marked dirty. Any + // subsequent calls to the container will trigger a resolve_all(), in which case + // the nodes are all resolved in REVERSE ORDER of construction, like a stack. With + // that in mind, we have to initialize the experts in line with that reverse order, + // because some experts rely on each other's construction/resolution to avoid + // errors (e.g., gain expert's dsa_cal is dependant on frequency be's coerced + // frequency, which is nan on dual_prop_node construction) After construction and + // subsequent resolution, the nodes will follow simple topological ruling as long + // as we only change one property at a time. + + // The current order should be: + // Frequency FE Expert -> LO Expert(s) -> MPM Expert -> Frequency BE Expert -> Gain + // Expert -> Programming Expert + + if (trx == TX_DIRECTION) { + expert_factory::add_worker_node<zbx_tx_programming_expert>(expert, + expert->node_retriever(), + fe_path, + fs_path("rx_frontends") / chan_idx, + chan_idx, + _tx_dsa_cal, + _cpld); + + expert_factory::add_worker_node<zbx_tx_gain_expert>(expert, + expert->node_retriever(), + fe_path, + chan_idx, + get_pwr_mgr(trx).at(chan_idx), + _tx_dsa_cal); + } else { + expert_factory::add_worker_node<zbx_rx_programming_expert>( + expert, expert->node_retriever(), fe_path, chan_idx, _rx_dsa_cal, _cpld); + + expert_factory::add_worker_node<zbx_rx_gain_expert>(expert, + expert->node_retriever(), + fe_path, + chan_idx, + get_pwr_mgr(trx).at(chan_idx), + _rx_dsa_cal); + } + + expert_factory::add_worker_node<zbx_freq_be_expert>( + expert, expert->node_retriever(), fe_path, trx, chan_idx); + + expert_factory::add_worker_node<zbx_band_inversion_expert>( + expert, expert->node_retriever(), fe_path, trx, chan_idx, _db_idx, _rpcc); + + + // Initialize our LO Control Experts + for (auto lo_select : ZBX_LOS) { + if (lo_select == RFDC_NCO) { + expert_factory::add_worker_node<zbx_rfdc_freq_expert>(expert, + expert->node_retriever(), + fe_path, + trx, + chan_idx, + _rpc_prefix, + _db_idx, + _mb_rpcc); + } else { + const zbx_lo_t lo = zbx_lo_ctrl::lo_string_to_enum(trx, chan_idx, lo_select); + std::shared_ptr<zbx_lo_ctrl> lo_ctrl = std::make_shared<zbx_lo_ctrl>( + lo, + [this, lo](const uint32_t addr, const uint16_t data) { + _cpld->lo_poke16(lo, addr, data); + }, + [this, lo](const uint32_t addr) { return _cpld->lo_peek16(lo, addr); }, + [this](const uhd::time_spec_t& sleep_time) { _regs.sleep(sleep_time); }, + LMX2572_DEFAULT_FREQ, + _prc_rate, + false); + expert_factory::add_worker_node<zbx_lo_expert>(expert, + expert->node_retriever(), + fe_path, + trx, + chan_idx, + lo_select, + lo_ctrl); + _lo_ctrl_map.insert({lo, lo_ctrl}); + } + } + + const double lo_step_size = _prc_rate / ZBX_RELATIVE_LO_STEP_SIZE; + RFNOC_LOG_DEBUG("LO step size: " << (lo_step_size / 1e6) << " MHz.") + expert_factory::add_worker_node<zbx_freq_fe_expert>(expert, + expert->node_retriever(), + fe_path, + trx, + chan_idx, + _rfdc_rate, + lo_step_size); + RFNOC_LOG_TRACE(fe_path + ", Experts created"); +} + +void zbx_dboard_impl::_init_frequency_prop_tree(uhd::property_tree::sptr subtree, + expert_container::sptr expert, + const fs_path fe_path) +{ + expert_factory::add_dual_prop_node<double>( + expert, subtree, fe_path / "freq", ZBX_DEFAULT_FREQ, AUTO_RESOLVE_ON_WRITE); + expert_factory::add_dual_prop_node<double>( + expert, subtree, fe_path / "if_freq", 0.0, AUTO_RESOLVE_ON_WRITE); + expert_factory::add_data_node<bool>(expert, fe_path / "is_highband", false); + expert_factory::add_data_node<int>( + expert, fe_path / "mixer1_m", 0, AUTO_RESOLVE_ON_WRITE); + expert_factory::add_data_node<int>( + expert, fe_path / "mixer1_n", 0, AUTO_RESOLVE_ON_WRITE); + expert_factory::add_data_node<int>( + expert, fe_path / "mixer2_m", 0, AUTO_RESOLVE_ON_WRITE); + expert_factory::add_data_node<int>( + expert, fe_path / "mixer2_n", 0, AUTO_RESOLVE_ON_WRITE); + expert_factory::add_data_node<bool>( + expert, fe_path / "band_inverted", false, AUTO_RESOLVE_ON_WRITE); + + subtree->create<double>(fe_path / "bandwidth" / "value") + .set(ZBX_DEFAULT_BANDWIDTH) + .set_coercer([](const double) { return ZBX_DEFAULT_BANDWIDTH; }); + subtree->create<meta_range_t>(fe_path / "bandwidth" / "range") + .set({ZBX_DEFAULT_BANDWIDTH, ZBX_DEFAULT_BANDWIDTH}) + .set_coercer([](const meta_range_t&) { + return meta_range_t(ZBX_DEFAULT_BANDWIDTH, ZBX_DEFAULT_BANDWIDTH); + }); + subtree->create<meta_range_t>(fe_path / "freq" / "range") + .set(ZBX_FREQ_RANGE) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update freq range!"); + }); +} + +void zbx_dboard_impl::_init_gain_prop_tree(uhd::property_tree::sptr subtree, + expert_container::sptr expert, + const uhd::direction_t trx, + const size_t chan_idx, + const fs_path fe_path) +{ + // First, overall gain nodes + const auto gain_base_path = fe_path / "gains"; + expert_factory::add_dual_prop_node<double>(expert, + subtree, + gain_base_path / ZBX_GAIN_STAGE_ALL / "value", + trx == TX_DIRECTION ? TX_MIN_GAIN : RX_MIN_GAIN, + AUTO_RESOLVE_ON_WRITE); + subtree->create<meta_range_t>(fe_path / "gains" / "all" / "range") + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update gain range!"); + }) + .set_publisher([this, trx, chan_idx]() { + return (trx == TX_DIRECTION) ? this->get_tx_gain_range(chan_idx) + : this->get_rx_gain_range(chan_idx); + }); + // Then, individual DSA/amp gain nodes + if (trx == TX_DIRECTION) { + // DSAs + for (const auto dsa : {ZBX_GAIN_STAGE_DSA1, ZBX_GAIN_STAGE_DSA2}) { + const auto gain_path = gain_base_path / dsa; + expert_factory::add_dual_prop_node<double>( + expert, subtree, gain_path / "value", 0, AUTO_RESOLVE_ON_WRITE); + subtree->create<meta_range_t>(gain_path / "range") + .set(uhd::meta_range_t(0, ZBX_TX_DSA_MAX_ATT, 1.0)); + expert_factory::add_worker_node<zbx_gain_coercer_expert>(_expert_container, + _expert_container->node_retriever(), + gain_path / "value", + uhd::meta_range_t(0, ZBX_TX_DSA_MAX_ATT, 1.0)); + } + // Amp + const auto amp_path = gain_base_path / ZBX_GAIN_STAGE_AMP; + expert_factory::add_dual_prop_node<double>(expert, + subtree, + amp_path / "value", + ZBX_TX_LOWBAND_GAIN, + AUTO_RESOLVE_ON_WRITE); + uhd::meta_range_t amp_gain_range; + for (const auto tx_gain_pair : ZBX_TX_GAIN_AMP_MAP) { + amp_gain_range.push_back(uhd::range_t(tx_gain_pair.first)); + } + subtree->create<meta_range_t>(amp_path / "range").set(amp_gain_range); + expert_factory::add_worker_node<zbx_gain_coercer_expert>(_expert_container, + _expert_container->node_retriever(), + amp_path / "value", + amp_gain_range); + } else { + // RX only has DSAs + for (const auto dsa : {ZBX_GAIN_STAGE_DSA1, + ZBX_GAIN_STAGE_DSA2, + ZBX_GAIN_STAGE_DSA3A, + ZBX_GAIN_STAGE_DSA3B}) { + const auto gain_path = gain_base_path / dsa; + expert_factory::add_dual_prop_node<double>( + expert, subtree, gain_path / "value", 0, AUTO_RESOLVE_ON_WRITE); + subtree->create<meta_range_t>(gain_path / "range") + .set(uhd::meta_range_t(0, ZBX_RX_DSA_MAX_ATT, 1.0)); + expert_factory::add_worker_node<zbx_gain_coercer_expert>(_expert_container, + _expert_container->node_retriever(), + gain_path / "value", + uhd::meta_range_t(0, ZBX_RX_DSA_MAX_ATT, 1.0)); + } + } + + const uhd::fs_path gain_profile_path = gain_base_path / "all" / "profile"; + expert_factory::add_prop_node<std::string>(expert, + subtree, + gain_profile_path, + ZBX_GAIN_PROFILE_DEFAULT, + AUTO_RESOLVE_ON_WRITE); + auto& gain_profile = (trx == TX_DIRECTION) ? _tx_gain_profile_api + : _rx_gain_profile_api; + auto& other_dir_gp = (trx == TX_DIRECTION) ? _rx_gain_profile_api + : _tx_gain_profile_api; + auto gain_profile_subscriber = [this, other_dir_gp, trx]( + const std::string& profile, const size_t chan) { + // Upon changing the gain profile, we need to import the new value into + // the property tree. + const auto path = fs_path("dboard") + / (trx == TX_DIRECTION ? "tx_frontends" : "rx_frontends") / chan + / "gains" / "all" / "profile"; + get_tree()->access<std::string>(path).set(profile); + // The CPLD does not have the option to have different ATR modes for RX + // and TX (it does have different modes for channel 0 and 1 though). + // This means we have to match up the gain profiles between RX and TX. + // The ZBX_GAIN_PROFILE_CPLD_NOATR profile uses the SW_DEFINED mode, + // and all the others use CLASSIC_ATR. So either both match + // ZBX_GAIN_PROFILE_CPLD_NOATR, or none do. + // This will not cause a loop, because the other_dir_gp will already + // match this one by the time we call it. + if ((profile == ZBX_GAIN_PROFILE_CPLD_NOATR + && other_dir_gp->get_gain_profile(chan) != ZBX_GAIN_PROFILE_CPLD_NOATR) + || (profile != ZBX_GAIN_PROFILE_CPLD_NOATR + && other_dir_gp->get_gain_profile(chan) == ZBX_GAIN_PROFILE_CPLD_NOATR)) { + RFNOC_LOG_DEBUG("Channel " << chan << ": Setting gain profile to `" << profile + << "' for both TX and RX."); + other_dir_gp->set_gain_profile(profile, chan); + } + }; + + gain_profile->add_subscriber(std::move(gain_profile_subscriber)); +} + +void zbx_dboard_impl::_init_antenna_prop_tree(uhd::property_tree::sptr subtree, + expert_container::sptr expert, + const uhd::direction_t trx, + const size_t chan_idx, + const fs_path fe_path) +{ + const std::string default_ant = trx == TX_DIRECTION ? DEFAULT_TX_ANTENNA + : DEFAULT_RX_ANTENNA; + expert_factory::add_prop_node<std::string>(expert, + subtree, + fe_path / "antenna" / "value", + default_ant, + AUTO_RESOLVE_ON_WRITE); + subtree->access<std::string>(fe_path / "antenna" / "value") + .set_coercer([trx](const std::string& ant_name) { + const auto ant_map = trx == TX_DIRECTION ? TX_ANTENNA_NAME_COMPAT_MAP + : RX_ANTENNA_NAME_COMPAT_MAP; + return ant_map.count(ant_name) ? ant_map.at(ant_name) : ant_name; + }); + subtree->create<std::vector<std::string>>(fe_path / "antenna" / "options") + .set(trx == TX_DIRECTION ? get_tx_antennas(chan_idx) : get_rx_antennas(chan_idx)) + .add_coerced_subscriber([](const std::vector<std::string>&) { + throw uhd::runtime_error("Attempting to update antenna options!"); + }); +} + +void zbx_dboard_impl::_init_programming_prop_tree(uhd::property_tree::sptr subtree, + expert_container::sptr expert, + const fs_path fe_path) +{ + expert_factory::add_prop_node<int>( + expert, subtree, fe_path / "rf" / "filter", 1, AUTO_RESOLVE_ON_WRITE); + expert_factory::add_prop_node<int>( + expert, subtree, fe_path / "if1" / "filter", 1, AUTO_RESOLVE_ON_WRITE); + expert_factory::add_prop_node<int>( + expert, subtree, fe_path / "if2" / "filter", 1, AUTO_RESOLVE_ON_WRITE); + expert_factory::add_prop_node<zbx_cpld_ctrl::atr_mode>(expert, + subtree, + fe_path / "atr_mode", + zbx_cpld_ctrl::atr_mode::CLASSIC_ATR, + AUTO_RESOLVE_ON_WRITE); +} + +void zbx_dboard_impl::_init_lo_prop_tree(uhd::property_tree::sptr subtree, + expert_container::sptr expert, + const uhd::direction_t trx, + const size_t chan_idx, + const fs_path fe_path) +{ + // Analog LO Specific + for (const std::string lo : {ZBX_LO1, ZBX_LO2}) { + expert_factory::add_prop_node<zbx_lo_source_t>(expert, + subtree, + fe_path / "ch" / lo / "source", + ZBX_DEFAULT_LO_SOURCE, + AUTO_RESOLVE_ON_WRITE); + expert_factory::add_prop_node<bool>( + expert, subtree, fe_path / lo / "enabled", false, AUTO_RESOLVE_ON_WRITE); + expert_factory::add_prop_node<bool>( + expert, subtree, fe_path / lo / "test_mode", false, AUTO_RESOLVE_ON_WRITE); + expert_factory::add_dual_prop_node<double>(expert, + subtree, + fe_path / "los" / lo / "freq" / "value", + LMX2572_DEFAULT_FREQ, + AUTO_RESOLVE_ON_WRITE); + + subtree->create<meta_range_t>(fe_path / "los" / lo / "freq/range") + .set_publisher( + [this, lo, chan_idx]() { return this->_get_lo_freq_range(lo, chan_idx); }) + .add_coerced_subscriber([](const meta_range_t&) { + throw uhd::runtime_error("Attempting to update freq range!"); + }); + subtree->create<std::vector<std::string>>(fe_path / "los" / lo / "source/options") + .set_publisher([this, lo, trx, chan_idx]() { + return trx == TX_DIRECTION ? this->get_tx_lo_sources(lo, chan_idx) + : this->get_rx_lo_sources(lo, chan_idx); + }) + .add_coerced_subscriber([](const std::vector<std::string>&) { + throw uhd::runtime_error("Attempting to update LO source options!"); + }); + + subtree + ->create<sensor_value_t>( + fe_path / "sensors" / boost::algorithm::to_lower_copy(lo) + "_locked") + .add_coerced_subscriber([](const sensor_value_t&) { + throw uhd::runtime_error("Attempting to write to sensor!"); + }) + .set_publisher([this, lo, trx, chan_idx]() { + return sensor_value_t(lo, + this->_lo_ctrl_map + .at(zbx_lo_ctrl::lo_string_to_enum(trx, chan_idx, lo)) + ->get_lock_status(), + "locked", + "unlocked"); + }); + } + + // The NCO gets a sub-node called 'reset'. It is read/write: Write will + // perform a reset, and read will return the reset status. The latter is + // also returned in the 'locked' sensor for the NCO, but the 'nco_locked' + // sensor node is read-only, and returns a sensor_value_t (not a bool). + // This node is primarily used for debugging, but can also serve as a manual + // reset line for the NCOs. + const auto nco = (trx == TX_DIRECTION) + ? (chan_idx == 0 ? rfdc_control::rfdc_type::TX0 + : rfdc_control::rfdc_type::TX1) + : (chan_idx == 0 ? rfdc_control::rfdc_type::RX0 + : rfdc_control::rfdc_type::RX1); + subtree->create<bool>(fe_path / "los" / RFDC_NCO / "reset") + .set_publisher([this]() { return this->_rfdcc->get_nco_reset_done(); }) + .add_coerced_subscriber([this, nco, chan_idx](const bool&) { + RFNOC_LOG_TRACE("Resetting NCO " << size_t(nco) << ", chan " << chan_idx); + this->_rfdcc->reset_ncos({nco}, this->_time_accessor(chan_idx)); + }); + + expert_factory::add_dual_prop_node<double>(expert, + subtree, + fe_path / "los" / RFDC_NCO / "freq" / "value", + // Initialize with current value + _mb_rpcc->rfdc_get_nco_freq(trx == TX_DIRECTION ? "tx" : "rx", _db_idx, chan_idx), + AUTO_RESOLVE_ON_WRITE); + + expert_factory::add_prop_node<zbx_lo_source_t>(expert, + subtree, + fe_path / "ch" / RFDC_NCO / "source", + ZBX_DEFAULT_LO_SOURCE, + AUTO_RESOLVE_ON_WRITE); + + // LO lock sensor + // We can't make this its own property value because it has to have access to two + // containers (two instances of zbx lo expert) + subtree->create<sensor_value_t>(fe_path / "sensors" / "lo_locked") + .set(sensor_value_t("all_los", false, "locked", "unlocked")) + .add_coerced_subscriber([](const sensor_value_t&) { + throw uhd::runtime_error("Attempting to write to sensor!"); + }) + .set_publisher([this, trx, chan_idx]() { + return sensor_value_t("all_los", + this->_get_all_los_locked(trx, chan_idx), + "locked", + "unlocked"); + }); + subtree->create<sensor_value_t>(fe_path / "sensors" / "nco_locked") + .add_coerced_subscriber([](const sensor_value_t&) { + throw uhd::runtime_error("Attempting to write to sensor!"); + }) + .set_publisher([this]() { + return sensor_value_t( + RFDC_NCO, this->_rfdcc->get_nco_reset_done(), "locked", "unlocked"); + }); +} +}}} // namespace uhd::usrp::zbx diff --git a/host/lib/usrp/dboard/zbx/zbx_expert.cpp b/host/lib/usrp/dboard/zbx/zbx_expert.cpp new file mode 100644 index 000000000..79e13e230 --- /dev/null +++ b/host/lib/usrp/dboard/zbx/zbx_expert.cpp @@ -0,0 +1,672 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/math.hpp> +#include <uhdlib/usrp/dboard/zbx/zbx_expert.hpp> +#include <uhdlib/utils/interpolation.hpp> +#include <uhdlib/utils/narrow.hpp> +#include <algorithm> +#include <array> + +using namespace uhd; + +namespace uhd { namespace usrp { namespace zbx { + +namespace { + +/********************************************************************* + * Misc/calculative helper functions + **********************************************************************/ +bool _is_band_highband(const tune_map_item_t tune_setting) +{ + // Lowband frequency paths do not utilize an RF filter + return tune_setting.rf_fir == 0; +} + +tune_map_item_t _get_tune_settings(const double freq, const uhd::direction_t trx) +{ + auto tune_setting = trx == RX_DIRECTION ? rx_tune_map.begin() : tx_tune_map.begin(); + + auto tune_settings_end = trx == RX_DIRECTION ? rx_tune_map.end() : tx_tune_map.end(); + + for (; tune_setting != tune_settings_end; ++tune_setting) { + if (tune_setting->max_band_freq >= freq) { + return *tune_setting; + } + } + // Didn't find a tune setting. This frequency should have been clipped, this is an + // internal error. + UHD_THROW_INVALID_CODE_PATH(); +} + +bool _is_band_inverted(const uhd::direction_t trx, + const double if2_freq, + const double rfdc_rate, + const tune_map_item_t tune_setting) +{ + const bool is_if2_nyquist2 = if2_freq > (rfdc_rate / 2); + + // We count the number of inversions introduced by the signal chain, starting + // at the RFDC + const int num_inversions = + // If we're in the second Nyquist zone, we're inverted + int(is_if2_nyquist2) + + // LO2 mixer may invert + int(tune_setting.mix2_m == -1) + + // LO1 mixer can only invert in the lowband + int(!_is_band_highband(tune_setting) && tune_setting.mix1_m == -1); + + // In the RX direction, an extra inversion is needed + // TODO: We don't know where this is coming from + const bool num_inversions_is_odd = num_inversions % 2 != 0; + if (trx == RX_DIRECTION) { + return !num_inversions_is_odd; + } else { + return num_inversions_is_odd; + } +} + +double _calc_lo2_freq( + const double if1_freq, const double if2_freq, const int mix2_m, const int mix2_n) +{ + return (if2_freq - (mix2_m * if1_freq)) / mix2_n; +} + +double _calc_if2_freq( + const double if1_freq, const double lo2_freq, const int mix2_m, const int mix2_n) +{ + return mix2_n * lo2_freq + mix2_m * if1_freq; +} + +std::string _get_trx_string(const direction_t dir) +{ + if (dir == RX_DIRECTION) { + return "rx"; + } else if (dir == TX_DIRECTION) { + return "tx"; + } else { + UHD_THROW_INVALID_CODE_PATH(); + } +} + +// For various RF performance considerations (such as spur reduction), different bands +// vary between using fixed IF1 and/or IF2 or using variable IF1 and/or IF2. Bands with a +// fixed IF1/IF2 have ifX_freq_min == IFX_freq_max, and _calc_ifX_freq() will return that +// single value. Bands with variable IF1/IF2 will shift the IFX based on where in the RF +// band we are tuning by using linear interpolation. (if1 calculation takes place only if +// tune frequency is lowband) +double _calc_if1_freq(const double tune_freq, const tune_map_item_t tune_setting) +{ + if (tune_setting.if1_freq_min == tune_setting.if1_freq_max) { + return tune_setting.if1_freq_min; + } + + return uhd::math::linear_interp(tune_freq, + tune_setting.min_band_freq, + tune_setting.if1_freq_min, + tune_setting.max_band_freq, + tune_setting.if1_freq_max); +} + +double _calc_ideal_if2_freq(const double tune_freq, const tune_map_item_t tune_setting) +{ + // linear_interp() wants to interpolate and will throw if these are identical: + if (tune_setting.if2_freq_min == tune_setting.if2_freq_max) { + return tune_setting.if2_freq_min; + } + + return uhd::math::linear_interp(tune_freq, + tune_setting.min_band_freq, + tune_setting.if2_freq_min, + tune_setting.max_band_freq, + tune_setting.if2_freq_max); +} + +} // namespace + +/*!--------------------------------------------------------- + * EXPERT RESOLVE FUNCTIONS + * + * This sections contains all expert resolve functions. + * These methods are triggered by any of the bound accessors becoming "dirty", + * or changing value + * -------------------------------------------------------- + */ +void zbx_scheduling_expert::resolve() +{ + // We currently have no fancy scheduling, but here is where we'd add it if + // we need to do that (e.g., plan out SYNC pulse timing vs. NCO timing etc.) + _frontend_time = _command_time; +} + +void zbx_freq_fe_expert::resolve() +{ + const double tune_freq = ZBX_FREQ_RANGE.clip(_desired_frequency); + _tune_settings = _get_tune_settings(tune_freq, _trx); + + // Set mixer values so the backend expert knows how to calculate final frequency + _mixer1_m = _tune_settings.mix1_m; + _mixer1_n = _tune_settings.mix1_n; + _mixer2_m = _tune_settings.mix2_m; + _mixer2_n = _tune_settings.mix2_n; + + _is_highband = _is_band_highband(_tune_settings); + _lo1_enabled = !_is_highband.get(); + + double if1_freq = tune_freq; + const double lo_step = _lo_freq_range.step(); + // If we need to apply an offset to avoid injection locking, we need to + // offset in different directions for different channels on the same zbx + const double lo_offset_sign = (_chan == 0) ? -1 : 1; + // In high band, LO1 is not needed (the signal is already at a high enough + // frequency for the second stage) + if (_lo1_enabled) { + // Calculate the ideal IF1: + if1_freq = _calc_if1_freq(tune_freq, _tune_settings); + // We calculate the LO1 frequency by first shifting the tune frequency to the + // desired IF, and then applying an offset such that CH0 and CH1 tune to distinct + // LO1 frequencies: This is done to prevent the LO's from interfering with each + // other in a phenomenon known as injection locking. + const double lo1_freq = + if1_freq + (_tune_settings.mix1_n * tune_freq) + (lo_offset_sign * lo_step); + // Now, quantize the LO frequency to the nearest valid value: + _desired_lo1_frequency = _lo_freq_range.clip(lo1_freq, true); + // Because LO1 frequency probably changed during quantization, we simply + // re-calculate the now-valid IF1 (the following equation is the same as + // the LO1 frequency calculation, but solved for if1_freq): + if1_freq = _desired_lo1_frequency - (_tune_settings.mix1_n * tune_freq); + } + + _lo2_enabled = true; + // Calculate ideal IF2 frequency: + const double if2_freq = _calc_ideal_if2_freq(tune_freq, _tune_settings); + // Calculate LO2 frequency from that: + _desired_lo2_frequency = _calc_lo2_freq(if1_freq, if2_freq, _mixer2_m, _mixer2_n); + // Similar to LO1, apply an offset such that CH0 and CH1 tune to distinct LO2 + // frequencies to prevent potential interference between CH0 and CH1 LO2's from + // injection locking: In highband (LO1 disabled), this must explicitly be done below. + // In lowband (LO1 enabled), the LO1 will have already been shifted and, as a result, + // the LO2's will have already been shifted to compensate for LO1 in previous + // function. Note that in lowband, the LO1's and LO2's will be offset between CH0 and + // CH1; however, they will be offset in opposite direction such that the NCO frequency + // will be the same between CH0 and CH1. This is not the case for highband (only LO2 + // and they must be offset). + if (!_lo1_enabled) { + _desired_lo2_frequency = _desired_lo2_frequency + (lo_offset_sign * lo_step); + } + // Now, quantize the LO frequency to the nearest valid value: + _desired_lo2_frequency = _lo_freq_range.clip(_desired_lo2_frequency, true); + // Calculate actual IF2 frequency from LO2 and IF1 frequencies: + _desired_if2_frequency = + _calc_if2_freq(if1_freq, _desired_lo2_frequency, _mixer2_m, _mixer2_n); + + // If the frequency is in a different tuning band, we need to switch filters + _rf_filter = _tune_settings.rf_fir; + _if1_filter = _tune_settings.if1_fir; + _if2_filter = _tune_settings.if2_fir; + _band_inverted = + _is_band_inverted(_trx, _desired_if2_frequency, _rfdc_rate, _tune_settings); +} + + +void zbx_freq_be_expert::resolve() +{ + if (_is_highband) { + _coerced_frequency = + ((_coerced_if2_frequency - (_coerced_lo2_frequency * _mixer2_n)) / _mixer2_m); + } else { + _coerced_frequency = + (_coerced_lo1_frequency + + ((_coerced_lo2_frequency * _mixer2_n - _coerced_if2_frequency) + / _mixer2_m)) + / _mixer1_n; + } + + // Users may change individual settings (LO frequencies, if2 frequencies) and throw + // the output frequency out of range. We have to stop here so that the gain API + // doesn't panic (Clipping here would have no effect on the actual output signal) + using namespace uhd::math::fp_compare; + if (fp_compare_delta<double>(_coerced_frequency.get()) < ZBX_MIN_FREQ + || fp_compare_delta<double>(_coerced_frequency.get()) > ZBX_MAX_FREQ) { + UHD_LOG_WARNING(get_name(), + "Resulting coerced frequency " << _coerced_frequency.get() + << " is out of range!"); + } +} + +void zbx_lo_expert::resolve() +{ + if (_test_mode_enabled.is_dirty()) { + _lo_ctrl->set_lo_test_mode_enabled(_test_mode_enabled); + } + + if (_set_is_enabled.is_dirty()) { + _lo_ctrl->set_lo_port_enabled(_set_is_enabled); + } + + if (_set_is_enabled && _desired_lo_frequency.is_dirty()) { + const double clipped_lo_freq = std::max( + LMX2572_MIN_FREQ, std::min(_desired_lo_frequency.get(), LMX2572_MAX_FREQ)); + _coerced_lo_frequency = _lo_ctrl->set_lo_freq(clipped_lo_freq); + UHD_LOG_TRACE(get_name(), + "Requested " << _get_trx_string(_trx) << _chan << " frequency " + << (_desired_lo_frequency / 1e6) << "MHz was coerced to " + << (_coerced_lo_frequency / 1e6) << "MHz"); + } +} + +void zbx_gain_coercer_expert::resolve() +{ + _gain_coerced = _valid_range.clip(_gain_desired, true); +} + +void zbx_tx_gain_expert::resolve() +{ + if (_profile != ZBX_GAIN_PROFILE_DEFAULT) { + return; + } + + // If a user passes in a gain value, we have to set the Power API tracking mode + if (_gain_in.is_dirty()) { + _power_mgr->set_tracking_mode(uhd::usrp::pwr_cal_mgr::tracking_mode::TRACK_GAIN); + } + + // Now we do the overall gain setting + // Look up DSA values by gain + _gain_out = ZBX_TX_GAIN_RANGE.clip(_gain_in, true); + const size_t gain_idx = _gain_out / TX_GAIN_STEP; + // Clip _frequency to valid ZBX range to avoid errors in the scenario when user + // manually configures LO frequencies and causes an illegal overall frequency + auto dsa_settings = + _dsa_cal->get_dsa_setting(ZBX_FREQ_RANGE.clip(_frequency), gain_idx); + // Now write to downstream nodes, converting attenuations to gains: + _dsa1 = static_cast<double>(ZBX_TX_DSA_MAX_ATT - dsa_settings[0]); + _dsa2 = static_cast<double>(ZBX_TX_DSA_MAX_ATT - dsa_settings[1]); + // Convert amp index to gain + _amp_gain = ZBX_TX_AMP_GAIN_MAP.at(static_cast<tx_amp>(dsa_settings[2])); +} + +void zbx_rx_gain_expert::resolve() +{ + if (_profile != ZBX_GAIN_PROFILE_DEFAULT) { + return; + } + + // If a user passes in a gain value, we have to set the Power API tracking mode + if (_gain_in.is_dirty()) { + _power_mgr->set_tracking_mode(uhd::usrp::pwr_cal_mgr::tracking_mode::TRACK_GAIN); + } + + // Now we do the overall gain setting + if (_frequency.get() <= RX_LOW_FREQ_MAX_GAIN_CUTOFF) { + _gain_out = ZBX_RX_LOW_FREQ_GAIN_RANGE.clip(_gain_in, true); + } else { + _gain_out = ZBX_RX_GAIN_RANGE.clip(_gain_in, true); + } + // Now we do the overall gain setting + // Look up DSA values by gain + const size_t gain_idx = _gain_out / RX_GAIN_STEP; + // Clip _frequency to valid ZBX range to avoid errors in the scenario when user + // manually configures LO frequencies and causes an illegal overall frequency + auto dsa_settings = + _dsa_cal->get_dsa_setting(ZBX_FREQ_RANGE.clip(_frequency), gain_idx); + // Now write to downstream nodes, converting attenuation to gains: + _dsa1 = ZBX_RX_DSA_MAX_ATT - dsa_settings[0]; + _dsa2 = ZBX_RX_DSA_MAX_ATT - dsa_settings[1]; + _dsa3a = ZBX_RX_DSA_MAX_ATT - dsa_settings[2]; + _dsa3b = ZBX_RX_DSA_MAX_ATT - dsa_settings[3]; +} + +void zbx_tx_programming_expert::resolve() +{ + if (_profile.is_dirty()) { + if (_profile == ZBX_GAIN_PROFILE_DEFAULT || _profile == ZBX_GAIN_PROFILE_MANUAL + || _profile == ZBX_GAIN_PROFILE_CPLD) { + _cpld->set_atr_mode(_chan, + zbx_cpld_ctrl::atr_mode_target::DSA, + zbx_cpld_ctrl::atr_mode::CLASSIC_ATR); + } else { + _cpld->set_atr_mode(_chan, + zbx_cpld_ctrl::atr_mode_target::DSA, + zbx_cpld_ctrl::atr_mode::SW_DEFINED); + } + } + + // If we're in any of the table modes, then we don't write DSA and amp values + // A note on caching: The CPLD object caches state, and only pokes the CPLD + // if it's changed. However, all DSAs are on the same register. That means + // the DSA register changes, all DSA values written to the CPLD will come + // from the input data nodes to this worker node. This can overwrite DSA + // values if the cached version and the actual value on the CPLD differ. + if (_profile == ZBX_GAIN_PROFILE_DEFAULT || _profile == ZBX_GAIN_PROFILE_MANUAL) { + // Convert gains back to attenuation + zbx_cpld_ctrl::tx_dsa_type dsa_settings = { + uhd::narrow_cast<uint32_t>(ZBX_TX_DSA_MAX_ATT - _dsa1.get()), + uhd::narrow_cast<uint32_t>(ZBX_TX_DSA_MAX_ATT - _dsa2.get())}; + _cpld->set_tx_gain_switches(_chan, ATR_ADDR_TX, dsa_settings); + _cpld->set_tx_gain_switches(_chan, ATR_ADDR_XX, dsa_settings); + } + + // If frequency changed, we might have changed bands and the CPLD dsa tables need to + // be reloaded + // TODO: This is a major hack, and these tables should be loaded outside of the + // tuning call. This means every tuning request involves a large amount of CPLD + // writes. + // We only write when we aren't using a command time, otherwise all those CPLD + // commands will line up in the CPLD command queue, and diminish any purpose + // of timed commands in the first place + // Clip _frequency to valid ZBX range to avoid errors in the scenario when user + // manually configures LO frequencies and causes an illegal overall frequency + if (_command_time == 0.0) { + _cpld->update_tx_dsa_settings( + _dsa_cal->get_band_settings(ZBX_FREQ_RANGE.clip(_frequency), 0 /*dsa1*/), + _dsa_cal->get_band_settings(ZBX_FREQ_RANGE.clip(_frequency), 1 /*dsa2*/)); + } + + for (const size_t idx : ATR_ADDRS) { + _cpld->set_lo_source(idx, + zbx_lo_ctrl::lo_string_to_enum(TX_DIRECTION, _chan, ZBX_LO1), + _lo1_source); + _cpld->set_lo_source(idx, + zbx_lo_ctrl::lo_string_to_enum(TX_DIRECTION, _chan, ZBX_LO2), + _lo2_source); + + _cpld->set_tx_rf_filter(_chan, idx, _rf_filter); + _cpld->set_tx_if1_filter(_chan, idx, _if1_filter); + _cpld->set_tx_if2_filter(_chan, idx, _if2_filter); + } + + // Convert amp gain to amp index + UHD_ASSERT_THROW(ZBX_TX_GAIN_AMP_MAP.count(_amp_gain.get())); + const tx_amp amp = ZBX_TX_GAIN_AMP_MAP.at(_amp_gain.get()); + _cpld->set_tx_antenna_switches(_chan, ATR_ADDR_0X, _antenna, tx_amp::BYPASS); + _cpld->set_tx_antenna_switches(_chan, ATR_ADDR_RX, _antenna, tx_amp::BYPASS); + _cpld->set_tx_antenna_switches(_chan, ATR_ADDR_TX, _antenna, amp); + _cpld->set_tx_antenna_switches(_chan, ATR_ADDR_XX, _antenna, amp); + + // We do not update LEDs on switching TX antenna value by definition +} + +void zbx_rx_programming_expert::resolve() +{ + if (_profile.is_dirty()) { + if (_profile == ZBX_GAIN_PROFILE_DEFAULT || _profile == ZBX_GAIN_PROFILE_MANUAL + || _profile == ZBX_GAIN_PROFILE_CPLD) { + _cpld->set_atr_mode(_chan, + zbx_cpld_ctrl::atr_mode_target::DSA, + zbx_cpld_ctrl::atr_mode::CLASSIC_ATR); + } else { + _cpld->set_atr_mode(_chan, + zbx_cpld_ctrl::atr_mode_target::DSA, + zbx_cpld_ctrl::atr_mode::SW_DEFINED); + } + } + + // If we're in any of the table modes, then we don't write DSA values + // A note on caching: The CPLD object caches state, and only pokes the CPLD + // if it's changed. However, all DSAs are on the same register. That means + // the DSA register changes, all DSA values written to the CPLD will come + // from the input data nodes to this worker node. This can overwrite DSA + // values if the cached version and the actual value on the CPLD differ. + if (_profile == ZBX_GAIN_PROFILE_DEFAULT || _profile == ZBX_GAIN_PROFILE_MANUAL) { + zbx_cpld_ctrl::rx_dsa_type dsa_settings = { + uhd::narrow_cast<uint32_t>(ZBX_RX_DSA_MAX_ATT - _dsa1.get()), + uhd::narrow_cast<uint32_t>(ZBX_RX_DSA_MAX_ATT - _dsa2.get()), + uhd::narrow_cast<uint32_t>(ZBX_RX_DSA_MAX_ATT - _dsa3a.get()), + uhd::narrow_cast<uint32_t>(ZBX_RX_DSA_MAX_ATT - _dsa3b.get())}; + _cpld->set_rx_gain_switches(_chan, ATR_ADDR_RX, dsa_settings); + _cpld->set_rx_gain_switches(_chan, ATR_ADDR_XX, dsa_settings); + } + + + // If frequency changed, we might have changed bands and the CPLD dsa tables need to + // be reloaded + // TODO: This is a major hack, and these tables should be loaded outside of the + // tuning call. This means every tuning request involves a large amount of CPLD + // writes. + // We only write when we aren't using a command time, otherwise all those CPLD + // commands will line up in the CPLD command queue, and diminish any purpose + // of timed commands in the first place + // Clip _frequency to valid ZBX range to avoid errors in the scenario when user + // manually configures LO frequencies and causes an illegal overall frequency + if (_command_time == 0.0) { + _cpld->update_rx_dsa_settings( + _dsa_cal->get_band_settings(ZBX_FREQ_RANGE.clip(_frequency), 0 /*dsa1*/), + _dsa_cal->get_band_settings(ZBX_FREQ_RANGE.clip(_frequency), 1 /*dsa2*/), + _dsa_cal->get_band_settings(ZBX_FREQ_RANGE.clip(_frequency), 2 /*dsa3a*/), + _dsa_cal->get_band_settings(ZBX_FREQ_RANGE.clip(_frequency), 3 /*dsa3b*/)); + } + + for (const size_t idx : ATR_ADDRS) { + _cpld->set_lo_source(idx, + zbx_lo_ctrl::lo_string_to_enum(RX_DIRECTION, _chan, ZBX_LO1), + _lo1_source); + _cpld->set_lo_source(idx, + zbx_lo_ctrl::lo_string_to_enum(RX_DIRECTION, _chan, ZBX_LO2), + _lo2_source); + + // If using the TX/RX terminal, only configure the ATR RX state since the + // state of the switch at other times is controlled by TX + if (_antenna != ANTENNA_TXRX || idx == ATR_ADDR_RX) { + _cpld->set_rx_antenna_switches(_chan, idx, _antenna); + } + + _cpld->set_rx_rf_filter(_chan, idx, _rf_filter); + _cpld->set_rx_if1_filter(_chan, idx, _if1_filter); + _cpld->set_rx_if2_filter(_chan, idx, _if2_filter); + } + + _update_leds(); +} + +void zbx_rx_programming_expert::_update_leds() +{ + if (_atr_mode != zbx_cpld_ctrl::atr_mode::CLASSIC_ATR) { + return; + } + // We default to the RX1 LED for all RX antenna values that are not TX/RX0 + const bool rx_on_trx = _antenna == ANTENNA_TXRX; + // clang-format off + // G==Green, R==Red RX2 TX/RX-G TX/RX-R + _cpld->set_leds(_chan, ATR_ADDR_0X, false, false, false); + _cpld->set_leds(_chan, ATR_ADDR_RX, !rx_on_trx, rx_on_trx, false); + _cpld->set_leds(_chan, ATR_ADDR_TX, false, false, true ); + _cpld->set_leds(_chan, ATR_ADDR_XX, !rx_on_trx, rx_on_trx, true ); + // clang-format on +} + +void zbx_band_inversion_expert::resolve() +{ + _rpcc->enable_iq_swap(_is_band_inverted.get(), _get_trx_string(_trx), _chan); +} + +void zbx_rfdc_freq_expert::resolve() +{ + // Because we can configure both IF2 and the RFDC NCO frequency, these may + // come into conflict. We choose IF2 over RFDC in that case. In other words + // the only time we choose the desired RFDC frequency over the IF2 (when in + // conflict) is when the RFDC freq was changed directly. + const double desired_rfdc_freq = [&]() -> double { + if (_rfdc_freq_desired.is_dirty() && !_if2_frequency_desired.is_dirty()) { + return _rfdc_freq_desired; + } + return _if2_frequency_desired; + }(); + + _rfdc_freq_coerced = _rpcc->rfdc_set_nco_freq( + _get_trx_string(_trx), _db_idx, _chan, desired_rfdc_freq); + _if2_frequency_coerced = _rfdc_freq_coerced; +} + +void zbx_sync_expert::resolve() +{ + // Some local helper consts + // clang-format off + constexpr std::array<std::array<zbx_lo_t, 4>, 2> los{{{ + zbx_lo_t::RX0_LO1, + zbx_lo_t::RX0_LO2, + zbx_lo_t::TX0_LO1, + zbx_lo_t::TX0_LO2 + }, { + zbx_lo_t::RX1_LO1, + zbx_lo_t::RX1_LO2, + zbx_lo_t::TX1_LO1, + zbx_lo_t::TX1_LO2 + }}}; + constexpr std::array<std::array<rfdc_control::rfdc_type, 2>, 2> ncos{{ + {rfdc_control::rfdc_type::RX0, rfdc_control::rfdc_type::TX0}, + {rfdc_control::rfdc_type::RX1, rfdc_control::rfdc_type::TX1} + }}; + // clang-format on + + // Now do some timing checks + const std::vector<bool> chan_needs_sync = {_fe_time.at(0) != uhd::time_spec_t::ASAP, + _fe_time.at(1) != uhd::time_spec_t::ASAP}; + // If there's no command time, no need to synchronize anything + if (!chan_needs_sync[0] && !chan_needs_sync[1]) { + UHD_LOG_TRACE(get_name(), "No command time: Skipping phase sync."); + return; + } + const bool times_match = _fe_time.at(0) == _fe_time.at(1); + + // ** Find LOs to synchronize ********************************************* + // Find dirty LOs which need sync'ing + std::set<zbx_lo_t> los_to_sync; + for (const size_t chan : ZBX_CHANNELS) { + if (chan_needs_sync[chan]) { + for (const auto& lo_idx : los[chan]) { + if (_lo_freqs.at(lo_idx).is_dirty()) { + los_to_sync.insert(lo_idx); + } + } + } + } + + // ** Find NCOs to synchronize ******************************************** + // Same rules apply as for LOs. + std::set<rfdc_control::rfdc_type> ncos_to_sync; + for (const size_t chan : ZBX_CHANNELS) { + if (chan_needs_sync[chan]) { + for (const auto& nco_idx : ncos[chan]) { + if (_nco_freqs.at(nco_idx).is_dirty()) { + ncos_to_sync.insert(nco_idx); + } + } + } + } + + // ** Find ADC/DAC gearboxes to synchronize ******************************* + // Gearboxes are special, because they only need to be synchronized once + // per session, assuming the command time has been set. Unfortunately we + // have no way here to know if the timekeeper time was updated, but it is + // well documented that in order to synchronize devices, one first has to + // make sure the timekeepers are running in sync (by calling + // set_time_next_pps() accordingly). + // The logic we use here is that we will always have to update the NCO when + // doing a synced tune, so we update all the gearboxes for the NCOs -- but + // only if they have not yet been synchronized. + std::set<rfdc_control::rfdc_type> gearboxes_to_sync; + if (!_adcs_synced) { + for (const auto rfdc : + {rfdc_control::rfdc_type::RX0, rfdc_control::rfdc_type::RX1}) { + if (ncos_to_sync.count(rfdc)) { + gearboxes_to_sync.insert(rfdc); + // Technically, they're not synced yet but this saves us from + // having to look up which RFDCs map to RX again later + _adcs_synced = true; + } + } + } + if (!_dacs_synced) { + for (const auto rfdc : + {rfdc_control::rfdc_type::TX0, rfdc_control::rfdc_type::TX1}) { + if (ncos_to_sync.count(rfdc)) { + gearboxes_to_sync.insert(rfdc); + // Technically, they're not synced yet but this saves us from + // having to look up which RFDCs map to TX again later + _dacs_synced = true; + } + } + } + + // ** Do synchronization ************************************************** + // This is where we orchestrate the sync commands. If sync commands happen + // at different times, we make sure to send out the earlier one first. + // If we need to schedule things a bit differently, e.g., we need to + // manually calculate offsets from the command time so that LO and NCO sync + // pulses line up, it most likely makes sense to use the scheduling expert + // for that, and calculate different times for different events there. + if (times_match) { + UHD_LOG_TRACE(get_name(), + "Syncing all channels: " << los_to_sync.size() << " LO(s), " + << ncos_to_sync.size() << " NCO(s), and " + << gearboxes_to_sync.size() << " gearbox(es).") + if (!gearboxes_to_sync.empty()) { + _rfdcc->reset_gearboxes( + std::vector<rfdc_control::rfdc_type>( + gearboxes_to_sync.cbegin(), gearboxes_to_sync.cend()), + _fe_time.at(0).get()); + } + if (!los_to_sync.empty()) { + _cpld->pulse_lo_sync( + 0, std::vector<zbx_lo_t>(los_to_sync.cbegin(), los_to_sync.cend())); + } + if (!ncos_to_sync.empty()) { + _rfdcc->reset_ncos(std::vector<rfdc_control::rfdc_type>( + ncos_to_sync.cbegin(), ncos_to_sync.cend()), + _fe_time.at(0).get()); + } + } else { + // If the command times differ, we need to manually reorder the commands + // such that the channel with the earlier time gets precedence + const size_t first_sync_chan = + (times_match || (_fe_time.at(0) <= _fe_time.at(1))) ? 0 : 1; + const auto sync_order = (first_sync_chan == 0) ? std::vector<size_t>{0, 1} + : std::vector<size_t>{1, 0}; + for (const size_t chan : sync_order) { + std::vector<zbx_lo_t> this_chan_los; + for (const zbx_lo_t lo_idx : los[chan]) { + if (los_to_sync.count(lo_idx)) { + this_chan_los.push_back(lo_idx); + } + } + + std::vector<rfdc_control::rfdc_type> this_chan_ncos; + for (const auto nco_idx : ncos[chan]) { + if (ncos_to_sync.count(nco_idx)) { + this_chan_ncos.push_back(nco_idx); + } + } + std::vector<rfdc_control::rfdc_type> this_chan_gearboxes; + for (const auto gb_idx : ncos[chan]) { + if (gearboxes_to_sync.count(gb_idx)) { + this_chan_gearboxes.push_back(gb_idx); + } + } + UHD_LOG_TRACE(get_name(), + "Syncing channel " << chan << ": " << this_chan_los.size() + << " LO(s) and " << this_chan_ncos.size() + << " NCO(s)."); + if (!this_chan_gearboxes.empty()) { + UHD_LOG_TRACE(get_name(), + "Resetting " << this_chan_gearboxes.size() << " gearboxes."); + _rfdcc->reset_gearboxes(this_chan_gearboxes, _fe_time.at(chan).get()); + } + if (!this_chan_los.empty()) { + _cpld->pulse_lo_sync(chan, this_chan_los); + } + if (!this_chan_ncos.empty()) { + _rfdcc->reset_ncos(this_chan_ncos, _fe_time.at(chan).get()); + } + } + } +} // zbx_sync_expert::resolve() + +// End expert resolve sections + +}}} // namespace uhd::usrp::zbx diff --git a/host/lib/usrp/dboard/zbx/zbx_lo_ctrl.cpp b/host/lib/usrp/dboard/zbx/zbx_lo_ctrl.cpp new file mode 100644 index 000000000..1af665207 --- /dev/null +++ b/host/lib/usrp/dboard/zbx/zbx_lo_ctrl.cpp @@ -0,0 +1,162 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/exception.hpp> +#include <uhd/utils/log.hpp> +#include <uhdlib/usrp/dboard/zbx/zbx_lo_ctrl.hpp> +#include <thread> + +namespace uhd { namespace usrp { namespace zbx { + +zbx_lo_ctrl::zbx_lo_ctrl(zbx_lo_t lo, + lmx2572_iface::write_fn_t&& poke16, + lmx2572_iface::read_fn_t&& peek16, + lmx2572_iface::sleep_fn_t&& sleep, + const double default_frequency, + const double db_prc_rate, + const bool testing_mode_enabled) + : _lo(lo) + , _log_id(ZBX_LO_LOG_ID.at(lo)) + , _freq(default_frequency) + , _db_prc_rate(db_prc_rate) + , _testing_mode_enabled(testing_mode_enabled) +{ + _lmx = lmx2572_iface::make(std::move(poke16), std::move(peek16), std::move(sleep)); + UHD_ASSERT_THROW(_lmx); + UHD_LOG_TRACE(_log_id, "LO initialized..."); + _lmx->reset(); + + set_lo_port_enabled(true); + // In ZBX, we always run the LOs in sync mode. It is theoretically possible + // to not do so, but we gain nothing by doing that. + _lmx->set_sync_mode(true); + set_lo_freq(LMX2572_DEFAULT_FREQ); + wait_for_lo_lock(); +} + +double zbx_lo_ctrl::set_lo_freq(const double freq) +{ + UHD_ASSERT_THROW(_lmx); + UHD_LOG_TRACE(_log_id, "Setting LO frequency " << freq / 1e6 << " MHz"); + + _freq = _lmx->set_frequency(freq, + _db_prc_rate, + false /*TODO: get_spur_dodging()*/); + _lmx->commit(); + return _freq; +} + +double zbx_lo_ctrl::get_lo_freq() +{ + return _freq; +} + +void zbx_lo_ctrl::wait_for_lo_lock() +{ + UHD_LOG_TRACE(_log_id, "Waiting for LO lock, " << ZBX_LO_LOCK_TIMEOUT_MS << " ms"); + const auto timeout = std::chrono::steady_clock::now() + + std::chrono::milliseconds(ZBX_LO_LOCK_TIMEOUT_MS); + while (std::chrono::steady_clock::now() < timeout && !get_lock_status()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + if (!get_lock_status()) { + // If we can't lock our LO, this could be a lot of possible issues + throw uhd::runtime_error(_log_id + " has failed to lock!"); + } +} + +bool zbx_lo_ctrl::get_lock_status() +{ + return _lmx->get_lock_status(); +} + +void zbx_lo_ctrl::set_lo_port_enabled(bool enable) +{ + UHD_LOG_TRACE(_log_id, + "Enabling LO " << (_testing_mode_enabled ? "test" : "output") << " port"); + + // We want to set the output port regardless of test mode being enabled + _lmx->set_output_enable(_get_output_port(false), enable); + + if (_testing_mode_enabled && enable) { + // If testing mode is enabled, also set the test port + _lmx->set_output_enable(_get_output_port(true), true); + } else { + // If testing mode is disabled, test port should be disabled + _lmx->set_output_enable(_get_output_port(true), false); + _lmx->set_mux_input( + _get_output_port(true), lmx2572_iface::mux_in_t::HIGH_IMPEDANCE); + } + + _lmx->set_enabled(enable); + _lmx->commit(); +} + +bool zbx_lo_ctrl::get_lo_port_enabled() +{ + return _lmx->get_enabled(); +} + +void zbx_lo_ctrl::set_lo_test_mode_enabled(bool enable) +{ + _testing_mode_enabled = enable; + set_lo_port_enabled(get_lo_port_enabled()); +} + +bool zbx_lo_ctrl::get_lo_test_mode_enabled() +{ + return _testing_mode_enabled; +} + +zbx_lo_t zbx_lo_ctrl::lo_string_to_enum( + const uhd::direction_t trx, const size_t channel, const std::string name) +{ + if (trx == TX_DIRECTION) { + if (channel == 0) { + if (name == ZBX_LO1) { + return zbx_lo_t::TX0_LO1; + } else if (name == ZBX_LO2) { + return zbx_lo_t::TX0_LO2; + } + } else if (channel == 1) { + if (name == ZBX_LO1) { + return zbx_lo_t::TX1_LO1; + } else if (name == ZBX_LO2) { + return zbx_lo_t::TX1_LO2; + } + } + } else { + if (channel == 0) { + if (name == ZBX_LO1) { + return zbx_lo_t::RX0_LO1; + } else if (name == ZBX_LO2) { + return zbx_lo_t::RX0_LO2; + } + } else if (channel == 1) { + if (name == ZBX_LO1) { + return zbx_lo_t::RX1_LO1; + } else if (name == ZBX_LO2) { + return zbx_lo_t::RX1_LO2; + } + } + } + UHD_THROW_INVALID_CODE_PATH(); +} + +lmx2572_iface::output_t zbx_lo_ctrl::_get_output_port(bool testing_mode) +{ + // Note: The LO output ports here are dependent to the LO and zbx hardware + // configuration, in no particular order (zbx radio configuration output vs. + // test port output) + if (!testing_mode) { + // Rev B has all LO outputs on Port A + return lmx2572_iface::output_t::RF_OUTPUT_A; + } else { + return lmx2572_iface::output_t::RF_OUTPUT_B; + } +} + +}}} // namespace uhd::usrp::zbx diff --git a/host/lib/usrp/mpmd/mpmd_devices.hpp b/host/lib/usrp/mpmd/mpmd_devices.hpp index 9cc046037..689e5c981 100644 --- a/host/lib/usrp/mpmd/mpmd_devices.hpp +++ b/host/lib/usrp/mpmd/mpmd_devices.hpp @@ -19,6 +19,6 @@ static const std::vector<std::string> MPM_DEVICE_TYPES = { MPM_CATCHALL_DEVICE_TYPE, "n3xx", "e3xx", -}; + "x4xx"}; #endif /* INCLUDED_MPMD_DEVICES_HPP */ diff --git a/host/lib/usrp/mpmd/mpmd_image_loader.cpp b/host/lib/usrp/mpmd/mpmd_image_loader.cpp index 145ad70c9..c15c4832c 100644 --- a/host/lib/usrp/mpmd/mpmd_image_loader.cpp +++ b/host/lib/usrp/mpmd/mpmd_image_loader.cpp @@ -10,10 +10,13 @@ #include <uhd/device.hpp> #include <uhd/exception.hpp> #include <uhd/image_loader.hpp> +#include <uhd/rfnoc/radio_control.hpp> +#include <uhd/rfnoc_graph.hpp> #include <uhd/types/component_file.hpp> #include <uhd/types/eeprom.hpp> #include <uhd/utils/paths.hpp> #include <uhd/utils/static.hpp> +#include <uhdlib/features/fpga_load_notification_iface.hpp> #include <uhdlib/utils/prefs.hpp> #include <boost/algorithm/string.hpp> #include <boost/archive/iterators/binary_from_base64.hpp> @@ -368,6 +371,19 @@ static bool mpmd_image_loader(const image_loader::image_loader_args_t& image_loa mpmd_send_fpga_to_device(image_loader_args, dev_addr); + { + // All MPM devices use RFNoC + auto graph = rfnoc::rfnoc_graph::make(find_hint); + for (size_t mb_index = 0; mb_index < graph->get_num_mboards(); mb_index++) { + auto mboard = graph->get_mb_controller(mb_index); + if (mboard->has_feature<uhd::features::fpga_load_notification_iface>()) { + auto& notifier = + mboard->get_feature<uhd::features::fpga_load_notification_iface>(); + notifier.onload(); + } + } + } + return true; } @@ -385,4 +401,5 @@ UHD_STATIC_BLOCK(register_mpm_image_loader) // time being image_loader::register_image_loader("n3xx", mpmd_image_loader, recovery_instructions); image_loader::register_image_loader("e3xx", mpmd_image_loader, recovery_instructions); + image_loader::register_image_loader("x4xx", mpmd_image_loader, recovery_instructions); } diff --git a/host/lib/usrp/mpmd/mpmd_mb_controller.cpp b/host/lib/usrp/mpmd/mpmd_mb_controller.cpp index dba8713c4..9bbd324b0 100644 --- a/host/lib/usrp/mpmd/mpmd_mb_controller.cpp +++ b/host/lib/usrp/mpmd/mpmd_mb_controller.cpp @@ -14,6 +14,44 @@ namespace { constexpr size_t MPMD_DEFAULT_LONG_TIMEOUT = 30000; // ms } // namespace +mpmd_mb_controller::fpga_onload::fpga_onload() +{} + +void mpmd_mb_controller::fpga_onload::onload() +{ + for (auto& cb : _cbs) + { + if (auto spt = cb.lock()) + { + spt->onload(); + } + } +} + +void mpmd_mb_controller::fpga_onload::request_cb(uhd::features::fpga_load_notification_iface::sptr handler) +{ + _cbs.emplace_back(handler); +} + +mpmd_mb_controller::ref_clk_calibration::ref_clk_calibration(uhd::usrp::mpmd_rpc_iface::sptr rpcc) + : _rpcc(rpcc) +{} + +void mpmd_mb_controller::ref_clk_calibration::set_ref_clk_tuning_word(uint32_t tuning_word) +{ + _rpcc->set_ref_clk_tuning_word(tuning_word); +} + +uint32_t mpmd_mb_controller::ref_clk_calibration::get_ref_clk_tuning_word() +{ + return _rpcc->get_ref_clk_tuning_word(); +} + +void mpmd_mb_controller::ref_clk_calibration::store_ref_clk_tuning_word(uint32_t tuning_word) +{ + _rpcc->store_ref_clk_tuning_word(tuning_word); +} + mpmd_mb_controller::mpmd_mb_controller( uhd::usrp::mpmd_rpc_iface::sptr rpcc, uhd::device_addr_t device_info) : _rpc(rpcc), _device_info(device_info) @@ -33,6 +71,14 @@ mpmd_mb_controller::mpmd_mb_controller( for (const auto& bank : _gpio_banks) { _gpio_srcs.insert({bank, _rpc->get_gpio_srcs(bank)}); } + + _fpga_onload = std::make_shared<fpga_onload>(); + register_feature(_fpga_onload); + + if (_rpc->supports_feature("ref_clk_calibration")) { + _ref_clk_cal = std::make_shared<ref_clk_calibration>(_rpc); + register_feature(_ref_clk_cal); + } } /****************************************************************************** @@ -142,16 +188,22 @@ std::vector<device_addr_t> mpmd_mb_controller::get_sync_sources() return result; } -void mpmd_mb_controller::set_clock_source_out(const bool /*enb*/) +void mpmd_mb_controller::set_clock_source_out(const bool enb) { - throw uhd::not_implemented_error( - "set_clock_source_out() not implemented on this device!"); + _rpc->set_clock_source_out(enb); } -void mpmd_mb_controller::set_time_source_out(const bool /*enb*/) +void mpmd_mb_controller::set_time_source_out(const bool enb) { - throw uhd::not_implemented_error( - "set_time_source_out() not implemented on this device!"); + if (_rpc->supports_feature("time_export")) + { + _rpc->set_trigger_io(enb ? "pps_output" : "off"); + } + else + { + throw uhd::not_implemented_error( + "set_time_source_out() not implemented on this device!"); + } } sensor_value_t mpmd_mb_controller::get_sensor(const std::string& name) diff --git a/host/lib/usrp/x300/x300_mb_controller.hpp b/host/lib/usrp/x300/x300_mb_controller.hpp index 6cfa3f525..a0ffb0654 100644 --- a/host/lib/usrp/x300/x300_mb_controller.hpp +++ b/host/lib/usrp/x300/x300_mb_controller.hpp @@ -11,6 +11,7 @@ #include "x300_device_args.hpp" #include "x300_radio_mbc_iface.hpp" #include "x300_regs.hpp" +#include <uhdlib/features/discoverable_feature_registry.hpp> #include <uhd/rfnoc/mb_controller.hpp> #include <uhd/types/sensors.hpp> #include <uhd/types/wb_iface.hpp> @@ -28,7 +29,8 @@ namespace uhd { namespace rfnoc { * - Controlling all time- and clock-related settings * - Initialize and hold the GPS control */ -class x300_mb_controller : public mb_controller +class x300_mb_controller : public mb_controller, + public ::uhd::features::discoverable_feature_registry { public: /************************************************************************** diff --git a/host/lib/usrp/x400/CMakeLists.txt b/host/lib/usrp/x400/CMakeLists.txt new file mode 100644 index 000000000..b3efe3b60 --- /dev/null +++ b/host/lib/usrp/x400/CMakeLists.txt @@ -0,0 +1,20 @@ +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/adc_self_calibration.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/x400_radio_control.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/x400_rfdc_control.cpp +) + +if(ENABLE_DPDK) + include_directories(${DPDK_INCLUDE_DIRS}) + add_definitions(-DHAVE_DPDK) +endif(ENABLE_DPDK) diff --git a/host/lib/usrp/x400/adc_self_calibration.cpp b/host/lib/usrp/x400/adc_self_calibration.cpp new file mode 100644 index 000000000..7deba7725 --- /dev/null +++ b/host/lib/usrp/x400/adc_self_calibration.cpp @@ -0,0 +1,196 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "adc_self_calibration.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/utils/scope_exit.hpp> +#include <chrono> +#include <thread> + +using namespace std::chrono_literals; + +namespace uhd { namespace features { + +adc_self_calibration::adc_self_calibration(uhd::usrp::x400_rpc_iface::sptr rpcc, + const std::string rpc_prefix, + const std::string unique_id, + size_t db_number, + uhd::usrp::x400::x400_dboard_iface::sptr daughterboard) + : _rpcc(rpcc) + , _rpc_prefix(rpc_prefix) + , _db_number(db_number) + , _daughterboard(daughterboard) + , _unique_id(unique_id) +{ +} + +void adc_self_calibration::run(size_t chan) +{ + const auto tx_gain_profile = + _daughterboard->get_tx_gain_profile_api()->get_gain_profile(chan); + const auto rx_gain_profile = + _daughterboard->get_rx_gain_profile_api()->get_gain_profile(chan); + if (tx_gain_profile != "default" || rx_gain_profile != "default") { + throw uhd::runtime_error("Cannot run ADC self calibration when gain " + "profile for RX or TX is not 'default'."); + } + + // The frequency that we need to feed into the ADC is, by decree, + // 13109 / 32768 times the ADC sample rate. (approx. 1.2GHz for a 3Gsps rate) + const double spll_freq = _rpcc->get_spll_freq(); + const double cal_tone_freq = spll_freq * 13109.0 / 32768.0; + + const auto cal_params = _daughterboard->get_adc_self_cal_params(cal_tone_freq); + + // Switch to CAL_LOOPBACK and save the current antenna + const auto rx_antenna = _daughterboard->get_rx_antenna(chan); + const auto tx_antenna = _daughterboard->get_tx_antenna(chan); + + auto reset_antennas = uhd::utils::scope_exit::make([&]() { + _daughterboard->set_rx_antenna(rx_antenna, chan); + _daughterboard->set_tx_antenna(tx_antenna, chan); + + // Waiting here allows some CPLD registers to be set. It's not clear + // to me why we require this wait. See azdo #1473824 + constexpr auto fudge_time = 100ms; + std::this_thread::sleep_for(fudge_time); + }); + + _daughterboard->set_rx_antenna("CAL_LOOPBACK", chan); + _daughterboard->set_tx_antenna("CAL_LOOPBACK", chan); + + // Configure the output DAC mux to output 1/2 full scale + // set_dac_mux_data uses 16-bit values. + _rpcc->set_dac_mux_data(32768 / 2, 0); + + const size_t motherboard_channel_number = _db_number * 2 + chan; + _rpcc->set_dac_mux_enable(motherboard_channel_number, 1); + auto disable_dac_mux = uhd::utils::scope_exit::make( + [&]() { _rpcc->set_dac_mux_enable(motherboard_channel_number, 0); }); + + // Save all of the LO frequencies & sources + const double original_rx_freq = _daughterboard->get_rx_frequency(chan); + std::map<std::string, std::tuple<std::string, double>> rx_lo_state; + for (auto rx_lo : _daughterboard->get_rx_lo_names(chan)) { + const std::string source(_daughterboard->get_rx_lo_source(rx_lo, chan)); + const double freq = _daughterboard->get_rx_lo_freq(rx_lo, chan); + rx_lo_state.emplace(std::piecewise_construct, + std::forward_as_tuple(rx_lo), + std::forward_as_tuple(source, freq)); + } + auto restore_rx_los = uhd::utils::scope_exit::make([&]() { + _daughterboard->set_rx_frequency(original_rx_freq, chan); + for (auto entry : rx_lo_state) { + auto& lo = std::get<0>(entry); + auto& state = std::get<1>(entry); + + auto& source = std::get<0>(state); + const double freq = std::get<1>(state); + _daughterboard->set_rx_lo_source(source, lo, chan); + _daughterboard->set_rx_lo_freq(freq, lo, chan); + } + }); + + const double original_tx_freq = _daughterboard->get_tx_frequency(chan); + std::map<std::string, std::tuple<std::string, double>> tx_lo_state; + for (auto tx_lo : _daughterboard->get_tx_lo_names(chan)) { + const std::string source(_daughterboard->get_tx_lo_source(tx_lo, chan)); + const double freq = _daughterboard->get_tx_lo_freq(tx_lo, chan); + tx_lo_state.emplace(std::piecewise_construct, + std::forward_as_tuple(tx_lo), + std::forward_as_tuple(source, freq)); + } + auto restore_tx_los = uhd::utils::scope_exit::make([&]() { + _daughterboard->set_tx_frequency(original_tx_freq, chan); + for (auto entry : tx_lo_state) { + auto& lo = std::get<0>(entry); + auto& state = std::get<1>(entry); + + auto& source = std::get<0>(state); + const double freq = std::get<1>(state); + _daughterboard->set_tx_lo_source(source, lo, chan); + _daughterboard->set_tx_lo_freq(freq, lo, chan); + } + }); + + _daughterboard->set_tx_frequency(cal_params.tx_freq, chan); + _daughterboard->set_rx_frequency(cal_params.rx_freq, chan); + + // Set & restore the gain + const double tx_gain = _daughterboard->get_tx_gain(chan); + const double rx_gain = _daughterboard->get_rx_gain(chan); + auto restore_gains = uhd::utils::scope_exit::make([&]() { + _daughterboard->get_rx_gain_profile_api()->set_gain_profile("default", chan); + _daughterboard->get_tx_gain_profile_api()->set_gain_profile("default", chan); + + _daughterboard->set_tx_gain(tx_gain, chan); + _daughterboard->set_rx_gain(rx_gain, chan); + }); + + // Set the threshold to detect half-scale + // The setup_threshold call uses 14-bit ADC values + constexpr int hysteresis_reset_time = 100; + constexpr int hysteresis_reset_threshold = 8000; + constexpr int hysteresis_set_threshold = 8192; + _rpcc->setup_threshold(_db_number, + chan, + 0, + "hysteresis", + hysteresis_reset_time, + hysteresis_reset_threshold, + hysteresis_set_threshold); + bool found_gain = false; + for (double i = cal_params.min_gain; i <= cal_params.max_gain; i += 1.0) { + _daughterboard->get_rx_gain_profile_api()->set_gain_profile("default", chan); + _daughterboard->get_tx_gain_profile_api()->set_gain_profile("default", chan); + + _daughterboard->set_tx_gain(i, chan); + _daughterboard->set_rx_gain(i, chan); + + // Set the daughterboard to use the duplex entry in the DSA table which was + // configured in the set_?x_gain call. (note that with a LabVIEW FPGA, we don't + // control the ATR lines, hence why we override them here) + _daughterboard->get_rx_gain_profile_api()->set_gain_profile("table_noatr", chan); + _daughterboard->get_tx_gain_profile_api()->set_gain_profile("table_noatr", chan); + + _daughterboard->set_rx_gain(0b11, chan); + _daughterboard->set_tx_gain(0b11, chan); + + // Wait for it to settle + constexpr auto settle_time = 10ms; + std::this_thread::sleep_for(settle_time); + + try { + const bool threshold_status = + _rpcc->get_threshold_status(_db_number, chan, 0); + if (threshold_status) { + found_gain = true; + break; + } + } catch (uhd::runtime_error&) { + // Catch & eat this error, the user has a 5.0 FPGA and so can't auto-gain + return; + } + } + + if (!found_gain) { + throw uhd::runtime_error( + "Could not find appropriate gain for performing ADC self cal"); + } + + // (if required) unfreeze calibration + const std::vector<int> current_frozen_state = _rpcc->get_cal_frozen(_db_number, chan); + _rpcc->set_cal_frozen(0, _db_number, chan); + auto refreeze_adcs = uhd::utils::scope_exit::make( + [&]() { _rpcc->set_cal_frozen(current_frozen_state[0], _db_number, chan); }); + + // Let the ADC calibrate + // 2000ms was found experimentally to be sufficient + constexpr auto calibration_time = 2000ms; + std::this_thread::sleep_for(calibration_time); +} + +}} // namespace uhd::features diff --git a/host/lib/usrp/x400/adc_self_calibration.hpp b/host/lib/usrp/x400/adc_self_calibration.hpp new file mode 100644 index 000000000..474c14e07 --- /dev/null +++ b/host/lib/usrp/x400/adc_self_calibration.hpp @@ -0,0 +1,46 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include <uhd/features/adc_self_calibration_iface.hpp> +#include <uhdlib/usrp/common/rpc.hpp> +#include <uhdlib/usrp/dboard/x400_dboard_iface.hpp> +#include <string> + +namespace uhd { namespace features { + +using namespace uhd::rfnoc; + +class adc_self_calibration : public adc_self_calibration_iface +{ +public: + adc_self_calibration(uhd::usrp::x400_rpc_iface::sptr rpcc, + const std::string rpc_prefix, + const std::string unique_id, + size_t db_number, + uhd::usrp::x400::x400_dboard_iface::sptr daughterboard); + + void run(const size_t channel) override; + +private: + //! Reference to the RPC client + uhd::usrp::x400_rpc_iface::sptr _rpcc; + + const std::string _rpc_prefix; + + const size_t _db_number; + + uhd::usrp::x400::x400_dboard_iface::sptr _daughterboard; + + const std::string _unique_id; + std::string get_unique_id() const + { + return _unique_id; + } +}; + +}} // namespace uhd::features diff --git a/host/lib/usrp/x400/x400_radio_control.cpp b/host/lib/usrp/x400/x400_radio_control.cpp new file mode 100644 index 000000000..281ae7916 --- /dev/null +++ b/host/lib/usrp/x400/x400_radio_control.cpp @@ -0,0 +1,752 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "x400_radio_control.hpp" +#include <uhd/rfnoc/registry.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/math.hpp> +#include <uhdlib/usrp/common/x400_rfdc_control.hpp> +#include <uhdlib/usrp/dboard/debug_dboard.hpp> +#include <uhdlib/usrp/dboard/null_dboard.hpp> +#include <uhdlib/usrp/dboard/zbx/zbx_dboard.hpp> + +namespace uhd { namespace rfnoc { + +x400_radio_control_impl::fpga_onload::fpga_onload(size_t num_channels, + uhd::features::adc_self_calibration_iface::sptr adc_self_cal, + std::string unique_id) + : _num_channels(num_channels), _adc_self_cal(adc_self_cal), _unique_id(unique_id) +{ +} + +void x400_radio_control_impl::fpga_onload::onload() +{ + for (size_t channel = 0; channel < _num_channels; channel++) { + if (_adc_self_cal) { + try { + _adc_self_cal->run(channel); + } catch (uhd::runtime_error& e) { + RFNOC_LOG_WARNING("Failure while running self cal on channel " + << channel << ": " << e.what()); + } + } + } +} + +x400_radio_control_impl::x400_radio_control_impl(make_args_ptr make_args) + : radio_control_impl(std::move(make_args)) +{ + RFNOC_LOG_TRACE("Initializing x400_radio_control"); + + UHD_ASSERT_THROW(get_block_id().get_block_count() < 2); + constexpr char radio_slot_name[2] = {'A', 'B'}; + _radio_slot = radio_slot_name[get_block_id().get_block_count()]; + _rpc_prefix = get_block_id().get_block_count() == 1 ? "db_1_" : "db_0_"; + + UHD_ASSERT_THROW(get_mb_controller()); + _mb_control = std::dynamic_pointer_cast<mpmd_mb_controller>(get_mb_controller()); + + _x4xx_timekeeper = std::dynamic_pointer_cast<mpmd_mb_controller::mpmd_timekeeper>( + _mb_control->get_timekeeper(0)); + UHD_ASSERT_THROW(_x4xx_timekeeper); + + _rpcc = _mb_control->dynamic_cast_rpc_as<uhd::usrp::x400_rpc_iface>(); + if (!_rpcc) { + _rpcc = std::make_shared<uhd::usrp::x400_rpc>(_mb_control->get_rpc_client()); + } + + _db_rpcc = _mb_control->dynamic_cast_rpc_as<uhd::usrp::dboard_base_rpc_iface>(); + if (!_db_rpcc) { + _db_rpcc = std::make_shared<uhd::usrp::dboard_base_rpc>( + _mb_control->get_rpc_client(), _rpc_prefix); + } + + const auto all_dboard_info = _rpcc->get_dboard_info(); + RFNOC_LOG_TRACE("Hardware detected " << all_dboard_info.size() << " daughterboards."); + + // If we have two radio blocks, but there is only one dboard plugged in, + // we skip initialization. The board needs to be in slot A + if (all_dboard_info.size() <= get_block_id().get_block_count()) { + RFNOC_LOG_WARNING("The number of discovered daughterboards did not match the " + "number of radio blocks. Skipping front end initialization."); + _daughterboard = std::make_shared<null_dboard_impl>(); + return; + } + + const double master_clock_rate = _rpcc->get_master_clock_rate(); + set_tick_rate(master_clock_rate); + _x4xx_timekeeper->update_tick_rate(master_clock_rate); + radio_control_impl::set_rate(master_clock_rate); + + for (auto& samp_rate_prop : _samp_rate_in) { + set_property(samp_rate_prop.get_id(), get_rate(), samp_rate_prop.get_src_info()); + } + for (auto& samp_rate_prop : _samp_rate_out) { + set_property(samp_rate_prop.get_id(), get_rate(), samp_rate_prop.get_src_info()); + } + + _validate_master_clock_rate_args(); + _init_mpm(); + + RFNOC_LOG_TRACE("Initializing RFDC controls..."); + _rfdcc = std::make_shared<x400::rfdc_control>( + // clang-format off + uhd::memmap32_iface_timed{ + [this](const uint32_t addr, const uint32_t data, const uhd::time_spec_t& time_spec) { + regs().poke32(addr + x400_regs::RFDC_CTRL_BASE, data, time_spec); + }, + [this](const uint32_t addr) { + return regs().peek32(addr + x400_regs::RFDC_CTRL_BASE); + } + }, + // clang-format on + get_unique_id() + "::RFDC"); + + const auto& dboard = all_dboard_info[get_block_id().get_block_count()]; + const std::string pid(dboard.at("pid").begin(), dboard.at("pid").end()); + RFNOC_LOG_TRACE("Initializing daughterboard driver for PID " << pid); + + // We may have physical daughterboards in the system, but no GPIO interface to the + // daughterboard in the FPGA. In this case, just instantiate the null daughterboard. + if (!_rpcc->is_db_gpio_ifc_present(get_block_id().get_block_count())) { + RFNOC_LOG_WARNING( + "Skipping daughterboard initialization, no GPIO interface in FPGA"); + _daughterboard = std::make_shared<null_dboard_impl>(); + return; + } + + if (std::stol(pid) == uhd::usrp::zbx::ZBX_PID) { + auto zbx_rpc_sptr = _mb_control->dynamic_cast_rpc_as<uhd::usrp::zbx_rpc_iface>(); + if (!zbx_rpc_sptr) { + zbx_rpc_sptr = std::make_shared<uhd::usrp::zbx_rpc>( + _mb_control->get_rpc_client(), _rpc_prefix); + } + _daughterboard = std::make_shared<uhd::usrp::zbx::zbx_dboard_impl>( + regs(), + regmap::PERIPH_BASE, + [this](const size_t instance) { return get_command_time(instance); }, + get_block_id().get_block_count(), + _radio_slot, + _rpc_prefix, + get_unique_id(), + _rpcc, + zbx_rpc_sptr, + _rfdcc, + get_tree()); + } else if (std::stol(pid) == uhd::rfnoc::DEBUG_DB_PID) { + _daughterboard = std::make_shared<debug_dboard_impl>(); + } else if (std::stol(pid) == uhd::rfnoc::IF_TEST_DBOARD_PID) { + _daughterboard = + std::make_shared<if_test_dboard_impl>(get_block_id().get_block_count(), + _rpc_prefix, + get_unique_id(), + _mb_control, + get_tree()); + } else if (std::stol(pid) == uhd::rfnoc::EMPTY_DB_PID) { + _daughterboard = std::make_shared<empty_slot_dboard_impl>(); + set_num_output_ports(0); + set_num_input_ports(0); + } else { + RFNOC_LOG_WARNING("Skipping Daughterboard initialization for unsupported PID " + << "0x" << std::hex << std::stol(pid)); + _daughterboard = std::make_shared<null_dboard_impl>(); + return; + } + + _init_prop_tree(); + + _rx_pwr_mgr = _daughterboard->get_pwr_mgr(uhd::RX_DIRECTION); + _tx_pwr_mgr = _daughterboard->get_pwr_mgr(uhd::TX_DIRECTION); + + _tx_gain_profile_api = _daughterboard->get_tx_gain_profile_api(); + _rx_gain_profile_api = _daughterboard->get_rx_gain_profile_api(); + + if (_daughterboard->is_adc_self_cal_supported()) { + _adc_self_calibration = + std::make_shared<uhd::features::adc_self_calibration>(_rpcc, + _rpc_prefix, + get_unique_id(), + get_block_id().get_block_count(), + _daughterboard); + register_feature(_adc_self_calibration); + } + + _fpga_onload = std::make_shared<fpga_onload>( + get_num_output_ports(), _adc_self_calibration, get_unique_id()); + register_feature(_fpga_onload); + _mb_control->_fpga_onload->request_cb(_fpga_onload); +} + +void x400_radio_control_impl::_init_prop_tree() +{ + auto subtree = get_tree()->subtree(fs_path("mboard")); + + for (size_t chan_idx = 0; chan_idx < get_num_output_ports(); chan_idx++) { + const fs_path rx_codec_path = + fs_path("rx_codec") / get_dboard_fe_from_chan(chan_idx, uhd::RX_DIRECTION); + const fs_path tx_codec_path = + fs_path("tx_codec") / get_dboard_fe_from_chan(chan_idx, uhd::TX_DIRECTION); + RFNOC_LOG_TRACE("Adding non-RFNoC block properties for channel " + << chan_idx << " to prop tree paths " << rx_codec_path << " and " + << tx_codec_path); + + // ADC calibration state attributes + subtree->create<bool>(rx_codec_path / "calibration_frozen") + .add_coerced_subscriber([this, chan_idx](bool state) { + _rpcc->set_cal_frozen(state, get_block_id().get_block_count(), chan_idx); + }) + .set_publisher([this, chan_idx]() { + const auto freeze_states = + _rpcc->get_cal_frozen(get_block_id().get_block_count(), chan_idx); + return freeze_states.at(0) == 1; + }); + + // RFDC NCO + // RX + subtree->create<double>(rx_codec_path / "rfdc" / "freq/value") + .add_desired_subscriber([this, chan_idx](double freq) { + _rpcc->rfdc_set_nco_freq(_get_trx_string(RX_DIRECTION), + get_block_id().get_block_count(), + chan_idx, + freq); + }) + .set_publisher([this, chan_idx]() { + const auto nco_freq = + _rpcc->rfdc_get_nco_freq(_get_trx_string(RX_DIRECTION), + get_block_id().get_block_count(), + chan_idx); + return nco_freq; + }); + + // TX + subtree->create<double>(tx_codec_path / "rfdc" / "freq/value") + .add_desired_subscriber([this, chan_idx](double freq) { + _rpcc->rfdc_set_nco_freq(_get_trx_string(TX_DIRECTION), + get_block_id().get_block_count(), + chan_idx, + freq); + }) + .set_publisher([this, chan_idx]() { + const auto nco_freq = + _rpcc->rfdc_get_nco_freq(_get_trx_string(TX_DIRECTION), + get_block_id().get_block_count(), + chan_idx); + return nco_freq; + }); + } +} + +void x400_radio_control_impl::_validate_master_clock_rate_args() +{ + auto block_args = get_block_args(); + + // Note: MCR gets set during the init() call (prior to this), which takes + // in arguments from the device args. So if block_args contains a + // master_clock_rate key, then it should better be whatever the device is + // configured to do. + const double master_clock_rate = _rpcc->get_master_clock_rate(); + if (!uhd::math::frequencies_are_equal(get_rate(), master_clock_rate)) { + throw uhd::runtime_error( + str(boost::format("Master clock rate mismatch. Device returns %f MHz, " + "but should have been %f MHz.") + % (master_clock_rate / 1e6) % (get_rate() / 1e6))); + } + RFNOC_LOG_DEBUG("Master Clock Rate is: " << (master_clock_rate / 1e6) << " MHz."); +} + +void x400_radio_control_impl::_init_mpm() +{ + // Init sensors + for (const auto& dir : std::vector<direction_t>{RX_DIRECTION, TX_DIRECTION}) { + // TODO: We should pull the number of channels from _daughterboard + for (size_t chan_idx = 0; chan_idx < uhd::usrp::zbx::ZBX_NUM_CHANS; chan_idx++) { + _init_mpm_sensors(dir, chan_idx); + } + } +} + +// @TODO: This should be a method on direction_t +// (or otherwise not duplicated from the implementation in zbx) +std::string x400_radio_control_impl::_get_trx_string(const direction_t dir) const +{ + if (dir == RX_DIRECTION) { + return "rx"; + } else if (dir == TX_DIRECTION) { + return "tx"; + } else { + UHD_THROW_INVALID_CODE_PATH(); + } +} + + +void x400_radio_control_impl::_init_mpm_sensors( + const direction_t dir, const size_t chan_idx) +{ + // TODO: We should pull the number of channels from _daughterboard + UHD_ASSERT_THROW(chan_idx < uhd::usrp::zbx::ZBX_NUM_CHANS); + const std::string trx = _get_trx_string(dir); + const fs_path fe_path = fs_path("dboard") + / (dir == RX_DIRECTION ? "rx_frontends" : "tx_frontends") + / chan_idx; + auto sensor_list = _db_rpcc->get_sensors(trx); + RFNOC_LOG_TRACE("Chan " << chan_idx << ": Found " << sensor_list.size() << " " << trx + << " sensors."); + for (const auto& sensor_name : sensor_list) { + RFNOC_LOG_TRACE("Adding " << trx << " sensor " << sensor_name); + get_tree() + ->create<sensor_value_t>(fe_path / "sensors" / sensor_name) + .add_coerced_subscriber([](const sensor_value_t&) { + throw uhd::runtime_error("Attempting to write to sensor!"); + }) + .set_publisher([this, trx, sensor_name, chan_idx]() { + return sensor_value_t( + this->_db_rpcc->get_sensor(trx, sensor_name, chan_idx)); + }); + } +} + +fs_path x400_radio_control_impl::_get_db_fe_path( + const size_t chan, const direction_t dir) const +{ + const std::string trx = _get_trx_string(dir); + return DB_PATH / (trx + "_frontends") / get_dboard_fe_from_chan(chan, dir); +} + + +double x400_radio_control_impl::set_rate(const double rate) +{ + // X400 does not support runtime rate changes + if (!uhd::math::frequencies_are_equal(rate, get_rate())) { + RFNOC_LOG_WARNING("Requesting invalid sampling rate from device: " + << (rate / 1e6) << " MHz. Actual rate is: " + << (get_rate() / 1e6) << " MHz."); + } + return get_rate(); +} + +eeprom_map_t x400_radio_control_impl::get_db_eeprom() +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_db_eeprom(); +} + +std::vector<uhd::usrp::pwr_cal_mgr::sptr> x400_radio_control_impl::get_pwr_mgr( + const uhd::direction_t trx) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_pwr_mgr(trx); +} + +std::string x400_radio_control_impl::get_tx_antenna(const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_antenna(chan); +} + +std::vector<std::string> x400_radio_control_impl::get_tx_antennas(const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_antennas(chan); +} + +void x400_radio_control_impl::set_tx_antenna(const std::string& ant, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + _daughterboard->set_tx_antenna(ant, chan); +} + +std::string x400_radio_control_impl::get_rx_antenna(const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_antenna(chan); +} + +std::vector<std::string> x400_radio_control_impl::get_rx_antennas(const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_antennas(chan); +} + +void x400_radio_control_impl::set_rx_antenna(const std::string& ant, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + _daughterboard->set_rx_antenna(ant, chan); +} + +double x400_radio_control_impl::get_tx_frequency(const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_frequency(chan); +} + +double x400_radio_control_impl::set_tx_frequency(const double freq, size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->set_tx_frequency(freq, chan); +} + +void x400_radio_control_impl::set_tx_tune_args( + const uhd::device_addr_t& args, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + _daughterboard->set_tx_tune_args(args, chan); +} + +uhd::freq_range_t x400_radio_control_impl::get_tx_frequency_range(const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_frequency_range(chan); +} + +double x400_radio_control_impl::get_rx_frequency(const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_frequency(chan); +} + +double x400_radio_control_impl::set_rx_frequency(const double freq, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->set_rx_frequency(freq, chan); +} + +void x400_radio_control_impl::set_rx_tune_args( + const uhd::device_addr_t& args, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + _daughterboard->set_rx_tune_args(args, chan); +} + +uhd::freq_range_t x400_radio_control_impl::get_rx_frequency_range(const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_frequency_range(chan); +} + +std::vector<std::string> x400_radio_control_impl::get_tx_gain_names( + const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_gain_names(chan); +} + +uhd::gain_range_t x400_radio_control_impl::get_tx_gain_range(const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_gain_range(chan); +} + +uhd::gain_range_t x400_radio_control_impl::get_tx_gain_range( + const std::string& name, const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_gain_range(name, chan); +} + +double x400_radio_control_impl::get_tx_gain(const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_gain(chan); +} + +double x400_radio_control_impl::get_tx_gain(const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_gain(name, chan); +} + +double x400_radio_control_impl::set_tx_gain(const double gain, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->set_tx_gain(gain, chan); +} + +double x400_radio_control_impl::set_tx_gain( + const double gain, const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->set_tx_gain(gain, name, chan); +} + +std::vector<std::string> x400_radio_control_impl::get_rx_gain_names( + const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_gain_names(chan); +} + +uhd::gain_range_t x400_radio_control_impl::get_rx_gain_range(const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_gain_range(chan); +} + +uhd::gain_range_t x400_radio_control_impl::get_rx_gain_range( + const std::string& name, const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_gain_range(name, chan); +} + +double x400_radio_control_impl::get_rx_gain(const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_gain(chan); +} + +double x400_radio_control_impl::get_rx_gain(const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_gain(name, chan); +} + +double x400_radio_control_impl::set_rx_gain(const double gain, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->set_rx_gain(gain, chan); +} + +double x400_radio_control_impl::set_rx_gain( + const double gain, const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->set_rx_gain(gain, name, chan); +} + +void x400_radio_control_impl::set_rx_agc(const bool enable, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + _daughterboard->set_rx_agc(enable, chan); +} + +meta_range_t x400_radio_control_impl::get_tx_bandwidth_range(size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_bandwidth_range(chan); +} + +double x400_radio_control_impl::get_tx_bandwidth(const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_bandwidth(chan); +} + +double x400_radio_control_impl::set_tx_bandwidth( + const double bandwidth, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->set_tx_bandwidth(bandwidth, chan); +} + +meta_range_t x400_radio_control_impl::get_rx_bandwidth_range(size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_bandwidth_range(chan); +} + +double x400_radio_control_impl::get_rx_bandwidth(const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_bandwidth(chan); +} + +double x400_radio_control_impl::set_rx_bandwidth( + const double bandwidth, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->set_rx_bandwidth(bandwidth, chan); +} + +std::vector<std::string> x400_radio_control_impl::get_rx_lo_names(const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_lo_names(chan); +} + +std::vector<std::string> x400_radio_control_impl::get_rx_lo_sources( + const std::string& name, const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_lo_sources(name, chan); +} + +freq_range_t x400_radio_control_impl::get_rx_lo_freq_range( + const std::string& name, const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_lo_freq_range(name, chan); +} + +void x400_radio_control_impl::set_rx_lo_source( + const std::string& src, const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + _daughterboard->set_rx_lo_source(src, name, chan); +} + +const std::string x400_radio_control_impl::get_rx_lo_source( + const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_lo_source(name, chan); +} + +void x400_radio_control_impl::set_rx_lo_export_enabled( + bool enabled, const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + _daughterboard->set_rx_lo_export_enabled(enabled, name, chan); +} + +bool x400_radio_control_impl::get_rx_lo_export_enabled( + const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_lo_export_enabled(name, chan); +} + +double x400_radio_control_impl::set_rx_lo_freq( + double freq, const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->set_rx_lo_freq(freq, name, chan); +} + +double x400_radio_control_impl::get_rx_lo_freq(const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_rx_lo_freq(name, chan); +} + +std::vector<std::string> x400_radio_control_impl::get_tx_lo_names(const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_lo_names(chan); +} + +std::vector<std::string> x400_radio_control_impl::get_tx_lo_sources( + const std::string& name, const size_t chan) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_lo_sources(name, chan); +} + +freq_range_t x400_radio_control_impl::get_tx_lo_freq_range( + const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_lo_freq_range(name, chan); +} + +void x400_radio_control_impl::set_tx_lo_source( + const std::string& src, const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + _daughterboard->set_tx_lo_source(src, name, chan); +} + +const std::string x400_radio_control_impl::get_tx_lo_source( + const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_lo_source(name, chan); +} + +void x400_radio_control_impl::set_tx_lo_export_enabled( + const bool enabled, const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + _daughterboard->set_tx_lo_export_enabled(enabled, name, chan); +} + +bool x400_radio_control_impl::get_tx_lo_export_enabled( + const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_lo_export_enabled(name, chan); +} + +double x400_radio_control_impl::set_tx_lo_freq( + const double freq, const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->set_tx_lo_freq(freq, name, chan); +} + +double x400_radio_control_impl::get_tx_lo_freq(const std::string& name, const size_t chan) +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_tx_lo_freq(name, chan); +} + +void x400_radio_control_impl::set_command_time(uhd::time_spec_t time, const size_t chan) +{ + node_t::set_command_time(time, chan); + _daughterboard->set_command_time(time, chan); +} + +/************************************************************************** + * Sensor API + *************************************************************************/ +std::vector<std::string> x400_radio_control_impl::get_rx_sensor_names( + const size_t chan) const +{ + const fs_path sensor_path = _get_db_fe_path(chan, RX_DIRECTION) / "sensors"; + if (get_tree()->exists(sensor_path)) { + return get_tree()->list(sensor_path); + } + return {}; +} + +uhd::sensor_value_t x400_radio_control_impl::get_rx_sensor( + const std::string& name, const size_t chan) +{ + return get_tree() + ->access<uhd::sensor_value_t>( + _get_db_fe_path(chan, RX_DIRECTION) / "sensors" / name) + .get(); +} + +std::vector<std::string> x400_radio_control_impl::get_tx_sensor_names( + const size_t chan) const +{ + const fs_path sensor_path = _get_db_fe_path(chan, TX_DIRECTION) / "sensors"; + if (get_tree()->exists(sensor_path)) { + return get_tree()->list(sensor_path); + } + return {}; +} + +uhd::sensor_value_t x400_radio_control_impl::get_tx_sensor( + const std::string& name, const size_t chan) +{ + return get_tree() + ->access<uhd::sensor_value_t>( + _get_db_fe_path(chan, TX_DIRECTION) / "sensors" / name) + .get(); +} + +/************************************************************************** + * Radio Identification API Calls + *************************************************************************/ +size_t x400_radio_control_impl::get_chan_from_dboard_fe( + const std::string& fe, const uhd::direction_t direction) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_chan_from_dboard_fe(fe, direction); +} + +std::string x400_radio_control_impl::get_dboard_fe_from_chan( + const size_t chan, const uhd::direction_t direction) const +{ + std::lock_guard<std::recursive_mutex> l(_lock); + return _daughterboard->get_dboard_fe_from_chan(chan, direction); +} + +UHD_RFNOC_BLOCK_REGISTER_FOR_DEVICE_DIRECT( + x400_radio_control, RADIO_BLOCK, X400, "Radio", true, "radio_clk", "ctrl_clk") + +}} // namespace uhd::rfnoc diff --git a/host/lib/usrp/x400/x400_radio_control.hpp b/host/lib/usrp/x400/x400_radio_control.hpp new file mode 100644 index 000000000..65b37cc2b --- /dev/null +++ b/host/lib/usrp/x400/x400_radio_control.hpp @@ -0,0 +1,198 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include "adc_self_calibration.hpp" +#include <uhd/rfnoc/noc_block_base.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/types/ranges.hpp> +#include <uhdlib/features/fpga_load_notification_iface.hpp> +#include <uhdlib/rfnoc/radio_control_impl.hpp> +#include <uhdlib/rfnoc/rf_control/dboard_iface.hpp> +#include <uhdlib/usrp/common/mpmd_mb_controller.hpp> +#include <uhdlib/usrp/common/rpc.hpp> +#include <uhdlib/usrp/common/x400_rfdc_control.hpp> +#include <uhdlib/utils/rpc.hpp> +#include <stddef.h> +#include <memory> +#include <mutex> +#include <string> +#include <vector> + +namespace uhd { namespace rfnoc { + +namespace x400_regs { + +//! Base address for the rf_timing_control module. This controls the NCOs and +// other things in the RFDC. +constexpr uint32_t RFDC_CTRL_BASE = radio_control_impl::regmap::PERIPH_BASE + 0x8000; + +} // namespace x400_regs + +class x400_radio_control_impl : public radio_control_impl +{ +public: + using sptr = std::shared_ptr<x400_radio_control_impl>; + + /************************************************************************ + * Structors + ***********************************************************************/ + x400_radio_control_impl(make_args_ptr make_args); + virtual ~x400_radio_control_impl() = default; + + std::string get_slot_name() const override + { + return _radio_slot; + } + + size_t get_chan_from_dboard_fe(const std::string&, uhd::direction_t) const override; + std::string get_dboard_fe_from_chan(size_t chan, uhd::direction_t) const override; + + uhd::eeprom_map_t get_db_eeprom() override; + + // Shim calls for every method in rf_control_core + double set_rate(const double rate) override; + std::string get_tx_antenna(const size_t chan) const override; + std::vector<std::string> get_tx_antennas(const size_t chan) const override; + void set_tx_antenna(const std::string& ant, const size_t chan) override; + std::string get_rx_antenna(const size_t chan) const override; + std::vector<std::string> get_rx_antennas(const size_t chan) const override; + void set_rx_antenna(const std::string& ant, const size_t chan) override; + double get_tx_frequency(const size_t chan) override; + double set_tx_frequency(const double freq, size_t chan) override; + void set_tx_tune_args(const uhd::device_addr_t& args, const size_t chan) override; + uhd::freq_range_t get_tx_frequency_range(const size_t chan) const override; + double get_rx_frequency(const size_t chan) override; + double set_rx_frequency(const double freq, const size_t chan) override; + void set_rx_tune_args(const uhd::device_addr_t& args, const size_t chan) override; + uhd::freq_range_t get_rx_frequency_range(const size_t chan) const override; + std::vector<std::string> get_tx_gain_names(const size_t chan) const override; + uhd::gain_range_t get_tx_gain_range(const size_t chan) const override; + uhd::gain_range_t get_tx_gain_range( + const std::string& name, const size_t chan) const override; + double get_tx_gain(const size_t chan) override; + double get_tx_gain(const std::string& name, const size_t chan) override; + double set_tx_gain(const double gain, const size_t chan) override; + double set_tx_gain( + const double gain, const std::string& name, const size_t chan) override; + std::vector<std::string> get_rx_gain_names(const size_t chan) const override; + uhd::gain_range_t get_rx_gain_range(const size_t chan) const override; + uhd::gain_range_t get_rx_gain_range( + const std::string& name, const size_t chan) const override; + double get_rx_gain(const size_t chan) override; + double get_rx_gain(const std::string& name, const size_t chan) override; + double set_rx_gain(const double gain, const size_t chan) override; + double set_rx_gain( + const double gain, const std::string& name, const size_t chan) override; + void set_rx_agc(const bool enable, const size_t chan) override; + meta_range_t get_tx_bandwidth_range(size_t chan) const override; + double get_tx_bandwidth(const size_t chan) override; + double set_tx_bandwidth(const double bandwidth, const size_t chan) override; + meta_range_t get_rx_bandwidth_range(size_t chan) const override; + double get_rx_bandwidth(const size_t chan) override; + double set_rx_bandwidth(const double bandwidth, const size_t chan) override; + + std::vector<std::string> get_rx_lo_names(const size_t chan) const override; + std::vector<std::string> get_rx_lo_sources( + const std::string& name, const size_t chan) const override; + freq_range_t get_rx_lo_freq_range( + const std::string& name, const size_t chan) const override; + void set_rx_lo_source( + const std::string& src, const std::string& name, const size_t chan) override; + const std::string get_rx_lo_source( + const std::string& name, const size_t chan) override; + void set_rx_lo_export_enabled( + bool enabled, const std::string& name, const size_t chan) override; + bool get_rx_lo_export_enabled( + const std::string& name, const size_t chan) override; + double set_rx_lo_freq( + double freq, const std::string& name, const size_t chan) override; + double get_rx_lo_freq(const std::string& name, const size_t chan) override; + std::vector<std::string> get_tx_lo_names(const size_t chan) const override; + std::vector<std::string> get_tx_lo_sources( + const std::string& name, const size_t chan) const override; + freq_range_t get_tx_lo_freq_range( + const std::string& name, const size_t chan) override; + void set_tx_lo_source( + const std::string& src, const std::string& name, const size_t chan) override; + const std::string get_tx_lo_source( + const std::string& name, const size_t chan) override; + void set_tx_lo_export_enabled( + const bool enabled, const std::string& name, const size_t chan) override; + bool get_tx_lo_export_enabled(const std::string& name, const size_t chan) override; + double set_tx_lo_freq( + const double freq, const std::string& name, const size_t chan) override; + double get_tx_lo_freq(const std::string& name, const size_t chan) override; + std::vector<std::string> get_rx_sensor_names(size_t chan) const override; + uhd::sensor_value_t get_rx_sensor(const std::string& name, size_t chan) override; + std::vector<std::string> get_tx_sensor_names(size_t chan) const override; + uhd::sensor_value_t get_tx_sensor(const std::string& name, size_t chan) override; + + void set_command_time(uhd::time_spec_t time, const size_t chan) override; + + // Non-API methods + // This is used for x4xx radio block unit tests + std::vector<uhd::usrp::pwr_cal_mgr::sptr> get_pwr_mgr(const uhd::direction_t trx); + +private: + //! Locks access to the API + mutable std::recursive_mutex _lock; + + std::string _get_trx_string(const direction_t dir) const; + + void _validate_master_clock_rate_args(); + void _init_mpm(); + void _init_mpm_sensors(const direction_t dir, const size_t chan_idx); + void _init_prop_tree(); + fs_path _get_db_fe_path(const size_t chan, const uhd::direction_t dir) const; + + //! Reference to the MB controller + uhd::rfnoc::mpmd_mb_controller::sptr _mb_control; + + //! Reference to the RPC client + uhd::usrp::x400_rpc_iface::sptr _rpcc; + uhd::usrp::dboard_base_rpc_iface::sptr _db_rpcc; + + //! Reference to the MB timekeeper + uhd::rfnoc::mpmd_mb_controller::mpmd_timekeeper::sptr _x4xx_timekeeper; + + std::string _radio_slot; + std::string _rpc_prefix; + + //! Reference to this radio block's RFDC control + x400::rfdc_control::sptr _rfdcc; + + uhd::usrp::x400::x400_dboard_iface::sptr _daughterboard; + + uhd::features::adc_self_calibration_iface::sptr _adc_self_calibration; + + class fpga_onload : public uhd::features::fpga_load_notification_iface + { + public: + using sptr = std::shared_ptr<fpga_onload>; + + fpga_onload(size_t num_channels, + uhd::features::adc_self_calibration_iface::sptr adc_self_cal, + std::string unique_id); + + void onload() override; + + private: + const size_t _num_channels; + uhd::features::adc_self_calibration_iface::sptr _adc_self_cal; + const std::string _unique_id; + std::string get_unique_id() const + { + return _unique_id; + } + }; + + fpga_onload::sptr _fpga_onload; +}; + +}} // namespace uhd::rfnoc diff --git a/host/lib/usrp/x400/x400_rfdc_control.cpp b/host/lib/usrp/x400/x400_rfdc_control.cpp new file mode 100644 index 000000000..b963c0a46 --- /dev/null +++ b/host/lib/usrp/x400/x400_rfdc_control.cpp @@ -0,0 +1,73 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/utils/log.hpp> +#include <uhdlib/usrp/common/x400_rfdc_control.hpp> +#include <unordered_map> + +using namespace uhd::rfnoc::x400; + +rfdc_control::rfdc_control(uhd::memmap32_iface_timed&& iface, const std::string& log_id) + : _iface(std::move(iface)), _log_id(log_id) +{ + // nop +} + +void rfdc_control::reset_ncos( + const std::vector<rfdc_type>& ncos, const uhd::time_spec_t& time) +{ + if (ncos.empty()) { + UHD_LOG_WARNING(_log_id, + "reset_ncos() called with empty NCO list! " + "Not resetting NCOs."); + return; + } + UHD_LOG_TRACE(_log_id, "Resetting " << ncos.size() << " NCOs..."); + + const uint32_t reset_word = 1; + // TODO: When the FPGA supports it, map the list of ncos into bits onto + // reset_word to set the bit for the specific NCOs that will be reset. + + _iface.poke32(regmap::NCO_RESET, reset_word, time); +} + +void rfdc_control::reset_gearboxes( + const std::vector<rfdc_type>& gearboxes, const uhd::time_spec_t& time) +{ + if (gearboxes.empty()) { + UHD_LOG_WARNING(_log_id, + "reset_gearboxes() called with empty gearbox list! " + "Not resetting gearboxes."); + return; + } + // This is intentionally at INFO: It should typically happen once per session. + UHD_LOG_INFO(_log_id, "Resetting " << gearboxes.size() << " gearbox(es)..."); + // TODO: Either the FPGA supports resetting gearboxes individually, or we + // remove this TODO + static const std::unordered_map<rfdc_type, const uint32_t> gb_map{ + {rfdc_type::TX0, regmap::DAC_RESET_MSB}, + {rfdc_type::TX1, regmap::DAC_RESET_MSB}, + {rfdc_type::RX0, regmap::ADC_RESET_MSB}, + {rfdc_type::RX1, regmap::ADC_RESET_MSB}}; + + uint32_t reset_word = 0; + for (const auto gb : gearboxes) { + reset_word = reset_word | (1 << gb_map.at(gb)); + } + + _iface.poke32(regmap::GEARBOX_RESET, reset_word, time); +} + +bool rfdc_control::get_nco_reset_done() +{ + return bool(_iface.peek32(regmap::NCO_RESET) & (1 << regmap::NCO_RESET_DONE_MSB)); +} + +double rfdc_control::set_nco_freq(const rfdc_type, const double freq) +{ + UHD_LOG_WARNING(_log_id, "set_nco_freq() called but not yet implemented!"); + return freq; +} diff --git a/host/python/uhd/imgbuilder/image_builder.py b/host/python/uhd/imgbuilder/image_builder.py index 626d1bbf7..9ce57d0e0 100755 --- a/host/python/uhd/imgbuilder/image_builder.py +++ b/host/python/uhd/imgbuilder/image_builder.py @@ -42,6 +42,8 @@ DEVICE_DIR_MAP = { 'n300': 'n3xx', 'n310': 'n3xx', 'n320': 'n3xx', + 'x400': 'x400', + 'x410': 'x400', } # Picks the default make target per device @@ -522,8 +524,13 @@ def convert_to_image_config(grc, grc_config_path): result["stream_endpoints"] = {} for sep in seps.values(): result["stream_endpoints"][sep["name"]] = {"ctrl": bool(sep["parameters"]["ctrl"]), - "data": bool(sep["parameters"]["data"]), - "buff_size": int(sep["parameters"]["buff_size"])} + "data": bool(sep["parameters"]["data"])} + if "buff_size" in sep["parameters"]: + result["stream_endpoints"][sep["name"]]["buff_size"] = \ + int(sep["parameters"]["buff_size"]) + if "buff_size_bytes" in sep["parameters"]: + result["stream_endpoints"][sep["name"]]["buff_size_bytes"] = \ + int(sep["parameters"]["buff_size_bytes"]) result["noc_blocks"] = {} for block in blocks.values(): @@ -875,7 +882,7 @@ def build_image(config, fpga_path, config_path, device, **args): requested. :param config: A dictionary containing the image configuration options. - This must obey the rfnoc_imagebuilder_args schema. + This must obey the rfnoc_image_builder_args schema. :param fpga_path: A path that holds the FPGA IP sources. :param device: The device to build for. :param **args: Additional options including diff --git a/host/python/uhd/imgbuilder/templates/modules/stream_endpoints.v.mako b/host/python/uhd/imgbuilder/templates/modules/stream_endpoints.v.mako index b8c18fba9..50fa09373 100644 --- a/host/python/uhd/imgbuilder/templates/modules/stream_endpoints.v.mako +++ b/host/python/uhd/imgbuilder/templates/modules/stream_endpoints.v.mako @@ -18,15 +18,25 @@ \ %for i, sep in enumerate(seps): <% -# If buff_size == 0, then we assume that we will never transmit through this SEP -buff_size = seps[sep]["buff_size"] -if buff_size > 0: - buff_size = int(math.ceil(math.log(buff_size, 2))) - # FIXME MTU assumed to be 10 here -- forcing to at least accommodate 2 pkts - buff_size = max(buff_size, 10+1) +# The schema allows buff_size_bytes or buff_size, but not both. +if "buff_size_bytes" in seps[sep]: + # Use buffer size in bytes + buff_size = "(" + str(seps[sep]["buff_size_bytes"]) + ")/(CHDR_W/8)" +elif "buff_size" in seps[sep]: + # Use buffer size in CHDR words + buff_size = str(seps[sep]["buff_size"]) else: - buff_size = 5 + # Min SRL-based FIFO size + buff_size = str("32") %>\ + // If requested buffer size is 0, use the minimum SRL-based FIFO size. + // Otherwise, make sure it's at least two MTU-sized packets. + localparam REQ_BUFF_SIZE_${sep.upper()} = ${buff_size}; + localparam INGRESS_BUFF_SIZE_${sep.upper()} = + REQ_BUFF_SIZE_${sep.upper()} == 0 ? 5 : + REQ_BUFF_SIZE_${sep.upper()} < 2*(2**MTU) ? MTU+1 : + $clog2(REQ_BUFF_SIZE_${sep.upper()}); + wire [CHDR_W-1:0] ${axis_outputs[sep].format(sep,"tdata")}; wire ${axis_outputs[sep].format(sep,"tlast")}; wire ${axis_outputs[sep].format(sep,"tvalid")}; @@ -49,7 +59,7 @@ else: .NUM_DATA_O (${int(seps[sep]["num_data_o"])}), .INST_NUM (${i}), .CTRL_XBAR_PORT (${i+1}), - .INGRESS_BUFF_SIZE (${buff_size}), + .INGRESS_BUFF_SIZE (INGRESS_BUFF_SIZE_${sep.upper()}), .MTU (MTU), .REPORT_STRM_ERRS (1) ) ${sep}_i ( diff --git a/host/python/uhd/imgbuilder/templates/rfnoc_image_core.v.mako b/host/python/uhd/imgbuilder/templates/rfnoc_image_core.v.mako index a5a019e16..b27e21fc4 100644 --- a/host/python/uhd/imgbuilder/templates/rfnoc_image_core.v.mako +++ b/host/python/uhd/imgbuilder/templates/rfnoc_image_core.v.mako @@ -26,19 +26,12 @@ `default_nettype none -<% - if hasattr(config, 'image_core_name'): - image_core_name = config.image_core_name - else: - image_core_name = config.device.type -%>\ -`include "${image_core_name}_rfnoc_image_core.vh" - module rfnoc_image_core #( - parameter CHDR_W = `CHDR_WIDTH, - parameter MTU = 10, - parameter [15:0] PROTOVER = {8'd1, 8'd0} + parameter CHDR_W = ${config.chdr_width}, + parameter MTU = 10, + parameter [15:0] PROTOVER = {8'd1, 8'd0}, + parameter RADIO_NIPC = 1 ) ( // Clocks input wire chdr_aclk, @@ -59,11 +52,6 @@ module rfnoc_image_core #( wire rfnoc_chdr_clk, rfnoc_chdr_rst; wire rfnoc_ctrl_clk, rfnoc_ctrl_rst; - // Check that CHDR_W parameter matches value used by RFNoC Image Builder - if (CHDR_W != `CHDR_WIDTH) begin - ERROR_CHDR_W_values_do_not_match(); - end - //--------------------------------------------------------------------------- // CHDR Crossbar diff --git a/host/python/uhd/usrp/cal/libtypes.py b/host/python/uhd/usrp/cal/libtypes.py index 1eb9ff360..448feec88 100644 --- a/host/python/uhd/usrp/cal/libtypes.py +++ b/host/python/uhd/usrp/cal/libtypes.py @@ -18,5 +18,7 @@ database = lib.cal.database Source = lib.cal.source IQCal = lib.cal.iq_cal PwrCal = lib.cal.pwr_cal +ZbxTxDsaCal = lib.cal.zbx_tx_dsa_cal +ZbxRxDsaCal = lib.cal.zbx_rx_dsa_cal InterpMode = lib.cal.interp_mode # pylint: enable=invalid-name diff --git a/host/python/uhd/utils/mpmtools.py b/host/python/uhd/utils/mpmtools.py index 90dd63be0..f03da82bb 100644 --- a/host/python/uhd/utils/mpmtools.py +++ b/host/python/uhd/utils/mpmtools.py @@ -16,7 +16,7 @@ from mprpc.exceptions import RPCError MPM_RPC_PORT = 49601 -def _claim_loop(host, port, cmd_q, token_q): +def _claim_loop(client, cmd_q, token_q): """ Process that runs a claim loop. @@ -39,7 +39,6 @@ def _claim_loop(host, port, cmd_q, token_q): signal.signal(signal.SIGTERM, _sig_term_handler) - client = RPCClient(host, port, pack_params={'use_bin_type': True}) try: while not exit_loop: try: @@ -84,10 +83,11 @@ class MPMClaimer: self.token = None self._cmd_q = multiprocessing.Queue() self._token_q = multiprocessing.Queue() + client = RPCClient(host, port, pack_params={'use_bin_type': True}) self._claim_loop = multiprocessing.Process( target=_claim_loop, name="Claimer Loop", - args=(host, port, self._cmd_q, self._token_q) + args=(client, self._cmd_q, self._token_q) ) self._claim_loop.daemon = True self._claim_loop.start() @@ -181,6 +181,12 @@ class MPMClient: """ self._claimer.unclaim() + def exit(self): + """ + Use claimer (instead of RPC method) to unclaim MPM device and exit claim loop + """ + self._claimer.exit() + def _add_command(self, command, docs, requires_token): """ Add a command to the current session diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt index 980f87d75..9b27e03c1 100644 --- a/host/tests/CMakeLists.txt +++ b/host/tests/CMakeLists.txt @@ -29,6 +29,7 @@ set(test_sources cal_data_iq_test.cpp cal_data_gain_pwr_test.cpp chdr_parse_test.cpp + cal_data_dsa_test.cpp chdr_test.cpp constrained_device_args_test.cpp convert_test.cpp @@ -42,7 +43,6 @@ set(test_sources isatty_test.cpp log_test.cpp math_test.cpp - mb_controller_test.cpp narrow_cast_test.cpp property_test.cpp ranges_test.cpp @@ -150,6 +150,8 @@ macro(UHD_ADD_RFNOC_BLOCK_TEST) EXTRA_SOURCES ${test_EXTRA_SOURCES} ${CMAKE_SOURCE_DIR}/lib/rfnoc/graph.cpp + INCLUDE_DIRS + ${test_INCLUDE_DIRS} ) endmacro(UHD_ADD_RFNOC_BLOCK_TEST) @@ -275,6 +277,24 @@ UHD_ADD_NONAPI_TEST( ${CMAKE_SOURCE_DIR}/lib/rfnoc/client_zero.cpp ) +UHD_ADD_NONAPI_TEST( + TARGET zbx_cpld_test.cpp + EXTRA_SOURCES + ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_lo_ctrl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/common/lmx2572.cpp + INCLUDE_DIRS + ${CMAKE_BINARY_DIR}/lib/ic_reg_maps +) + +UHD_ADD_NONAPI_TEST( + TARGET lmx2572_test.cpp + EXTRA_SOURCES + ${CMAKE_SOURCE_DIR}/lib/usrp/common/lmx2572.cpp + INCLUDE_DIRS + ${CMAKE_BINARY_DIR}/lib/ic_reg_maps +) + set_source_files_properties( ${CMAKE_SOURCE_DIR}/lib/utils/system_time.cpp PROPERTIES COMPILE_DEFINITIONS @@ -329,7 +349,6 @@ UHD_ADD_RFNOC_BLOCK_TEST( TARGET siggen_block_test.cpp ) - UHD_ADD_RFNOC_BLOCK_TEST( TARGET split_stream_block_test.cpp ) @@ -346,6 +365,35 @@ UHD_ADD_RFNOC_BLOCK_TEST( TARGET window_block_test.cpp ) +UHD_ADD_RFNOC_BLOCK_TEST( + TARGET x4xx_radio_block_test.cpp + EXTRA_SOURCES + ${CMAKE_SOURCE_DIR}/lib/usrp/common/lmx2572.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/common/pwr_cal_mgr.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/x400/x400_radio_control.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/x400/x400_rfdc_control.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/x400/adc_self_calibration.cpp + ${CMAKE_SOURCE_DIR}/lib/rfnoc/radio_control_impl.cpp + ${CMAKE_SOURCE_DIR}/lib/rfnoc/rf_control/gain_profile.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/mpmd/mpmd_mb_controller.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_dboard.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_dboard_init.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_lo_ctrl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp + ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_expert.cpp + ${CMAKE_SOURCE_DIR}/lib/utils/compat_check.cpp + ${CMAKE_SOURCE_DIR}/lib/features/discoverable_feature_registry.cpp + $<TARGET_OBJECTS:uhd_rpclib> + INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/lib/deps/rpclib/include + INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/lib/deps/flatbuffers/include +) + +UHD_ADD_NONAPI_TEST( + TARGET "mb_controller_test.cpp" + EXTRA_SOURCES + ${CMAKE_SOURCE_DIR}/lib/features/discoverable_feature_registry.cpp +) + UHD_ADD_NONAPI_TEST( TARGET "transport_test.cpp" EXTRA_SOURCES @@ -382,6 +430,12 @@ UHD_ADD_NONAPI_TEST( ${CMAKE_SOURCE_DIR}/lib/rfnoc/rf_control/gain_profile.cpp ) +UHD_ADD_NONAPI_TEST( + TARGET "x400_rfdc_control_test.cpp" + EXTRA_SOURCES + ${CMAKE_SOURCE_DIR}/lib/usrp/x400/x400_rfdc_control.cpp +) + ######################################################################## # demo of a loadable module ######################################################################## diff --git a/host/tests/cal_data_dsa_test.cpp b/host/tests/cal_data_dsa_test.cpp new file mode 100644 index 000000000..d3c224f88 --- /dev/null +++ b/host/tests/cal_data_dsa_test.cpp @@ -0,0 +1,91 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/cal/dsa_cal.hpp> +#include <uhd/exception.hpp> +#include <boost/test/unit_test.hpp> +#include <iostream> +#include <fstream> + +using namespace uhd::usrp::cal; + +BOOST_AUTO_TEST_CASE(test_pwr_cal_api) +{ + const std::string name = "Mock Gain/Power Data"; + const std::string serial = "ABC1234"; + const uint64_t timestamp = 0x12340000; + + auto dsa_data = zbx_tx_dsa_cal::make(name, serial, timestamp); + BOOST_CHECK_EQUAL(dsa_data->get_name(), name); + BOOST_CHECK_EQUAL(dsa_data->get_serial(), serial); + BOOST_CHECK_EQUAL(dsa_data->get_timestamp(), timestamp); + + BOOST_REQUIRE_THROW(dsa_data->get_dsa_setting(0, 0), uhd::runtime_error); + + std::array<std::array<uint32_t, 3>, 61> gains1{{{1, 2}, {3, 4}, {5, 6}}}; + std::array<std::array<uint32_t, 3>, 61> gains2{{{7, 8}, {9, 0}, {1, 2}}}; + + dsa_data->add_frequency_band(1E9, "low", gains1); + dsa_data->add_frequency_band(4E9, "high", gains2); + + auto expected = gains1[0]; + auto calculated = dsa_data->get_dsa_setting(1E9, 0); + BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end()); + + calculated = dsa_data->get_dsa_setting(1E8, 0); + BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end()); + + expected = gains1[1]; + calculated = dsa_data->get_dsa_setting(1E9, 1); + BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end()); + + calculated = dsa_data->get_dsa_setting(1E8, 1); + BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end()); + + expected = gains2[1]; + calculated = dsa_data->get_dsa_setting(3E9, 1); + BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end()); + + calculated = dsa_data->get_dsa_setting(4E9, 1); + BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end()); + + BOOST_REQUIRE_THROW(dsa_data->get_dsa_setting(5E9, 1), uhd::value_error); +} + +BOOST_AUTO_TEST_CASE(test_pwr_cal_serdes) +{ + const std::string name = "Mock Gain/DSA Data"; + const std::string serial = "FOOBAR"; + const uint64_t timestamp = 0xCAFEBABE; + auto cal_data = zbx_tx_dsa_cal::make(name, serial, timestamp); + + std::vector<double> freqs{2e9, 6e9}; + + int i = 0; + for (auto freq : freqs) { + std::array<std::array<uint32_t, 3>, 61> gains; + for (auto& gain : gains) { + for (auto& step : gain) { + step = i++; + } + } + cal_data->add_frequency_band(freq, std::to_string(freq), gains); + } + + const auto serialized = cal_data->serialize(); + BOOST_REQUIRE_THROW(container::make<zbx_rx_dsa_cal>(serialized), uhd::runtime_error); + auto des_cal_data = container::make<zbx_tx_dsa_cal>(serialized); + BOOST_CHECK_EQUAL(des_cal_data->get_name(), cal_data->get_name()); + BOOST_CHECK_EQUAL(des_cal_data->get_serial(), cal_data->get_serial()); + BOOST_CHECK_EQUAL(des_cal_data->get_timestamp(), cal_data->get_timestamp()); +} + +BOOST_AUTO_TEST_CASE(test_pwr_cal_des_fail) +{ + std::vector<uint8_t> not_actual_data(42, 23); + + BOOST_REQUIRE_THROW(container::make<zbx_tx_dsa_cal>(not_actual_data), uhd::runtime_error); +} diff --git a/host/tests/devtest/CMakeLists.txt b/host/tests/devtest/CMakeLists.txt index e6cef17fe..3f09620bf 100644 --- a/host/tests/devtest/CMakeLists.txt +++ b/host/tests/devtest/CMakeLists.txt @@ -50,5 +50,8 @@ endif(ENABLE_N300) if(ENABLE_E320) ADD_DEVTEST("e320" "e3xx" "E32x") endif(ENABLE_E320) +if(ENABLE_X400) + ADD_DEVTEST("x4x0" "x4xx" "X4x0") +endif(ENABLE_X400) # Formatting message(STATUS "") diff --git a/host/tests/devtest/devtest_x4x0.py b/host/tests/devtest/devtest_x4x0.py new file mode 100644 index 000000000..c0b8bf4aa --- /dev/null +++ b/host/tests/devtest/devtest_x4x0.py @@ -0,0 +1,75 @@ +# +# Copyright 2015 Ettus Research LLC +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Run device tests for the x4x0 series. +""" + +# pylint: disable=wrong-import-position +# pylint: disable=unused-import +from benchmark_rate_test import uhd_benchmark_rate_test +uhd_benchmark_rate_test.tests = { + 'mimo_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '0,1', + 'rate': 1e6, + 'acceptable-underruns': 500, + 'tx_buffer': (0.1*1e6)+32e6*8*1/32, # 32 MB DRAM for each channel (32 bit OTW format), + 'rx_buffer': 0.1*1e6, + }, + 'mimo_fast': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '0,1', + 'rate': 4.096e6, + 'acceptable-underruns': 500, + 'tx_buffer': (0.1*12.288e6)+32e6*8*1/32, # 32 MB DRAM for each channel (32 bit OTW format), + 'rx_buffer': 0.1*12.288e6, + }, + 'siso_chan0_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '0', + 'rate': 1e6, + 'acceptable-underruns': 10, + 'tx_buffer': (0.1*1e6)+32e6*8*1/32, # 32 MB DRAM for each channel (32 bit OTW format), + 'rx_buffer': 0.1*1e6, + }, + 'siso_chan1_slow': { + 'duration': 1, + 'direction': 'tx,rx', + 'chan': '1', + 'rate': 1e6, + 'acceptable-underruns': 10, + 'tx_buffer': (0.1*1e6)+32e6*8*1/32, # 32 MB DRAM for each channel (32 bit OTW format), + 'rx_buffer': 0.1*1e6, + }, +} + +from tx_waveforms_test import uhd_tx_waveforms_test +uhd_tx_waveforms_test.tests = { + 'chan0': { + 'chan': '0', + }, + 'chan1': { + 'chan': '0', + }, + 'both_chans': { + 'chan': '0,1', + }, +} + +from rx_samples_to_file_test import rx_samples_to_file_test +from tx_bursts_test import uhd_tx_bursts_test +from test_pps_test import uhd_test_pps_test + +# Enable these when GPIO API is enabled +# from gpio_test import gpio_test +# from bitbang_test import bitbang_test + +from list_sensors_test import list_sensors_test +from python_api_test import uhd_python_api_test diff --git a/host/tests/devtest/multi_usrp_test.py b/host/tests/devtest/multi_usrp_test.py index b9add8381..943733ba9 100755 --- a/host/tests/devtest/multi_usrp_test.py +++ b/host/tests/devtest/multi_usrp_test.py @@ -754,6 +754,25 @@ def get_device_config(usrp_type, device_config_path=None): 'get_gpio_banks', ], } + if usrp_type == 'x410': + return { + 'skip': [ + # No AGC on ZBX + 'set_rx_agc', + # No IQ imbalance on ZBX + 'set_rx_iq_balance', + 'set_tx_iq_balance', + # No DC offset on ZBX + 'set_rx_dc_offset', + 'set_tx_dc_offset', + # No LO source control on ZBX + 'set_rx_lo_source', + 'set_tx_lo_source', + 'set_rx_lo_export_enabled', + 'set_tx_lo_export_enabled', + ], + 'clock_sources': ['internal', 'mboard'], + } return {} def dump_defaults(usrp_type): diff --git a/host/tests/lmx2572_test.cpp b/host/tests/lmx2572_test.cpp new file mode 100644 index 000000000..21ed1e7f5 --- /dev/null +++ b/host/tests/lmx2572_test.cpp @@ -0,0 +1,150 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "lmx2572_regs.hpp" +#include <uhdlib/usrp/common/lmx2572.hpp> +#include <boost/test/unit_test.hpp> +#include <map> + + +class lmx2572_mem +{ +public: + lmx2572_mem() + { + // Copy silicone defaults into mem + for (uint8_t addr = 0; addr < regs.get_num_regs(); addr++) { + mem[addr] = regs.get_reg(addr); + } + } + + void poke16(const uint8_t addr, const uint16_t data) + { + if (regs.get_ro_regs().count(addr)) { + throw uhd::runtime_error("Writing to RO reg!"); + } + mem[addr] = data; + } + + uint16_t peek16(const uint8_t addr) + { + return mem.at(addr); + } + + lmx2572_regs_t regs; + std::map<uint8_t, uint16_t> mem; +}; + + +BOOST_AUTO_TEST_CASE(lmx_init_test) +{ + auto mem = lmx2572_mem{}; + auto lo = lmx2572_iface::make( + [&](const uint8_t addr, const uint16_t data) { mem.poke16(addr, data); }, + [&](const uint8_t addr) -> uint16_t { return mem.peek16(addr); }, + [](const uhd::time_spec_t&) {}); + lo->reset(); +} + +void UHD_CHECK_REGMAP( + std::map<uint8_t, uint16_t> expected, std::map<uint8_t, uint16_t> actual) +{ + for (const auto& expected_r : expected) { + // Little hack so if this fails, we see all the info: + const std::string exp_str = "R" + std::to_string(expected_r.first) + + "==" + std::to_string(expected_r.second); + const std::string act_str = "R" + std::to_string(expected_r.first) + + "==" + std::to_string(actual.at(expected_r.first)); + BOOST_CHECK_EQUAL(exp_str, act_str); + } +} + +BOOST_AUTO_TEST_CASE(lmx_sync_tune_test) +{ + auto mem = lmx2572_mem{}; + auto lo = lmx2572_iface::make( + [&](const uint8_t addr, const uint16_t data) { mem.poke16(addr, data); }, + [&](const uint8_t addr) -> uint16_t { return mem.peek16(addr); }, + [](const uhd::time_spec_t&) {}); + lo->reset(); + // Mimick ZBX settings: + constexpr bool zbx_spur_dodging = false; + lo->set_sync_mode(true); + lo->set_output_enable(lmx2572_iface::output_t::RF_OUTPUT_A, true); + lo->set_output_enable(lmx2572_iface::output_t::RF_OUTPUT_B, false); + // Test Category 1A + SYNC: + lo->set_frequency( + 50 * 64e6, 64e6, zbx_spur_dodging); + lo->commit(); + // These values are generated with TICS PRO. We don't check all the values, + // mainly the ones related to sync operation. + UHD_CHECK_REGMAP( + std::map<uint8_t, uint16_t>{ + {36, 0x0032}, // Lower bits of N-divider, integer part + {42, 0x0000}, // PLL_NUM upper + {43, 0x0000}, // PLL_NUM lower + }, + mem.mem); + // Test max frequency just to test boundary conditions: + lo->set_frequency( + 100 * 64e6, 64e6, zbx_spur_dodging); + lo->commit(); + + // Test Category 1B + SYNC: + // Will set CHDIV to 2. + lo->set_frequency( + 40 * 64e6, 64e6, zbx_spur_dodging); + lo->commit(); + UHD_CHECK_REGMAP( + std::map<uint8_t, uint16_t>{ + {36, 0x0028}, + {42, 0x0000}, + {43, 0x0000}, + }, + mem.mem); + + // Test Category 2 + SYNC: + // Will set CHDIV to 8. + lo->set_frequency( + 10 * 64e6, 64e6, zbx_spur_dodging); + lo->commit(); + UHD_CHECK_REGMAP( + std::map<uint8_t, uint16_t>{ + {36, 0x0050}, + {42, 0x0000}, + {43, 0x0000}, + }, + mem.mem); + // VCO_PHASE_SYNC_EN must be off in this case, b/c we're using the SYNC pin + BOOST_CHECK_EQUAL(mem.mem[0] & (1 << 14), 0); + + // Test Category 3 + SYNC: + // Will set CHDIV to 1. + lo->set_frequency( + 50.5 * 64e6, 64e6, zbx_spur_dodging); + lo->commit(); + UHD_CHECK_REGMAP( + std::map<uint8_t, uint16_t>{ + {36, 0x0032}, + }, + mem.mem); + // VCO_PHASE_SYNC_EN must be on in this case + BOOST_CHECK(mem.mem[0] & (1 << 14)); + + // Will set CHDIV to 2. + lo->set_frequency( + 50.5 * 64e6 / 2, 64e6, zbx_spur_dodging); + lo->commit(); + UHD_CHECK_REGMAP( + std::map<uint8_t, uint16_t>{ + {11, 0xB028}, // PLL_R == 2. Note this is a ZBX-specific design choice. + {36, 0x0032}, // With PLL_R == 2, you would expect this to be 100, but it's + // only half that! + }, + mem.mem); + // VCO_PHASE_SYNC_EN must be on in this case + BOOST_CHECK(mem.mem[0] & (1 << 14)); +} diff --git a/host/tests/mb_controller_test.cpp b/host/tests/mb_controller_test.cpp index 188dddecc..30b9f64f3 100644 --- a/host/tests/mb_controller_test.cpp +++ b/host/tests/mb_controller_test.cpp @@ -5,6 +5,7 @@ // #include <uhd/rfnoc/mb_controller.hpp> +#include <uhdlib/features/discoverable_feature_registry.hpp> #include <boost/test/unit_test.hpp> #include <iostream> @@ -49,7 +50,8 @@ private: } }; -class mock_mb_controller : public mb_controller +class mock_mb_controller : public mb_controller, + public ::uhd::features::discoverable_feature_registry { public: mock_mb_controller() diff --git a/host/tests/rfnoc_block_tests/x4xx_radio_block_test.cpp b/host/tests/rfnoc_block_tests/x4xx_radio_block_test.cpp new file mode 100644 index 000000000..2f2e51d60 --- /dev/null +++ b/host/tests/rfnoc_block_tests/x4xx_radio_block_test.cpp @@ -0,0 +1,1222 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "../../lib/usrp/x400/x400_radio_control.hpp" +#include "../rfnoc_graph_mock_nodes.hpp" +#include "x4xx_zbx_mpm_mock.hpp" +#include <uhd/rfnoc/actions.hpp> +#include <uhd/rfnoc/defaults.hpp> +#include <uhd/rfnoc/mock_block.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/math.hpp> +#include <uhdlib/rfnoc/graph.hpp> +#include <uhdlib/rfnoc/node_accessor.hpp> +#include <uhdlib/usrp/dboard/zbx/zbx_constants.hpp> +#include <uhdlib/usrp/dboard/zbx/zbx_dboard.hpp> +#include <uhdlib/utils/narrow.hpp> +#include <math.h> +#include <boost/test/unit_test.hpp> +#include <chrono> +#include <cmath> +#include <iomanip> +#include <iostream> +#include <thread> + +using namespace uhd; +using namespace uhd::rfnoc; +using namespace std::chrono_literals; +using namespace uhd::usrp::zbx; +using namespace uhd::experts; + +// Redeclare this here, since it's only defined outside of UHD_API +noc_block_base::make_args_t::~make_args_t() = default; + +namespace { + +/* This class extends mock_reg_iface_t by adding a constructor that initializes + * some of the read memory to contain the memory size for the radio block. + */ +class x4xx_radio_mock_reg_iface_t : public mock_reg_iface_t +{ + // Start address of CPLD register space + static constexpr uint32_t cpld_offset = radio_control_impl::regmap::PERIPH_BASE; + // Start address of RFDC control register space + static constexpr uint32_t rfdc_offset = + radio_control_impl::regmap::PERIPH_BASE + 0x8000; + +public: + x4xx_radio_mock_reg_iface_t(size_t num_channels) + { + for (size_t chan = 0; chan < num_channels; chan++) { + const uint32_t reg_compat = + radio_control_impl::regmap::REG_COMPAT_NUM + + chan * radio_control_impl::regmap::REG_CHAN_OFFSET; + read_memory[reg_compat] = (radio_control_impl::MINOR_COMPAT + | (radio_control_impl::MAJOR_COMPAT << 16)); + } + read_memory[radio_control_impl::regmap::REG_RADIO_WIDTH] = + (32 /* bits per sample */ << 16) | 1 /* sample per clock */; + } + + void _poke_cb(uint32_t addr, uint32_t data, uhd::time_spec_t, bool) override + { + // Are we on the peripheral? + if (addr >= radio_control_impl::regmap::PERIPH_BASE) { + // handle all the periphs stuff that is not CPLD here + } else { + return; + } + + // Are we on the CPLD? + if (addr >= cpld_offset && addr < rfdc_offset) { + _poke_cpld_cb(addr, data); + return; + } + + // Are we poking the RFDC controls? + if (addr >= rfdc_offset) { + _poke_rfdc_cb(addr, data); + return; + } + } + + void _poke_cpld_cb(const uint32_t addr, const uint32_t data) + { + switch (addr - cpld_offset) { + /// CURRENT_CONFIG_REG + case 0x1000: + // FIXME: We write to all regs during init + // BOOST_REQUIRE(false); // Not a write-register + break; + /// SW_CONFIG + case 0x1008: { + // This register is RW so update read_memory + read_memory[addr] = data; + // If we're in SW-defined mode, also update CURRENT_CONFIG_REG + uint32_t& rf_opt = read_memory[cpld_offset + 0x1004]; + uint32_t& ccr = read_memory[cpld_offset + 0x1000]; + // Check if RF0_OPTION is SW_DEFINED + if ((rf_opt & 0x00FF) == 0) { + ccr = (ccr & 0xFF00) | (data & 0x00FF); + } + // Check if RF1_OPTION is SW_DEFINED + if ((rf_opt & 0xFF00) == 0) { + ccr = (ccr & 0x00FF) | (data & 0xFF00); + } + } break; + /// LO SPI transactions + case 0x1020: + _poke_lo_spi(addr, data); + return; + /// LO SYNC + case 0x1024: + // We make these bits sticky, because they might get strobed in + // multiple calls. In order to see what was strobed within an + // API call, we keep bits as they are. + read_memory[addr] |= data; + return; + // TX0 Table Select + case 0x4000: + case 0x4004: + case 0x4008: + case 0x400C: + case 0x4010: + case 0x4014: { + read_memory[addr] = data; + const uint32_t src_table_offset = data * 4; + const uint32_t dst_table_offset = (addr - cpld_offset) - 0x4000; + // Now we fake the transaction that copies ?X?_TABLE_* to + // ?X?_DSA* + read_memory[cpld_offset + 0x3000 + dst_table_offset] = + read_memory[cpld_offset + 0x5000 + src_table_offset]; + } + return; + // RX0 Table Select + case 0x4800: + case 0x4804: + case 0x4808: + case 0x480C: + case 0x4810: + case 0x4814: { + read_memory[addr] = data; + const uint32_t src_table_offset = data * 4; + const uint32_t dst_table_offset = (addr - cpld_offset) - 0x4800; + // Now we fake the transaction that copies ?X?_TABLE_* to + // ?X?_DSA* + read_memory[cpld_offset + 0x3800 + dst_table_offset] = + read_memory[cpld_offset + 0x5800 + src_table_offset]; + } + return; + default: // All other CPLD registers are read-write + read_memory[addr] = data; + return; + } + } + + void _poke_rfdc_cb(const uint32_t addr, const uint32_t data) + { + read_memory[addr] |= data; + } + + void _poke_lo_spi(const uint32_t addr, const uint32_t data) + { + // UHD_LOG_INFO("TEST", "Detected LO SPI transaction!"); + const uint16_t spi_data = data & 0xFFFF; + const uint8_t spi_addr = (data >> 16) & 0x7F; + const bool read = bool(data & (1 << 23)); + const uint8_t lo_sel = (data >> 24) & 0x7; + const bool start_xact = bool(data & (1 << 28)); + // UHD_LOG_INFO("TEST", + // "Transaction record: Read: " + // << (read ? "yes" : "no") << " Address: " << int(spi_addr) << std::hex + // << " Data: 0x" << spi_data << " LO sel: " << int(lo_sel) << std::dec + // << " Start Transaction: " << start_xact); + if (!start_xact) { + // UHD_LOG_INFO("TEST", "Register probably just initialized. Ignoring."); + return; + } + switch (spi_addr) { + case 0: + _muxout_to_lock = spi_data & (1 << 2); + break; + case 125: + BOOST_REQUIRE(read); + read_memory[addr] = 0x2288; + break; + default: + break; + } + if (read) { + read_memory[addr] = (read_memory[addr] & 0xFFFF) | (spi_addr << 16) + | (lo_sel << 24) | (1 << 31); + } + if (_muxout_to_lock) { + // UHD_LOG_INFO("TEST", "Muxout set to lock. Returning all ones."); + read_memory[addr] = 0xFFFF; + return; + } + return; + } + + bool _muxout_to_lock = false; +}; // class x4xx_radio_mock_reg_iface_t + +/* + * x400_radio_fixture is a class which is instantiated before each test + * case is run. It sets up the block container, mock register interface, + * and x400_radio_control object, all of which are accessible to the test + * case. The instance of the object is destroyed at the end of each test + * case. + */ +constexpr size_t DEFAULT_MTU = 8000; + +//! Helper class to make sure we get the most logging regardless of environment +// settings +struct uhd_log_enabler +{ + uhd_log_enabler(uhd::log::severity_level level) + { + std::cout << "Setting log level to " << level << "..." << std::endl; + uhd::log::set_log_level(level); + uhd::log::set_console_level(level); + std::this_thread::sleep_for(10ms); + } +}; + +struct x400_radio_fixture +{ + x400_radio_fixture() + : ule(uhd::log::warning) // Note: When debugging this test, either set + // this to a lower level, or create a + // uhd_log_enabler in the test-under-test + , num_channels(uhd::usrp::zbx::ZBX_NUM_CHANS) + , num_input_ports(num_channels) + , num_output_ports(num_channels) + , reg_iface(std::make_shared<x4xx_radio_mock_reg_iface_t>(num_channels)) + , rpcs(std::make_shared<uhd::test::x4xx_mock_rpc_server>(device_info)) + , mbc(std::make_shared<mpmd_mb_controller>(rpcs, device_info)) + , block_container(get_mock_block(RADIO_BLOCK, + num_channels, + num_channels, + device_info, + DEFAULT_MTU, + X400, + reg_iface, + mbc)) + , test_radio(block_container.get_block<x400_radio_control_impl>()) + { + node_accessor.init_props(test_radio.get()); + } + + ~x400_radio_fixture() {} + + + // Must remain the first member so we make sure the log level is high + uhd_log_enabler ule; + const size_t num_channels; + const size_t num_input_ports; + const size_t num_output_ports; + uhd::device_addr_t device_info = uhd::device_addr_t("master_clock_rate=122.88e6"); + std::shared_ptr<x4xx_radio_mock_reg_iface_t> reg_iface; + std::shared_ptr<uhd::test::x4xx_mock_rpc_server> rpcs; + mpmd_mb_controller::sptr mbc; + + mock_block_container block_container; + std::shared_ptr<x400_radio_control_impl> test_radio; + node_accessor_t node_accessor{}; +}; + +} // namespace + + +/****************************************************************************** + * RFNoC Graph Test + * + * This test case ensures that the Radio Block can be added to an RFNoC graph. + *****************************************************************************/ +BOOST_FIXTURE_TEST_CASE(x400_radio_test_graph, x400_radio_fixture) +{ + detail::graph_t graph{}; + detail::graph_t::graph_edge_t edge_port_info0; + edge_port_info0.src_port = 0; + edge_port_info0.dst_port = 0; + edge_port_info0.property_propagation_active = true; + edge_port_info0.edge = detail::graph_t::graph_edge_t::DYNAMIC; + detail::graph_t::graph_edge_t edge_port_info1; + edge_port_info1.src_port = 1; + edge_port_info1.dst_port = 1; + edge_port_info1.property_propagation_active = true; + edge_port_info1.edge = detail::graph_t::graph_edge_t::DYNAMIC; + + mock_radio_node_t mock_radio_block{0}; + mock_terminator_t mock_sink_term(2, {}, "MOCK_SINK"); + mock_terminator_t mock_source_term(2, {}, "MOCK_SOURCE"); + + UHD_LOG_INFO("TEST", "Priming mock block properties"); + node_accessor.init_props(&mock_radio_block); + mock_source_term.set_edge_property<std::string>( + "type", "sc16", {res_source_info::OUTPUT_EDGE, 0}); + mock_source_term.set_edge_property<std::string>( + "type", "sc16", {res_source_info::OUTPUT_EDGE, 1}); + mock_sink_term.set_edge_property<std::string>( + "type", "sc16", {res_source_info::INPUT_EDGE, 0}); + mock_sink_term.set_edge_property<std::string>( + "type", "sc16", {res_source_info::INPUT_EDGE, 1}); + + UHD_LOG_INFO("TEST", "Creating graph..."); + graph.connect(&mock_source_term, test_radio.get(), edge_port_info0); + graph.connect(&mock_source_term, test_radio.get(), edge_port_info1); + graph.connect(test_radio.get(), &mock_sink_term, edge_port_info0); + graph.connect(test_radio.get(), &mock_sink_term, edge_port_info1); + UHD_LOG_INFO("TEST", "Committing graph..."); + graph.commit(); + UHD_LOG_INFO("TEST", "Commit complete."); +} + +BOOST_FIXTURE_TEST_CASE(zbx_api_freq_tx_test, x400_radio_fixture) +{ + const std::string log = "ZBX_API_TX_FREQUENCY_TEST"; + const double ep = 10; + // TODO: consult step size + uhd::freq_range_t zbx_freq(ZBX_MIN_FREQ, ZBX_MAX_FREQ, 100e6); + for (size_t chan : {0, 1}) { + UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " FREQ CHANGE (SET->RETURN)\n"); + for (double iter = zbx_freq.start(); iter <= zbx_freq.stop(); + iter += zbx_freq.step()) { + UHD_LOG_INFO(log, "Testing freq: " << iter); + + const double freq = test_radio->set_tx_frequency(iter, chan); + BOOST_REQUIRE(abs(iter - freq) < ep); + } + + UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " FREQ CHANGE (SET->GET)\n"); + for (double iter = zbx_freq.start(); iter <= zbx_freq.stop(); + iter += zbx_freq.step()) { + UHD_LOG_INFO(log, "Testing freq: " << iter); + + test_radio->set_tx_frequency(iter, chan); + const double freq = test_radio->get_tx_frequency(chan); + BOOST_REQUIRE(abs(iter - freq) < ep); + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_api_freq_rx_test, x400_radio_fixture) +{ + const std::string log = "ZBX_API_RX_FREQUENCY_TEST"; + const double ep = 10; + // TODO: consult step size + uhd::freq_range_t zbx_freq(ZBX_MIN_FREQ, ZBX_MAX_FREQ, 100e6); + + for (size_t chan : {0, 1}) { + UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " FREQ CHANGE (SET->RETURN)\n"); + for (double iter = zbx_freq.start(); iter <= zbx_freq.stop(); + iter += zbx_freq.step()) { + UHD_LOG_INFO(log, "Testing freq: " << iter); + + const double freq = test_radio->set_rx_frequency(iter, chan); + BOOST_REQUIRE(abs(iter - freq) < ep); + } + UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " FREQ CHANGE (SET->GET\n"); + for (double iter = zbx_freq.start(); iter <= zbx_freq.stop(); + iter += zbx_freq.step()) { + UHD_LOG_INFO(log, "Testing freq: " << iter); + + test_radio->set_rx_frequency(iter, chan); + const double freq = test_radio->get_rx_frequency(chan); + BOOST_REQUIRE(abs(iter - freq) < ep); + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_frequency_test, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + const std::string log = "ZBX_FREQUENCY_TEST"; + const double ep = 10; + // TODO: consult step size + uhd::freq_range_t zbx_freq(ZBX_MIN_FREQ, ZBX_MAX_FREQ, 100e6); + + for (auto fe_path : { + fs_path("dboard/tx_frontends/0"), + fs_path("dboard/tx_frontends/1"), + fs_path("dboard/rx_frontends/0"), + fs_path("dboard/rx_frontends/1"), + }) { + UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " FREQ CHANGE\n"); + for (double iter = zbx_freq.start(); iter <= zbx_freq.stop(); + iter += zbx_freq.step()) { + UHD_LOG_INFO(log, "Testing freq: " << iter); + + tree->access<double>(fe_path / "freq").set(iter); + + const double ret_value = tree->access<double>(fe_path / "freq").get(); + + BOOST_REQUIRE(abs(iter - ret_value) < ep); + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_api_tx_gain_test, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + const std::string log = "ZBX TX GAIN TEST"; + uhd::freq_range_t zbx_gain(TX_MIN_GAIN, TX_MAX_GAIN, 1); + + for (size_t chan : {0, 1}) { + UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " GAIN CHANGE (SET->RETURN)\n"); + for (double iter = zbx_gain.start(); iter <= zbx_gain.stop(); + iter += zbx_gain.step()) { + UHD_LOG_INFO(log, "Testing gain: " << iter); + + const double ret_gain = test_radio->set_tx_gain(iter, chan); + + BOOST_CHECK_EQUAL(iter, ret_gain); + } + UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " GAIN CHANGE (SET->GET)\n"); + for (double iter = zbx_gain.start(); iter <= zbx_gain.stop(); + iter += zbx_gain.step()) { + UHD_LOG_INFO(log, "Testing gain: " << iter); + + test_radio->set_tx_gain(iter, chan); + const double ret_gain = test_radio->get_tx_gain(chan); + + BOOST_CHECK_EQUAL(iter, ret_gain); + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_api_tx_gain_stage_test, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + const std::string log = "ZBX API TX GAIN STAGE TEST"; + + for (size_t chan : {0, 1}) { + test_radio->set_tx_gain_profile(ZBX_GAIN_PROFILE_MANUAL, chan); + + UHD_LOG_INFO( + log, "BEGIN TEST: tx" << chan << " GAIN STAGE CHANGE (SET->RETURN)\n"); + for (auto gain_stage : ZBX_TX_GAIN_STAGES) { + if (gain_stage == ZBX_GAIN_STAGE_AMP) { + for (double amp : {ZBX_TX_LOWBAND_GAIN, ZBX_TX_HIGHBAND_GAIN}) { + UHD_LOG_INFO(log, "Testing dsa: " << amp); + const double ret_gain = + test_radio->set_tx_gain(amp, gain_stage, chan); + UHD_LOG_INFO(log, "return: " << ret_gain); + BOOST_CHECK_EQUAL(amp, ret_gain); + } + } else { + for (unsigned int iter = 0; iter <= ZBX_TX_DSA_MAX_ATT; iter++) { + UHD_LOG_INFO(log, "Testing dsa: " << iter); + const double ret_gain = + test_radio->set_tx_gain(iter, gain_stage, chan); + BOOST_CHECK_EQUAL(iter, ret_gain); + } + } + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_api_tx_gain_stage_test_set_get, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + const std::string log = "ZBX API TX GAIN STAGE TEST"; + + for (size_t chan : {0, 1}) { + test_radio->set_tx_gain_profile(ZBX_GAIN_PROFILE_MANUAL, chan); + UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " GAIN STAGE CHANGE (SET->GET)\n"); + for (auto gain_stage : ZBX_TX_GAIN_STAGES) { + if (gain_stage == ZBX_GAIN_STAGE_AMP) { + for (double amp : + {/*ZBX_TX_BYPASS_GAIN, currently disabled*/ ZBX_TX_LOWBAND_GAIN, + ZBX_TX_HIGHBAND_GAIN}) { + UHD_LOG_INFO(log, "Testing amp: " << amp); + test_radio->set_tx_gain(amp, gain_stage, chan); + const double ret_gain = test_radio->get_tx_gain(gain_stage, chan); + BOOST_CHECK_EQUAL(amp, ret_gain); + } + } else { + for (unsigned int iter = 0; iter <= ZBX_TX_DSA_MAX_ATT; iter++) { + UHD_LOG_INFO(log, "Testing dsa: " << iter); + test_radio->set_tx_gain(iter, gain_stage, chan); + const double ret_gain = test_radio->get_tx_gain(gain_stage, chan); + BOOST_CHECK_EQUAL(iter, ret_gain); + } + } + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_api_rx_gain_test, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + const std::string log = "ZBX RX API GAIN TEST"; + uhd::freq_range_t zbx_gain(TX_MIN_GAIN, TX_MAX_GAIN, 1); + + for (size_t chan : {0, 1}) { + UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " GAIN CHANGE (SET->RETURN)\n"); + for (double iter = zbx_gain.start(); iter <= zbx_gain.stop(); + iter += zbx_gain.step()) { + UHD_LOG_INFO(log, "Testing gain: " << iter); + + const double ret_gain = test_radio->set_rx_gain(iter, chan); + + BOOST_CHECK_EQUAL(iter, ret_gain); + } + UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " GAIN CHANGE (SET->GET)\n"); + for (double iter = zbx_gain.start(); iter <= zbx_gain.stop(); + iter += zbx_gain.step()) { + UHD_LOG_INFO(log, "Testing gain: " << iter); + + test_radio->set_rx_gain(iter, chan); + const double ret_gain = test_radio->get_rx_gain(chan); + + BOOST_CHECK_EQUAL(iter, ret_gain); + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_api_rx_gain_stage_test, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + const std::string log = "ZBX API RX GAIN STAGE TEST"; + + for (size_t chan : {0, 1}) { + test_radio->set_rx_gain_profile(ZBX_GAIN_PROFILE_MANUAL, chan); + + UHD_LOG_INFO( + log, "BEGIN TEST: rx" << chan << " GAIN STAGE CHANGE (SET->RETURN)\n"); + for (auto gain_stage : ZBX_RX_GAIN_STAGES) { + for (unsigned int iter = 0; iter <= ZBX_RX_DSA_MAX_ATT; iter++) { + UHD_LOG_INFO(log, "Testing dsa: " << gain_stage << " " << iter); + const double ret_gain = test_radio->set_rx_gain(iter, gain_stage, chan); + + BOOST_CHECK_EQUAL(iter, ret_gain); + } + } + + UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " GAIN STAGE CHANGE (SET->GET)\n"); + for (auto gain_stage : ZBX_RX_GAIN_STAGES) { + for (unsigned int iter = 0; iter <= ZBX_RX_DSA_MAX_ATT; iter++) { + UHD_LOG_INFO(log, "Testing " << gain_stage << " " << iter); + + test_radio->set_rx_gain(iter, gain_stage, chan); + const double ret_gain = test_radio->get_rx_gain(gain_stage, chan); + + BOOST_CHECK_EQUAL(iter, ret_gain); + } + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_tx_gain_test, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + const std::string log = "ZBX GAIN TEST"; + uhd::freq_range_t zbx_gain(TX_MIN_GAIN, TX_MAX_GAIN, 1); + + for (auto fe_path : + {fs_path("dboard/tx_frontends/0"), fs_path("dboard/tx_frontends/1")}) { + UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " GAIN CHANGE\n"); + for (double iter = zbx_gain.start(); iter <= zbx_gain.stop(); + iter += zbx_gain.step()) { + UHD_LOG_INFO(log, "Testing gain: " << iter); + const auto gain_path = fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value"; + tree->access<double>(gain_path).set(iter); + const double ret_gain = tree->access<double>(gain_path).get(); + BOOST_CHECK_EQUAL(iter, ret_gain); + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_rx_gain_test, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + const std::string log = "ZBX GAIN TEST"; + uhd::freq_range_t zbx_gain(RX_MIN_GAIN, RX_MAX_GAIN, 1); + + for (auto fe_path : + {fs_path("dboard/rx_frontends/0"), fs_path("dboard/rx_frontends/1")}) { + UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " GAIN CHANGE\n"); + for (double iter = zbx_gain.start(); iter <= zbx_gain.stop(); + iter += zbx_gain.step()) { + UHD_LOG_INFO(log, "Testing gain: " << iter); + const auto gain_path = fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value"; + tree->access<double>(gain_path).set(iter); + const double ret_gain = tree->access<double>(gain_path).get(); + BOOST_CHECK_EQUAL(iter, ret_gain); + } + } +} + +// Have to be careful about LO testing; it'll throw off the coerced frequency a bunch, +// possibly to illegal values like negative frequencies, and could make the gain API +// freak out. We use the center frequency to set initial mixer values, then try to test +// all LO's in the valid zbx range. +// TODO: expand this +const std::map<double, std::vector<std::array<double, 2>>> valid_lo_freq_map = { + {1e9, {{4.5e9, 4.5e9}, {5e9, 5e9}, {5.5e9, 5.5e9}, {6e9, 6e9}}}, + {2e9, {{4.5e9, 4.5e9}, {5e9, 5e9}, {5.5e9, 5.5e9}, {6e9, 6e9}}}}; + +// TODO: More frequencies_are_equal issues, too much variance +BOOST_FIXTURE_TEST_CASE(zbx_api_tx_lo_test, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + const std::string log = "ZBX TX TEST"; + const double ep = 10; + + for (size_t chan : {0, 1}) { + UHD_LOG_INFO(log, "BEGIN TEST: TX" << chan << " FREQ CHANGE (SET->RETURN)\n"); + for (auto iter = valid_lo_freq_map.begin(); iter != valid_lo_freq_map.end(); + iter++) { + for (auto iter_lo = iter->second.begin(); iter_lo != iter->second.end(); + iter_lo++) { + // Just so we're clear about our value mapping + const double req_freq = iter->first; + const double req_lo1 = iter_lo->at(0); + const double req_lo2 = iter_lo->at(1); + + UHD_LOG_INFO(log, + "Testing center freq " << req_freq / 1e6 << "MHz, lo1 freq " + << req_lo1 / 1e6 << "MHz, lo2 freq " + << req_lo2 / 1e6 << "MHz"); + // Need to set center frequency first, it'll set all the mixer values + test_radio->set_tx_frequency(iter->first, chan); + const double lo1_ret = + test_radio->set_tx_lo_freq(iter_lo->at(0), ZBX_LO1, chan); + const double lo2_ret = + test_radio->set_tx_lo_freq(iter_lo->at(1), ZBX_LO2, chan); + // No use comparing set_tx_freq, we've already ran that test and + // get_tx_frequency would return who knows what at this point + BOOST_REQUIRE(abs(iter_lo->at(0) - lo1_ret) < ep); + BOOST_REQUIRE(abs(iter_lo->at(1) - lo2_ret) < ep); + } + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_api_rx_lo_test, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + const std::string log = "ZBX RX LO TEST"; + const double ep = 10; + + for (size_t chan : {0, 1}) { + UHD_LOG_INFO(log, "BEGIN TEST: RX" << chan << " FREQ CHANGE (SET->RETURN)\n"); + for (auto iter = valid_lo_freq_map.begin(); iter != valid_lo_freq_map.end(); + iter++) { + for (auto iter_lo = iter->second.begin(); iter_lo != iter->second.end(); + iter_lo++) { + // Just so we're clear about our value mapping + const double req_freq = iter->first; + const double req_lo1 = iter_lo->at(0); + const double req_lo2 = iter_lo->at(1); + + UHD_LOG_INFO(log, + "Testing center freq " << req_freq / 1e6 << "MHz, lo1 freq " + << req_lo1 / 1e6 << "MHz, lo2 freq " + << req_lo2 / 1e6 << "MHz"); + // Need to set center frequency first, it'll set all the mixer values + test_radio->set_rx_frequency(iter->first, chan); + const double lo1_ret = + test_radio->set_rx_lo_freq(iter_lo->at(0), ZBX_LO1, chan); + const double lo2_ret = + test_radio->set_rx_lo_freq(iter_lo->at(1), ZBX_LO2, chan); + // No use comparing set_tx_freq, we've already ran that test and + // get_tx_frequency would return who knows what at this point + BOOST_REQUIRE(abs(iter_lo->at(0) - lo1_ret) < ep); + BOOST_REQUIRE(abs(iter_lo->at(1) - lo2_ret) < ep); + } + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_lo_tree_test, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + const std::string log = "ZBX LO1 TEST"; + const double ep = 10; + + for (auto fe_path : { + fs_path("dboard/tx_frontends/0"), + fs_path("dboard/tx_frontends/1"), + fs_path("dboard/rx_frontends/0"), + fs_path("dboard/rx_frontends/1"), + }) { + UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " LO FREQ CHANGE (SET->RETURN)\n"); + for (auto iter = valid_lo_freq_map.begin(); iter != valid_lo_freq_map.end(); + iter++) { + for (auto iter_lo = iter->second.begin(); iter_lo != iter->second.end(); + iter_lo++) { + // Just so we're clear about our value mapping + const double req_freq = iter->first; + const double req_lo1 = iter_lo->at(0); + const double req_lo2 = iter_lo->at(1); + UHD_LOG_INFO(log, + "Testing lo1 freq " << req_lo1 / 1e6 << "MHz, lo2 freq " + << req_lo2 / 1e6 << "MHz at center frequency " + << req_freq / 1e6 << "MHz"); + tree->access<double>(fe_path / "freq").set(req_freq); + const double ret_lo1 = + tree->access<double>(fe_path / "los" / ZBX_LO1 / "freq" / "value") + .set(req_lo1) + .get(); + const double ret_lo2 = + tree->access<double>(fe_path / "los" / ZBX_LO2 / "freq" / "value") + .set(req_lo2) + .get(); + BOOST_REQUIRE(abs(req_lo1 - ret_lo1) < ep); + BOOST_REQUIRE(abs(req_lo2 - ret_lo2) < ep); + } + } + } +} + + +BOOST_FIXTURE_TEST_CASE(zbx_ant_test, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + std::string log = "ZBX RX ANTENNA TEST"; + + for (auto fe_path : + {fs_path("dboard/rx_frontends/0"), fs_path("dboard/rx_frontends/1")}) { + UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " ANTENNA CHANGE\n"); + for (auto iter : RX_ANTENNAS) { + UHD_LOG_INFO(log, "Testing Antenna: " << iter); + + tree->access<std::string>(fe_path / "antenna/value").set(iter); + + std::string ret_ant = + tree->access<std::string>(fe_path / "antenna/value").get(); + BOOST_CHECK_EQUAL(iter, ret_ant); + } + } + log = "ZBX TX ANTENNA TEST"; + for (auto fe_path : + {fs_path("dboard/tx_frontends/0"), fs_path("dboard/tx_frontends/1")}) { + UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " ANTENNA CHANGE\n"); + for (auto iter : TX_ANTENNAS) { + UHD_LOG_INFO(log, "Testing Antenna: " << iter); + + tree->access<std::string>(fe_path / "antenna/value").set(iter); + + std::string ret_ant = + tree->access<std::string>(fe_path / "antenna/value").get(); + BOOST_CHECK_EQUAL(iter, ret_ant); + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_freq_coercion_test, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + const std::string log = "ZBX_FREQUENCY_COERCION_TEST"; + const double ep = 10; + + for (auto fe_path : { + fs_path("dboard/tx_frontends/0"), + fs_path("dboard/tx_frontends/1"), + fs_path("dboard/rx_frontends/0"), + fs_path("dboard/rx_frontends/1"), + }) { + UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " FREQUENCY COERCION\n"); + double ret_value = + tree->access<double>(fe_path / "freq").set(ZBX_MIN_FREQ - 1e6).get(); + + BOOST_REQUIRE(abs(ZBX_MIN_FREQ - ret_value) < ep); + + ret_value = tree->access<double>(fe_path / "freq").set(ZBX_MAX_FREQ + 1e6).get(); + + BOOST_REQUIRE(abs(ZBX_MAX_FREQ - ret_value) < ep); + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_tx_gain_coercion_test, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + const std::string log = "ZBX_GAIN_COERCION_TEST"; + + for (auto fe_path : + {fs_path("dboard/tx_frontends/0"), fs_path("dboard/tx_frontends/1")}) { + uhd::gain_range_t zbx_gain(TX_MIN_GAIN, TX_MAX_GAIN, 0.1); + UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " TX GAIN COERCION\n"); + for (double iter = zbx_gain.start(); iter <= zbx_gain.stop(); + iter += zbx_gain.step()) { + const auto gain_path = fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value"; + const double ret_val = tree->access<double>(gain_path).set(iter).get(); + BOOST_CHECK_EQUAL(ret_val, std::round(iter)); + } + } + for (auto fe_path : + {fs_path("dboard/rx_frontends/0"), fs_path("dboard/rx_frontends/1")}) { + uhd::gain_range_t zbx_gain(RX_MIN_GAIN, RX_MAX_GAIN, 0.1); + UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " RX GAIN COERCION\n"); + for (double iter = zbx_gain.start(); iter <= zbx_gain.stop(); + iter += zbx_gain.step()) { + const auto gain_path = fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value"; + const double ret_val = tree->access<double>(gain_path).set(iter).get(); + BOOST_CHECK_EQUAL(ret_val, std::round(iter)); + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_phase_sync_test, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + const std::string log = "ZBX_PHASE_SYNC_TEST"; + constexpr uint32_t lo_sync_addr = 0x1024 + 0x80000; + constexpr uint32_t nco_sync_addr = 0x88000; + constexpr uint32_t gearbox_addr = 0x88004; + auto& regs = reg_iface->read_memory; + UHD_LOG_INFO("TEST", "Setting 1 GHz defaults..."); + // Confirm default + test_radio->set_rx_frequency(1e9, 0); + test_radio->set_rx_frequency(1e9, 1); + test_radio->set_tx_frequency(1e9, 0); + test_radio->set_tx_frequency(1e9, 1); + // Enable time stamp + UHD_LOG_INFO("TEST", "Enabling time stamp chan 0..."); + test_radio->set_command_time(uhd::time_spec_t(2.0), 0); + // Don't pick the ZBX default frequency here + UHD_LOG_INFO("TEST", "Setting RX chan 0 to 2.3 GHz..."); + test_radio->set_rx_frequency(2.3e9, 0); + // Check we synced RX LOs chan 0 and RX NCO chan 0, and ADC gearboxes + BOOST_CHECK_EQUAL(regs[lo_sync_addr], 0b11 << 4); + BOOST_CHECK_EQUAL(regs[nco_sync_addr], 1); + BOOST_CHECK_EQUAL(regs[gearbox_addr], 1); + // Reset strobes + regs[lo_sync_addr] = 0; + regs[nco_sync_addr] = 0; + regs[gearbox_addr] = 0; + UHD_LOG_INFO("TEST", "Enabling time stamp chan 1..."); + test_radio->set_command_time(uhd::time_spec_t(2.0), 1); + UHD_LOG_INFO("TEST", "Setting RX chan 1 to 2.3 GHz..."); + test_radio->set_rx_frequency(2.3e9, 1); + // Check we synced RX LOs chan 1 and RX NCO chan 1. ADC gearbox only gets + // reset once, and should be left untouched. + BOOST_CHECK_EQUAL(regs[lo_sync_addr], 0b11 << 6); + BOOST_CHECK_EQUAL(regs[nco_sync_addr], 1); + BOOST_CHECK_EQUAL(regs[gearbox_addr], 0); + // Reset strobes + regs[lo_sync_addr] = 0; + regs[nco_sync_addr] = 0; + regs[gearbox_addr] = 0; + UHD_LOG_INFO("TEST", "Setting TX chan 0 to 2.3 GHz..."); + test_radio->set_tx_frequency(2.3e9, 0); + // Check we synced TX LOs chan 0 and TX NCO chan 0, and DAC gearboxes + BOOST_CHECK_EQUAL(regs[lo_sync_addr], 0x3 << 0); + BOOST_CHECK_EQUAL(regs[nco_sync_addr], 1); + BOOST_CHECK_EQUAL(regs[gearbox_addr], 1 << 1); + // Reset strobe + regs[lo_sync_addr] = 0; + regs[nco_sync_addr] = 0; + regs[gearbox_addr] = 0; + UHD_LOG_INFO("TEST", "Setting TX chan 1 to 2.3 GHz..."); + test_radio->set_tx_frequency(2.3e9, 1); + // Check we synced TX LOs chan 1 and TX NCO chan 1. DAC gearbox only gets + // reset once, and should be left untouched. + BOOST_CHECK_EQUAL(regs[lo_sync_addr], 0xC << 0); + BOOST_CHECK_EQUAL(regs[nco_sync_addr], 1); + BOOST_CHECK_EQUAL(regs[gearbox_addr], 0); + // Reset strobe + regs[lo_sync_addr] = 0; + regs[nco_sync_addr] = 0; + regs[gearbox_addr] = 0; +} + +BOOST_FIXTURE_TEST_CASE(can_set_rfdc_test, x400_radio_fixture) +{ + test_radio->set_tx_lo_freq(3.141e9, "rfdc", 1); + test_radio->get_tx_lo_freq("rfdc", 1); + + test_radio->set_rx_lo_freq(2.141e9, "rfdc", 0); + test_radio->get_rx_lo_freq("rfdc", 0); +} + +BOOST_FIXTURE_TEST_CASE(zbx_tx_power_api, x400_radio_fixture) +{ + constexpr double tx_given_gain = 30; + constexpr double tx_given_power = -30; + + auto tree = test_radio->get_tree(); + const std::string log = "ZBX_TX_POWER_TRACKING_TEST"; + auto tx_pwr_mgr = test_radio->get_pwr_mgr(TX_DIRECTION); + + for (size_t chan = 0; chan < ZBX_NUM_CHANS; chan++) { + // Start in gain tracking mode + double gain_coerced = test_radio->set_tx_gain(tx_given_gain, chan); + BOOST_CHECK_EQUAL(gain_coerced, tx_given_gain); + for (const double freq : {6e+08, 1e+09, 2e+09, 3e+09, 4e+09, 5e+09, 6e+09}) { + // Setting a power reference should kick us into power tracking mode + test_radio->set_tx_power_reference(tx_given_power, chan); + + test_radio->set_tx_frequency(freq, chan); + // If the tracking mode is properly set, we should not deviate much + // regarding power + const double pow_diff = std::abs<double>( + tx_given_power - test_radio->get_tx_power_reference(chan)); + BOOST_CHECK_MESSAGE(pow_diff < 3.0, "power differential is too large: " << pow_diff); + + // Back to gain mode + gain_coerced = test_radio->set_tx_gain(tx_given_gain, chan); + BOOST_CHECK_EQUAL(gain_coerced, tx_given_gain); + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_rx_power_api, x400_radio_fixture) +{ + constexpr double rx_given_gain = 30; + constexpr double rx_given_power = -30; + + auto tree = test_radio->get_tree(); + const std::string log = "ZBX_RX_POWER_TRACKING_TEST"; + auto rx_pwr_mgr = test_radio->get_pwr_mgr(RX_DIRECTION); + + for (size_t chan = 0; chan < ZBX_NUM_CHANS; chan++) { + // Start in gain tracking mode + double gain_coerced = test_radio->set_rx_gain(rx_given_gain, chan); + BOOST_REQUIRE_EQUAL(gain_coerced, rx_given_gain); + for (const double freq : {1e+09, 2e+09, 3e+09, 4e+09, 5e+09, 6e+09}) { + // Setting a power reference should kick us into power tracking mode + test_radio->set_rx_power_reference(rx_given_power, chan); + // Now go tune + test_radio->set_rx_frequency(freq, chan); + // If the tracking mode is properly set, we should match our expected criteria + // for power reference levels + const double actual_power = test_radio->get_rx_power_reference(chan); + const double pow_diff = std::abs<double>(rx_given_power - actual_power); + BOOST_CHECK_MESSAGE(pow_diff < 3.0, + "power differential is too large (" + << pow_diff << "): Expected close to: " << rx_given_power + << " Actual: " << actual_power << " Frequency: " << (freq/1e6)); + + gain_coerced = test_radio->set_rx_gain(rx_given_gain, chan); + BOOST_REQUIRE_EQUAL(gain_coerced, rx_given_gain); + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_tx_lo_injection_locking, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + + // As of right now, we don't have a way to directly get the DB prc rate, this is the + // value of the prc map per DEFAULT_MCR, in the mock RPC server:db_0_get_db_prc_rate() + constexpr double db_prc_rate = 61.44e6; + constexpr double lo_step_size = db_prc_rate / ZBX_RELATIVE_LO_STEP_SIZE; + + uhd::freq_range_t zbx_freq(ZBX_MIN_FREQ, ZBX_MAX_FREQ, 100e6); + + for (double iter = zbx_freq.start(); iter <= zbx_freq.stop(); + iter += zbx_freq.step()) { + for (const size_t chan : {0, 1}) { + test_radio->set_tx_frequency(iter, chan); + + // The step alignment only applies to the desired LO frequency, the actual + // returned frequency may vary slightly + const double lo1_freq = std::round(test_radio->get_tx_lo_freq(ZBX_LO1, chan)); + const double lo2_freq = std::round(test_radio->get_tx_lo_freq(ZBX_LO2, chan)); + + const double lo1_div = lo1_freq / lo_step_size; + const double lo2_div = lo2_freq / lo_step_size; + + // Test whether our tuned frequencies align with the lo step size + BOOST_CHECK_EQUAL(std::floor(lo1_div), lo1_div); + BOOST_CHECK_EQUAL(std::floor(lo2_div), lo2_div); + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_rx_lo_injection_locking, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + + // As of right now, we don't have a way to directly get the DB prc rate, this is the + // value of the prc map per DEFAULT_MCR, in the mock RPC server:db_0_get_db_prc_rate() + constexpr double db_prc_rate = 61.44e6; + constexpr double lo_step_size = db_prc_rate / ZBX_RELATIVE_LO_STEP_SIZE; + + uhd::freq_range_t zbx_freq(ZBX_MIN_FREQ, ZBX_MAX_FREQ, 100e6); + + for (double iter = zbx_freq.start(); iter <= zbx_freq.stop(); + iter += zbx_freq.step()) { + for (const size_t chan : {0, 1}) { + test_radio->set_rx_frequency(iter, chan); + + // The step alignment only applies to the desired LO frequency, the actual + // returned frequency may vary slightly + const double lo1_freq = std::round(test_radio->get_rx_lo_freq(ZBX_LO1, chan)); + const double lo2_freq = std::round(test_radio->get_rx_lo_freq(ZBX_LO2, chan)); + + const double lo1_div = lo1_freq / lo_step_size; + const double lo2_div = lo2_freq / lo_step_size; + + // Test whether our tuned frequencies align with the lo step size + BOOST_CHECK_EQUAL(std::floor(lo1_div), lo1_div); + BOOST_CHECK_EQUAL(std::floor(lo2_div), lo2_div); + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_rx_gain_profile_test, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + const std::string log = "ZBX_GAIN_PROFILE_TEST"; + auto& regs = reg_iface->read_memory; + constexpr uint32_t current_config = radio_control_impl::regmap::PERIPH_BASE + 0x1000; + constexpr uint32_t rf_option = radio_control_impl::regmap::PERIPH_BASE + 0x1004; + constexpr uint32_t sw_config = radio_control_impl::regmap::PERIPH_BASE + 0x1008; + constexpr uint32_t rx0_dsa = radio_control_impl::regmap::PERIPH_BASE + 0x3800; + constexpr uint32_t rx0_table = radio_control_impl::regmap::PERIPH_BASE + 0x5800; + BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "default"); + BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "default"); + BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(1), "default"); + BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(1), "default"); + // Everything should be classic_atr + BOOST_CHECK_EQUAL(regs[0x81004], 0x01010101); + // Can't set gain stages in this profile + BOOST_REQUIRE_THROW(test_radio->set_rx_gain(10, "DSA1", 0), uhd::key_error); + BOOST_REQUIRE_THROW(test_radio->set_tx_gain(10, "DSA1", 0), uhd::key_error); + + //** manual gain profile ** + test_radio->set_rx_gain_profile("manual", 0); + // Must provide valid gain name in this profile + BOOST_REQUIRE_THROW(test_radio->set_rx_gain(23, 0), uhd::runtime_error); + BOOST_REQUIRE_THROW(test_radio->set_rx_gain(10, "banana", 0), uhd::key_error); + // Now manually set the DSAs + BOOST_CHECK_EQUAL(5, test_radio->set_rx_gain(5, "DSA1", 0)); + BOOST_CHECK_EQUAL(5, test_radio->set_rx_gain(5, "DSA2", 0)); + BOOST_CHECK_EQUAL(5, test_radio->set_rx_gain(5, "DSA3A", 0)); + BOOST_CHECK_EQUAL(5, test_radio->set_rx_gain(5, "DSA3B", 0)); + // Check the registers were written to correctly (gain 5 == att 10) + BOOST_CHECK_EQUAL(regs[rx0_dsa + 1 * 4], 0xAAAA); + BOOST_CHECK_EQUAL(regs[rx0_dsa + 3 * 4], 0xAAAA); + // Check the getters: + BOOST_CHECK_EQUAL(test_radio->get_rx_gain("DSA1", 0), 5); + BOOST_CHECK_EQUAL(test_radio->get_rx_gain("DSA2", 0), 5); + BOOST_CHECK_EQUAL(test_radio->get_rx_gain("DSA3A", 0), 5); + BOOST_CHECK_EQUAL(test_radio->get_rx_gain("DSA3B", 0), 5); + // Even in 'manual', we can load from the table. Let's create a table entry: + regs[rx0_table + 5 * 4] = 0x7777; + // Now, let it be loaded into RX and XX: + BOOST_CHECK_EQUAL(5, test_radio->set_rx_gain(5, "TABLE", 0)); + BOOST_CHECK_EQUAL(regs[rx0_dsa + 1 * 4], 0x7777); + BOOST_CHECK_EQUAL(regs[rx0_dsa + 3 * 4], 0x7777); + // Note: If we read back the DSAs via get_rx_gain() now, they will still say + // 5. We might want to change that, but it will require extra peeks. The + // only good way to do that is to amend set_?x_gain() to do that peek when + // updating gains via table. + // Test DSA coercion + BOOST_CHECK_EQUAL(15, test_radio->set_rx_gain(39, "DSA1", 0)); + BOOST_CHECK_EQUAL(0, test_radio->set_rx_gain(-17, "DSA1", 0)); + + // If we go back to 'default', we also reset the DSAs. That's because the + // desired, previously loaded default value will trigger the previous DSA + // values again. + UHD_LOG_INFO(log, "resetting to default"); + test_radio->set_rx_gain_profile("default", 0); + BOOST_CHECK_EQUAL(0, test_radio->get_rx_gain("DSA1", 0)); + + //** table_noatr profile : ** + UHD_LOG_INFO(log, "setting to table_noatr"); + test_radio->set_rx_gain_profile("table_noatr", 0); + // This will set DSA config for chan 0 to 0 == SW_DEFINED + BOOST_CHECK_EQUAL(regs[rf_option], 0x01000101); + BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "table_noatr"); + // Yup, this will also change TX gain profile; they're coupled. + BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "table_noatr"); + BOOST_REQUIRE_THROW(test_radio->set_rx_gain(10, "all", 0), uhd::key_error); + BOOST_CHECK_EQUAL(8.0, test_radio->set_rx_gain(8, "TABLE", 0)); + BOOST_CHECK_EQUAL(regs[sw_config], 0x80000); + // Returns the current config. Note the asymmetry to the previous API call. + // We can't, however, know which entry from the TABLE we used, so we just + // return the current config (which is the entry from the DSA table, not the + // TABLE it writes to). + BOOST_CHECK_EQUAL(0, test_radio->get_rx_gain("TABLE", 0)); + // Let's pretend we're using config 7 + regs[current_config] = 0x70000; + BOOST_CHECK_EQUAL(7, test_radio->get_rx_gain("TABLE", 0)); + // And back + regs[current_config] = 0x00000; + // Now we fake an FPGA-gain-change transaction that UHD is unaware of. We + // keep the current config of 0, and update RX0_DSA*[0]. + regs[rx0_dsa + 0 * 4] = 0x4444; // Turn it up to attenuation 4 == gain 11 + BOOST_CHECK_EQUAL(11.0, test_radio->get_rx_gain("DSA1", 0)); + + //** table profile ** + test_radio->set_rx_gain_profile("table", 0); + BOOST_CHECK_EQUAL(regs[rf_option], 0x01010101); + BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "table"); + // Yup, this will also change TX gain profile; they're coupled. + BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "table"); + // Create another table entry + regs[rx0_table + 23 * 4] = 0xBBBB; + BOOST_CHECK_EQUAL(23.0, test_radio->set_rx_gain(23, "TABLE", 0)); + // get_rx_gain() for "TABLE" returns the current DSA table index, not actual gain + BOOST_CHECK_EQUAL(0.0, test_radio->get_rx_gain("TABLE", 0)); + // This will update RX and XX registers (that's the difference to table_noatr) + BOOST_CHECK_EQUAL(regs[rx0_dsa + 1 * 4], 0xBBBB); // att 0xB == gain 4.0 + BOOST_CHECK_EQUAL(regs[rx0_dsa + 3 * 4], 0xBBBB); + BOOST_CHECK_EQUAL(4.0, test_radio->get_rx_gain("DSA1", 0)); + BOOST_CHECK_EQUAL(4.0, test_radio->get_rx_gain("DSA2", 0)); + BOOST_CHECK_EQUAL(4.0, test_radio->get_rx_gain("DSA3A", 0)); + BOOST_CHECK_EQUAL(4.0, test_radio->get_rx_gain("DSA3B", 0)); + // Test table coercion + UHD_LOG_INFO(log, "Testing TABLE coercion"); + BOOST_CHECK_EQUAL(0.0, test_radio->set_rx_gain(-17, "TABLE", 0)); + BOOST_CHECK_EQUAL(255.0, test_radio->set_rx_gain(1e9, "TABLE", 0)); +} + +BOOST_FIXTURE_TEST_CASE(zbx_tx_gain_profile_test, x400_radio_fixture) +{ + auto tree = test_radio->get_tree(); + const std::string log = "ZBX_GAIN_PROFILE_TEST"; + auto& regs = reg_iface->read_memory; + constexpr uint32_t current_config = radio_control_impl::regmap::PERIPH_BASE + 0x1000; + constexpr uint32_t rf_option = radio_control_impl::regmap::PERIPH_BASE + 0x1004; + constexpr uint32_t sw_config = radio_control_impl::regmap::PERIPH_BASE + 0x1008; + constexpr uint32_t tx0_dsa = radio_control_impl::regmap::PERIPH_BASE + 0x3000; + constexpr uint32_t tx0_table = radio_control_impl::regmap::PERIPH_BASE + 0x5000; + BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "default"); + BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "default"); + BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(1), "default"); + BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(1), "default"); + const double default_dsa1 = test_radio->get_tx_gain("DSA1", 0); + // Everything should be classic_atr + BOOST_CHECK_EQUAL(regs[0x81004], 0x01010101); + // Can't set gain stages in this profile + BOOST_REQUIRE_THROW(test_radio->set_rx_gain(10, "DSA1", 0), uhd::key_error); + BOOST_REQUIRE_THROW(test_radio->set_tx_gain(10, "DSA1", 0), uhd::key_error); + + //** manual gain profile ** + test_radio->set_tx_gain_profile("manual", 0); + // Must provide valid gain name in this profile + BOOST_REQUIRE_THROW(test_radio->set_tx_gain(23, 0), uhd::runtime_error); + BOOST_REQUIRE_THROW(test_radio->set_tx_gain(23, "all", 0), uhd::key_error); + BOOST_REQUIRE_THROW(test_radio->set_tx_gain(10, "banana", 0), uhd::key_error); + // Now manually set the DSAs + BOOST_CHECK_EQUAL(21, test_radio->set_tx_gain(21, "DSA1", 0)); + BOOST_CHECK_EQUAL(21, test_radio->set_tx_gain(21, "DSA2", 0)); + // Check the registers were written to correctly (gain 5 == att 10) + BOOST_CHECK_EQUAL(regs[tx0_dsa + 2 * 4], 0x0A0A); + BOOST_CHECK_EQUAL(regs[tx0_dsa + 3 * 4], 0x0A0A); + // Check the getters: + BOOST_CHECK_EQUAL(test_radio->get_tx_gain("DSA1", 0), 21); + BOOST_CHECK_EQUAL(test_radio->get_tx_gain("DSA2", 0), 21); + // Even in 'manual', we can load from the table. Let's create a table entry: + regs[tx0_table + 5 * 4] = 0x0707; + // Now, let it be loaded into RX and XX: + BOOST_CHECK_EQUAL(5, test_radio->set_tx_gain(5, "TABLE", 0)); + BOOST_CHECK_EQUAL(regs[tx0_dsa + 2 * 4], 0x0707); + BOOST_CHECK_EQUAL(regs[tx0_dsa + 3 * 4], 0x0707); + // Note: If we read back the DSAs via get_tx_gain() now, they will still say + // 5. We might want to change that, but it will require extra peeks. The + // only good way to do that is to amend set_?x_gain() to do that peek when + // updating gains via table. + // Test DSA coercion + BOOST_CHECK_EQUAL(31, test_radio->set_tx_gain(39, "DSA1", 0)); + BOOST_CHECK_EQUAL(0, test_radio->set_tx_gain(-17, "DSA1", 0)); + + // If we go back to 'default', we also reset the DSAs. That's because the + // desired, previously loaded default value will trigger the previous DSA + // values again. + UHD_LOG_INFO(log, "resetting to default"); + test_radio->set_tx_gain_profile("default", 0); + BOOST_CHECK_EQUAL(default_dsa1, test_radio->get_tx_gain("DSA1", 0)); + + //** table_noatr profile : ** + UHD_LOG_INFO(log, "setting to table_noatr"); + test_radio->set_tx_gain_profile("table_noatr", 0); + // This will set DSA config for chan 0 to 0 == SW_DEFINED + BOOST_CHECK_EQUAL(regs[rf_option], 0x01000101); + BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "table_noatr"); + // Yup, this will also change RX gain profile; they're coupled. + BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "table_noatr"); + BOOST_REQUIRE_THROW(test_radio->set_tx_gain(10, "all", 0), uhd::key_error); + BOOST_CHECK_EQUAL(8.0, test_radio->set_tx_gain(8, "TABLE", 0)); + BOOST_CHECK_EQUAL(regs[sw_config], 0x80000); + // Returns the current config. Note the asymmetry to the previous API call. + // We can't, however, know which entry from the TABLE we used, so we just + // return the current config (which is the entry from the DSA table, not the + // TABLE it writes to). + BOOST_CHECK_EQUAL(0, test_radio->get_tx_gain("TABLE", 0)); + // Let's pretend we're using config 7 + regs[current_config] = 0x70000; + BOOST_CHECK_EQUAL(7, test_radio->get_tx_gain("TABLE", 0)); + // And back + regs[current_config] = 0x00000; + // Now we fake an FPGA-gain-change transaction that UHD is unaware of. We + // keep the current config of 0, and update TX0_DSA*[0]. + regs[tx0_dsa + 0 * 4] = 0x0404; // Turn it up to attenuation 4 == gain 27 + BOOST_CHECK_EQUAL(27.0, test_radio->get_tx_gain("DSA1", 0)); + + //** table profile ** + test_radio->set_tx_gain_profile("table", 0); + BOOST_CHECK_EQUAL(regs[rf_option], 0x01010101); + BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "table"); + // Yup, this will also change RX gain profile; they're coupled. + BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "table"); + // Create another table entry + regs[tx0_table + 23 * 4] = 0x0B0B; + BOOST_CHECK_EQUAL(23.0, test_radio->set_tx_gain(23, "TABLE", 0)); + // get_tx_gain() for "TABLE" returns the current DSA table index, not actual gain + BOOST_CHECK_EQUAL(0.0, test_radio->get_tx_gain("TABLE", 0)); + // This will update RX and XX registers (that's the difference to table_noatr) + BOOST_CHECK_EQUAL(regs[tx0_dsa + 2 * 4], 0x0B0B); // att 0xB == gain 20.0 + BOOST_CHECK_EQUAL(regs[tx0_dsa + 3 * 4], 0x0B0B); + BOOST_CHECK_EQUAL(20.0, test_radio->get_tx_gain("DSA1", 0)); + BOOST_CHECK_EQUAL(20.0, test_radio->get_tx_gain("DSA2", 0)); + // Test table coercion + UHD_LOG_INFO(log, "Testing TABLE coercion"); + BOOST_CHECK_EQUAL(0.0, test_radio->set_tx_gain(-17, "TABLE", 0)); + BOOST_CHECK_EQUAL(255.0, test_radio->set_tx_gain(1e9, "TABLE", 0)); +} + +// TODO: +// - concurrent/consecutive configuration +// - Threading tests +// - Error cases diff --git a/host/tests/rfnoc_block_tests/x4xx_zbx_mpm_mock.hpp b/host/tests/rfnoc_block_tests/x4xx_zbx_mpm_mock.hpp new file mode 100644 index 000000000..ad47089a5 --- /dev/null +++ b/host/tests/rfnoc_block_tests/x4xx_zbx_mpm_mock.hpp @@ -0,0 +1,337 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include <uhd/types/device_addr.hpp> +#include <uhdlib/usrp/common/rpc.hpp> +#include <uhdlib/usrp/dboard/zbx/zbx_constants.hpp> +#include <uhdlib/usrp/dboard/zbx/zbx_dboard.hpp> +#include <stdlib.h> +#include <boost/test/unit_test.hpp> +#include <map> +#include <memory> +#include <string> +#include <vector> + +using namespace uhd::usrp; + +namespace uhd { namespace test { + +namespace { +constexpr double DEFAULT_MCR = 122.88e6; +} +//! Mock MPM server for X410/ZBX +// +// This is a mock server that mimicks an X410 with a ZBX daughterboard. +class x4xx_mock_rpc_server : public x400_rpc_iface, public mpmd_rpc_iface, public dboard_base_rpc_iface, public zbx_rpc_iface +{ +public: + x4xx_mock_rpc_server(const uhd::device_addr_t& device_info) + : _device_info(device_info) + {} + + uhd::rpc_client::sptr get_raw_rpc_client() override + { + // This function is unimplemented! Perhaps you need to: + // - Add it to the appropriate RPC interface, + // - Retrofit all calls to your desired function to directly use the RPC interface, and + // - Add a mock implementation here. + UHD_THROW_INVALID_CODE_PATH(); + } + + /************************************************************************** + * RPC Call Mockups + * + * The following public methods are replacements of that normally happens in + * the Python-based MPM. Some notes on writing mocks: + * - These are mocks, so don't go fancy and only let them do the bare + * minimum required for tests + * - Remember to add them to _init_rpc() further down + *************************************************************************/ + size_t get_num_timekeepers() override + { + return 1; + } + + std::vector<std::string> get_mb_sensors() override + { + return {"ref_locked"}; + } + + std::vector<std::string> get_gpio_banks() override + { + return {}; + } + + bool supports_feature(const std::string& feature) override + { + return feature == "ref_clk_calibration"; + } + + std::vector<std::map<std::string, std::string>> get_dboard_info() override + { + return {{ + // One entry per dboard info + {"pid", std::to_string(uhd::usrp::zbx::ZBX_PID)} + // End of entries + }}; + } + + bool is_db_gpio_ifc_present(const size_t) override + { + return true; + } + + void set_tick_period(const size_t, const uint64_t) override + { + // nop + } + + double get_master_clock_rate() override + { + return _device_info.cast<double>("master_clock_rate", DEFAULT_MCR); + } + + std::vector<std::string> get_sensors(const std::string&) override + { + return {}; + } + + std::map<std::string, std::string> get_sensor(const std::string&, const std::string&, size_t) override + { + return {}; + } + + void set_cal_frozen(bool, size_t, size_t) override + { + // nop + } + + std::vector<int> get_cal_frozen(size_t, size_t) override + { + return {}; + } + + std::map<std::string, std::vector<uint8_t>> get_db_eeprom(const size_t) override + { + return {{ + // One line per entry + {"pid", s2u8("mock")}, // Used to specify power cal API + {"serial", s2u8("BADCODE")} + // End of entries + }}; + } + + double get_dboard_prc_rate() override + { + const double mcr = _device_info.cast<double>("master_clock_rate", DEFAULT_MCR); + static const std::map<double, double> prc_map{ + {122.88e6, 61.44e6}, {125e6, 62.5e6}}; + return prc_map.at(mcr); + } + + double rfdc_set_nco_freq(const std::string& trx, + const size_t /*db_id*/, + const size_t chan, + const double freq) + { + BOOST_REQUIRE(trx == "rx" || trx == "tx"); + BOOST_REQUIRE(chan < uhd::usrp::zbx::ZBX_NUM_CHANS); + nco_freq[trx][chan] = freq; + return freq; + } + + double rfdc_get_nco_freq( + const std::string& trx, const size_t /*db_id*/, const size_t chan) + { + BOOST_REQUIRE(trx == "rx" || trx == "tx"); + BOOST_REQUIRE(chan < uhd::usrp::zbx::ZBX_NUM_CHANS); + // On construction, the expert will ask for the current nco frequency, and our + // nco_freq map won't have a value yet. + if (nco_freq.find(trx) == nco_freq.end() + || nco_freq.at(trx).find(chan) == nco_freq.at(trx).end()) { + return 0; + } + return nco_freq.at(trx).at(chan); + } + + double get_dboard_sample_rate() override + { + const double mcr = _device_info.cast<double>("master_clock_rate", DEFAULT_MCR); + static const std::map<double, double> spll_map{ + // One line per entry + {122.88e6, 2.94912e9}, + {122.88e6 * 4, 2.94912e9} + // End of entries + }; + return spll_map.at(mcr); + } + + void enable_iq_swap(const bool, + const std::string&, + const size_t) override + { + // nop + } + + std::vector<std::string> get_gpio_srcs(const std::string& /*bank*/) override + { + return {}; + } + + uint64_t get_timekeeper_time(size_t /*timekeeper_idx*/, bool /*last_pps*/) override + { + return 0; + } + + void set_timekeeper_time(size_t /*timekeeper_idx*/, uint64_t /*ticks*/, bool /*last_pps*/) override + { + // nop + } + + std::string get_time_source() override + { + return ""; + } + + std::vector<std::string> get_time_sources() override + { + return {}; + } + + std::string get_clock_source() override + { + return ""; + } + + std::vector<std::string> get_clock_sources() override + { + return {}; + } + + std::map<std::string, std::string> get_sync_source() override + { + return {}; + } + + std::vector<std::map<std::string, std::string>> get_sync_sources() override + { + return {}; + } + + void set_clock_source_out(bool /*enb*/) override + { + // nop + } + + void set_trigger_io(const std::string& /*direction*/) override + { + // nop + } + + std::map<std::string, std::string> get_mb_eeprom() override + { + return {}; + } + + std::vector<std::string> get_gpio_src(const std::string& /*bank*/) override + { + return {}; + } + + void set_gpio_src(const std::string& /*bank*/, const std::vector<std::string>& /*src*/) override + { + // nop + } + + void set_ref_clk_tuning_word(uint32_t /*tuning_word*/) override + { + // nop + } + + uint32_t get_ref_clk_tuning_word() override + { + return 0; + } + + void store_ref_clk_tuning_word(uint32_t /*tuning_word*/) override + { + // nop + } + + sensor_value_t::sensor_map_t get_mb_sensor(const std::string& /*sensor*/) override + { + return {}; + } + + void set_time_source(const std::string& /*source*/) override + { + // nop + } + + void set_clock_source(const std::string& /*source*/) override + { + // nop + } + + void set_sync_source(const std::map<std::string, std::string>& /*source*/) override + { + // nop + } + + bool get_threshold_status(size_t /*db_number*/, size_t /*chan*/, size_t /*threshold_block*/) override + { + return false; + } + + void set_dac_mux_enable(size_t /*motherboard_channel_number*/, int /*enable*/) override + { + // nop + } + + void set_dac_mux_data(size_t /*i*/, size_t /*q*/) override + { + // nop + } + + double get_spll_freq() override + { + return 0.0; + } + + void setup_threshold( + size_t /*db_number*/, + size_t /*chan*/, + size_t /*threshold_block*/, + const std::string& /*mode*/, + size_t /*delay*/, + size_t /*under*/, + size_t /*over*/) override + { + // nop + } + + /////////////////////////////////////////////////////////////////////////// + // Public attributes for easy inspection + // + // Use this in the mock functions to cache values, or expose values that get + // tested later + std::map<std::string, std::map<size_t, double>> if2_freq; + std::map<std::string, std::map<size_t, double>> nco_freq; + // + /////////////////////////////////////////////////////////////////////////// + +private: + uhd::device_addr_t _device_info; + + static std::vector<uint8_t> s2u8(const std::string& s) + { + return std::vector<uint8_t>(s.begin(), s.end()); + } +}; + +}} // namespace uhd::test diff --git a/host/tests/streaming_performance/run_X4xx_max_rate_tests.py b/host/tests/streaming_performance/run_X4xx_max_rate_tests.py new file mode 100644 index 000000000..ea424f14f --- /dev/null +++ b/host/tests/streaming_performance/run_X4xx_max_rate_tests.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +""" +Copyright 2021 Ettus Research, A National Instrument Brand + +SPDX-License-Identifier: GPL-3.0-or-later + +Runs streaming tests for X4xx at rates in the neighborhoood of the maximum +rate that UHD can sustain. Each test consists of a batch of runs of the +benchmark rate C++ example with different streaming parameters. + +To run all the tests, execute it with all supported options for the test_type +parameter: + 1Gbe, 1x10Gbe, 2x10Gbe, 1x100Gbe, 2x100Gbe + +Example usage:: +run_X4xx_max_rate_tests.py --path <benchmark_rate_dir>/benchmark_rate --addr 192.168.10.2 --second_addr 192.168.20.2 --mgmt_addr 192.168.40.2 --test_type 1x100Gbe --use_dpdk 1 +""" +import argparse +import sys +import time +import datetime +import batch_run_benchmark_rate + + +def parse_args(): + """ + Parse command line arguments + """ + parser = argparse.ArgumentParser() + parser.add_argument( + "--path", + type=str, + required=True, + help="path to benchmark rate example") + parser.add_argument( + "--test_type", + type=str, + required=True, + choices=["1Gbe", "1x10Gbe", "2x10Gbe", "1x100Gbe", "2x100Gbe"], + help="test type you would like to run eg. 1Gbe, 1x10Gbe, 2x10Gbe, 1x100Gbe, 2x100Gbe") + parser.add_argument( + "--addr", + type=str, + default = "", + help="address of first interface") + parser.add_argument( + "--second_addr", + type=str, + default = "", + help="address of second interface") + parser.add_argument( + "--mgmt_addr", + type=str, + default = "", + help="mgmt address") + parser.add_argument( + "--use_dpdk", + default = False, + action="store_true", + help="enable DPDK (you must run the script as root to use this)") + + return parser.parse_args() + +def run_test(path, params, iterations, label): + """ + Runs benchmark rate for the number of iterations in the command line arguments. + """ + print("-----------------------------------------------------------") + print(label + "\n") + results = batch_run_benchmark_rate.run(path, iterations, params) + stats = batch_run_benchmark_rate.calculate_stats(results) + print(batch_run_benchmark_rate.get_summary_string(stats, iterations, params)) + +def run_tests_for_single(path, base_params, iterations, duration, rate): + + base_params["duration"] = duration + + rx_params = base_params.copy() + + # Run 20 Msps RX with one channel + rx_params["rx_rate"] = str(rate) + rx_params["rx_channels"] = "0" + print(rx_params) + run_test(path, rx_params, iterations, "1xRX @"+ str(rate/1e6) +" Msps") + + # Run 10 Msps with two channels + rx_params["rx_rate"] = str(rate/2) + rx_params["rx_channels"] = "0,1" + print(rx_params) + run_test(path, rx_params, iterations, "2xRX @"+ str(rate/2e6) +" Msps") + + tx_params = base_params.copy() + + # Run 20 Msps TX with one channel + tx_params["tx_rate"] = str(rate) + tx_params["tx_channels"] = "0" + print(tx_params) + run_test(path, tx_params, iterations, "1xTX @"+ str(rate/1e6) +" Msps") + + # Run 10 Msps TX with two channels + tx_params["tx_rate"] = "100e5" + tx_params["tx_channels"] = "0,1" + print(tx_params) + run_test(path, tx_params, iterations, "2xTX @"+ str(rate/2e6) +" Msps") + + trx_params = base_params.copy() + + # Run 20 Msps TRX with one channel + trx_params["tx_rate"] = str(rate) + trx_params["rx_rate"] = str(rate) + trx_params["tx_channels"] = "0" + trx_params["rx_channels"] = "0" + print(trx_params) + run_test(path, trx_params, iterations, "1xTRX @"+ str(rate/1e6) +" Msps") + + # Run 10 Msps TRX with two channels + trx_params["tx_rate"] = str(rate/2) + trx_params["rx_rate"] = str(rate/2) + trx_params["tx_channels"] = "0,1" + trx_params["rx_channels"] = "0,1" + print(trx_params) + run_test(path, trx_params, iterations, "2xTRX @"+ str(rate/2e6) +" Msps") + + +def run_tests_for_dual(base_params, path, duration, iterations, rate): + + base_params["duration"] = duration + + rx_params = base_params.copy() + # Run 200 Msps with two channels + rx_params["rx_rate"] = str(rate) + rx_params["rx_channels"] = "0,1" + print(rx_params) + run_test(path, rx_params, iterations, "2xRX @"+ str(rate/1e6) +" Msps") + + tx_params = base_params.copy() + + # Run 200 Msps TX with two channels + tx_params["tx_rate"] = str(rate) + tx_params["tx_channels"] = "0,1" + print(tx_params) + run_test(path, tx_params, iterations, "2xTX @"+ str(rate/1e6) +" Msps") + + trx_params = base_params.copy() + + # Run 100 Msps TRX with two channels + trx_params["tx_rate"] = str(rate/2) + trx_params["rx_rate"] = str(rate/2) + trx_params["tx_channels"] = "0,1" + trx_params["rx_channels"] = "0,1" + print(trx_params) + run_test(path, trx_params, iterations, "2xTRX @"+ str(rate/2e6) +" Msps") + + + +def main(): + args = parse_args() + + base_params = { + "args" : f"addr={args.addr},second_addr={args.second_addr}", + } + if args.use_dpdk: + base_params["args"] += f",mgmt_addr={args.mgmt_addr},use_dpdk=1" + + start_time = time.time() + + rate = { + "1Gbe": 200e5, + "1x10Gbe": 200e6, + "2x10Gbe": 200e6, + "1x100Gbe": 200e7, # Rate doesn't matter here as 100Gbe has no DUC + "2x100Gbe": 200e7} + + test_config = { + "1Gbe": run_tests_for_single, + "1x10Gbe": run_tests_for_single, + "2x10Gbe": run_tests_for_dual, + "1x100Gbe": run_tests_for_single, + "2x100Gbe": run_tests_for_dual} + + + # Run 10 test iterations for 60 seconds each + test_config[args.test_type](args.path, base_params, 10, 60, rate[args.test_type]) + # Run 2 test iterations for 600 seconds each + test_config[args.test_type](args.path, base_params, 2, 600, rate[args.test_type]) + + end_time = time.time() + elapsed = end_time - start_time + print("Elapsed time: {}".format(datetime.timedelta(seconds=elapsed))) + return True + +if __name__ == "__main__": + sys.exit(not main()) diff --git a/host/tests/x400_rfdc_control_test.cpp b/host/tests/x400_rfdc_control_test.cpp new file mode 100644 index 000000000..ec80682fa --- /dev/null +++ b/host/tests/x400_rfdc_control_test.cpp @@ -0,0 +1,52 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhdlib/usrp/common/x400_rfdc_control.hpp> +#include <boost/test/unit_test.hpp> + +using uhd::rfnoc::x400::rfdc_control; + +struct x400_rfdc_fixture +{ + static constexpr size_t RFDC_MEM_SIZE = 1; + + x400_rfdc_fixture() + : mem(1 << rfdc_control::regmap::NCO_RESET_DONE_MSB, RFDC_MEM_SIZE) + , rfdcc( + uhd::memmap32_iface_timed{ + [&](const uint32_t addr, const uint32_t data, const uhd::time_spec_t&) { + mem[addr] = data; + }, + [&](const uint32_t addr) { return mem[addr]; }}, + "TEST::RFDC") + { + // nop + } + + std::vector<uint32_t> mem; + rfdc_control rfdcc; +}; + + +BOOST_FIXTURE_TEST_CASE(test_nco_reset, x400_rfdc_fixture) +{ + rfdcc.reset_ncos({rfdc_control::rfdc_type::RX0}, uhd::time_spec_t::ASAP); + BOOST_CHECK(mem[rfdc_control::regmap::NCO_RESET] & 0x1); + // Fake self-clearing bit + mem[rfdc_control::regmap::NCO_RESET] = 1 << rfdc_control::regmap::NCO_RESET_DONE_MSB; + // This should print a warning: + rfdcc.reset_ncos({}, uhd::time_spec_t::ASAP); + BOOST_CHECK_EQUAL(mem[rfdc_control::regmap::NCO_RESET] + & (1 << rfdc_control::regmap::NCO_RESET_START_MSB), + 0); + BOOST_CHECK(rfdcc.get_nco_reset_done()); +} + +BOOST_FIXTURE_TEST_CASE(test_nco_freq, x400_rfdc_fixture) +{ + // TODO: Add checks when implemented + rfdcc.set_nco_freq(rfdc_control::rfdc_type::RX0, 1e9); +} diff --git a/host/tests/zbx_cpld_test.cpp b/host/tests/zbx_cpld_test.cpp new file mode 100644 index 000000000..b125e8435 --- /dev/null +++ b/host/tests/zbx_cpld_test.cpp @@ -0,0 +1,135 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/types/time_spec.hpp> +#include <uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp> +#include <boost/test/unit_test.hpp> +#include <iostream> + +using namespace uhd::usrp::zbx; + +struct mock_reg_iface_type +{ + uint32_t last_addr = 0; + zbx_cpld_ctrl::chan_t last_chan; + std::map<uint32_t, uint32_t> memory; + uhd::time_spec_t sleep_counter = uhd::time_spec_t(0.0); +}; + + +struct zbx_cpld_fixture +{ + zbx_cpld_fixture() + : cpld( + [&](const uint32_t addr, + const uint32_t data, + const zbx_cpld_ctrl::chan_t chan) { + std::cout << "[MOCKREGS] poke32(" << addr << ", " << data << ")" + << std::endl; + mock_reg_iface.last_addr = addr; + mock_reg_iface.last_chan = chan; + mock_reg_iface.memory[addr] = data; + }, + [&](const uint32_t addr) -> uint32_t { + std::cout << "[MOCKREGS] peek32(" << addr << ") => " + << mock_reg_iface.memory.at(addr) << std::endl; + return mock_reg_iface.memory.at(addr); + }, + [&](const uhd::time_spec_t& time) { mock_reg_iface.sleep_counter += time; }, + "TEST::CPLD") + { + // nop + } + + mock_reg_iface_type mock_reg_iface; + zbx_cpld_ctrl cpld; +}; + + +BOOST_FIXTURE_TEST_CASE(zbx_cpld_ctrl_test, zbx_cpld_fixture) +{ + cpld.set_scratch(23); + BOOST_CHECK_EQUAL(cpld.get_scratch(), 23); + + cpld.pulse_lo_sync(0, {zbx_lo_t::TX0_LO1}); + BOOST_CHECK_EQUAL(mock_reg_iface.memory[0x1024], 1); + mock_reg_iface.memory[0x1024] = 0; + // Make sure there are no caching issues: + cpld.pulse_lo_sync(0, {zbx_lo_t::TX0_LO1}); + BOOST_CHECK_EQUAL(mock_reg_iface.memory[0x1024], 1); + // Now all: + cpld.pulse_lo_sync(0, + {zbx_lo_t::TX0_LO1, + zbx_lo_t::TX0_LO2, + zbx_lo_t::TX1_LO1, + zbx_lo_t::TX1_LO2, + zbx_lo_t::RX0_LO1, + zbx_lo_t::RX0_LO2, + zbx_lo_t::RX1_LO1, + zbx_lo_t::RX1_LO2}); + BOOST_CHECK_EQUAL(mock_reg_iface.memory[0x1024], 0xFF); + mock_reg_iface.memory[0x1024] = 0; + cpld.set_lo_sync_bypass(true); + BOOST_CHECK_THROW(cpld.pulse_lo_sync(0, {zbx_lo_t::TX0_LO1}), uhd::runtime_error); + BOOST_CHECK_EQUAL(mock_reg_iface.memory[0x1024], 0x100); +} + +BOOST_FIXTURE_TEST_CASE(zbx_tx_amp_test, zbx_cpld_fixture) +{ + cpld.set_tx_antenna_switches( + 0, 0, uhd::usrp::zbx::DEFAULT_TX_ANTENNA, tx_amp::HIGHBAND); + BOOST_CHECK(tx_amp::HIGHBAND == cpld.get_tx_amp_settings(0, 0, false)); + mock_reg_iface.memory[0x2000] = 0; + BOOST_CHECK(tx_amp::HIGHBAND == cpld.get_tx_amp_settings(0, 0, false)); + BOOST_CHECK(tx_amp::HIGHBAND != cpld.get_tx_amp_settings(0, 0, true)); + BOOST_CHECK(tx_amp::BYPASS == cpld.get_tx_amp_settings(0, 0, false)); +} + +BOOST_FIXTURE_TEST_CASE(zbx_get_set_dsa_test, zbx_cpld_fixture) +{ + // We only test the first table index + constexpr size_t dsa_table_index = 0; + for (const size_t chan : {0, 1}) { + const uint32_t tx_dsa_table_addr = 0x3000 + chan * 0x400; + const uint32_t rx_dsa_table_addr = 0x3800 + chan * 0x400; + + auto& tx_dsa_reg = mock_reg_iface.memory[tx_dsa_table_addr]; + auto& rx_dsa_reg = mock_reg_iface.memory[rx_dsa_table_addr]; + + // We'll skip DSA3A/B, because they work just like the rest and would + // make this test much longer and less readable without adding much test + // coverage. + for (const auto dsa : + {zbx_cpld_ctrl::dsa_type::DSA1, zbx_cpld_ctrl::dsa_type::DSA2}) { + const size_t tx_shift = (dsa == zbx_cpld_ctrl::dsa_type::DSA1) ? 0 : 8; + const size_t rx_shift = (dsa == zbx_cpld_ctrl::dsa_type::DSA1) ? 0 : 4; + // 0xB and 0xC are just random attenuation values. They are valid + // for both TX and RX. + cpld.set_tx_dsa(chan, dsa_table_index, dsa, 0xB); + BOOST_CHECK_EQUAL((tx_dsa_reg >> tx_shift) & 0x1F, 0xB); + tx_dsa_reg = 0x0C0C; + BOOST_CHECK_EQUAL(0xB, cpld.get_tx_dsa(chan, dsa_table_index, dsa, false)); + BOOST_CHECK_EQUAL(0xC, cpld.get_tx_dsa(chan, dsa_table_index, dsa, true)); + + cpld.set_rx_dsa(chan, 0, dsa, 0xB); + BOOST_CHECK_EQUAL((rx_dsa_reg >> rx_shift) & 0xF, 0xB); + rx_dsa_reg = 0xCC; + BOOST_CHECK_EQUAL(0xB, cpld.get_rx_dsa(chan, dsa_table_index, dsa, false)); + BOOST_CHECK_EQUAL(0xC, cpld.get_rx_dsa(chan, dsa_table_index, dsa, true)); + } + } +} + +BOOST_FIXTURE_TEST_CASE(zbx_set_from_table_test, zbx_cpld_fixture) +{ + constexpr uint32_t tx_sel_addr = 0x4000; + constexpr size_t chan = 0; + constexpr uint8_t idx = 2; + + auto& tx_table_select = mock_reg_iface.memory[tx_sel_addr + idx * 4]; + cpld.set_tx_gain_switches(chan, idx, 23); + BOOST_REQUIRE_EQUAL(tx_table_select, 23); +} diff --git a/host/utils/CMakeLists.txt b/host/utils/CMakeLists.txt index ab7d645f4..1ac0b1c99 100644 --- a/host/utils/CMakeLists.txt +++ b/host/utils/CMakeLists.txt @@ -16,6 +16,7 @@ set(util_runtime_sources uhd_cal_rx_iq_balance.cpp uhd_cal_tx_dc_offset.cpp uhd_cal_tx_iq_balance.cpp + uhd_adc_self_cal.cpp ) #for each source: build an executable and install diff --git a/host/utils/rfnoc_image_builder.py b/host/utils/rfnoc_image_builder.py index 285dfe3e9..07419cb90 100755 --- a/host/utils/rfnoc_image_builder.py +++ b/host/utils/rfnoc_image_builder.py @@ -46,7 +46,7 @@ def setup_parser(): help="Path to grc file to generate config from") parser.add_argument( "-F", "--fpga-dir", - help="Path to directory for the FPGA source tree." + help="Path to directory for the FPGA source tree. " "Defaults to the FPGA source tree of the current repo.", required=False, default=None) @@ -79,7 +79,7 @@ def setup_parser(): action="store_true") parser.add_argument( "-d", "--device", - help="Device to be programmed [x300, x310, e310, e320, n300, n310, n320]." + help="Device to be programmed [x300, x310, e310, e320, n300, n310, n320, x410]. " "Needs to be specified either here, or in the configuration file.", default=None) parser.add_argument( diff --git a/host/utils/uhd_adc_self_cal.cpp b/host/utils/uhd_adc_self_cal.cpp new file mode 100644 index 000000000..39dab3e15 --- /dev/null +++ b/host/utils/uhd_adc_self_cal.cpp @@ -0,0 +1,76 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/features/adc_self_calibration_iface.hpp> +#include <uhd/rfnoc/radio_control.hpp> +#include <uhd/rfnoc_graph.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/version.hpp> +#include <boost/program_options.hpp> +#include <iostream> +#include <sstream> + +namespace po = boost::program_options; +using namespace uhd; + +/**************************************************************************** + * main + ***************************************************************************/ +int UHD_SAFE_MAIN(int argc, char* argv[]) +{ + po::options_description desc("Allowed options"); + // clang-format off + desc.add_options() + ("help", "help message") + ("version", "print the version string and exit") + ("args", po::value<std::string>()->default_value(""), "device address args") + ; + // clang-format on + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + // print the help message + if (vm.count("help")) { + std::cout << "UHD ADC self calibration " << desc << std::endl; + return EXIT_FAILURE; + } + + if (vm.count("version")) { + std::cout << uhd::get_version_string() << std::endl; + return EXIT_SUCCESS; + } + + rfnoc::rfnoc_graph::sptr graph = + rfnoc::rfnoc_graph::make(vm["args"].as<std::string>()); + + size_t num_calibrations = 0; + for (auto radio_id : graph->find_blocks("Radio")) { + auto radio_blk = graph->get_block<uhd::rfnoc::radio_control>(radio_id); + if (radio_blk->has_feature<uhd::features::adc_self_calibration_iface>()) { + auto& feature = + radio_blk->get_feature<uhd::features::adc_self_calibration_iface>(); + + // Run it on all channels + const size_t num_channels = radio_blk->get_num_output_ports(); + for (size_t i = 0; i < num_channels; i++) { + std::cout << "Calibrating on channel " << i << " of " << radio_id << "..." + << std::endl; + feature.run(i); + std::cout << "Finished!" << std::endl; + num_calibrations++; + } + } + } + if (num_calibrations > 0) { + std::cout << "Calibrated " << num_calibrations << " channels" << std::endl; + } else { + std::cerr << "WARNING: Did not find any channels to calibrate!" << std::endl; + } + + return EXIT_SUCCESS; +} diff --git a/mpm/CMakeLists.txt b/mpm/CMakeLists.txt index 8dfd7da6c..731e2ba13 100644 --- a/mpm/CMakeLists.txt +++ b/mpm/CMakeLists.txt @@ -128,7 +128,7 @@ if(_has_no_psabi) message(STATUS "Disabling psABI warnings.") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-psabi") endif(_has_no_psabi) -set(MPM_ALL_DEVICES n3xx e320 e31x sim tests) +set(MPM_ALL_DEVICES n3xx e320 e31x x4xx sim tests) set(MPM_DEVICE "n3xx" CACHE STRING "Choose an MPM device to build") set_property(CACHE MPM_DEVICE PROPERTY STRINGS ${MPM_ALL_DEVICES}) # Validate MPM_DEVICE @@ -146,6 +146,9 @@ elseif(MPM_DEVICE STREQUAL "e320") set(ENABLE_E320 ON) elseif(MPM_DEVICE STREQUAL "e31x") set(ENABLE_E300 ON) +elseif(MPM_DEVICE STREQUAL "x4xx") + set(ENABLE_X400 ON) + set(DEVICE_LIBRARIES "metal") elseif(MPM_DEVICE STREQUAL "sim") set(ENABLE_SIM TRUE) set(ENABLE_LIBMPM OFF) @@ -157,6 +160,7 @@ MPM_REGISTER_COMPONENT("Mykonos" ENABLE_MYKONOS OFF "ENABLE_LIBMPM" OFF OFF) MPM_REGISTER_COMPONENT("Magnesium" ENABLE_MAGNESIUM OFF "ENABLE_MYKONOS" OFF OFF) MPM_REGISTER_COMPONENT("E320" ENABLE_E320 OFF "ENABLE_LIBMPM" OFF OFF) MPM_REGISTER_COMPONENT("E300" ENABLE_E300 OFF "ENABLE_LIBMPM" OFF OFF) +MPM_REGISTER_COMPONENT("X400" ENABLE_X400 OFF "ENABLE_LIBMPM;ENABLE_REGMAPS" OFF OFF) if(NOT ENABLE_SIM) add_subdirectory(include) @@ -173,6 +177,7 @@ if(NOT ENABLE_SIM) target_link_libraries(usrp-periphs udev ${Boost_LIBRARIES} + ${DEVICE_LIBRARIES} ) install(TARGETS usrp-periphs LIBRARY DESTINATION ${LIBRARY_DIR} COMPONENT libraries) diff --git a/mpm/include/mpm/CMakeLists.txt b/mpm/include/mpm/CMakeLists.txt index d4caff1c4..4a5a3acc2 100644 --- a/mpm/include/mpm/CMakeLists.txt +++ b/mpm/include/mpm/CMakeLists.txt @@ -18,3 +18,7 @@ add_subdirectory(chips) add_subdirectory(dboards) add_subdirectory(spi) add_subdirectory(types) + +if(ENABLE_X400) + add_subdirectory(rfdc) +endif(ENABLE_X400) diff --git a/mpm/include/mpm/i2c/i2c_iface.hpp b/mpm/include/mpm/i2c/i2c_iface.hpp index c49a70b48..02dc23acf 100644 --- a/mpm/include/mpm/i2c/i2c_iface.hpp +++ b/mpm/include/mpm/i2c/i2c_iface.hpp @@ -43,16 +43,17 @@ public: /*! * \param tx Buffer of data to send - * \param rx Buffer to hold read data + * \param rx_num_bytes Number of bytes to read into rx return buffer * \param do_close If true, close file descriptor at end of function + * \return Buffer of rx data * * All data in tx will be transmitted. - * The amount of data read will be determined by the number of elements - * in the rx vector. Those elements will be overwritten with the data. - * Use the resize() function for a new rx vector. + * The amount of data read will be determined by num_rx_bytes and written + * into the return buffer. */ - virtual int transfer( - std::vector<uint8_t>* tx, std::vector<uint8_t>* rx, bool do_close = true) = 0; + virtual std::vector<uint8_t> transfer( + std::vector<uint8_t>& tx, size_t num_rx_bytes, bool do_close = true) = 0; + }; }}; /* namespace mpm::i2c */ diff --git a/mpm/include/mpm/i2c/i2c_python.hpp b/mpm/include/mpm/i2c/i2c_python.hpp index ebc9cb0a1..18209adf2 100644 --- a/mpm/include/mpm/i2c/i2c_python.hpp +++ b/mpm/include/mpm/i2c/i2c_python.hpp @@ -14,5 +14,9 @@ void export_i2c(py::module& top_module) { auto m = top_module.def_submodule("i2c"); + m.def("make_i2cdev", &mpm::i2c::i2c_iface::make_i2cdev); m.def("make_i2cdev_regs_iface", &mpm::i2c::make_i2cdev_regs_iface); + + py::class_<mpm::i2c::i2c_iface, std::shared_ptr<mpm::i2c::i2c_iface>>(m, "i2c_iface") + .def("transfer", (std::vector<uint8_t> (mpm::i2c::i2c_iface::*)(std::vector<uint8_t>&, size_t, bool)) &mpm::i2c::i2c_iface::transfer, "Transfer i2c data"); } diff --git a/mpm/include/mpm/rfdc/CMakeLists.txt b/mpm/include/mpm/rfdc/CMakeLists.txt new file mode 100644 index 000000000..cbd1ce5bc --- /dev/null +++ b/mpm/include/mpm/rfdc/CMakeLists.txt @@ -0,0 +1,13 @@ +# +# Copyright 2019 Ettus Research, National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0 +# +install(FILES + rfdc_ctrl.hpp + rfdc_throw.h + xrfdc_hw.h + xrfdc_mts.h + xrfdc.h + DESTINATION ${INCLUDE_DIR}/mpm/rfdc +) diff --git a/mpm/include/mpm/rfdc/rfdc_ctrl.hpp b/mpm/include/mpm/rfdc/rfdc_ctrl.hpp new file mode 100644 index 000000000..411b70c90 --- /dev/null +++ b/mpm/include/mpm/rfdc/rfdc_ctrl.hpp @@ -0,0 +1,755 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#include "xrfdc.h" +#include "xrfdc_mts.h" +#include <boost/noncopyable.hpp> +#include <vector> + +#ifdef LIBMPM_PYTHON +# include <pybind11/stl.h> +#endif + +#define THRESHOLDS_PER_BLOCK 2 + +namespace mpm { namespace rfdc { + +/** + * A class to control the Xilinx RFdc driver. + * This will be imported into a MPM shared library. + */ +class rfdc_ctrl : public boost::noncopyable +{ + XRFdc rfdc_inst; + XRFdc* rfdc_inst_ptr; + XRFdc_MultiConverter_Sync_Config rfdc_dac_sync_config; + XRFdc_MultiConverter_Sync_Config rfdc_adc_sync_config; + uint16_t rfdc_device_id; + +public: + /** + * These macros are placed within enums so they can be imported to Python. + * They are originally defined in xrfdc.h + */ + enum threshold_id_options { + THRESHOLD_0 = XRFDC_UPDATE_THRESHOLD_0, + THRESHOLD_1 = XRFDC_UPDATE_THRESHOLD_1, + THRESHOLD_BOTH = XRFDC_UPDATE_THRESHOLD_BOTH + }; + enum threshold_mode_options { + TRSHD_OFF = XRFDC_TRSHD_OFF, + TRSHD_STICKY_OVER = XRFDC_TRSHD_STICKY_OVER, + TRSHD_STICKY_UNDER = XRFDC_TRSHD_STICKY_UNDER, + TRSHD_HYSTERESIS = XRFDC_TRSHD_HYSTERISIS + }; + enum threshold_clr_mode_options { + THRESHOLD_CLRMD_MANUAL = XRFDC_THRESHOLD_CLRMD_MANUAL_CLR, + THRESHOLD_CLRMD_AUTO = XRFDC_THRESHOLD_CLRMD_AUTO_CLR, + // The XRFdc Threshold clear modes currently only go up to 2 + // This assumes there will never be a clear mode of value 99 + THRESHOLD_CLRMD_UNKNOWN = 99 + }; + enum decoder_mode_options { + DECODER_MAX_SNR_MODE = XRFDC_DECODER_MAX_SNR_MODE, // for non-randomized decoder + DECODER_MAX_LINEARITY_MODE = + XRFDC_DECODER_MAX_LINEARITY_MODE // for randomized decoder + }; + enum nyquist_zone_options { + ODD_NYQUIST_ZONE = XRFDC_ODD_NYQUIST_ZONE, + EVEN_NYQUIST_ZONE = XRFDC_EVEN_NYQUIST_ZONE + }; + enum mixer_mode_options { + MIXER_MODE_OFF = XRFDC_MIXER_MODE_OFF, + MIXER_MODE_C2C = XRFDC_MIXER_MODE_C2C, // Complex to complex + MIXER_MODE_C2R = XRFDC_MIXER_MODE_C2R, // Complex to real + MIXER_MODE_R2C = XRFDC_MIXER_MODE_R2C, // Real to complex + MIXER_MODE_R2R = XRFDC_MIXER_MODE_R2R // Real to real + }; + /** + * See section "RF-ADC Settings" of the Xilinx + * "RF Data Converter Interface User Guide" to learn + * more about the calibration modes. + */ + enum calibration_mode_options { + CALIB_MODE1 = XRFDC_CALIB_MODE1, + CALIB_MODE2 = XRFDC_CALIB_MODE2 + }; + enum event_type_options { + MIXER_EVENT = XRFDC_EVENT_MIXER, + CRSE_DLY_EVENT = XRFDC_EVENT_CRSE_DLY, + QMC_EVENT = XRFDC_EVENT_QMC, + }; + enum interp_decim_options { + INTERP_DECIM_OFF = XRFDC_INTERP_DECIM_OFF, + INTERP_DECIM_1X = XRFDC_INTERP_DECIM_1X, + INTERP_DECIM_2X = XRFDC_INTERP_DECIM_2X, + INTERP_DECIM_4X = XRFDC_INTERP_DECIM_4X, + INTERP_DECIM_8X = XRFDC_INTERP_DECIM_8X, + }; + enum fabric_clk_div_options { + DIV_1 = XRFDC_FAB_CLK_DIV1, + DIV_2 = XRFDC_FAB_CLK_DIV2, + DIV_4 = XRFDC_FAB_CLK_DIV4, + DIV_8 = XRFDC_FAB_CLK_DIV8, + DIV_16 = XRFDC_FAB_CLK_DIV16, + }; + + /** + * Assignes the rfdc_inst_ptr to an instance of the Xilinx RFdc driver + */ + rfdc_ctrl(); + + /** + * Closes the libmetal device + */ + ~rfdc_ctrl(); + + /** + * Initializes the driver by reading configuration settings + * from the device found in the device tree and applying them to + * the driver instance. + * Throws an exception if init fails. + * + * @param rfdc_device_id the device ID of the rfdc device + */ + void init(uint16_t rfdc_device_id); + + /** + * Starts up the requested tile while retaining register values. + * + * @param tile_id the ID of the tile to start. + * Pass -1 to select all tiles. + * @param is_dac whether the tile is a DAC (true) or ADC (false) + * + * @return true if the operation was successful + */ + bool startup_tile(int tile_id, bool is_dac); + + /** + * Shuts down the requested tile while retaining register values. + * + * @param tile_id the ID of the tile to stop. + * Pass -1 to select all tiles. + * @param is_dac whether the tile is a DAC (true) or ADC (false) + * + * @return true if the operation was successful + */ + bool shutdown_tile(int tile_id, bool is_dac); + + /** + * Restarts the requested tile while resetting registers to default values. + * + * @param tile_id the ID of the tile to restart. + * Pass -1 to select all tiles. + * @param is_dac whether the tile is a DAC (true) or ADC (false) + * + * @return true if the operation was successful + */ + bool reset_tile(int tile_id, bool is_dac); + + /** + * Triggers an update event for a given component. + * + * @param tile_id the tile ID of the block to trigger + * @param block_id the block ID of the block to trigger + * @param is_dac whether the block is a DAC (true) or ADC (false) + * @param event_type which component of block to update + * See event_type_options for valid values. + * + * @return true if the operation was successful + */ + bool trigger_update_event(uint32_t tile_id, uint32_t block_id, + bool is_dac, event_type_options event_type); + + /** + * Enable/Disable gain correction for a given block. + * + * @param tile_id the tile ID of the block to set + * @param block_id the block ID of the block to set + * @param is_dac whether the block is a DAC (true) or ADC (false) + * @param enable whether to enable or disable gain correction + * + * @return true if the operation was successful + */ + bool set_gain_enable(uint32_t tile_id, uint32_t block_id, bool is_dac, bool enable); + + /** + * Set gain correction on a given ADC or DAC block + * + * @param tile_id the tile ID of the block to set + * @param block_id the block ID of the block to set + * @param is_dac whether the block is a DAC (true) or ADC (false) + * @param gain the gain correction to set. + * Valid values are 0.0-2.0 + * + * @return true if the operation was successful + */ + bool set_gain(uint32_t tile_id, uint32_t block_id, bool is_dac, double gain); + + /** + * Set the threshold settings for a given ADC + * + * @param tile_id the tile ID of the block to set + * @param block_id the block ID of the block to set + * @param threshold_id the ID of the threshold to modify. + * See threshold_id_options for valid values. + * @param mode the threshold mode to set. + * See threshold_mode_options for valid values. + * @param average_val the average threshold value + * @param under_val the under threshold value + * @param over_val the over threshold value + * + * @return true if the operation was successful + */ + bool set_threshold_settings(uint32_t tile_id, + uint32_t block_id, + threshold_id_options threshold_id, + threshold_mode_options mode, + uint32_t average_val, + uint32_t under_val, + uint32_t over_val); + + /** + * Clears the sticky line which indicates a threshold has been breached. + * This will also set the sticky clear mode to be manual. + * + * @param tile_id the tile ID of the block to set + * @param block_id the block ID of the block to set + * @param threshold_id the ID of the threshold to modify. + * See threshold_id_options for valid values. + * + * @return true if the operation was successful + */ + bool clear_threshold_sticky( + uint32_t tile_id, uint32_t block_id, threshold_id_options threshold_id); + + /** + * Sets whether the threshold breach sticky is cleared manually + * or automatically (when QMC gain is changed). + * + * @param tile_id the tile ID of the block to set + * @param block_id the block ID of the block to set + * @param threshold_id the ID of the threshold to modify. + * See threshold_id_options for valid values. + * @param mode What mode to set for the threshold sticky clear mode. + * See threshold_clr_mode_options for valid values. + * + * @return true if the operation was successful + */ + bool set_threshold_clr_mode(uint32_t tile_id, + uint32_t block_id, + threshold_id_options threshold_id, + threshold_clr_mode_options clear_mode); + + /** + * Gets the threshold sticky clear mode + * + * @param tile_id the tile ID of the block to set + * @param block_id the block ID of the block to set + * @param threshold_id the ID of the threshold to modify. + * See threshold_id_options for valid values. + * Note: THRESHOLD_BOTH is not a valid threshold_id for this + * method and will result in THRESHOLD_CLRMD_UNKNOWN. + * @param mode What mode to set for the threshold sticky clear mode. + * See threshold_clr_mode_options for valid values. + * + * @return threshold_clr_mode_options which is currently set. + * A value of THRESHOLD_CLRMD_UNKNOWN indicates that the hardware + * setting is currently unknown or an invalid ID was given. + */ + threshold_clr_mode_options get_threshold_clr_mode( + uint32_t tile_id, uint32_t block_id, threshold_id_options threshold_id); + + /** + * Sets the decoder mode of a given DAC + * An auto-clear takes place when the gain setting is changed. + * + * @param tile_id the tile ID of the block to set + * @param block_id the block ID of the block to set + * @param decoder_mode the desired decoder mode for the DAC. + * See decoder_mode_options for valid values. + * + * @return true if the operation was successful + */ + bool set_decoder_mode( + uint32_t tile_id, uint32_t block_id, decoder_mode_options decoder_mode); + + /** + * Resets the NCO phase of the current block phase accumulator. + * + * @param tile_id the tile ID of the block to set + * @param block_id the block ID of the block to set + * @param is_dac whether the block is a DAC (true) or ADC (false) + * + * @return true if the operation was successful + */ + bool reset_nco_phase(uint32_t tile_id, uint32_t block_id, bool is_dac); + + /** + * Sets the NCO event source for a given DAC or ADC + * + * @param tile_id the tile ID of the block to set + * @param block_id the block ID of the block to set + * @param is_dac whether the block is a DAC (true) or ADC (false) + * + * @return true if the operation was successful + */ + bool set_nco_event_src(uint32_t tile_id, uint32_t block_id, bool is_dac); + + /** + * Sets the NCO frequency for a given DAC or ADC + * + * @param tile_id the tile ID of the block to set + * @param block_id the block ID of the block to set + * @param is_dac whether the block is a DAC (true) or ADC (false) + * @param freq the NCO frequency to set + * Frequencies are specified in Hz. + * + * @return true if the operation was successful + */ + bool set_nco_freq(uint32_t tile_id, uint32_t block_id, bool is_dac, double freq); + + /** + * Gets the NCO frequency for a given DAC or ADC + * + * @param tile_id the tile ID of the block + * @param block_id the block ID of the block + * @param is_dac whether the block is a DAC (true) or ADC (false) + * + * @return freq of the NCO in Hz + */ + double get_nco_freq(uint32_t tile_id, uint32_t block_id, bool is_dac); + + /** + * Sets the mixer mode of the given block + * + * @param tile_id the tile ID of the block to set + * @param block_id the block ID of the block to set + * @param is_dac whether the block is a DAC (true) or ADC (false) + * @param mixer_mode the mixer mode to set. + * See mixer_mode_options for valid values + * + * @return true if the operation was successful + */ + bool set_mixer_mode( + uint32_t tile_id, uint32_t block_id, bool is_dac, mixer_mode_options mixer_mode); + + /** + * Sets the Nyquist Zone of a give block + * + * @param tile_id the tile ID of the block to set + * @param block_id the block ID of the block to set + * @param is_dac whether the block is a DAC (true) or ADC (false) + * @param nyquist_zone the nyquist zone to set + * See nyquist_zone_options for valid values. + * + * @return true if the operation was successful + */ + bool set_nyquist_zone(uint32_t tile_id, + uint32_t block_id, + bool is_dac, + nyquist_zone_options nyquist_zone); + + /** + * Sets the calibration mode of a given ADC + * + * @param tile_id the tile ID of the block to set + * @param block_id the block ID of the block to set + * @param calibration_mode the calibration mode to set. + * See calibration_mode_options for valid values. + * See section "RF-ADC Settings" of the Xilinx + * "RF Data Converter Interface User Guide" to learn + * more about the modes. + * + * @return true if the operation was successful + */ + bool set_calibration_mode( + uint32_t tile_id, uint32_t block_id, calibration_mode_options calibration_mode); + + /** + * Enables/Disables the Inverse-Sinc filter on a DAC block. + * + * @param tile_id the tile ID of the block to set + * @param block_id the block ID of the block to set + * @param enable enables the filter if true, disables if false + * + * @return true if the operation was successful + */ + bool enable_inverse_sinc_filter(uint32_t tile_id, uint32_t block_id, bool enable); + + /** + * Sets the sample rate for a given tile. + * + * @param tile_id the ID of the tile to set + * @param is_dac whether the tile is a DAC (true) or ADC (false) + * @param sample_rate the rate in Hz to sample at + * + * @return true if the operation was successful + */ + bool set_sample_rate(uint32_t tile_id, bool is_dac, double sample_rate); + + /** + * Gets the sample rate for a given block. + * + * @param tile_id the ID of the tile to set + * @param block_id the ID of the block to set + * @param is_dac whether the tile is a DAC (true) or ADC (false) + * + * @return sample rate of the block in Hz + */ + double get_sample_rate(uint32_t tile_id, uint32_t block_id, bool is_dac); + + /** + * Specifies the IF for the given ADC or DAC. + * Setting this will determine the Nyquist zone, mixer mode, + * Inverse Sinc filter, and mixer NCO frequency. + * + * @param tile_id the tile ID of the block to set + * @param block_id the block ID of the block to set + * @param is_dac whether the block is a DAC (true) or ADC (false) + * @param if_freq the IF frequency expected for the block. + * Frequencies are specified in Hz. + * + * @return true all resulting settings were successfully changed + */ + bool set_if(uint32_t tile_id, uint32_t block_id, bool is_dac, double if_freq); + + /** + * Sets the decimation factor for a given ADC block + * + * @param tile_id the ID of the tile to set + * @param block_id the block ID of the block to set + * @param decimation_factor the desired factor + * See interp_decim_options for valid values + * + * @return true if the operation was successful + */ + bool set_decimation_factor( + uint32_t tile_id, uint32_t block_id, interp_decim_options decimation_factor); + + /** + * Gets the decimation factor for a given ADC block + * + * @param tile_id the ID of the tile to get + * @param block_id the block ID of the block to get + * + * @return the actual decimation factor + * See interp_decim_options for valid values + */ + interp_decim_options get_decimation_factor(uint32_t tile_id, uint32_t block_id); + + /** + * Sets the interpolation factor for a given DAC block + * + * @param tile_id the ID of the tile to set + * @param block_id the block ID of the block to set + * @param interpolation_factor the desired factor + * See interp_decim_options for valid values. + * + * @return true if the operation was successful + */ + bool set_interpolation_factor( + uint32_t tile_id, uint32_t block_id, interp_decim_options interpolation_factor); + + /** + * Gets the interpolation factor for a given DAC block + * + * @param tile_id the ID of the tile to get + * @param block_id the block ID of the block to get + * + * @return the actual interpolation factor + * See interp_decim_options for valid values + */ + interp_decim_options get_interpolation_factor(uint32_t tile_id, uint32_t block_id); + + /** + * Sets the number of valid read words for a given ADC block + * + * @param tile_id the ID of the tile to set + * @param block_id the block ID of the block to set + * @param valid_read_words the number of valid read words + * + * @return true if the operation was successful + */ + bool set_data_read_rate( + uint32_t tile_id, uint32_t block_id, uint32_t valid_read_words); + + /** + * Gets the number of valid read words for a given ADC/DAC block + * + * @param tile_id the ID of the tile to get + * @param block_id the block ID of the block to get + * @param is_dac whether the tile is a DAC (true) or ADC (false) + * + * @return the valid read words + */ + uint32_t get_data_read_rate(uint32_t tile_id, uint32_t block_id, bool is_dac); + + /** + * Sets the number of valid write words for a given DAC block + * + * @param tile_id the ID of the tile to set + * @param block_id the block ID of the block to set + * @param valid_write_words the number of valid write words + * + * @return true if the operation was successful + */ + bool set_data_write_rate( + uint32_t tile_id, uint32_t block_id, uint32_t valid_write_words); + + /** + * Gets the number of valid write words for a given ADC/DAC block + * + * @param tile_id the ID of the tile to get + * @param block_id the block ID of the block to get + * @param is_dac whether the tile is a DAC (true) or ADC (false) + + * + * @return the valid write words + */ + uint32_t get_data_write_rate(uint32_t tile_id, uint32_t block_id, bool is_dac); + + /** + * Sets the clock fabric output divider of a given tile + * + * @param tile_id the ID of the tile to set + * @param is_dac whether the tile is a DAC (true) or ADC (false) + * @param divider the divider to set + * See fabric_clk_div_options for valid values + * + * @return true if the operation was successful + */ + bool set_fabric_clk_div( + uint32_t tile_id, bool is_dac, fabric_clk_div_options divider); + + /** + * Gets the fabric clock divider rate of a Tile + * + * @param tile_id the ID of the tile to get + * @param is_dac whether the tile is a DAC (true) or ADC (false) + * + * @return the fabric clock divider + * See fabric_clk_div_options for valid values + */ + fabric_clk_div_options get_fabric_clk_div(uint32_t tile_id, bool is_dac); + + /** + * Sets the FIFO for an ADC/DAC + * + * @param tile_id the ID of the tile to get + * @param is_dac whether the tile is a DAC (true) or ADC (false) + * @param enable enables (true) or disables (false) the FIFO + + * + * @return true if the operation was successful + */ + bool set_data_fifo_state(uint32_t tile_id, bool is_dac, bool enable); + + /** + * Gets the FIFO for an ADC/DAC + * + * @param tile_id the ID of the tile to get + * @param is_dac whether the tile is a DAC (true) or ADC (false) + + * + * @return true if FIFO is enabled, false if it is disabled + */ + bool get_data_fifo_state(uint32_t tile_id, bool is_dac); + + /** + * Clears the interrupts for the data FIFO (FIFOUSRDAT) + * + * @param tile_id the ID of the tile to get + * @param block_id specify ADC/DAC block + * @param is_dac whether the tile is a DAC (true) or ADC (false) + */ + void clear_data_fifo_interrupts( + const uint32_t tile_id, const uint32_t block_id, const bool is_dac); + + /** + * Perform Multi-tile Synchronization on ADC or DAC tiles + * + * @param tiles tiles vector to specify which DAC/ADC tiles to synchronize + * @param is_dac whether the tile is a DAC (true) or ADC (false) + * + * @return true if synchronization completed successfully + */ + bool sync_tiles(const std::vector<uint32_t>& tiles, bool is_dac, uint32_t latency); + + /** + * Get post-sync latency between ADC or DAC tiles + * + * @param tile_index specify ADC or DAC target tile + * @param is_dac whether the tile is a DAC (true) or ADC (false) + * + * @return the measured relative latency value of each tile + */ + uint32_t get_tile_latency(uint32_t tile_index, bool is_dac); + + /** + * Get post-sync offset between ADC or DAC tile and reference tile + * + * @param tile_index specify ADC or DAC target tile + * @param is_dac whether the tile is a DAC (true) or ADC (false) + * + * @return value the interface data was delayed to achieve alignment + */ + uint32_t get_tile_offset(uint32_t tile_index, bool is_dac); + + /** + * Sets whether or not the ADC calibration blocks are frozen + * + * @param tile_id specify ADC target tile + * @param block_id specify ADC block + * @param frozen specify whether or not the ADC calibration blocks should be frozen + */ + void set_cal_frozen(uint32_t tile_id, uint32_t block_id, bool frozen); + + /** + * Sets whether or not the ADC calibration blocks are frozen + * + * @param tile_id specify ADC target tile + * @param block_id specify ADC block + * + * @return true if the cal blocks are frozen, false if not + */ + bool get_cal_frozen(uint32_t tile_id, uint32_t block_id); + + void set_adc_cal_coefficients(uint32_t tile_id, uint32_t block_id, uint32_t cal_block, std::vector<uint32_t> coefs); + std::vector<uint32_t> get_adc_cal_coefficients(uint32_t tile_id, uint32_t block_id, uint32_t cal_block); + + /** + * Resets an internal mixer with known valid settings. + */ + bool reset_mixer_settings( uint32_t tile_id, uint32_t block_id, bool is_dac); + +private: + /* Indicates whether libmetal was initialized successfully and can + * be safely deinitialized. + */ + bool metal_init_complete = false; + + // Stores the current threshold clear mode according to + // [Tile ID][Block ID][Threshold ID] + threshold_clr_mode_options threshold_clr_modes[XRFDC_TILE_ID_MAX + 1] + [XRFDC_BLOCK_ID_MAX + 1] + [THRESHOLDS_PER_BLOCK]; +}; +}}; /* namespace mpm::rfdc */ + +#ifdef LIBMPM_PYTHON +void export_rfdc(py::module& top_module) +{ + using namespace mpm::rfdc; + auto m = top_module.def_submodule("rfdc"); + + py::class_<rfdc_ctrl, std::shared_ptr<rfdc_ctrl>>(m, "rfdc_ctrl") + .def(py::init()) + .def("init", &rfdc_ctrl::init) + .def("startup_tile", &rfdc_ctrl::startup_tile) + .def("shutdown_tile", &rfdc_ctrl::shutdown_tile) + .def("reset_tile", &rfdc_ctrl::reset_tile) + .def("trigger_update_event", &rfdc_ctrl::trigger_update_event) + .def("set_gain_enable", &rfdc_ctrl::set_gain_enable) + .def("set_gain", &rfdc_ctrl::set_gain) + .def("set_threshold_settings", &rfdc_ctrl::set_threshold_settings) + .def("clear_threshold_sticky", &rfdc_ctrl::clear_threshold_sticky) + .def("set_threshold_clr_mode", &rfdc_ctrl::set_threshold_clr_mode) + .def("get_threshold_clr_mode", &rfdc_ctrl::get_threshold_clr_mode) + .def("set_decoder_mode", &rfdc_ctrl::set_decoder_mode) + .def("reset_nco_phase", &rfdc_ctrl::reset_nco_phase) + .def("set_nco_event_src", &rfdc_ctrl::set_nco_event_src) + .def("set_nco_freq", &rfdc_ctrl::set_nco_freq) + .def("get_nco_freq", &rfdc_ctrl::get_nco_freq) + .def("reset_mixer_settings", &rfdc_ctrl::reset_mixer_settings) + .def("set_mixer_mode", &rfdc_ctrl::set_mixer_mode) + .def("set_nyquist_zone", &rfdc_ctrl::set_nyquist_zone) + .def("set_calibration_mode", &rfdc_ctrl::set_calibration_mode) + .def("enable_inverse_sinc_filter", &rfdc_ctrl::enable_inverse_sinc_filter) + .def("set_sample_rate", &rfdc_ctrl::set_sample_rate) + .def("get_sample_rate", &rfdc_ctrl::get_sample_rate) + .def("set_if", &rfdc_ctrl::set_if) + .def("set_decimation_factor", &rfdc_ctrl::set_decimation_factor) + .def("get_decimation_factor", &rfdc_ctrl::get_decimation_factor) + .def("set_interpolation_factor", &rfdc_ctrl::set_interpolation_factor) + .def("get_interpolation_factor", &rfdc_ctrl::get_interpolation_factor) + .def("set_data_read_rate", &rfdc_ctrl::set_data_read_rate) + .def("get_data_read_rate", &rfdc_ctrl::get_data_read_rate) + .def("set_data_write_rate", &rfdc_ctrl::set_data_write_rate) + .def("get_data_write_rate", &rfdc_ctrl::get_data_write_rate) + .def("set_fabric_clk_div", &rfdc_ctrl::set_fabric_clk_div) + .def("get_fabric_clk_div", &rfdc_ctrl::get_fabric_clk_div) + .def("set_data_fifo_state", &rfdc_ctrl::set_data_fifo_state) + .def("get_data_fifo_state", &rfdc_ctrl::get_data_fifo_state) + .def("clear_data_fifo_interrupts", &rfdc_ctrl::clear_data_fifo_interrupts) + .def("sync_tiles", &rfdc_ctrl::sync_tiles) + .def("get_tile_latency", &rfdc_ctrl::get_tile_latency) + .def("get_tile_offset", &rfdc_ctrl::get_tile_offset) + .def("set_cal_frozen", &rfdc_ctrl::set_cal_frozen) + .def("get_cal_frozen", &rfdc_ctrl::get_cal_frozen) + .def("set_adc_cal_coefficients", &rfdc_ctrl::set_adc_cal_coefficients) + .def("get_adc_cal_coefficients", &rfdc_ctrl::get_adc_cal_coefficients); + + py::enum_<mpm::rfdc::rfdc_ctrl::threshold_id_options>(m, "threshold_id_options") + .value("THRESHOLD_0", mpm::rfdc::rfdc_ctrl::THRESHOLD_0) + .value("THRESHOLD_1", mpm::rfdc::rfdc_ctrl::THRESHOLD_1) + .value("THRESHOLD_BOTH", mpm::rfdc::rfdc_ctrl::THRESHOLD_BOTH); + + py::enum_<mpm::rfdc::rfdc_ctrl::threshold_mode_options>(m, "threshold_mode_options") + .value("TRSHD_OFF", mpm::rfdc::rfdc_ctrl::TRSHD_OFF) + .value("TRSHD_STICKY_OVER", mpm::rfdc::rfdc_ctrl::TRSHD_STICKY_OVER) + .value("TRSHD_STICKY_UNDER", mpm::rfdc::rfdc_ctrl::TRSHD_STICKY_UNDER) + .value("TRSHD_HYSTERESIS", mpm::rfdc::rfdc_ctrl::TRSHD_HYSTERESIS); + + py::enum_<mpm::rfdc::rfdc_ctrl::threshold_clr_mode_options>( + m, "threshold_clr_mode_options") + .value("THRESHOLD_CLRMD_MANUAL", mpm::rfdc::rfdc_ctrl::THRESHOLD_CLRMD_MANUAL) + .value("THRESHOLD_CLRMD_AUTO", mpm::rfdc::rfdc_ctrl::THRESHOLD_CLRMD_AUTO) + .value("THRESHOLD_CLRMD_UNKNOWN", mpm::rfdc::rfdc_ctrl::THRESHOLD_CLRMD_UNKNOWN); + + py::enum_<mpm::rfdc::rfdc_ctrl::decoder_mode_options>(m, "decoder_mode_options") + .value("DECODER_MAX_SNR_MODE", mpm::rfdc::rfdc_ctrl::DECODER_MAX_SNR_MODE) + .value("DECODER_MAX_LINEARITY_MODE", + mpm::rfdc::rfdc_ctrl::DECODER_MAX_LINEARITY_MODE); + + py::enum_<mpm::rfdc::rfdc_ctrl::nyquist_zone_options>(m, "nyquist_zone_options") + .value("ODD_NYQUIST_ZONE", mpm::rfdc::rfdc_ctrl::ODD_NYQUIST_ZONE) + .value("EVEN_NYQUIST_ZONE", mpm::rfdc::rfdc_ctrl::EVEN_NYQUIST_ZONE); + + py::enum_<mpm::rfdc::rfdc_ctrl::mixer_mode_options>(m, "mixer_mode_options") + .value("MIXER_MODE_OFF", mpm::rfdc::rfdc_ctrl::MIXER_MODE_OFF) + .value("MIXER_MODE_C2C", mpm::rfdc::rfdc_ctrl::MIXER_MODE_C2C) + .value("MIXER_MODE_C2R", mpm::rfdc::rfdc_ctrl::MIXER_MODE_C2R) + .value("MIXER_MODE_R2C", mpm::rfdc::rfdc_ctrl::MIXER_MODE_R2C) + .value("MIXER_MODE_R2R", mpm::rfdc::rfdc_ctrl::MIXER_MODE_R2R); + + py::enum_<mpm::rfdc::rfdc_ctrl::calibration_mode_options>( + m, "calibration_mode_options") + .value("CALIB_MODE1", mpm::rfdc::rfdc_ctrl::CALIB_MODE1) + .value("CALIB_MODE2", mpm::rfdc::rfdc_ctrl::CALIB_MODE2); + + py::enum_<mpm::rfdc::rfdc_ctrl::event_type_options>(m, "event_type_options") + .value("MIXER_EVENT", mpm::rfdc::rfdc_ctrl::MIXER_EVENT) + .value("CRSE_DLY_EVENT", mpm::rfdc::rfdc_ctrl::CRSE_DLY_EVENT) + .value("QMC_EVENT", mpm::rfdc::rfdc_ctrl::QMC_EVENT); + + py::enum_<mpm::rfdc::rfdc_ctrl::interp_decim_options>(m, "interp_decim_options") + .value("INTERP_DECIM_OFF", mpm::rfdc::rfdc_ctrl::INTERP_DECIM_OFF) + .value("INTERP_DECIM_1X", mpm::rfdc::rfdc_ctrl::INTERP_DECIM_1X) + .value("INTERP_DECIM_2X", mpm::rfdc::rfdc_ctrl::INTERP_DECIM_2X) + .value("INTERP_DECIM_4X", mpm::rfdc::rfdc_ctrl::INTERP_DECIM_4X) + .value("INTERP_DECIM_8X", mpm::rfdc::rfdc_ctrl::INTERP_DECIM_8X); + + py::enum_<mpm::rfdc::rfdc_ctrl::fabric_clk_div_options>(m, "fabric_clk_div_options") + .value("DIV_1", mpm::rfdc::rfdc_ctrl::DIV_1) + .value("DIV_2", mpm::rfdc::rfdc_ctrl::DIV_2) + .value("DIV_4", mpm::rfdc::rfdc_ctrl::DIV_4) + .value("DIV_8", mpm::rfdc::rfdc_ctrl::DIV_8) + .value("DIV_16", mpm::rfdc::rfdc_ctrl::DIV_16); +} +#endif diff --git a/mpm/include/mpm/rfdc/rfdc_throw.h b/mpm/include/mpm/rfdc/rfdc_throw.h new file mode 100755 index 000000000..20a592696 --- /dev/null +++ b/mpm/include/mpm/rfdc/rfdc_throw.h @@ -0,0 +1,10 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +/** + * A function to throw MPM exceptions from within the Xilinx RFdc API + */ +void rfdc_throw(const char* msg); diff --git a/mpm/include/mpm/rfdc/xrfdc.h b/mpm/include/mpm/rfdc/xrfdc.h new file mode 100644 index 000000000..ad2c06fa8 --- /dev/null +++ b/mpm/include/mpm/rfdc/xrfdc.h @@ -0,0 +1,2092 @@ +/****************************************************************************** +* +* Copyright (C) 2017-2019 Xilinx, Inc. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* XILINX BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Except as contained in this notice, the name of the Xilinx shall not be used +* in advertising or otherwise to promote the sale, use or other dealings in +* this Software without prior written authorization from Xilinx. +* +******************************************************************************/ +/*****************************************************************************/ +/** +* +* @file xrfdc.h +* @addtogroup rfdc_v6_0 +* @{ +* @details +* +* The Xilinx� LogiCORE IP Zynq UltraScale+ RFSoC RF Data Converter IP core +* provides a configurable wrapper to allow the RF DAC and RF ADC blocks to be +* used in IP Integrator designs. Multiple tiles are available on each RFSoC +* and each tile can have a number of data converters (analog-to-digital (ADC) +* and digital-to-analog (DAC)). The RF ADCs can sample input frequencies up +* to 4 GHz at 4 GSPS with excellent noise spectral density. The RF DACs +* generate output carrier frequencies up to 4 GHz using the 2nd Nyquist zone +* with excellent noise spectral density at an update rate of 6.4 GSPS. +* The RF data converters also include power efficient digital down-converters +* (DDCs) and digital up-converters (DUCs) that include programmable interpolation +* and decimation, NCO and complex mixer. The DDCs and DUCs can also support +* dual-band operation. +* A maximum of 4 tiles are available on for DAC and ADC operations each. Each +* tile can have a maximum of 4 blocks/slices. +* This driver provides APIs to configure various functionalities. Similarly +* the driver provides APIs to read back configurations. +* Some of the features that the driver supports are: +* 1) Setting up and reading back fine mixer settings +* 2) Setting up and reading back coarse mixer settings +* 3) Reading back interpolation or decimation factors +* 4) Setting up and reading back QMC settings which include gain, phase etc +* 5) Setting up and reading back decoder mode settings +* 6) Setting up and reading back coarse delay settings +* All the APIs implemented in the driver provide appropriate range checks. +* An API has been provided for debug purpose which will dump all registers +* for a requested tile. +* Inline functions have also been provided to read back the parameters +* initially configured through the GUI. +* +* There are plans to add more features, e.g. Support for multi band, PLL +* configurations etc. +* +* <pre> +* MODIFICATION HISTORY: +* +* Ver Who Date Changes +* ----- --- -------- ----------------------------------------------- +* 1.0 sk 05/16/17 Initial release +* 2.0 sk 08/09/17 Fixed coarse Mixer configuration settings +* CR# 977266, 977872. +* Return error for Slice Event on 4G ADC Block. +* Corrected Interrupt Macro names and values. +* 08/16/17 Add support for SYSREF and PL event sources. +* 08/18/17 Add API to enable and disable FIFO. +* 08/23/17 Add API to configure Nyquist zone. +* 08/30/17 Add additional info to BlockStatus. +* 08/30/17 Add support for Coarse Mixer BYPASS mode. +* 08/31/17 Removed Tile Reset Assert and Deassert. +* 09/07/17 Add support for negative NCO freq. +* 09/15/17 Fixed NCO freq precision issue. +* 09/15/17 Fixed Immediate Event source issue and also +* updated the Immediate Macro value to 0. +* 2.1 sk 09/15/17 Remove Libmetal library dependency for MB. +* 09/18/17 Add API to clear the interrupts. +* sk 09/21/17 Add __BAREMETAL__ compiler flag option +* for Baremetal. +* sk 09/21/17 Add support for Over voltage and Over +* Range interrupts. +* sk 09/22/17 Add s64 typedef for Linux. +* sk 09/24/17 Fixed Get_Tile/BlockBaseAddr always +* giving ADC related address. +* sk 09/25/17 Modified XRFdc_GetBlockStatus API to give +* correct information and also updates the +* description for Vector Param in intr handler +* Add API to get Output current and removed +* GetTermVoltage and GetOutputCurr inline functions. +* 2.2 sk 10/05/17 Fixed XRFdc_GetNoOfADCBlocks API for 4GSPS. +* Enable the decoder clock based on decoder mode. +* Add API to get the current FIFO status. +* Updated XRFdc_DumpRegs API for better readability +* of output register dump. +* Add support for 4GSPS CoarseMixer frequency. +* 10/11/17 Modify float types to double to increase precision. +* 10/12/17 Update BlockStatus API to give current status. +* In BYPASS mode, input datatype can be Real or IQ +* hence checked both while reading the mixer mode. +* 10/17/17 Fixed Set Threshold API Issue. +* 2.2 sk 10/18/17 Add support for FIFO and DATA overflow interrupt +* 2.3 sk 11/06/17 Fixed PhaseOffset truncation issue. +* Provide user configurability for FineMixerScale. +* 11/08/17 Return error for DAC R2C mode and ADC C2R mode. +* 11/10/17 Corrected FIFO and DATA Interrupt masks. +* 11/20/17 Fixed StartUp, Shutdown and Reset API for Tile_Id -1. +* 11/20/17 Remove unwanted ADC block checks in 4GSPS mode. +* 3.0 sk 12/11/17 Added DDC and DUC support. +* 12/13/17 Add CoarseMixMode field in Mixer_Settings structure. +* 12/15/17 Add support to switch calibration modes. +* 12/15/17 Add support for mixer frequencies > Fs/2 and < -Fs/2. +* sg 13/01/18 Added PLL and external clock switch support +* Added API to get PLL lock status. +* Added API to get clock source. +* sk 01/18/18 Add API to get driver version. +* 3.1 jm 01/24/18 Add Multi-tile sync support. +* sk 01/25/18 Updated Set and Get Interpolation/Decimation factor +* API's to consider the actual factor value. +* 3.2 sk 02/02/18 Add API's to configure inverse-sinc. +* sk 02/27/18 Add API's to configure Multiband. +* sk 03/09/18 Update PLL structure in XRFdc_DynamicPLLConfig API. +* sk 03/09/18 Update ADC and DAC datatypes in Mixer API and use +* input datatype for ADC in threshold and QMC APIs. +* sk 03/09/18 Removed FIFO disable check in DDC and DUC APIs. +* sk 03/09/18 Add support for Marker event source for DAC block. +* jm 03/12/18 Fixed DAC latency calculation in MTS. +* jm 03/12/18 Added support for reloading DTC scans. +* jm 03/12/18 Add option to configure sysref capture after MTS. +* sk 03/22/18 Updated PLL settings based on latest IP values. +* 4.0 sk 04/09/18 Added API to enable/disable the sysref. +* sk 04/09/18 Updated max VCO to 13108MHz to support max DAC +* sample rate of 6.554MHz. +* rk 04/17/18 Adjust calculated latency by sysref period, where doing +* so results in closer alignment to the target latency. +* sk 04/17/18 Corrected Set/Get MixerSettings API description for +* FineMixerScale parameter. +* sk 04/19/18 Enable VCO Auto selection while configuring the clock. +* sk 04/24/18 Add API to get PLL Configurations. +* sk 04/24/18 Add API to get the Link Coupling mode. +* sk 04/28/18 Implement timeouts for PLL Lock, Startup and shutdown. +* sk 05/30/18 Removed CalibrationMode check for DAC. +* sk 06/05/18 Updated minimum Ref clock value to 102.40625MHz. +* 5.0 sk 06/25/18 Update DAC min sampling rate to 500MHz and also update +* VCO Range, PLL_DIVIDER and PLL_FPDIV ranges. +* Update PLL structure with calculated sampling rate. +* sk 06/25/18 Add XRFdc_GetFabClkOutDiv() API to read fabric clk div. +* Add Inline APIs XRFdc_CheckBlockEnabled(), +* XRFdc_CheckTileEnabled(). +* sk 07/06/18 Add support to dump HSCOM regs in XRFdc_DumpRegs() API +* sk 07/12/18 Fixed Multiband crossbar settings in C2C mode. +* sk 07/19/18 Add MixerType member to MixerSettings structure and +* Update Mixer Settings APIs to consider the MixerType +* variable. +* sk 07/19/18 Add XRFdc_GetMultibandConfig() API to read Multiband +* configuration. +* sk 07/20/18 Update the APIs to check the corresponding section +* (Digital/Analog)enable/disable. +* sk 07/26/18 Fixed Doxygen, coverity warnings. +* sk 08/03/18 Fixed MISRAC warnings. +* sk 08/24/18 Move mixer related APIs to xrfdc_mixer.c file. +* Define asserts for Linux, Re-arranged XRFdc_RestartIPSM, +* XRFdc_CfgInitialize() and XRFdc_MultiBand() APIs. +* Reorganize the code to improve readability and +* optimization. +* mus 08/17/18 Removed structure paddings from XRFdc_Config structure. +* It has been done to have 1:1 mapping between +* XRFdc_Config structure and device tree property +* "param-list", over linux platform. +* sk 09/24/18 Update powerup-state value based on PLL mode in +* XRFdc_DynamicPLLConfig() API. +* sk 10/10/18 Check for DigitalPath enable in XRFdc_GetNyquistZone() +* and XRFdc_GetCalibrationMode() APIs for Multiband. +* sk 10/13/18 Add support to read the REFCLKDIV param from design. +* Update XRFdc_SetPLLConfig() API to support range of +* REF_CLK_DIV values(1 to 4). +* Add XRFDC_MIXER_MODE_R2R option to support BYPASS mode +* for Real input. +* 5.1 cog 01/29/19 Replace structure reference ADC checks with +* function. +* cog 01/29/19 Added XRFdc_SetDither() and XRFdc_GetDither() APIs. +* cog 01/29/19 Rename DataType for mixer input to MixerInputDataType +* for readability. +* cog 01/29/19 Refactoring of interpolation and decimation APIs and +* changed fabric rate for decimation X8 for non-high speed ADCs. +* cog 01/29/19 New inline functions to determine max & min sampling rates. +* 6.0 cog 02/17/19 Added Inverse-Sinc Second Nyquist Zone Support +* cog 02/17/19 Added new clock Distribution functionality. +* cog 02/17/19 Refactored to improve delay balancing in clock +* distribution. +* cog 02/17/19 Added delay calculation & metal log messages. +* cog 02/17/19 Added Intratile clock settings. +* cog 02/17/19 XRFdc_GetPLLConfig() now uses register values to get the +* PLL configuration for new IPs and is no longer static. +* cog 02/17/19 Refactoring of interpolation and decimation APIs and +* changed fabric rate for decimation X8 for non-high speed ADCs. +* cog 02/17/19 Added XRFdc_SetIMRPassMode() and XRFdc_SetIMRPassMode() APIs +* cog 02/17/19 Added XRFdc_SetDACMode() and XRFdc_GetDACMode() APIs +* cog 02/17/19 Added XRFdc_SetSignalDetector() and XRFdc_GetSignalDetector() APIs +* cog 02/17/19 Added XRFdc_DisableCoefficientsOverride(), XRFdc_SetCalCoefficients +* and XRFdc_GetCalCoefficients APIs. +* cog 02/19/19 New definitions for clock detection. +* 6.0 cog 02/20/19 Added handling for new ADC common mode over/under +* voltage interrupts. +* cog 02/20/19 XRFdc_GetIntrStatus now populates a pointer with the +* status and returns an error code. +* cog 02/20/19 XRFdc_IntrClr, XRFdc_IntrDisable and XRFdc_IntrEnable +* now return error codes. +* cog 02/21/19 Added XRFdc_SetCalFreeze() and XRFdc_GetCalFreeze() APIs +* cog 04/15/19 Rename XRFdc_SetDACMode() and XRFdc_GetDACMode() APIs to +* XRFdc_SetDataPathMode() and XRFdc_GetDataPathMode() respectively. +* +* </pre> +* +******************************************************************************/ + + +#ifndef RFDC_H_ +#define RFDC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/***************************** Include Files *********************************/ + +#include "rfdc_throw.h" +#include <stdlib.h> +#include <stdint.h> + +#ifdef __BAREMETAL__ +#include "xil_assert.h" +#include "xdebug.h" +#include "sleep.h" +#endif +#include <metal/sys.h> +#include <metal/device.h> +#include <metal/irq.h> +#include <metal/atomic.h> +#include <metal/io.h> +#include <metal/sleep.h> +#include "metal/alloc.h" +#include "xrfdc_hw.h" + +/**************************** Type Definitions *******************************/ +#define XRFdc_IsADC4GSPS(InstPtr) XRFdc_IsHighSpeedADC(InstPtr, 0) + +#ifndef __BAREMETAL__ +typedef __u32 u32; +typedef __u16 u16; +typedef __u8 u8; +typedef __s32 s32; +typedef __u64 u64; +typedef __s64 s64; +typedef __s8 s8; +#endif + +/** +* The handler data type allows the user to define a callback function to +* respond to interrupt events in the system. This function is executed +* in interrupt context, so amount of processing should be minimized. +* +* @param CallBackRef is the callback reference passed in by the upper +* layer when setting the callback functions, and passed back to +* the upper layer when the callback is invoked. Its type is +* not important to the driver, so it is a void pointer. +* @param Type indicates ADC/DAC. +* @param Tile_Id indicates Tile number (0-3). +* @param Block_Id indicates Block number (0-3). +* @param StatusEvent indicates one or more interrupt occurred. +*/ +typedef void (*XRFdc_StatusHandler) (void *CallBackRef, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 StatusEvent); +#ifndef __BAREMETAL__ +#pragma pack(1) +#endif +/** + * PLL settings. + */ +typedef struct { + u32 Enabled; /* PLL Enables status (not a setter) */ + double RefClkFreq; + double SampleRate; + u32 RefClkDivider; + u32 FeedbackDivider; + u32 OutputDivider; + u32 FractionalMode; /* Fractional mode is currently not supported */ + u64 FractionalData; /* Fractional data is currently not supported */ + u32 FractWidth; /* Fractional width is currently not supported */ +} XRFdc_PLL_Settings; +/** +* ClkIntraTile Settings. +*/ +typedef struct { + u8 SourceTile; + u8 PLLEnable; + XRFdc_PLL_Settings PLLSettings; + u8 DivisionFactor; + u8 Delay; + u8 DistributedClock; +} XRFdc_Tile_Clock_Settings; +/** +* Clk Distribution. +*/ +typedef struct { + u8 Enabled; + u8 DistributionSource; + u8 UpperBound; + u8 LowerBound; + u8 MaxDelay; + u8 MinDelay; + u8 IsDelayBalanced; +} XRFdc_Distribution; +/** +* Clk Distribution Settings. +*/ +typedef struct { + XRFdc_Tile_Clock_Settings DAC[4]; + XRFdc_Tile_Clock_Settings ADC[4]; + XRFdc_Distribution DistributionStatus[8]; +} XRFdc_Distribution_Settings; +#ifndef __BAREMETAL__ +#pragma pack() +#endif + +/** + * ADC Signal Detect Settings. + */ +typedef struct { + u8 Mode; + u8 TimeConstant; + u8 Flush; + u8 EnableIntegrator; + u16 HighThreshold; + u16 LowThreshold; + u8 HysteresisEnable; +}XRFdc_Signal_Detector_Settings; +/** + * QMC settings. + */ +typedef struct { + u32 EnablePhase; + u32 EnableGain; + double GainCorrectionFactor; + double PhaseCorrectionFactor; + s32 OffsetCorrectionFactor; + u32 EventSource; +} XRFdc_QMC_Settings; + +/** + * Coarse delay settings. + */ +typedef struct { + u32 CoarseDelay; + u32 EventSource; +} XRFdc_CoarseDelay_Settings; + +/** + * Mixer settings. + */ +typedef struct { + double Freq; + double PhaseOffset; + u32 EventSource; + u32 CoarseMixFreq; + u32 MixerMode; + u8 FineMixerScale; /* NCO output scale, valid values 0,1 and 2 */ + u8 MixerType; +} XRFdc_Mixer_Settings; + +/** + * ADC block Threshold settings. + */ +typedef struct { + u32 UpdateThreshold; /* Selects which threshold to update */ + u32 ThresholdMode[2]; /* Entry 0 for Threshold0 and 1 for Threshold1 */ + u32 ThresholdAvgVal[2]; /* Entry 0 for Threshold0 and 1 for Threshold1 */ + u32 ThresholdUnderVal[2]; /* Entry 0 for Threshold0 and 1 for Threshold1 */ + u32 ThresholdOverVal[2]; /* Entry 0 is for Threshold0 and 1 for Threshold1 */ +} XRFdc_Threshold_Settings; + +/** + * RFSoC Calibration coefficients generic struct + */ +typedef struct { + u32 Coeff0; + u32 Coeff1; + u32 Coeff2; + u32 Coeff3; + u32 Coeff4; + u32 Coeff5; + u32 Coeff6; + u32 Coeff7; +} XRFdc_Calibration_Coefficients; + +/** + * RFSoC Calibration freeze settings struct + */ +typedef struct { + u32 CalFrozen; /*Status indicates calibration freeze state*/ + u32 DisableFreezePin; /*Disable the calibration freeze pin*/ + u32 FreezeCalibration; /*Setter for freezing*/ +} XRFdc_Cal_Freeze_Settings; + +/** + * RFSoC Tile status. + */ +typedef struct { + u32 IsEnabled; /* 1, if tile is enabled, 0 otherwise */ + u32 TileState; /* Indicates Tile Current State */ + u8 BlockStatusMask; /* Bit mask for block status, 1 indicates block enable */ + u32 PowerUpState; + u32 PLLState; +} XRFdc_TileStatus; + +/** + * RFSoC Data converter IP status. + */ +typedef struct { + XRFdc_TileStatus DACTileStatus[4]; + XRFdc_TileStatus ADCTileStatus[4]; + u32 State; +} XRFdc_IPStatus; + +/** + * status of DAC or ADC blocks in the RFSoC Data converter. + */ +typedef struct { + double SamplingFreq; + u32 AnalogDataPathStatus; + u32 DigitalDataPathStatus; + u8 DataPathClocksStatus; /* Indicates all required datapath + clocks are enabled or not, 1 if all clocks enabled, 0 otherwise */ + u8 IsFIFOFlagsEnabled; /* Indicates FIFO flags enabled or not, + 1 if all flags enabled, 0 otherwise */ + u8 IsFIFOFlagsAsserted; /* Indicates FIFO flags asserted or not, + 1 if all flags asserted, 0 otherwise */ +} XRFdc_BlockStatus; + +#ifndef __BAREMETAL__ +#pragma pack(1) +#endif +/** + * DAC block Analog DataPath Config settings. + */ +typedef struct { + u32 BlockAvailable; + u32 InvSyncEnable; + u32 MixMode; + u32 DecoderMode; +} XRFdc_DACBlock_AnalogDataPath_Config; + +/** + * DAC block Digital DataPath Config settings. + */ +typedef struct { + u32 MixerInputDataType; + u32 DataWidth; + u32 InterpolationMode; + u32 FifoEnable; + u32 AdderEnable; + u32 MixerType; +} XRFdc_DACBlock_DigitalDataPath_Config; + +/** + * ADC block Analog DataPath Config settings. + */ +typedef struct { + u32 BlockAvailable; + u32 MixMode; +} XRFdc_ADCBlock_AnalogDataPath_Config; + +/** + * DAC block Digital DataPath Config settings. + */ +typedef struct { + u32 MixerInputDataType; + u32 DataWidth; + u32 DecimationMode; + u32 FifoEnable; + u32 MixerType; +} XRFdc_ADCBlock_DigitalDataPath_Config; + +/** + * DAC Tile Config structure. + */ +typedef struct { + u32 Enable; + u32 PLLEnable; + double SamplingRate; + double RefClkFreq; + double FabClkFreq; + u32 FeedbackDiv; + u32 OutputDiv; + u32 RefClkDiv; + u32 MultibandConfig; + double MaxSampleRate; + u32 NumSlices; + XRFdc_DACBlock_AnalogDataPath_Config DACBlock_Analog_Config[4]; + XRFdc_DACBlock_DigitalDataPath_Config DACBlock_Digital_Config[4]; +} XRFdc_DACTile_Config; + +/** + * ADC Tile Config Structure. + */ +typedef struct { + u32 Enable; /* Tile Enable status */ + u32 PLLEnable; /* PLL enable Status */ + double SamplingRate; + double RefClkFreq; + double FabClkFreq; + u32 FeedbackDiv; + u32 OutputDiv; + u32 RefClkDiv; + u32 MultibandConfig; + double MaxSampleRate; + u32 NumSlices; + XRFdc_ADCBlock_AnalogDataPath_Config ADCBlock_Analog_Config[4]; + XRFdc_ADCBlock_DigitalDataPath_Config ADCBlock_Digital_Config[4]; +} XRFdc_ADCTile_Config; + +/** + * RFdc Config Structure. + */ +typedef struct { + u32 DeviceId; + metal_phys_addr_t BaseAddr; + u32 ADCType; /* ADC Type 4GSPS or 2GSPS*/ + u32 MasterADCTile; /* ADC master Tile */ + u32 MasterDACTile; /* DAC Master Tile */ + u32 ADCSysRefSource; + u32 DACSysRefSource; + u32 IPType; + XRFdc_DACTile_Config DACTile_Config[4]; + XRFdc_ADCTile_Config ADCTile_Config[4]; +} XRFdc_Config; +#ifndef __BAREMETAL__ +#pragma pack() +#endif +/** + * DAC Block Analog DataPath Structure. + */ +typedef struct { + u32 Enabled; /* DAC Analog Data Path Enable */ + u32 MixedMode; + double TerminationVoltage; + double OutputCurrent; + u32 InverseSincFilterEnable; + u32 DecoderMode; + void *FuncHandler; + u32 NyquistZone; + u8 AnalogPathEnabled; + u8 AnalogPathAvailable; + XRFdc_QMC_Settings QMC_Settings; + XRFdc_CoarseDelay_Settings CoarseDelay_Settings; +} XRFdc_DACBlock_AnalogDataPath; + +/** + * DAC Block Digital DataPath Structure. + */ +typedef struct { + u32 MixerInputDataType; + u32 DataWidth; + int ConnectedIData; + int ConnectedQData; + u32 InterpolationFactor; + u8 DigitalPathEnabled; + u8 DigitalPathAvailable; + XRFdc_Mixer_Settings Mixer_Settings; +} XRFdc_DACBlock_DigitalDataPath; + +/** + * ADC Block Analog DataPath Structure. + */ +typedef struct { + u32 Enabled; /* ADC Analog Data Path Enable */ + XRFdc_QMC_Settings QMC_Settings; + XRFdc_CoarseDelay_Settings CoarseDelay_Settings; + XRFdc_Threshold_Settings Threshold_Settings; + u32 NyquistZone; + u8 CalibrationMode; + u8 AnalogPathEnabled; + u8 AnalogPathAvailable; +} XRFdc_ADCBlock_AnalogDataPath; + +/** + * ADC Block Digital DataPath Structure. + */ +typedef struct { + u32 MixerInputDataType; + u32 DataWidth; + u32 DecimationFactor; + int ConnectedIData; + int ConnectedQData; + u8 DigitalPathEnabled; + u8 DigitalPathAvailable; + XRFdc_Mixer_Settings Mixer_Settings; +} XRFdc_ADCBlock_DigitalDataPath; + +/** + * DAC Tile Structure. + */ +typedef struct { + u32 TileBaseAddr; /* Tile BaseAddress*/ + u32 NumOfDACBlocks; /* Number of DAC block enabled */ + XRFdc_PLL_Settings PLL_Settings; + u8 MultibandConfig; + XRFdc_DACBlock_AnalogDataPath DACBlock_Analog_Datapath[4]; + XRFdc_DACBlock_DigitalDataPath DACBlock_Digital_Datapath[4]; +} XRFdc_DAC_Tile; + +/** + * ADC Tile Structure. + */ +typedef struct { + u32 TileBaseAddr; + u32 NumOfADCBlocks; /* Number of ADC block enabled */ + XRFdc_PLL_Settings PLL_Settings; + u8 MultibandConfig; + XRFdc_ADCBlock_AnalogDataPath ADCBlock_Analog_Datapath[4]; + XRFdc_ADCBlock_DigitalDataPath ADCBlock_Digital_Datapath[4]; +} XRFdc_ADC_Tile; + +/** + * RFdc Structure. + */ +typedef struct { + XRFdc_Config RFdc_Config; /* Config Structure */ + u32 IsReady; + u32 ADC4GSPS; + metal_phys_addr_t BaseAddr; /* BaseAddress */ + struct metal_io_region *io; /* Libmetal IO structure */ + struct metal_device *device; /* Libmetal device structure */ + XRFdc_DAC_Tile DAC_Tile[4]; + XRFdc_ADC_Tile ADC_Tile[4]; + XRFdc_StatusHandler StatusHandler; /* Event handler function */ + void *CallBackRef; /* Callback reference for event handler */ + u8 UpdateMixerScale; /* Set to 1, if user overwrite mixer scale */ +} XRFdc; + +/***************** Macros (Inline Functions) Definitions *********************/ + +# ifndef __BAREMETAL__ +# define Xil_AssertNonvoid(Expression) \ + { \ + if (!(Expression)) { \ + rfdc_throw(#Expression); \ + } \ + } +# define Xil_AssertVoid(Expression) \ + { \ + if (!(Expression)) { \ + rfdc_throw(#Expression); \ + } \ + } +# define Xil_AssertVoidAlways() \ + { \ + rfdc_throw("Assert false"); \ + } +# endif + +#define MAX(x,y) (x>y)?x:y +#define MIN(x,y) (x<y)?x:y +#define XRFDC_SUCCESS 0U +#define XRFDC_FAILURE 1U +#define XRFDC_COMPONENT_IS_READY 0x11111111U +#define XRFDC_NUM_SLICES_HSADC 2 +#define XRFDC_NUM_SLICES_LSADC 4 +#ifndef __BAREMETAL__ +#define XRFDC_PLATFORM_DEVICE_DIR "/sys/bus/platform/devices/" +#define XRFDC_BUS_NAME "platform" +#define XRFDC_SIGNATURE "usp_rf_data_converter" /* String in RFDC node name */ +#define XRFDC_CONFIG_DATA_PROPERTY "param-list" /* device tree property */ +#define XRFDC_COMPATIBLE_PROPERTY "compatible" /* device tree property */ +#define XRFDC_NUM_INSTANCES_PROPERTY "num-insts" /* device tree property */ +#define XRFDC_COMPATIBLE_STRING "xlnx,usp-rf-data-converter-" +#define XRFDC_DEVICE_ID_SIZE 4U +#define XRFDC_NUM_INST_SIZE 4U +#define XRFDC_CONFIG_DATA_SIZE sizeof(XRFdc_Config) +#endif +#define XRFDC_REGION_SIZE 0x40000U +#define XRFDC_DRP_BASE(type, tile) ((type) == XRFDC_ADC_TILE ? \ + XRFDC_ADC_TILE_DRP_ADDR(tile) : XRFDC_DAC_TILE_DRP_ADDR(tile)) + +#define XRFDC_CTRL_STS_BASE(Type, Tile) ((Type) == XRFDC_ADC_TILE ? \ + XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile) : \ + XRFDC_DAC_TILE_CTRL_STATS_ADDR(Tile)) + +#define XRFDC_BLOCK_BASE(Type, Tile, Block) ((Type) == XRFDC_ADC_TILE ? \ + (XRFDC_ADC_TILE_DRP_ADDR(Tile) + XRFDC_BLOCK_ADDR_OFFSET(Block)) : \ + (XRFDC_DAC_TILE_DRP_ADDR(Tile) + XRFDC_BLOCK_ADDR_OFFSET(Block))) + +#define XRFDC_ADC_TILE 0U +#define XRFDC_DAC_TILE 1U +#define XRFDC_TILE_ID_MAX 0x3U +#define XRFDC_BLOCK_ID_MAX 0x3U +#define XRFDC_EVNT_SRC_IMMEDIATE 0x00000000U +#define XRFDC_EVNT_SRC_SLICE 0x00000001U +#define XRFDC_EVNT_SRC_TILE 0x00000002U +#define XRFDC_EVNT_SRC_SYSREF 0x00000003U +#define XRFDC_EVNT_SRC_MARKER 0x00000004U +#define XRFDC_EVNT_SRC_PL 0x00000005U +#define XRFDC_EVENT_MIXER 0x00000001U +#define XRFDC_EVENT_CRSE_DLY 0x00000002U +#define XRFDC_EVENT_QMC 0x00000004U +#define XRFDC_SELECT_ALL_TILES -1 +#define XRFDC_ADC_4GSPS 1U + +#define XRFDC_CRSE_DLY_MAX 0x7U +#define XRFDC_CRSE_DLY_MAX_EXT 0x28U +#define XRFDC_NCO_FREQ_MULTIPLIER ((0x1LLU << 48U) - 2) /* 2^48 -2 */ +#define XRFDC_NCO_FREQ_MIN_MULTIPLIER (0x1LLU << 48U) /* 2^48 */ +#define XRFDC_NCO_PHASE_MULTIPLIER (1U << 17U) /* 2^17 */ +#define XRFDC_QMC_PHASE_MULT (1U << 11U) /* 2^11 */ +#define XRFDC_QMC_GAIN_MULT (1U << 14U) /* 2^14 */ + +#define XRFDC_DATA_TYPE_IQ 0x00000001U +#define XRFDC_DATA_TYPE_REAL 0x00000000U + +#define XRFDC_TRSHD_OFF 0x0U +#define XRFDC_TRSHD_STICKY_OVER 0x00000001U +#define XRFDC_TRSHD_STICKY_UNDER 0x00000002U +#define XRFDC_TRSHD_HYSTERISIS 0x00000003U + +/* Mixer modes */ +#define XRFDC_MIXER_MODE_OFF 0x0U +#define XRFDC_MIXER_MODE_C2C 0x1U +#define XRFDC_MIXER_MODE_C2R 0x2U +#define XRFDC_MIXER_MODE_R2C 0x3U +#define XRFDC_MIXER_MODE_R2R 0x4U + +#define XRFDC_I_IQ_COS_MINSIN 0x00000C00U +#define XRFDC_Q_IQ_SIN_COS 0x00001000U +#define XRFDC_EN_I_IQ 0x00000001U +#define XRFDC_EN_Q_IQ 0x00000004U + +#define XRFDC_MIXER_TYPE_COARSE 0x1U +#define XRFDC_MIXER_TYPE_FINE 0x2U + +#define XRFDC_MIXER_TYPE_OFF 0x3U + +#define XRFDC_COARSE_MIX_OFF 0x0U +#define XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_TWO 0x2U +#define XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_FOUR 0x4U +#define XRFDC_COARSE_MIX_MIN_SAMPLE_FREQ_BY_FOUR 0x8U +#define XRFDC_COARSE_MIX_BYPASS 0x10U + +#define XRFDC_COARSE_MIX_MODE_C2C_C2R 0x1U +#define XRFDC_COARSE_MIX_MODE_R2C 0x2U + +#define XRFDC_CRSE_MIX_OFF 0x924U +#define XRFDC_CRSE_MIX_BYPASS 0x0U +#define XRFDC_CRSE_4GSPS_ODD_FSBYTWO 0x492U +#define XRFDC_CRSE_MIX_I_ODD_FSBYFOUR 0x2CBU +#define XRFDC_CRSE_MIX_Q_ODD_FSBYFOUR 0x659U +#define XRFDC_CRSE_MIX_I_Q_FSBYTWO 0x410U +#define XRFDC_CRSE_MIX_I_FSBYFOUR 0x298U +#define XRFDC_CRSE_MIX_Q_FSBYFOUR 0x688U +#define XRFDC_CRSE_MIX_I_MINFSBYFOUR 0x688U +#define XRFDC_CRSE_MIX_Q_MINFSBYFOUR 0x298U +#define XRFDC_CRSE_MIX_R_I_FSBYFOUR 0x8A0U +#define XRFDC_CRSE_MIX_R_Q_FSBYFOUR 0x70CU +#define XRFDC_CRSE_MIX_R_I_MINFSBYFOUR 0x8A0U +#define XRFDC_CRSE_MIX_R_Q_MINFSBYFOUR 0x31CU + +#define XRFDC_MIXER_SCALE_AUTO 0x0U +#define XRFDC_MIXER_SCALE_1P0 0x1U +#define XRFDC_MIXER_SCALE_0P7 0x2U + +#define XRFDC_MIXER_PHASE_OFFSET_UP_LIMIT 180 +#define XRFDC_MIXER_PHASE_OFFSET_LOW_LIMIT (-180) +#define XRFDC_UPDATE_THRESHOLD_0 0x1U +#define XRFDC_UPDATE_THRESHOLD_1 0x2U +#define XRFDC_UPDATE_THRESHOLD_BOTH 0x4U +#define XRFDC_THRESHOLD_CLRMD_MANUAL_CLR 0x1U +#define XRFDC_THRESHOLD_CLRMD_AUTO_CLR 0x2U +#define XRFDC_DECODER_MAX_SNR_MODE 0x1U +#define XRFDC_DECODER_MAX_LINEARITY_MODE 0x2U +#define XRFDC_OUTPUT_CURRENT_32MA 32U +#define XRFDC_OUTPUT_CURRENT_20MA 20U + +#define XRFDC_ADC_MIXER_MODE_IQ 0x1U +#define XRFDC_DAC_MIXER_MODE_REAL 0x2U + +#define XRFDC_ODD_NYQUIST_ZONE 0x1U +#define XRFDC_EVEN_NYQUIST_ZONE 0x2U + +#define XRFDC_INTERP_DECIM_OFF 0x0U +#define XRFDC_INTERP_DECIM_1X 0x1U +#define XRFDC_INTERP_DECIM_2X 0x2U +#define XRFDC_INTERP_DECIM_3X 0x3U +#define XRFDC_INTERP_DECIM_4X 0x4U +#define XRFDC_INTERP_DECIM_5X 0x5U +#define XRFDC_INTERP_DECIM_6X 0x6U +#define XRFDC_INTERP_DECIM_8X 0x8U +#define XRFDC_INTERP_DECIM_10X 0xAU +#define XRFDC_INTERP_DECIM_12X 0xCU +#define XRFDC_INTERP_DECIM_16X 0x10U +#define XRFDC_INTERP_DECIM_20X 0x14U +#define XRFDC_INTERP_DECIM_24X 0x18U +#define XRFDC_INTERP_DECIM_40X 0x28U + +#define XRFDC_FAB_CLK_DIV1 0x1U +#define XRFDC_FAB_CLK_DIV2 0x2U +#define XRFDC_FAB_CLK_DIV4 0x3U +#define XRFDC_FAB_CLK_DIV8 0x4U +#define XRFDC_FAB_CLK_DIV16 0x5U + +#define XRFDC_CALIB_MODE1 0x1U +#define XRFDC_CALIB_MODE2 0x2U +#define XRFDC_TI_DCB_MODE1_4GSPS 0x00007800U +#define XRFDC_TI_DCB_MODE1_2GSPS 0x00005000U + +/* PLL Configuration */ +#define XRFDC_PLL_UNLOCKED 0x1U +#define XRFDC_PLL_LOCKED 0x2U + +#define XRFDC_EXTERNAL_CLK 0x0U +#define XRFDC_INTERNAL_PLL_CLK 0x1U + +#define PLL_FPDIV_MIN 13U +#define PLL_FPDIV_MAX 128U +#define PLL_DIVIDER_MIN 2U +#define PLL_DIVIDER_MAX 28U +#define VCO_RANGE_MIN 8500U +#define VCO_RANGE_MAX 13200U +#define XRFDC_PLL_LPF1_VAL 0x6U +#define XRFDC_PLL_CRS2_VAL 0x7008U +#define XRFDC_VCO_UPPER_BAND 0x0U +#define XRFDC_VCO_LOWER_BAND 0x1U +#define XRFDC_REF_CLK_DIV_1 0x1U +#define XRFDC_REF_CLK_DIV_2 0x2U +#define XRFDC_REF_CLK_DIV_3 0x3U +#define XRFDC_REF_CLK_DIV_4 0x4U + +#define XRFDC_SINGLEBAND_MODE 0x1U +#define XRFDC_MULTIBAND_MODE_2X 0x2U +#define XRFDC_MULTIBAND_MODE_4X 0x4U + +#define XRFDC_MB_DATATYPE_C2C 0x1U +#define XRFDC_MB_DATATYPE_R2C 0x2U +#define XRFDC_MB_DATATYPE_C2R 0x4U + +#define XRFDC_SB_C2C_BLK0 0x82U +#define XRFDC_SB_C2C_BLK1 0x64U +#define XRFDC_SB_C2R 0x40U +#define XRFDC_MB_C2C_BLK0 0x5EU +#define XRFDC_MB_C2C_BLK1 0x5DU +#define XRFDC_MB_C2R_BLK0 0x5CU +#define XRFDC_MB_C2R_BLK1 0x0U + +#define XRFDC_MIXER_MODE_BYPASS 0x2U + +#define XRFDC_LINK_COUPLING_DC 0x0U +#define XRFDC_LINK_COUPLING_AC 0x1U + +#define XRFDC_MB_MODE_SB 0x0U +#define XRFDC_MB_MODE_2X_BLK01 0x1U +#define XRFDC_MB_MODE_2X_BLK23 0x2U +#define XRFDC_MB_MODE_2X_BLK01_BLK23 0x3U +#define XRFDC_MB_MODE_4X 0x4U + +#define XRFDC_MILLI 1000U +#define XRFDC_DAC_SAMPLING_MIN 500 +#define XRFDC_DAC_SAMPLING_MAX 6554 +#define XRFDC_ADC_4G_SAMPLING_MIN 1000 +#define XRFDC_ADC_4G_SAMPLING_MAX 4116 +#define XRFDC_ADC_2G_SAMPLING_MIN 500 +#define XRFDC_ADC_2G_SAMPLING_MAX 2058 +#define XRFDC_REFFREQ_MIN 102.40625 +#define XRFDC_REFFREQ_MAX 614 + +#define XRFDC_DIGITALPATH_ENABLE 0x1U +#define XRFDC_ANALOGPATH_ENABLE 0x1U + +#define XRFDC_BLK_ID0 0x0U +#define XRFDC_BLK_ID1 0x1U +#define XRFDC_BLK_ID2 0x2U +#define XRFDC_BLK_ID3 0x3U +#define XRFDC_BLK_ID4 0x4U + +#define XRFDC_TILE_ID0 0x0U +#define XRFDC_TILE_ID1 0x1U +#define XRFDC_TILE_ID2 0x2U +#define XRFDC_TILE_ID3 0x3U +#define XRFDC_TILE_ID4 0x4U + +#define XRFDC_NUM_OF_BLKS1 0x1U +#define XRFDC_NUM_OF_BLKS2 0x2U +#define XRFDC_NUM_OF_BLKS3 0x3U +#define XRFDC_NUM_OF_BLKS4 0x4U + +#define XRFDC_NUM_OF_TILES1 0x1U +#define XRFDC_NUM_OF_TILES2 0x2U +#define XRFDC_NUM_OF_TILES3 0x3U +#define XRFDC_NUM_OF_TILES4 0x4U + +#define XRFDC_SM_STATE0 0x0U +#define XRFDC_SM_STATE1 0x1U +#define XRFDC_SM_STATE15 0xFU + +#define XRFDC_DECIM_4G_DATA_TYPE 0x3U +#define XRFDC_DECIM_2G_IQ_DATA_TYPE 0x2U + +#define XRFDC_DAC_MAX_WR_FAB_RATE 16U +#define XRFDC_ADC_MAX_RD_FAB_RATE 8U + +#define XRFDC_MIN_PHASE_CORR_FACTOR -26.5 +#define XRFDC_MAX_PHASE_CORR_FACTOR 26.5 +#define XRFDC_MAX_GAIN_CORR_FACTOR 2.0 +#define XRFDC_MIN_GAIN_CORR_FACTOR 0.0 + +#define XRFDC_FAB_RATE_8 8 +#define XRFDC_FAB_RATE_4 4 +#define XRFDC_FAB_RATE_2 2 +#define XRFDC_FAB_RATE_1 1 + +#define XRFDC_HSCOM_PWR_STATS_PLL 0xFFC0U +#define XRFDC_HSCOM_PWR_STATS_EXTERNAL 0xF240U + +#define XRFDC_CLK_DST_DAC0 0 +#define XRFDC_CLK_DST_DAC1 1 +#define XRFDC_CLK_DST_DAC2 2 +#define XRFDC_CLK_DST_DAC3 3 +#define XRFDC_CLK_DST_ADC0 4 +#define XRFDC_CLK_DST_ADC1 5 +#define XRFDC_CLK_DST_ADC2 6 +#define XRFDC_CLK_DST_ADC3 7 +#define XRFDC_CLK_DST_INVALID 0xFFU + +#define XRFDC_CLK_DISTR_MUX4A_SRC_INT 0x0008U +#define XRFDC_CLK_DISTR_MUX4A_SRC_STH 0x0000U +#define XRFDC_CLK_DISTR_MUX6_SRC_OFF 0x0000U +#define XRFDC_CLK_DISTR_MUX6_SRC_INT 0x0100U +#define XRFDC_CLK_DISTR_MUX6_SRC_NTH 0x0080U +#define XRFDC_CLK_DISTR_MUX7_SRC_OFF 0x0000U +#define XRFDC_CLK_DISTR_MUX7_SRC_STH 0x0200U +#define XRFDC_CLK_DISTR_MUX7_SRC_INT 0x0400U +#define XRFDC_CLK_DISTR_MUX8_SRC_NTH 0x0000U +#define XRFDC_CLK_DISTR_MUX8_SRC_INT 0x8000U +#define XRFDC_CLK_DISTR_MUX9_SRC_NTH 0x4000U +#define XRFDC_CLK_DISTR_MUX9_SRC_INT 0x0000U +#define XRFDC_CLK_DISTR_MUX5A_SRC_PLL 0x0800U +#define XRFDC_CLK_DISTR_MUX5A_SRC_RX 0x0040U +#define XRFDC_CLK_DISTR_OFF (XRFDC_CLK_DISTR_MUX4A_SRC_INT | \ + XRFDC_CLK_DISTR_MUX6_SRC_OFF | \ + XRFDC_CLK_DISTR_MUX7_SRC_OFF | \ + XRFDC_CLK_DISTR_MUX8_SRC_NTH | \ + XRFDC_CLK_DISTR_MUX9_SRC_INT) +#define XRFDC_CLK_DISTR_LEFTMOST_TILE 0x0000U +#define XRFDC_CLK_DISTR_CONT_LEFT_EVEN 0x8208U +#define XRFDC_CLK_DISTR_CONT_LEFT_ODD 0x8200U +#define XRFDC_CLK_DISTR_RIGHTMOST_TILE 0x4008 +#define XRFDC_CLK_DISTR_CONT_RIGHT_EVEN 0x4080 +#define XRFDC_CLK_DISTR_CONT_RIGHT_HWL_ODD 0x4088 + +#define XRFDC_CLK_DISTR_MUX4A_SRC_CLR 0x0008U +#define XRFDC_CLK_DISTR_MUX6_SRC_CLR 0x0180U +#define XRFDC_CLK_DISTR_MUX7_SRC_CLR 0x0600U +#define XRFDC_CLK_DISTR_MUX8_SRC_CLR 0x8000U +#define XRFDC_CLK_DISTR_MUX9_SRC_CLR 0x4000U + +#define XRFDC_DIST_MAX 8 + +#define XRFDC_NET_CTRL_CLK_REC_PLL 0x1U +#define XRFDC_NET_CTRL_CLK_REC_DIST_T1 0x2U +#define XRFDC_NET_CTRL_CLK_T1_SRC_LOCAL 0x4U +#define XRFDC_NET_CTRL_CLK_T1_SRC_DIST 0x8U +#define XRFDC_NET_CTRL_CLK_INPUT_DIST 0x20U +#define XRFDC_DIST_CTRL_TO_PLL_DIV 0x10U +#define XRFDC_DIST_CTRL_TO_T1 0x20U +#define XRFDC_DIST_CTRL_DIST_SRC_LOCAL 0x40U +#define XRFDC_DIST_CTRL_DIST_SRC_PLL 0x800U +#define XRFDC_DIST_CTRL_CLK_T1_SRC_LOCAL 0x1000U +#define XRFDC_PLLREFDIV_INPUT_OFF 0x20U +#define XRFDC_PLLREFDIV_INPUT_DIST 0x40U +#define XRFDC_PLLREFDIV_INPUT_FABRIC 0x60U +#define XRFDC_PLLOPDIV_INPUT_DIST_LOCAL 0x800U + +#define XRFDC_TILE_SOURCE_RX 0U +#define XRFDC_TILE_SOURCE_DIST 1U +#define XRFDC_TILE_SOURCE_FABRIC 2U + +#define XRFDC_DIST_OUT_NONE 0U +#define XRFDC_DIST_OUT_RX 1U +#define XRFDC_DIST_OUT_OUTDIV 2U + +#define XRFDC_PLL_SOURCE_NONE 0U +#define XRFDC_PLL_SOURCE_RX 1U +#define XRFDC_PLL_SOURCE_OUTDIV 2U + +#define XRFDC_PLL_OUTDIV_MODE_1 0x0U +#define XRFDC_PLL_OUTDIV_MODE_2 0x1U +#define XRFDC_PLL_OUTDIV_MODE_3 0x2U +#define XRFDC_PLL_OUTDIV_MODE_N 0x3U + +#define XRFDC_PLL_OUTDIV_MODE_3_VAL 0x1U + +#define XRFDC_DIVISION_FACTOR_MIN 1 + +#define XRFDC_DITH_ENABLE 1 +#define XRFDC_DITH_DISABLE 0 + +#define XRFDC_SIGDET_MODE_AVG 0 +#define XRFDC_SIGDET_MODE_RNDM 1 +#define XRFDC_SIGDET_TC_2_0 0 +#define XRFDC_SIGDET_TC_2_2 1 +#define XRFDC_SIGDET_TC_2_4 2 +#define XRFDC_SIGDET_TC_2_8 3 +#define XRFDC_SIGDET_TC_2_12 4 +#define XRFDC_SIGDET_TC_2_14 5 +#define XRFDC_SIGDET_TC_2_16 6 +#define XRFDC_SIGDET_TC_2_18 7 + +#define XRFDC_DISABLED 0 +#define XRFDC_ENABLED 1 + +#define XRFDC_CAL_BLOCK_OCB1 0 +#define XRFDC_CAL_BLOCK_OCB2 1 +#define XRFDC_CAL_BLOCK_GCB 2 +#define XRFDC_CAL_BLOCK_TSCB 3 + +#define XRFDC_INV_SYNC_MODE_MAX 2 + +#define XRFDC_INV_SYNC_EN_MAX 1 + + +#define XRFDC_CTRL_MASK 0x4800 +#define XRFDC_EXPORTCTRL_CLKDIST 0x4000 +#define XRFDC_PREMIUMCTRL_CLKDIST 0x0800 + +#define XRFDC_DAC_MODE_7G_NQ1 0U +#define XRFDC_DAC_MODE_7G_NQ2 1U +#define XRFDC_DAC_MODE_10G_IMR 2U +#define XRFDC_DAC_MODE_10G_BYPASS 3U +#define XRFDC_DAC_MODE_MAX XRFDC_DAC_MODE_10G_BYPASS + +#define XRFDC_DAC_IMR_MODE_LOWPASS 0U +#define XRFDC_DAC_IMR_MODE_HIGHPASS 1U +#define XRFDC_DAC_IMR_MODE_MAX XRFDC_DAC_IMR_MODE_HIGHPASS + +#define XRFDC_CLOCK_DETECT_CLK 0x1U +#define XRFDC_CLOCK_DETECT_DIST 0x2U +#define XRFDC_CLOCK_DETECT_BOTH 0x3U + +#define XRFDC_CAL_UNFREEZE_CALIB 0U +#define XRFDC_CAL_FREEZE_CALIB 1U +#define XRFDC_CAL_FRZ_PIN_ENABLE 0U +#define XRFDC_CAL_FRZ_PIN_DISABLE 1U +/*****************************************************************************/ +/** +* +* Execute Read modify Write +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param BaseAddr is address of a block. +* @param RegAddr is register offset value. +* @param Mask contains bit mask value. +* @param Data contains value to be written to register. +* +* @return +* - None +* +******************************************************************************/ +static inline void XRFdc_ClrSetReg(XRFdc *InstancePtr, u32 BaseAddr, + u32 RegAddr, u16 Mask, u16 Data) +{ + u16 ReadReg; + + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, RegAddr); + ReadReg = (ReadReg & ~Mask) | (Data & Mask); + XRFdc_WriteReg16(InstancePtr, BaseAddr, RegAddr, ReadReg); +} + +/*****************************************************************************/ +/** +* +* Execute Read and clear +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param BaseAddr is address of a block. +* @param RegAddr is register offset value. +* @param Mask contains bit mask value. +* +* @return +* - None +* +******************************************************************************/ +static inline void XRFdc_ClrReg(XRFdc *InstancePtr, u32 BaseAddr, + u32 RegAddr, u16 Mask) +{ + u16 ReadReg; + + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, RegAddr); + ReadReg &= ~Mask; + XRFdc_WriteReg16(InstancePtr, BaseAddr, RegAddr, ReadReg); +} + +/*****************************************************************************/ +/** +* +* Execute Read and mask with the value +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param BaseAddr is address of a block. +* @param RegAddr is register offset value. +* @param Mask contains bit mask value. +* +* @return +* - None +* +******************************************************************************/ +static inline u16 XRFdc_RDReg(XRFdc *InstancePtr, u32 BaseAddr, + u32 RegAddr, u16 Mask) +{ + u16 ReadReg; + + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, RegAddr); + ReadReg &= Mask; + + return ReadReg; +} + +/*****************************************************************************/ +/** +* +* Checks whether DAC block is available or not. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* +* @return +* - Return 1 if DAC block is available, otherwise 0. +* +******************************************************************************/ +static inline u32 XRFdc_IsDACBlockEnabled(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id) +{ + return InstancePtr->RFdc_Config.DACTile_Config[Tile_Id]. + DACBlock_Analog_Config[Block_Id].BlockAvailable; +} + +/*****************************************************************************/ +/** +* +* Checks whether ADC block is available or not. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3 in DAC/ADC-2GSPS and 0-1 in ADC-4GSPS. +* +* @return +* - Return 1 if ADC block is available, otherwise 0. +* +******************************************************************************/ +static inline u32 XRFdc_IsADCBlockEnabled(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id) +{ + u32 IsBlockAvail; + + if (InstancePtr->RFdc_Config.ADCType == XRFDC_ADC_4GSPS) { + if ((Block_Id == 2U) || (Block_Id == 3U)) { + IsBlockAvail = 0; + goto RETURN_PATH; + } + if (Block_Id == 1U) { + Block_Id = 2U; + } + } + IsBlockAvail = InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id]. + ADCBlock_Analog_Config[Block_Id].BlockAvailable; +RETURN_PATH: + return IsBlockAvail; +} + +/*****************************************************************************/ +/** +* +* Checks whether DAC Digital path is enabled or not. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* +* @return +* - Return 1 if DAC digital path is enabled, otherwise 0. +* +******************************************************************************/ +static inline u32 XRFdc_IsDACDigitalPathEnabled(XRFdc *InstancePtr, + u32 Tile_Id, u32 Block_Id) +{ + u32 Status; + + if (InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[Block_Id]. + Mixer_Settings.MixerType == XRFDC_MIXER_TYPE_OFF) { + Status = 0U; + } else { + Status = 1U; + } + + return Status; +} + +/*****************************************************************************/ +/** +* +* Checks whether ADC digital path is enabled or not. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3 in DAC/ADC-2GSPS and 0-1 in ADC-4GSPS. +* +* @return +* - Return 1 if ADC digital path is enabled, otherwise 0. +* +******************************************************************************/ +static inline u32 XRFdc_IsADCDigitalPathEnabled(XRFdc *InstancePtr, + u32 Tile_Id, u32 Block_Id) +{ + u32 IsBlockAvail; + + if (InstancePtr->RFdc_Config.ADCType == XRFDC_ADC_4GSPS) { + if ((Block_Id == 2U) || (Block_Id == 3U)) { + IsBlockAvail = 0; + goto RETURN_PATH; + } + if (Block_Id == 1U) { + Block_Id = 2U; + } + } + + if (InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[Block_Id]. + Mixer_Settings.MixerType == XRFDC_MIXER_TYPE_OFF) { + IsBlockAvail = 0; + } else { + IsBlockAvail = 1; + } + +RETURN_PATH: + return IsBlockAvail; +} + +/*****************************************************************************/ +/** +* +* Checks whether ADC/DAC Digital path is enabled or not. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* +* @return +* - XRFDC_SUCCESS if Digital path is enabled. +* - XRFDC_FAILURE if Digital path is not enabled. +* +******************************************************************************/ +static inline u32 XRFdc_CheckDigitalPathEnabled(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id, u32 Block_Id) +{ + u32 IsBlockAvail; + u32 Status; + + if ((Type != XRFDC_ADC_TILE) && (Type != XRFDC_DAC_TILE)) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if ((Tile_Id > XRFDC_TILE_ID_MAX) || (Block_Id > XRFDC_BLOCK_ID_MAX)) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if (Type == XRFDC_ADC_TILE) { + IsBlockAvail = XRFdc_IsADCDigitalPathEnabled(InstancePtr, Tile_Id, + Block_Id); + } else { + IsBlockAvail = XRFdc_IsDACDigitalPathEnabled(InstancePtr, Tile_Id, + Block_Id); + } + if (IsBlockAvail == 0U) { + Status = XRFDC_FAILURE; + } else { + Status = XRFDC_SUCCESS; + } +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Get IP BaseAddress. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* +* @return +* - Return IP BaseAddress. +* +******************************************************************************/ +static inline u32 XRFdc_Get_IPBaseAddr(XRFdc *InstancePtr) +{ + return (u32)InstancePtr->BaseAddr; +} + +/*****************************************************************************/ +/** +* +* Get Tile BaseAddress +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* +* @return +* - Return Tile BaseAddress. +* +******************************************************************************/ +static inline u32 XRFdc_Get_TileBaseAddr(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id) +{ + u32 BaseAddr; + + if (Type == XRFDC_ADC_TILE) { + BaseAddr = InstancePtr->BaseAddr + XRFDC_ADC_TILE_DRP_ADDR(Tile_Id); + } else { + BaseAddr = InstancePtr->BaseAddr + XRFDC_DAC_TILE_DRP_ADDR(Tile_Id); + } + + return BaseAddr; +} + +/*****************************************************************************/ +/** +* +* Get Block BaseAddress +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3 in DAC/ADC-2GSPS and 0-1 in ADC-4GSPS. +* +* @return +* - Return Block BaseAddress. +* +******************************************************************************/ +static inline u32 XRFdc_Get_BlockBaseAddr(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id, u32 Block_Id) +{ + u32 BaseAddr; + + if (Type == XRFDC_ADC_TILE) { + if (InstancePtr->RFdc_Config.ADCType == XRFDC_ADC_4GSPS) { + if (Block_Id == 1U) { + Block_Id = 2U; + } + } + BaseAddr = InstancePtr->BaseAddr + XRFDC_ADC_TILE_DRP_ADDR(Tile_Id) + + XRFDC_BLOCK_ADDR_OFFSET(Block_Id); + } else { + BaseAddr = InstancePtr->BaseAddr + XRFDC_DAC_TILE_DRP_ADDR(Tile_Id) + + XRFDC_BLOCK_ADDR_OFFSET(Block_Id); + } + + return BaseAddr; +} + +/*****************************************************************************/ +/** +* +* Get Number of DAC Blocks enabled. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* +* @return +* - Return number of DAC blocks enabled. +* +******************************************************************************/ +static inline u32 XRFdc_GetNoOfDACBlock(XRFdc *InstancePtr, u32 Tile_Id) +{ + return InstancePtr->DAC_Tile[Tile_Id].NumOfDACBlocks; +} + +/*****************************************************************************/ +/** +* +* Get Number of ADC Blocks enabled. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* +* @return +* - Return number of ADC blocks enabled. +* +******************************************************************************/ +static inline u32 XRFdc_GetNoOfADCBlocks(XRFdc *InstancePtr, u32 Tile_Id) +{ + return InstancePtr->ADC_Tile[Tile_Id].NumOfADCBlocks; +} + +/*****************************************************************************/ +/** +* +* Get ADC type is High Speed or Medium Speed. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* +* @return +* - Return 1 if ADC type is 4GSPS, otherwise 0. +* +******************************************************************************/ + +static inline u32 XRFdc_IsHighSpeedADC(XRFdc *InstancePtr, int Tile) +{ + if (InstancePtr->RFdc_Config.ADCTile_Config[Tile].NumSlices == 0) { + return InstancePtr->ADC4GSPS; + } else { + return (InstancePtr->RFdc_Config.ADCTile_Config[Tile].NumSlices == XRFDC_NUM_SLICES_HSADC); + } +} + +/*****************************************************************************/ +/** +* +* Get Mixer Input Data Type for ADC/DAC block. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* +* @return +* - Return MixerInputDataType of ADC/DAC block. +* +******************************************************************************/ +static inline u32 XRFdc_GetDataType(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id) +{ + u32 MixerInputDataType; + + if (Type == XRFDC_ADC_TILE) { + MixerInputDataType = InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id]. + ADCBlock_Digital_Config[Block_Id].MixerInputDataType; + } else { + MixerInputDataType = InstancePtr->RFdc_Config.DACTile_Config[Tile_Id]. + DACBlock_Digital_Config[Block_Id].MixerInputDataType; + } + + return MixerInputDataType; +} + +/*****************************************************************************/ +/** +* +* Get Data Width for ADC/DAC block. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* +* @return +* - Return DataWidth of ADC/DAC block. +* +******************************************************************************/ +static inline u32 XRFdc_GetDataWidth(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id) +{ + u32 DataWidth; + + if (Type == XRFDC_ADC_TILE) { + DataWidth = InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id]. + ADCBlock_Digital_Config[Block_Id].DataWidth; + } else { + DataWidth = InstancePtr->RFdc_Config.DACTile_Config[Tile_Id]. + DACBlock_Digital_Config[Block_Id].DataWidth; + } + + return DataWidth; +} + +/*****************************************************************************/ +/** +* +* Get Inversesync filter for DAC block. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* +* @return +* - Return Inversesync filter for DAC block +* +******************************************************************************/ +static inline u32 XRFdc_GetInverseSincFilter(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id) +{ + return InstancePtr->RFdc_Config.DACTile_Config[Tile_Id]. + DACBlock_Analog_Config[Block_Id].InvSyncEnable; +} + +/*****************************************************************************/ +/** +* +* Get Mixed mode for DAC block. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* +* @return +* - Return mixed mode for DAC block +* +******************************************************************************/ +static inline u32 XRFdc_GetMixedMode(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id) +{ + return InstancePtr->DAC_Tile[Tile_Id]. + DACBlock_Analog_Datapath[Block_Id].MixedMode; +} + +/*****************************************************************************/ +/** +* +* Get Master Tile for ADC/DAC tiles. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* +* @return +* - Return Master Tile for ADC/DAC tiles +* +******************************************************************************/ +static inline u32 XRFdc_GetMasterTile(XRFdc *InstancePtr, u32 Type) +{ + u32 MasterTile; + + if (Type == XRFDC_ADC_TILE) { + MasterTile = InstancePtr->RFdc_Config.MasterADCTile; + } else { + MasterTile = InstancePtr->RFdc_Config.MasterDACTile; + } + + return MasterTile; +} + +/*****************************************************************************/ +/** +* +* Get Sysref source for ADC/DAC tile. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* +* @return +* - Return Sysref source for ADC/DAC tile +* +******************************************************************************/ +static inline u32 XRFdc_GetSysRefSource(XRFdc *InstancePtr, u32 Type) +{ + u32 SysRefSource; + + if (Type == XRFDC_ADC_TILE) { + SysRefSource = InstancePtr->RFdc_Config.ADCSysRefSource; + } else { + SysRefSource = InstancePtr->RFdc_Config.DACSysRefSource; + } + + return SysRefSource; +} + +/*****************************************************************************/ +/** +* +* Get Fabric Clock frequency. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* +* @return +* - Return Fabric Clock frequency for ADC/DAC tile +* +******************************************************************************/ +static inline double XRFdc_GetFabClkFreq(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id) +{ + double FabClkFreq; + + if (Type == XRFDC_ADC_TILE) { + FabClkFreq = InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id].FabClkFreq; + } else { + FabClkFreq = InstancePtr->RFdc_Config.DACTile_Config[Tile_Id].FabClkFreq; + } + + return FabClkFreq; +} + +/*****************************************************************************/ +/** +* +* Get whether FIFO is enabled or not. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* +* @return +* - Return 1 if FIFO is enabled, otherwise 0. +* +******************************************************************************/ +static inline u32 XRFdc_IsFifoEnabled(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id) +{ + u32 FifoEnable; + + if (Type == XRFDC_ADC_TILE) { + FifoEnable = InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id]. + ADCBlock_Digital_Config[Block_Id].FifoEnable; + } else { + FifoEnable = InstancePtr->RFdc_Config.DACTile_Config[Tile_Id]. + DACBlock_Digital_Config[Block_Id].FifoEnable; + } + + return FifoEnable; +} + +/*****************************************************************************/ +/** +* +* Get Data Converter connected for digital data path I +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is Digital Data Path number. +* +* @return +* - Return Data converter Id. +* +******************************************************************************/ +static inline int XRFdc_GetConnectedIData(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id, u32 Block_Id) +{ + int ConnectedIData; + + if (Type == XRFDC_ADC_TILE) { + ConnectedIData = InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Block_Id].ConnectedIData; + } else { + ConnectedIData = InstancePtr->DAC_Tile[Tile_Id]. + DACBlock_Digital_Datapath[Block_Id].ConnectedIData; + } + + return ConnectedIData; +} + +/*****************************************************************************/ +/** +* +* Get Data Converter connected for digital data path Q +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is Digital Data Path number. +* +* @return +* - Return Data converter Id. +* +******************************************************************************/ +static inline int XRFdc_GetConnectedQData(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id, u32 Block_Id) +{ + int ConnectedQData; + + if (Type == XRFDC_ADC_TILE) { + ConnectedQData = InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Block_Id].ConnectedQData; + } else { + ConnectedQData = InstancePtr->DAC_Tile[Tile_Id]. + DACBlock_Digital_Datapath[Block_Id].ConnectedQData; + } + + return ConnectedQData; +} + +/*****************************************************************************/ +/** +* +* Set Data Converter connected for digital data path I and Q +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is Digital Data Path number. +* @param ConnectedIData is Converter Id to which DigitalPathI connected. +* @param ConnectedQData is Converter Id to which DigitalPathQ connected. +* +* @return +* - None. +* +******************************************************************************/ +static inline void XRFdc_SetConnectedIQData(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id, u32 Block_Id, int ConnectedIData, int ConnectedQData) +{ + if (Type == XRFDC_ADC_TILE) { + InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Block_Id].ConnectedIData = ConnectedIData; + InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Block_Id].ConnectedQData = ConnectedQData; + } else { + InstancePtr->DAC_Tile[Tile_Id]. + DACBlock_Digital_Datapath[Block_Id].ConnectedIData = ConnectedIData; + InstancePtr->DAC_Tile[Tile_Id]. + DACBlock_Digital_Datapath[Block_Id].ConnectedQData = ConnectedQData; + } +} + +/*****************************************************************************/ +/** +* +* Get Multiband Config data +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* +* @return +* - Return Multiband Configuration. +* +******************************************************************************/ +static inline u32 XRFdc_GetMultibandConfig(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id) +{ + u32 MultibandConfig; + + if (Type == XRFDC_ADC_TILE) { + MultibandConfig = InstancePtr->ADC_Tile[Tile_Id].MultibandConfig; + } else { + MultibandConfig = InstancePtr->DAC_Tile[Tile_Id].MultibandConfig; + } + + return MultibandConfig; +} + +/*****************************************************************************/ +/** +* +* Checks whether ADC/DAC block is enabled or not. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* +* @return +* - XRFDC_SUCCESS if block enabled. +* - XRFDC_FAILURE if Block not enabled. +* +******************************************************************************/ +static inline u32 XRFdc_CheckBlockEnabled(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id, u32 Block_Id) +{ + u32 IsBlockAvail; + u32 Status; + + if ((Type != XRFDC_ADC_TILE) && (Type != XRFDC_DAC_TILE)) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if ((Tile_Id > XRFDC_TILE_ID_MAX) || (Block_Id > XRFDC_BLOCK_ID_MAX)) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if (Type == XRFDC_ADC_TILE) { + IsBlockAvail = XRFdc_IsADCBlockEnabled(InstancePtr, Tile_Id, Block_Id); + } else { + IsBlockAvail = XRFdc_IsDACBlockEnabled(InstancePtr, Tile_Id, Block_Id); + } + if (IsBlockAvail == 0U) { + Status = XRFDC_FAILURE; + } else { + Status = XRFDC_SUCCESS; + } +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Checks whether ADC/DAC tile is enabled or not. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC. +* @param Tile_Id Valid values are 0-3. +* +* @return +* - XRFDC_SUCCESS if tile enabled. +* - XRFDC_FAILURE if tile not enabled. +* +******************************************************************************/ +static inline u32 XRFdc_CheckTileEnabled(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id) +{ + u32 IsTileAvail; + u32 Status; + + if ((Type != XRFDC_ADC_TILE) && (Type != XRFDC_DAC_TILE)) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if (Tile_Id > XRFDC_TILE_ID_MAX) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if (Type == XRFDC_ADC_TILE) { + IsTileAvail = InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id].Enable; + } else { + IsTileAvail = InstancePtr->RFdc_Config.DACTile_Config[Tile_Id].Enable; + } + if (IsTileAvail == 0U) { + Status = XRFDC_FAILURE; + } else { + Status = XRFDC_SUCCESS; + } +RETURN_PATH: + return Status; +} +/*****************************************************************************/ +/** +* +* Gets ADC/DAC tile maximum sampling rate. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC. +* @param Tile_Id Valid values are 0-3. +* @param MaxSampleRatePtr pointer for maximum sample rate. +* +* @return +* - XRFDC_SUCCESS if found sampling rate. +* - XRFDC_FAILURE if could not find sampling rate. +* +******************************************************************************/ +static inline u32 XRFdc_GetMaxSampleRate(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id, double *MaxSampleRatePtr) +{ + u32 Status; + + if ((Type != XRFDC_ADC_TILE) && (Type != XRFDC_DAC_TILE)) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if (Tile_Id > XRFDC_TILE_ID_MAX) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if (Type == XRFDC_ADC_TILE) { + *MaxSampleRatePtr = InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id].MaxSampleRate*1000; + if (*MaxSampleRatePtr == 0) { + *MaxSampleRatePtr = XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id)?XRFDC_ADC_4G_SAMPLING_MAX:XRFDC_ADC_2G_SAMPLING_MAX; + } + } else { + *MaxSampleRatePtr = InstancePtr->RFdc_Config.DACTile_Config[Tile_Id].MaxSampleRate*1000; + if (*MaxSampleRatePtr == 0) { + *MaxSampleRatePtr = XRFDC_DAC_SAMPLING_MAX; + } + } + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/*****************************************************************************/ +/** +* +* Gets ADC/DAC tile minimum sampling rate. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC. +* @param Tile_Id Valid values are 0-3. +* @param MinSampleRatePtr pointer for minimum sample rate. +* +* @return +* - XRFDC_SUCCESS if found sampling rate. +* - XRFDC_FAILURE if could not find sampling rate. +* +******************************************************************************/ +static inline u32 XRFdc_GetMinSampleRate(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id, double *MinSampleRatePtr) +{ + u32 Status; + + if ((Type != XRFDC_ADC_TILE) && (Type != XRFDC_DAC_TILE)) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if (Tile_Id > XRFDC_TILE_ID_MAX) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if (Type == XRFDC_ADC_TILE) { + *MinSampleRatePtr = XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id)?XRFDC_ADC_4G_SAMPLING_MIN:XRFDC_ADC_2G_SAMPLING_MIN; + } else { + *MinSampleRatePtr = XRFDC_DAC_SAMPLING_MIN; + } + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/*****************************************************************************/ +/** +* +* This API is used to get the driver version. +* +* @param None +* +* @return +* Driver version number +* +* @note None +* +******************************************************************************/ +static inline double XRFdc_GetDriverVersion(void) +{ + return 6.0; +} + +/************************** Function Prototypes ******************************/ + +XRFdc_Config *XRFdc_LookupConfig(u16 DeviceId); +u32 XRFdc_CfgInitialize(XRFdc *InstancePtr, XRFdc_Config *ConfigPtr); +u32 XRFdc_StartUp(XRFdc *InstancePtr, u32 Type, int Tile_Id); +u32 XRFdc_Shutdown(XRFdc *InstancePtr, u32 Type, int Tile_Id); +u32 XRFdc_Reset(XRFdc *InstancePtr, u32 Type, int Tile_Id); +u32 XRFdc_GetIPStatus(XRFdc *InstancePtr, XRFdc_IPStatus *IPStatusPtr); +u32 XRFdc_GetBlockStatus(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, XRFdc_BlockStatus *BlockStatusPtr); +u32 XRFdc_SetMixerSettings(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, XRFdc_Mixer_Settings *MixerSettingsPtr); +u32 XRFdc_GetMixerSettings(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id, u32 Block_Id, + XRFdc_Mixer_Settings *MixerSettingsPtr); +u32 XRFdc_SetQMCSettings(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, XRFdc_QMC_Settings *QMCSettingsPtr); +u32 XRFdc_GetQMCSettings(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, XRFdc_QMC_Settings *QMCSettingsPtr); +u32 XRFdc_GetCoarseDelaySettings(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id, u32 Block_Id, + XRFdc_CoarseDelay_Settings *CoarseDelaySettingsPtr); +u32 XRFdc_SetCoarseDelaySettings(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, XRFdc_CoarseDelay_Settings *CoarseDelaySettingsPtr); +u32 XRFdc_GetInterpolationFactor(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id, u32 *InterpolationFactorPtr); +u32 XRFdc_GetDecimationFactor(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id, u32 *DecimationFactorPtr); +u32 XRFdc_GetFabWrVldWords(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id, u32 Block_Id, u32 *FabricDataRatePtr); +u32 XRFdc_GetFabRdVldWords(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id, u32 Block_Id, u32 *FabricDataRatePtr); +u32 XRFdc_SetFabRdVldWords(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 FabricRdVldWords); +u32 XRFdc_SetFabWrVldWords(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 FabricWrVldWords); +u32 XRFdc_GetThresholdSettings(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id, XRFdc_Threshold_Settings *ThresholdSettingsPtr); +u32 XRFdc_SetThresholdSettings(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + XRFdc_Threshold_Settings *ThresholdSettingsPtr); +u32 XRFdc_SetDecoderMode(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 DecoderMode); +u32 XRFdc_UpdateEvent(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, u32 Block_Id, + u32 Event); +u32 XRFdc_GetDecoderMode(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 *DecoderModePtr); +u32 XRFdc_ResetNCOPhase(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id); +void XRFdc_DumpRegs(XRFdc *InstancePtr, u32 Type, int Tile_Id); +u32 XRFdc_MultiBand(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u8 DigitalDataPathMask, u32 MixerInOutDataType, u32 DataConverterMask); +u32 XRFdc_IntrHandler(u32 Vector, void *XRFdcPtr); +u32 XRFdc_IntrClr(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 IntrMask); +u32 XRFdc_GetIntrStatus(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 *IntrStsPtr); +u32 XRFdc_IntrDisable(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 IntrMask); +u32 XRFdc_IntrEnable(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 IntrMask); +u32 XRFdc_SetThresholdClrMode(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id, u32 ThresholdToUpdate, u32 ClrMode); +u32 XRFdc_ThresholdStickyClear(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id, u32 ThresholdToUpdate); +void XRFdc_SetStatusHandler(XRFdc *InstancePtr, void *CallBackRef, + XRFdc_StatusHandler FunctionPtr); +u32 XRFdc_SetupFIFO(XRFdc *InstancePtr, u32 Type, int Tile_Id, u8 Enable); +u32 XRFdc_GetFIFOStatus(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id, u8 *EnablePtr); +u32 XRFdc_SetNyquistZone(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 NyquistZone); +u32 XRFdc_GetNyquistZone(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 *NyquistZonePtr); +u32 XRFdc_GetOutputCurr(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id, u32 *OutputCurrPtr); +u32 XRFdc_SetDecimationFactor(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 DecimationFactor); +u32 XRFdc_SetInterpolationFactor(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 InterpolationFactor); +u32 XRFdc_SetFabClkOutDiv(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u16 FabClkDiv); +u32 XRFdc_SetCalibrationMode(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u8 CalibrationMode); +u32 XRFdc_GetCalibrationMode(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u8 *CalibrationModePtr); +u32 XRFdc_GetClockSource(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 *ClockSourcePtr); +u32 XRFdc_GetPLLLockStatus(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 *LockStatusPtr); +u32 XRFdc_GetPLLConfig(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id, XRFdc_PLL_Settings *PLLSettings); +u32 XRFdc_DynamicPLLConfig(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u8 Source, double RefClkFreq, double SamplingRate); +u32 XRFdc_SetInvSincFIR(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, u16 Mode); +u32 XRFdc_GetInvSincFIR(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u16 *ModePtr); +u32 XRFdc_GetLinkCoupling(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 *ModePtr); +u32 XRFdc_GetFabClkOutDiv(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u16 *FabClkDivPtr); +u32 XRFdc_SetDither(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, u32 Mode); +u32 XRFdc_GetDither(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, u32 *ModePtr); +u32 XRFdc_SetClkDistribution(XRFdc *InstancePtr, XRFdc_Distribution_Settings + *DistributionSettingsPtr); +u32 XRFdc_GetClkDistribution(XRFdc *InstancePtr, XRFdc_Distribution_Settings + *DistributionSettingsPtr); +u32 XRFdc_SetTileClkSettings(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + XRFdc_Tile_Clock_Settings *SettingsPtr); +u32 XRFdc_SetDataPathMode(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 Mode); +u32 XRFdc_GetDataPathMode(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 *ModePtr); +u32 XRFdc_SetIMRPassMode(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 Mode); +u32 XRFdc_GetIMRPassMode(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 *ModePtr); +u32 XRFdc_SetSignalDetector(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + XRFdc_Signal_Detector_Settings *SettingsPtr); +u32 XRFdc_GetSignalDetector(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + XRFdc_Signal_Detector_Settings *SettingsPtr); +u32 XRFdc_DisableCoefficientsOverride(XRFdc *InstancePtr, u32 Tile_Id, u32 + Block_Id, u32 CalibrationBlock); +u32 XRFdc_SetCalCoefficients(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 CalibrationBlock, XRFdc_Calibration_Coefficients *CoeffPtr); +u32 XRFdc_GetCalCoefficients(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 CalibrationBlock, XRFdc_Calibration_Coefficients *CoeffPtr); +u32 XRFdc_SetCalFreeze(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + XRFdc_Cal_Freeze_Settings *CalFreezePtr); +u32 XRFdc_GetCalFreeze(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + XRFdc_Cal_Freeze_Settings *CalFreezePtr); +#ifndef __BAREMETAL__ +s32 XRFdc_GetDeviceNameByDeviceId(char *DevNamePtr, u16 DevId); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* RFDC_H_ */ +/** @} */ diff --git a/mpm/include/mpm/rfdc/xrfdc_hw.h b/mpm/include/mpm/rfdc/xrfdc_hw.h new file mode 100644 index 000000000..f02bfdb2c --- /dev/null +++ b/mpm/include/mpm/rfdc/xrfdc_hw.h @@ -0,0 +1,2410 @@ +/****************************************************************************** +* +* Copyright (C) 2017-2019 Xilinx, Inc. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* XILINX BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Except as contained in this notice, the name of the Xilinx shall not be used +* in advertising or otherwise to promote the sale, use or other dealings in +* this Software without prior written authorization from Xilinx. +* +******************************************************************************/ +/*****************************************************************************/ +/** +* +* @file xrfdc_hw.h +* @addtogroup rfdc_v6_0 +* @{ +* +* This header file contains the identifiers and basic HW access driver +* functions (or macros) that can be used to access the device. Other driver +* functions are defined in xrfdc.h. +* +* <pre> +* MODIFICATION HISTORY: +* +* Ver Who Date Changes +* ----- --- -------- ----------------------------------------------- +* 1.0 sk 05/16/17 Initial release +* 2.1 sk 09/15/17 Remove Libmetal library dependency for MB. +* sk 09/21/17 Add support for Over voltage and Over +* Range interrupts. +* 2.3 sk 11/10/17 Corrected FIFO and DATA Interrupt masks. +* 2.4 sk 12/11/17 Added DDC and DUC support. +* 3.0 sg 13/01/18 Added PLL and external clock switch support +* 3.1 jm 01/24/18 Add Multi-tile sync support. +* sk 02/27/18 Add API's to configure Multiband. +* 4.0 sk 04/09/18 Removed redundant inclusion of xparameters.h file. +* 5.0 sk 08/03/18 Fixed MISRAC warnings. +* sk 08/24/18 Reorganize the code to improve readability and +* optimization. +* 5.1 cog 01/29/19 Added XRFdc_SetDither() and XRFdc_GetDither() APIs. +* 6.0 cog 02/17/19 New Interp/Decimation Mask. +* cog 02/17/19 Added new Inverse-Sinc mask. +* cog 02/17/19 Added new clock Distribution Defs. +* cog 02/17/19 Added new intratile clock Defs. +* cog 02/17/19 New Masks and offsets for XRFdc_GetPLLConfig() API. +* cog 02/17/19 New Masks and offsets for XRFdc_SetIMRPassMode() and +* XRFdc_SetIMRPassMode() APIs +* cog 02/17/19 New Masks and offsets for XRFdc_SetDACMode() and +* XRFdc_GetDACMode() APIs +* cog 02/17/19 New Masks and offsets for XRFdc_SetSignalDetector() and +* XRFdc_GetSignalDetector() APIs. +* cog 02/17/19 New Masks and offsets for XRFdc_DisableCoefficientsOverride(), +* XRFdc_SetCalCoefficients and XRFdc_GetCalCoefficients APIs. +* cog 02/19/19 New Masks and offsets for clock detection register. +* cog 02/20/19 New Masks for ADC common mode over/under voltage interrupts. +* cog 02/21/19 New Masks and offsets for XRFdc_SetCalFreeze() and +* XRFdc_GetCalFreeze() APIs. +* cog 03/25/19 The new common mode over/under voltage interrupts mask +* bits were clashing with other interrupt bits. +* cog 03/25/19 Added more calibration bypass masks. +* +*</pre> +* +******************************************************************************/ + +#ifndef RFDC_HW_H_ +#define RFDC_HW_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/***************************** Include Files *********************************/ + +#ifdef __BAREMETAL__ +#include "xil_io.h" +#endif +#include "metal/io.h" +/************************** Constant Definitions *****************************/ + +/** @name Register Map + * + * Register offsets from the base address of an RFDC ADC and DAC device. + * @{ + */ + +#define XRFDC_CLK_EN_OFFSET 0x000U /**< ADC Clock Enable + Register */ +#define XRFDC_ADC_DEBUG_RST_OFFSET 0x004U /**< ADC Debug Reset + Register */ +#define XRFDC_ADC_FABRIC_RATE_OFFSET 0x008U /**< ADC Fabric Rate + Register */ +#define XRFDC_ADC_FABRIC_OFFSET 0x00CU /**< ADC Fabric Register */ +#define XRFDC_ADC_FABRIC_ISR_OFFSET 0x010U /**< ADC Fabric ISR + Register */ +#define XRFDC_DAC_FABRIC_ISR_OFFSET 0x014U /**< DAC Fabric ISR + Register */ +#define XRFDC_ADC_FABRIC_IMR_OFFSET 0x014U /**< ADC Fabric IMR + Register */ +#define XRFDC_DAC_FABRIC_IMR_OFFSET 0x018U /**< DAC Fabric IMR + Register */ +#define XRFDC_ADC_FABRIC_DBG_OFFSET 0x018U /**< ADC Fabric Debug + Register */ +#define XRFDC_ADC_UPDATE_DYN_OFFSET 0x01CU /**< ADC Update Dynamic + Register */ +#define XRFDC_DAC_UPDATE_DYN_OFFSET 0x020U /**< DAC Update Dynamic + Register */ +#define XRFDC_ADC_FIFO_LTNC_CRL_OFFSET 0x020U /**< ADC FIFO Latency + Control Register */ +#define XRFDC_ADC_DEC_ISR_OFFSET 0x030U /**< ADC Decoder interface + ISR Register */ +#define XRFDC_DAC_DATAPATH_OFFSET 0x034U /**< ADC Decoder interface + IMR Register */ +#define XRFDC_ADC_DEC_IMR_OFFSET 0x034U /**< ADC Decoder interface + IMR Register */ +#define XRFDC_DATPATH_ISR_OFFSET 0x038U /**< ADC Data Path + ISR Register */ +#define XRFDC_DATPATH_IMR_OFFSET 0x03CU /**< ADC Data Path + IMR Register */ +#define XRFDC_ADC_DECI_CONFIG_OFFSET 0x040U /**< ADC Decimation + Config Register */ +#define XRFDC_DAC_INTERP_CTRL_OFFSET 0x040U /**< DAC Interpolation + Control Register */ +#define XRFDC_ADC_DECI_MODE_OFFSET 0x044U /**< ADC Decimation mode + Register */ +#define XRFDC_DAC_ITERP_DATA_OFFSET 0x044U /**< DAC interpolation data */ +#define XRFDC_ADC_MXR_CFG0_OFFSET 0x080U /**< ADC I channel mixer + config Register */ +#define XRFDC_ADC_MXR_CFG1_OFFSET 0x084U /**< ADC Q channel mixer + config Register */ +#define XRFDC_MXR_MODE_OFFSET 0x088U /**< ADC/DAC mixer mode + Register */ +#define XRFDC_NCO_UPDT_OFFSET 0x08CU /**< ADC/DAC NCO Update + mode Register */ +#define XRFDC_NCO_RST_OFFSET 0x090U /**< ADC/DAC NCO Phase + Reset Register */ +#define XRFDC_ADC_NCO_FQWD_UPP_OFFSET 0x094U /**< ADC NCO Frequency + Word[47:32] Register */ +#define XRFDC_ADC_NCO_FQWD_MID_OFFSET 0x098U /**< ADC NCO Frequency + Word[31:16] Register */ +#define XRFDC_ADC_NCO_FQWD_LOW_OFFSET 0x09CU /**< ADC NCO Frequency + Word[15:0] Register */ +#define XRFDC_NCO_PHASE_UPP_OFFSET 0x0A0U /**< ADC/DAC NCO Phase[17:16] + Register */ +#define XRFDC_NCO_PHASE_LOW_OFFSET 0x0A4U /**< ADC/DAC NCO Phase[15:0] + Register */ +#define XRFDC_ADC_NCO_PHASE_MOD_OFFSET 0x0A8U /**< ADC NCO Phase + Mode Register */ +#define XRFDC_QMC_UPDT_OFFSET 0x0C8U /**< ADC/DAC QMC Update Mode + Register */ +#define XRFDC_QMC_CFG_OFFSET 0x0CCU /**< ADC/DAC QMC Config + Register */ +#define XRFDC_QMC_OFF_OFFSET 0x0D0U /**< ADC/DAC QMC Offset + Correction Register */ +#define XRFDC_QMC_GAIN_OFFSET 0x0D4U /**< ADC/DAC QMC Gain + Correction Register */ +#define XRFDC_QMC_PHASE_OFFSET 0x0D8U /**< ADC/DAC QMC Phase + Correction Register */ +#define XRFDC_ADC_CRSE_DLY_UPDT_OFFSET 0x0DCU /**< ADC Coarse Delay + Update Register */ +#define XRFDC_DAC_CRSE_DLY_UPDT_OFFSET 0x0E0U /**< DAC Coarse Delay + Update Register */ +#define XRFDC_ADC_CRSE_DLY_CFG_OFFSET 0x0E0U /**< ADC Coarse delay + Config Register */ +#define XRFDC_DAC_CRSE_DLY_CFG_OFFSET 0x0DCU /**< DAC Coarse delay + Config Register */ +#define XRFDC_ADC_DAT_SCAL_CFG_OFFSET 0x0E4U /**< ADC Data Scaling + Config Register */ +#define XRFDC_ADC_SWITCH_MATRX_OFFSET 0x0E8U /**< ADC Switch Matrix + Config Register */ +#define XRFDC_ADC_TRSHD0_CFG_OFFSET 0x0ECU /**< ADC Threshold0 + Config Register */ +#define XRFDC_ADC_TRSHD0_AVG_UP_OFFSET 0x0F0U /**< ADC Threshold0 + Average[31:16] Register */ +#define XRFDC_ADC_TRSHD0_AVG_LO_OFFSET 0x0F4U /**< ADC Threshold0 + Average[15:0] Register */ +#define XRFDC_ADC_TRSHD0_UNDER_OFFSET 0x0F8U /**< ADC Threshold0 + Under Threshold Register */ +#define XRFDC_ADC_TRSHD0_OVER_OFFSET 0x0FCU /**< ADC Threshold0 + Over Threshold Register */ +#define XRFDC_ADC_TRSHD1_CFG_OFFSET 0x100U /**< ADC Threshold1 + Config Register */ +#define XRFDC_ADC_TRSHD1_AVG_UP_OFFSET 0x104U /**< ADC Threshold1 + Average[31:16] Register */ +#define XRFDC_ADC_TRSHD1_AVG_LO_OFFSET 0x108U /**< ADC Threshold1 + Average[15:0] Register */ +#define XRFDC_ADC_TRSHD1_UNDER_OFFSET 0x10CU /**< ADC Threshold1 + Under Threshold Register */ +#define XRFDC_ADC_TRSHD1_OVER_OFFSET 0x110U /**< ADC Threshold1 + Over Threshold Register */ +#define XRFDC_ADC_FEND_DAT_CRL_OFFSET 0x140U /**< ADC Front end + Data Control Register */ +#define XRFDC_ADC_TI_DCB_CRL0_OFFSET 0x144U /**< ADC Time Interleaved + digital correction block gain control0 Register */ +#define XRFDC_ADC_TI_DCB_CRL1_OFFSET 0x148U /**< ADC Time Interleaved + digital correction block gain control1 Register */ +#define XRFDC_ADC_TI_DCB_CRL2_OFFSET 0x14CU /**< ADC Time Interleaved + digital correction block gain control2 Register */ +#define XRFDC_ADC_TI_DCB_CRL3_OFFSET 0x150U /**< ADC Time Interleaved + digital correction block gain control3 Register */ +#define XRFDC_ADC_TI_TISK_CRL0_OFFSET 0x154U /**< ADC Time skew correction + control bits0 Register */ +#define XRFDC_DAC_MC_CFG0_OFFSET 0x1C4U /**< Static Configuration + data for DAC Analog */ +#define XRFDC_ADC_TI_TISK_CRL1_OFFSET 0x158U /**< ADC Time skew correction + control bits1 Register */ +#define XRFDC_ADC_TI_TISK_CRL2_OFFSET 0x15CU /**< ADC Time skew correction + control bits2 Register */ +#define XRFDC_ADC_TI_TISK_CRL3_OFFSET 0x160U /**< ADC Time skew correction + control bits3 Register */ +#define XRFDC_ADC_TI_TISK_CRL4_OFFSET 0x164U /**< ADC Time skew correction + control bits4 Register */ +#define XRFDC_ADC_TI_TISK_DAC0_OFFSET 0x168U /**< ADC Time skew DAC + cal code of subadc ch0 Register */ +#define XRFDC_ADC_TI_TISK_DAC1_OFFSET 0x16CU /**< ADC Time skew DAC + cal code of subadc ch1 Register */ +#define XRFDC_ADC_TI_TISK_DAC2_OFFSET 0x170U /**< ADC Time skew DAC + cal code of subadc ch2 Register */ +#define XRFDC_ADC_TI_TISK_DAC3_OFFSET 0x174U /**< ADC Time skew DAC + cal code of subadc ch3 Register */ +#define XRFDC_ADC_TI_TISK_DACP0_OFFSET 0x178U /**< ADC Time skew DAC + cal code of subadc ch0 Register */ +#define XRFDC_ADC_TI_TISK_DACP1_OFFSET 0x17CU /**< ADC Time skew DAC + cal code of subadc ch1 Register */ +#define XRFDC_ADC_TI_TISK_DACP2_OFFSET 0x180U /**< ADC Time skew DAC + cal code of subadc ch2 Register */ +#define XRFDC_ADC_TI_TISK_DACP3_OFFSET 0x184U /**< ADC Time skew DAC + cal code of subadc ch3 Register */ +#define XRFDC_ADC0_SUBDRP_ADDR_OFFSET 0x198U /**< subadc0, sub-drp address + of target Register */ +#define XRFDC_ADC0_SUBDRP_DAT_OFFSET 0x19CU /**< subadc0, sub-drp data + of target Register */ +#define XRFDC_ADC1_SUBDRP_ADDR_OFFSET 0x1A0U /**< subadc1, sub-drp address + of target Register */ +#define XRFDC_ADC1_SUBDRP_DAT_OFFSET 0x1A4U /**< subadc1, sub-drp data + of target Register */ +#define XRFDC_ADC2_SUBDRP_ADDR_OFFSET 0x1A8U /**< subadc2, sub-drp address + of target Register */ +#define XRFDC_ADC2_SUBDRP_DAT_OFFSET 0x1ACU /**< subadc2, sub-drp data + of target Register */ +#define XRFDC_ADC3_SUBDRP_ADDR_OFFSET 0x1B0U /**< subadc3, sub-drp address + of target Register */ +#define XRFDC_ADC3_SUBDRP_DAT_OFFSET 0x1B4U /**< subadc3, sub-drp data + of target Register */ +#define XRFDC_ADC_RX_MC_PWRDWN_OFFSET 0x1C0U /**< ADC Static configuration + bits for ADC(RX) analog Register */ +#define XRFDC_ADC_DAC_MC_CFG0_OFFSET 0x1C4U /**< ADC/DAC Static + configuration bits for ADC/DAC analog Register */ +#define XRFDC_ADC_DAC_MC_CFG1_OFFSET 0x1C8U /**< ADC/DAC Static + configuration bits for ADC/DAC analog Register */ +#define XRFDC_ADC_DAC_MC_CFG2_OFFSET 0x1CCU /**< ADC/DAC Static + configuration bits for ADC/DAC analog Register */ +#define XRFDC_DAC_MC_CFG3_OFFSET 0x1D0U /**< DAC Static + configuration bits for DAC analog Register */ +#define XRFDC_ADC_RXPR_MC_CFG0_OFFSET 0x1D0U /**< ADC RX Pair static + Configuration Register */ +#define XRFDC_ADC_RXPR_MC_CFG1_OFFSET 0x1D4U /**< ADC RX Pair static + Configuration Register */ +#define XRFDC_ADC_TI_DCBSTS0_BG_OFFSET 0x200U /**< ADC DCB Status0 + BG Register */ +#define XRFDC_ADC_TI_DCBSTS0_FG_OFFSET 0x204U /**< ADC DCB Status0 + FG Register */ +#define XRFDC_ADC_TI_DCBSTS1_BG_OFFSET 0x208U /**< ADC DCB Status1 + BG Register */ +#define XRFDC_ADC_TI_DCBSTS1_FG_OFFSET 0x20CU /**< ADC DCB Status1 + FG Register */ +#define XRFDC_ADC_TI_DCBSTS2_BG_OFFSET 0x210U /**< ADC DCB Status2 + BG Register */ +#define XRFDC_ADC_TI_DCBSTS2_FG_OFFSET 0x214U /**< ADC DCB Status2 + FG Register */ +#define XRFDC_ADC_TI_DCBSTS3_BG_OFFSET 0x218U /**< ADC DCB Status3 + BG Register */ +#define XRFDC_ADC_TI_DCBSTS3_FG_OFFSET 0x21CU /**< ADC DCB Status3 + FG Register */ +#define XRFDC_ADC_TI_DCBSTS4_MB_OFFSET 0x220U /**< ADC DCB Status4 + MSB Register */ +#define XRFDC_ADC_TI_DCBSTS4_LB_OFFSET 0x224U /**< ADC DCB Status4 + LSB Register */ +#define XRFDC_ADC_TI_DCBSTS5_MB_OFFSET 0x228U /**< ADC DCB Status5 + MSB Register */ +#define XRFDC_ADC_TI_DCBSTS5_LB_OFFSET 0x22CU /**< ADC DCB Status5 + LSB Register */ +#define XRFDC_ADC_TI_DCBSTS6_MB_OFFSET 0x230U /**< ADC DCB Status6 + MSB Register */ +#define XRFDC_ADC_TI_DCBSTS6_LB_OFFSET 0x234U /**< ADC DCB Status6 + LSB Register */ +#define XRFDC_ADC_TI_DCBSTS7_MB_OFFSET 0x238U /**< ADC DCB Status7 + MSB Register */ +#define XRFDC_ADC_TI_DCBSTS7_LB_OFFSET 0x23CU /**< ADC DCB Status7 + LSB Register */ +#define XRFDC_ADC_FIFO_LTNCY_LB_OFFSET 0x280U /**< ADC FIFO Latency + measurement LSB Register */ +#define XRFDC_ADC_FIFO_LTNCY_MB_OFFSET 0x284U /**< ADC FIFO Latency + measurement MSB Register */ +#define XRFDC_DAC_DECODER_CTRL_OFFSET 0x180U /**< DAC Unary Decoder/ + Randomizer settings */ +#define XRFDC_DAC_DECODER_CLK_OFFSET 0x184U /**< Decoder Clock enable */ + +#define XRFDC_ADC_SIG_DETECT_CTRL_OFFSET 0x114 /**< ADC Signal Detector Control */ +#define XRFDC_ADC_SIG_DETECT_THRESHOLD0_LEVEL_OFFSET 0x118 /**< ADC Signal Detector Theshold 0 */ +#define XRFDC_ADC_SIG_DETECT_THRESHOLD0_CNT_ON_OFFSET 0x11C /**< ADC Signal Detector Theshold 0 on Counter */ +#define XRFDC_ADC_SIG_DETECT_THRESHOLD0_CNT_OFF_OFFSET 0x120 /**< ADC Signal Detector Theshold 0 off Counter */ +#define XRFDC_ADC_SIG_DETECT_THRESHOLD1_LEVEL_OFFSET 0x124 /**< ADC Signal Detector Theshold 1 */ +#define XRFDC_ADC_SIG_DETECT_THRESHOLD1_CNT_ON_OFFSET 0x128 /**< ADC Signal Detector Theshold 1 on Counter */ +#define XRFDC_ADC_SIG_DETECT_THRESHOLD1_CNT_OFF_OFFSET 0x12C /**< ADC Signal Detector Theshold 1 off Counter */ +#define XRFDC_ADC_SIG_DETECT_MAGN_OFFSET 0x130 /**< ADC Signal Detector Magintude */ + + +#define XRFDC_HSCOM_CLK_DSTR_OFFSET 0x088U /**< Clock Distribution Register*/ +#define XRFDC_HSCOM_CLK_DSTR_MASK 0xC788U /**< Clock Distribution Register*/ +#define XRFDC_HSCOM_CLK_DSTR_MASK_ALT 0x1870U /**< Clock Distribution Register + for Intratile*/ +#define XRFDC_HSCOM_PWR_OFFSET 0x094 /**< Control register during + power-up sequence */ +#define XRFDC_HSCOM_CLK_DIV_OFFSET 0xB0 /**< Fabric clk out divider */ +#define XRFDC_HSCOM_PWR_STATE_OFFSET 0xB4 /**< Check powerup state */ +#define XRFDC_HSCOM_UPDT_DYN_OFFSET 0x0B8 /**< Trigger the update + dynamic event */ +#define XRFDC_HSCOM_EFUSE_2_OFFSET 0x144 +#define XRFDC_DAC_INVSINC_OFFSET 0x0C0U /**< Invsinc control */ +#define XRFDC_DAC_MB_CFG_OFFSET 0x0C4U /**< Multiband config */ +#define XRFDC_MTS_SRDIST 0x1CA0U +#define XRFDC_MTS_SRCAP_T1 (0x24U << 2U) +#define XRFDC_MTS_SRCAP_PLL (0x0CU << 2U) +#define XRFDC_MTS_SRCAP_DIG (0x2CU << 2U) +#define XRFDC_MTS_SRDTC_T1 (0x27U << 2U) +#define XRFDC_MTS_SRDTC_PLL (0x26U << 2U) +#define XRFDC_MTS_SRFLAG (0x49U << 2U) +#define XRFDC_MTS_CLKSTAT (0x24U << 2U) +#define XRFDC_MTS_SRCOUNT_CTRL 0x004CU +#define XRFDC_MTS_SRCOUNT_VAL 0x0050U +#define XRFDC_MTS_SRFREQ_VAL 0x0054U +#define XRFDC_MTS_FIFO_CTRL_ADC 0x0010U +#define XRFDC_MTS_FIFO_CTRL_DAC 0x0014U +#define XRFDC_MTS_DELAY_CTRL 0x0028U +#define XRFDC_MTS_ADC_MARKER 0x0018U +#define XRFDC_MTS_ADC_MARKER_CNT 0x0010U +#define XRFDC_MTS_DAC_MARKER_CTRL 0x0048U +#define XRFDC_MTS_DAC_MARKER_CNT (0x92U << 2U) +#define XRFDC_MTS_DAC_MARKER_LOC (0x93U << 2U) + +#define XRFDC_RESET_OFFSET 0x00U /**< Tile reset register */ +#define XRFDC_RESTART_OFFSET 0x04U /**< Tile restart register */ +#define XRFDC_RESTART_STATE_OFFSET 0x08U /**< Tile restart state register */ +#define XRFDC_CURRENT_STATE_OFFSET 0x0CU /**< Current state register */ +#define XRFDC_CLOCK_DETECT_OFFSET 0x80U /**< Clock detect register */ +#define XRFDC_STATUS_OFFSET 0x228U /**< Common status register */ +#define XRFDC_COMMON_INTR_STS 0x100U /**< Common Intr Status register */ +#define XRFDC_COMMON_INTR_ENABLE 0x104U /**< Common Intr enable register */ +#define XRFDC_INTR_STS 0x200U /**< Intr status register */ +#define XRFDC_INTR_ENABLE 0x204U /**< Intr enable register */ +#define XRFDC_CONV_INTR_STS(X) (0x208U + (X * 0x08U)) +#define XRFDC_CONV_INTR_EN(X) (0x20CU + (X * 0x08U)) +#define XRFDC_CONV_CAL_STGS(X) (0x234U + (X * 0x04U)) +#define XRFDC_CAL_GCB_COEFF0_FAB(X) (0x280U + (X * 0x10U)) +#define XRFDC_CAL_GCB_COEFF1_FAB(X) (0x284U + (X * 0x10U)) +#define XRFDC_CAL_GCB_COEFF2_FAB(X) (0x288U + (X * 0x10U)) +#define XRFDC_CAL_GCB_COEFF3_FAB(X) (0x28CU + (X * 0x10U)) +#define XRFDC_PLL_FREQ 0x300U /**< PLL output frequency (before divider) register */ +#define XRFDC_PLL_FS 0x304U /**< Sampling rate register */ +#define XRFDC_FIFO_ENABLE 0x230U /**< FIFO Enable and Disable */ +#define XRFDC_PLL_SDM_CFG0 0x00U /**< PLL Configuration bits for sdm */ +#define XRFDC_PLL_SDM_SEED0 0x18U /**< PLL Bits for sdm LSB */ +#define XRFDC_PLL_SDM_SEED1 0x1CU /**< PLL Bits for sdm MSB */ +#define XRFDC_PLL_VREG 0x44U /**< PLL bits for voltage regulator */ +#define XRFDC_PLL_VCO0 0x54U /**< PLL bits for coltage controlled oscillator LSB */ +#define XRFDC_PLL_VCO1 0x58U /**< PLL bits for coltage controlled oscillator MSB */ +#define XRFDC_PLL_CRS1 0x28U /**< PLL bits for coarse frequency control LSB */ +#define XRFDC_PLL_CRS2 0x2CU /**< PLL bits for coarse frequency control MSB */ +#define XRFDC_PLL_DIVIDER0 0x30U /**< PLL Output Divider LSB register */ +#define XRFDC_PLL_DIVIDER1 0x34U /**< PLL Output Divider MSB register */ +#define XRFDC_PLL_SPARE0 0x38U /**< PLL spare inputs LSB */ +#define XRFDC_PLL_SPARE1 0x3CU /**< PLL spare inputs MSB */ +#define XRFDC_PLL_REFDIV 0x40U /**< PLL Reference Divider register */ +#define XRFDC_PLL_VREG 0x44U /**< PLL voltage regulator */ +#define XRFDC_PLL_CHARGEPUMP 0x48U /**< PLL bits for charge pumps */ +#define XRFDC_PLL_LPF0 0x4CU /**< PLL bits for loop filters LSB */ +#define XRFDC_PLL_LPF1 0x50U /**< PLL bits for loop filters MSB */ +#define XRFDC_PLL_FPDIV 0x5CU /**< PLL Feedback Divider register */ +#define XRFDC_CLK_NETWORK_CTRL0 0x8CU /**< Clock network control and trim register */ +#define XRFDC_CLK_NETWORK_CTRL1 0x90U /**< Multi-tile sync and clock source control register */ + +#define XRFDC_HSCOM_NETWORK_CTRL1_MASK 0x02FU /**< Clock Network Register Mask for IntraTile*/ +#define XRFDC_PLL_REFDIV_MASK 0x0E0U /**< PLL Reference Divider Register Mask for IntraTile */ +#define XRFDC_PLL_DIVIDER0_ALT_MASK 0x800U /**< PLL Output Divider Register Mask for IntraTile */ + +#define XRFDC_CAL_OCB1_OFFSET_COEFF0 0x200 /**< Foreground offset correction block */ +#define XRFDC_CAL_OCB1_OFFSET_COEFF1 0x208 /**< Foreground offset correction block */ +#define XRFDC_CAL_OCB1_OFFSET_COEFF2 0x210 /**< Foreground offset correction block */ +#define XRFDC_CAL_OCB1_OFFSET_COEFF3 0x218 /**< Foreground offset correction block */ +#define XRFDC_CAL_OCB2_OFFSET_COEFF0 0x204 /**< Background offset correction block */ +#define XRFDC_CAL_OCB2_OFFSET_COEFF1 0x20C /**< Background offset correction block */ +#define XRFDC_CAL_OCB2_OFFSET_COEFF2 0x214 /**< Background offset correction block */ +#define XRFDC_CAL_OCB2_OFFSET_COEFF3 0x21C /**< Background offset correction block */ +#define XRFDC_CAL_GCB_OFFSET_COEFF0 0x220 /**< Background gain correction block */ +#define XRFDC_CAL_GCB_OFFSET_COEFF1 0x224 /**< Background gain correction block */ +#define XRFDC_CAL_GCB_OFFSET_COEFF2 0x228 /**< Background gain correction block */ +#define XRFDC_CAL_GCB_OFFSET_COEFF3 0x22C /**< Background gain correction block */ +#define XRFDC_CAL_GCB_OFFSET_COEFF0_ALT 0x220 /**< Background gain correction block (below Gen 3) */ +#define XRFDC_CAL_GCB_OFFSET_COEFF1_ALT 0x228 /**< Background gain correction block (below Gen 3) */ +#define XRFDC_CAL_GCB_OFFSET_COEFF2_ALT 0x230 /**< Background gain correction block (below Gen 3) */ +#define XRFDC_CAL_GCB_OFFSET_COEFF3_ALT 0x238 /**< Background gain correction block (below Gen 3) */ +#define XRFDC_CAL_TSCB_OFFSET_COEFF0 0x170 /**< Background time skew correction block */ +#define XRFDC_CAL_TSCB_OFFSET_COEFF1 0x174 /**< Background time skew correction block */ +#define XRFDC_CAL_TSCB_OFFSET_COEFF2 0x178 /**< Background time skew correction block */ +#define XRFDC_CAL_TSCB_OFFSET_COEFF3 0x17C /**< Background time skew correction block */ +#define XRFDC_CAL_TSCB_OFFSET_COEFF4 0x180 /**< Background time skew correction block */ +#define XRFDC_CAL_TSCB_OFFSET_COEFF5 0x184 /**< Background time skew correction block */ +#define XRFDC_CAL_TSCB_OFFSET_COEFF6 0x188 /**< Background time skew correction block */ +#define XRFDC_CAL_TSCB_OFFSET_COEFF7 0x18C /**< Background time skew correction block */ +#define XRFDC_CAL_TSCB_OFFSET_COEFF0_ALT 0x168 /**< Background time skew correction block (below Gen 3) */ +#define XRFDC_CAL_TSCB_OFFSET_COEFF1_ALT 0x16C /**< Background time skew correction block (below Gen 3) */ +#define XRFDC_CAL_TSCB_OFFSET_COEFF2_ALT 0x170 /**< Background time skew correction block (below Gen 3) */ +#define XRFDC_CAL_TSCB_OFFSET_COEFF3_ALT 0x174 /**< Background time skew correction block (below Gen 3) */ +#define XRFDC_CAL_TSCB_OFFSET_COEFF4_ALT 0x178 /**< Background time skew correction block (below Gen 3) */ +#define XRFDC_CAL_TSCB_OFFSET_COEFF5_ALT 0x17C /**< Background time skew correction block (below Gen 3) */ +#define XRFDC_CAL_TSCB_OFFSET_COEFF6_ALT 0x180 /**< Background time skew correction block (below Gen 3) */ +#define XRFDC_CAL_TSCB_OFFSET_COEFF7_ALT 0x184 /**< Background time skew correction block (below Gen 3) */ + + +/* @} */ + +/** @name Calibration Coefficients - Calibration coefficients and disable registers + * + * This register contains bits for calibration coefficients + * for ADC. + * @{ + */ + +#define XRFDC_CAL_OCB_MASK 0xFFFFU /**< offsets coeff mask*/ +#define XRFDC_CAL_GCB_MASK 0x0FFFU /**< gain coeff mask*/ +#define XRFDC_CAL_GCB_FAB_MASK 0xFFF0U /**< gain coeff mask for IP Gen 2 or below*/ +#define XRFDC_CAL_TSCB_MASK 0x01FFU /**< time skew coeff mask*/ + +#define XRFDC_CAL_GCB_FLSH_MASK 0x1000U /**< GCB accumulator flush mask*/ +#define XRFDC_CAL_GCB_ACEN_MASK 0x0800U /**< GCB accumulator enable mask*/ +#define XRFDC_CAL_GCB_ENFL_MASK 0x1800U /**< GCB accumulator enable mask*/ + +#define XRFDC_CAL_OCB_EN_MASK 0x0001U /**< offsets coeff override enable mask*/ +#define XRFDC_CAL_GCB_EN_MASK 0x0080U /**< gain coeff override enable mask*/ +#define XRFDC_CAL_TSCB_EN_MASK 0x8000U /**< time skew coeff override enable mask*/ + +#define XRFDC_CAL_OCB_EN_SHIFT 0U /**< offsets coeff shift*/ +#define XRFDC_CAL_GCB_EN_SHIFT 7U /**< gain coeff shift*/ +#define XRFDC_CAL_TSCB_EN_SHIFT 15U /**< time skew coeff shift*/ +#define XRFDC_CAL_GCB_FLSH_SHIFT 12U /**< GCB accumulator flush shift*/ +#define XRFDC_CAL_GCB_ACEN_SHIFT 11U /**< GCB accumulator enable shift*/ + +#define XRFDC_CAL_SLICE_SHIFT 16U /**<Coefficient shift for HSADCs*/ + +/* @} */ +/** @name Calibration Coefficients - Calibration coefficients and disable registers + * + * This register contains bits for calibration coefficients + * for ADC. + * @{ + */ + +#define XRFDC_CAL_FREEZE_CAL_MASK 0x1U /**< Calibration freeze enable mask*/ +#define XRFDC_CAL_FREEZE_STS_MASK 0x2U /**< Calibration freeze status mask*/ +#define XRFDC_CAL_FREEZE_PIN_MASK 0x4U /**< Calibration freeze pin disable mask*/ + +#define XRFDC_CAL_FREEZE_CAL_SHIFT 0U /**< Calibration freeze enable shift*/ +#define XRFDC_CAL_FREEZE_STS_SHIFT 1U /**< Calibration freeze status shift*/ +#define XRFDC_CAL_FREEZE_PIN_SHIFT 2U /**< Calibration freeze pin disable shift*/ + +/* @} */ + +/** @name FIFO Enable - FIFO enable and disable register + * + * This register contains bits for FIFO enable and disable + * for ADC and DAC. + * @{ + */ + +#define XRFDC_FIFO_EN_MASK 0x00000001U /**< FIFO enable/disable */ +#define XRFDC_RESTART_MASK 0x00000001U /**< Restart bit mask */ + +/* @} */ + +/** @name Clock Enable - FIFO Latency, fabric, DataPath, + * full-rate, output register + * + * This register contains bits for various clock enable options of + * the ADC. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_CLK_EN_CAL_MASK 0x00000001U /**< Enable Output + Register clock */ +#define XRFDC_CLK_EN_DIG_MASK 0x00000002U /**< Enable full-rate clock */ +#define XRFDC_CLK_EN_DP_MASK 0x00000004U /**< Enable Data Path clock */ +#define XRFDC_CLK_EN_FAB_MASK 0x00000008U /**< Enable fabric clock */ +#define XRFDC_DAT_CLK_EN_MASK 0x0000000FU /**< Data Path Clk enable */ +#define XRFDC_CLK_EN_LM_MASK 0x00000010U /**< Enable for FIFO + Latency measurement clock */ + +/* @} */ + +/** @name Debug reset - FIFO Latency, fabric, DataPath, + * full-rate, output register + * + * This register contains bits for various Debug reset options of + * the ADC. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_DBG_RST_CAL_MASK 0x00000001U /**< Reset clk_cal + clock domain */ +#define XRFDC_DBG_RST_DP_MASK 0x00000002U /**< Reset data path + clock domain */ +#define XRFDC_DBG_RST_FAB_MASK 0x00000004U /**< Reset clock fabric + clock domain */ +#define XRFDC_DBG_RST_DIG_MASK 0x00000008U /**< Reset clk_dig clock + domain */ +#define XRFDC_DBG_RST_DRP_CAL_MASK 0x00000010U /**< Reset subadc-drp + register on clock cal */ +#define XRFDC_DBG_RST_LM_MASK 0x00000020U /**< Reset FIFO Latency + measurement clock domain */ + +/* @} */ + +/** @name Fabric rate - Fabric data rate for read and write + * + * This register contains bits for read and write fabric data + * rate for ADC. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_ADC_FAB_RATE_WR_MASK 0x0000000FU /**< ADC FIFO Write Number + of Words per clock */ +#define XRFDC_DAC_FAB_RATE_WR_MASK 0x0000001FU /**< DAC FIFO Write Number + of Words per clock */ +#define XRFDC_FAB_RATE_RD_MASK 0x00000F00U /**< FIFO Read Number + of words per clock */ +#define XRFDC_FAB_RATE_RD_SHIFT 8U /**< Fabric Read shift */ + +/* @} */ + +/** @name Fabric Offset - FIFO de-skew + * + * This register contains bits of Fabric Offset. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_FAB_RD_PTR_OFFST_MASK 0x0000003FU /**< FIFO read pointer + offset for interface de-skew */ + +/* @} */ + +/** @name Fabric ISR - Interrupt status register for FIFO interface + * + * This register contains bits of margin-indicator and user-data overlap + * (overflow/underflow). Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_FAB_ISR_USRDAT_OVR_MASK 0x00000001U /**< User-data overlap- + data written faster than read (overflow) */ +#define XRFDC_FAB_ISR_USRDAT_UND_MASK 0x00000002U /**< User-data overlap- + data read faster than written (underflow) */ +#define XRFDC_FAB_ISR_USRDAT_MASK 0x00000003U /**< User-data overlap Mask */ +#define XRFDC_FAB_ISR_MARGIND_OVR_MASK 0x00000004U /**< Marginal-indicator + overlap (overflow) */ +#define XRFDC_FAB_ISR_MARGIND_UND_MASK 0x00000008U /**< Marginal-indicator + overlap (underflow) */ +/* @} */ + +/** @name Fabric IMR - Interrupt mask register for FIFO interface + * + * This register contains bits of margin-indicator and user-data overlap + * (overflow/underflow). Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_FAB_IMR_USRDAT_OVR_MASK 0x00000001U /**< User-data overlap- + data written faster than read (overflow) */ +#define XRFDC_FAB_IMR_USRDAT_UND_MASK 0x00000002U /**< User-data overlap- + data read faster than written (underflow) */ +#define XRFDC_FAB_IMR_USRDAT_MASK 0x00000003U /**< User-data overlap Mask */ +#define XRFDC_FAB_IMR_MARGIND_OVR_MASK 0x00000004U /**< Marginal-indicator + overlap (overflow) */ +#define XRFDC_FAB_IMR_MARGIND_UND_MASK 0x00000008U /**< Marginal-indicator + overlap (underflow) */ +/* @} */ + +/** @name Update Dynamic - Trigger a dynamic update event + * + * This register contains bits of update event for slice, nco, qmc + * and coarse delay. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_UPDT_EVNT_MASK 0x0000000FU /**< Update event mask */ +#define XRFDC_UPDT_EVNT_SLICE_MASK 0x00000001U /**< Trigger a slice update + event apply to _DCONFIG reg */ +#define XRFDC_UPDT_EVNT_NCO_MASK 0x00000002U /**< Trigger a update event + apply to NCO_DCONFIG reg */ +#define XRFDC_UPDT_EVNT_QMC_MASK 0x00000004U /**< Trigger a update event + apply to QMC_DCONFIG reg */ +#define XRFDC_ADC_UPDT_CRSE_DLY_MASK 0x00000008U /**< ADC Trigger a update event + apply to Coarse delay_DCONFIG reg */ +#define XRFDC_DAC_UPDT_CRSE_DLY_MASK 0x00000020U /**< DAC Trigger a update event + apply to Coarse delay_DCONFIG reg */ +/* @} */ + +/** @name FIFO Latency control - Config registers for FIFO Latency measurement + * + * This register contains bits of FIFO Latency ctrl for disable, restart and + * set fifo latency measurement. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_FIFO_LTNCY_PRD_MASK 0x00000007U /**< Set FIFO Latency + measurement period */ +#define XRFDC_FIFO_LTNCY_RESTRT_MASK 0x00000008U /**< Restart FIFO Latency + measurement */ +#define XRFDC_FIFO_LTNCY_DIS_MASK 0x000000010U /**< Disable FIFO Latency + measurement */ + +/* @} */ + +/** @name Decode ISR - ISR for Decoder Interface + * + * This register contains bits of subadc 0,1,2 and 3 decoder overflow + * and underflow range. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_DEC_ISR_SUBADC_MASK 0x000000FFU /**< subadc decoder + Mask */ +#define XRFDC_DEC_ISR_SUBADC0_UND_MASK 0x00000001U /**< subadc0 decoder + underflow range */ +#define XRFDC_DEC_ISR_SUBADC0_OVR_MASK 0x00000002U /**< subadc0 decoder + overflow range */ +#define XRFDC_DEC_ISR_SUBADC1_UND_MASK 0x00000004U /**< subadc1 decoder + underflow range */ +#define XRFDC_DEC_ISR_SUBADC1_OVR_MASK 0x00000008U /**< subadc1 decoder + overflow range */ +#define XRFDC_DEC_ISR_SUBADC2_UND_MASK 0x00000010U /**< subadc2 decoder + underflow range */ +#define XRFDC_DEC_ISR_SUBADC2_OVR_MASK 0x00000020U /**< subadc2 decoder + overflow range */ +#define XRFDC_DEC_ISR_SUBADC3_UND_MASK 0x00000040U /**< subadc3 decoder + underflow range */ +#define XRFDC_DEC_ISR_SUBADC3_OVR_MASK 0x00000080U /**< subadc3 decoder + overflow range */ + +/* @} */ + +/** @name Decode IMR - IMR for Decoder Interface + * + * This register contains bits of subadc 0,1,2 and 3 decoder overflow + * and underflow range. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_DEC_IMR_SUBADC0_UND_MASK 0x00000001U /**< subadc0 decoder + underflow range */ +#define XRFDC_DEC_IMR_SUBADC0_OVR_MASK 0x00000002U /**< subadc0 decoder + overflow range */ +#define XRFDC_DEC_IMR_SUBADC1_UND_MASK 0x00000004U /**< subadc1 decoder + underflow range */ +#define XRFDC_DEC_IMR_SUBADC1_OVR_MASK 0x00000008U /**< subadc1 decoder + overflow range */ +#define XRFDC_DEC_IMR_SUBADC2_UND_MASK 0x00000010U /**< subadc2 decoder + underflow range */ +#define XRFDC_DEC_IMR_SUBADC2_OVR_MASK 0x00000020U /**< subadc2 decoder + overflow range */ +#define XRFDC_DEC_IMR_SUBADC3_UND_MASK 0x00000040U /**< subadc3 decoder + underflow range */ +#define XRFDC_DEC_IMR_SUBADC3_OVR_MASK 0x00000080U /**< subadc3 decoder + overflow range */ +#define XRFDC_DEC_IMR_MASK 0x000000FFU + +/* @} */ + +/** @name DataPath (DAC)- FIFO Latency, Image Reject Filter, Mode, + * + * This register contains bits for DataPath latency, Image Reject Filter + * and the Mode for the DAC. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_DATAPATH_MODE_MASK 0x00000003U /**< DataPath Mode */ +#define XRFDC_DATAPATH_IMR_MASK 0x00000004U /**< IMR Mode */ +#define XRFDC_DATAPATH_LATENCY_MASK 0x00000008U /**< DataPath Latency */ + +/* @} */ + + + +/** @name DataPath ISR - ISR for Data Path interface + * + * This register contains bits of QMC Gain/Phase overflow, offset overflow, + * Decimation I-Path and Interpolation Q-Path overflow for stages 0,1,2. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_ADC_DAT_PATH_ISR_MASK 0x000000FFU /**< ADC Data Path Overflow */ +#define XRFDC_DAC_DAT_PATH_ISR_MASK 0x000001FFU /**< DAC Data Path Overflow */ +#define XRFDC_DAT_ISR_DECI_IPATH_MASK 0x00000007U /**< Decimation I-Path + overflow for stages 0,1,2 */ +#define XRFDC_DAT_ISR_INTR_QPATH_MASK 0x00000038U /**< Interpolation + Q-Path overflow for stages 0,1,2 */ +#define XRFDC_DAT_ISR_QMC_GAIN_MASK 0x00000040U /**< QMC Gain/Phase + overflow */ +#define XRFDC_DAT_ISR_QMC_OFFST_MASK 0x00000080U /**< QMC offset + overflow */ +#define XRFDC_DAC_DAT_ISR_INVSINC_MASK 0x00000100U /**< Inverse Sinc offset + overflow */ + +/* @} */ + +/** @name DataPath IMR - IMR for Data Path interface + * + * This register contains bits of QMC Gain/Phase overflow, offset overflow, + * Decimation I-Path and Interpolation Q-Path overflow for stages 0,1,2. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_DAT_IMR_DECI_IPATH_MASK 0x00000007U /**< Decimation I-Path + overflow for stages 0,1,2 */ +#define XRFDC_DAT_IMR_INTR_QPATH_MASK 0x00000038U /**< Interpolation + Q-Path overflow for stages 0,1,2 */ +#define XRFDC_DAT_IMR_QMC_GAIN_MASK 0x00000040U /**< QMC Gain/Phase + overflow */ +#define XRFDC_DAT_IMR_QMC_OFFST_MASK 0x00000080U /**< QMC offset + overflow */ +#define XRFDC_ADC_DAT_IMR_MASK 0x000000FFU /**< ADC DataPath mask */ +#define XRFDC_DAC_DAT_IMR_MASK 0x00000FFFU /**< DAC DataPath mask */ + +/* @} */ + +/** @name Decimation Config - Decimation control + * + * This register contains bits to configure the decimation in terms of + * the type of data. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_DEC_CFG_MASK 0x00000003U /**< ChannelA (2GSPS real + data from Mixer I output) */ +#define XRFDC_DEC_CFG_CHA_MASK 0x00000000U /**< ChannelA(I) */ +#define XRFDC_DEC_CFG_CHB_MASK 0x00000001U /**< ChannelB (2GSPS real + data from Mixer Q output) */ +#define XRFDC_DEC_CFG_IQ_MASK 0x00000002U /**< IQ-2GSPS */ +#define XRFDC_DEC_CFG_4GSPS_MASK 0x00000003U /**< 4GSPS may be I or Q + or Real depending on high level block config */ + +/* @} */ + +/** @name Decimation Mode - Decimation Rate + * + * This register contains bits to configures the decimation rate. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_DEC_MOD_MASK 0x00000007U /**< Decimation mode Mask */ +#define XRFDC_DEC_MOD_MASK_EXT 0x0000003FU /**< Decimation mode Mask */ + +/* @} */ + +/** @name Mixer config0 - Configure I channel coarse mixer mode of operation + * + * This register contains bits to set the output data sequence of + * I channel. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_MIX_CFG0_MASK 0x00000FFFU /**< Mixer Config0 Mask */ +#define XRFDC_MIX_I_DAT_WRD0_MASK 0x00000007U /**< Output data word[0] + of I channel */ +#define XRFDC_MIX_I_DAT_WRD1_MASK 0x00000038U /**< Output data word[1] + of I channel */ +#define XRFDC_MIX_I_DAT_WRD2_MASK 0x000001C0U /**< Output data word[2] + of I channel */ +#define XRFDC_MIX_I_DAT_WRD3_MASK 0x00000E00U /**< Output data word[3] + of I channel */ + +/* @} */ + +/** @name Mixer config1 - Configure Q channel coarse mixer mode of operation + * + * This register contains bits to set the output data sequence of + * Q channel. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_MIX_CFG1_MASK 0x00000FFFU /**< Mixer Config0 Mask */ +#define XRFDC_MIX_Q_DAT_WRD0_MASK 0x00000007U /**< Output data word[0] + of Q channel */ +#define XRFDC_MIX_Q_DAT_WRD1_MASK 0x00000038U /**< Output data word[1] + of Q channel */ +#define XRFDC_MIX_Q_DAT_WRD2_MASK 0x000001C0U /**< Output data word[2] + of Q channel */ +#define XRFDC_MIX_Q_DAT_WRD3_MASK 0x00000E00U /**< Output data word[3] + of Q channel */ + +/* @} */ + +/** @name Mixer mode - Configure mixer mode of operation + * + * This register contains bits to set NCO phases, NCO output scale + * and fine mixer multipliers. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_EN_I_IQ_MASK 0x00000003U /**< Enable fine mixer + multipliers on IQ i/p for I output */ +#define XRFDC_EN_Q_IQ_MASK 0x0000000CU /**< Enable fine mixer + multipliers on IQ i/p for Q output */ +#define XRFDC_FINE_MIX_SCALE_MASK 0x00000010U /**< NCO output scale */ +#define XRFDC_SEL_I_IQ_MASK 0x00000F00U /**< Select NCO phases + for I output */ +#define XRFDC_SEL_Q_IQ_MASK 0x0000F000U /**< Select NCO phases + for Q output */ +#define XRFDC_I_IQ_COS_MINSIN 0x00000C00U /**< Select NCO phases + for I output */ +#define XRFDC_Q_IQ_SIN_COS 0x00001000U /**< Select NCO phases + for Q output */ +#define XRFDC_MIXER_MODE_C2C_MASK 0x0000000FU /**< Mixer mode C2C Mask */ +#define XRFDC_MIXER_MODE_R2C_MASK 0x00000005U /**< Mixer mode R2C Mask */ +#define XRFDC_MIXER_MODE_C2R_MASK 0x00000003U /**< Mixer mode C2R Mask */ +#define XRFDC_MIXER_MODE_OFF_MASK 0x00000000U /**< Mixer mode OFF Mask */ +/* @} */ + +/** @name NCO update - NCO update mode + * + * This register contains bits to Select event source, delay and reset delay. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_NCO_UPDT_MODE_MASK 0x00000007U /**< NCO event source + selection mask */ +#define XRFDC_NCO_UPDT_MODE_GRP 0x00000000U /**< NCO event source + selection is Group */ +#define XRFDC_NCO_UPDT_MODE_SLICE 0x00000001U /**< NCO event source + selection is slice */ +#define XRFDC_NCO_UPDT_MODE_TILE 0x00000002U /**< NCO event source + selection is tile */ +#define XRFDC_NCO_UPDT_MODE_SYSREF 0x00000003U /**< NCO event source + selection is Sysref */ +#define XRFDC_NCO_UPDT_MODE_MARKER 0x00000004U /**< NCO event source + selection is Marker */ +#define XRFDC_NCO_UPDT_MODE_FABRIC 0x00000005U /**< NCO event source + selection is fabric */ +#define XRFDC_NCO_UPDT_DLY_MASK 0x00001FF8U /**< delay in clk_dp + cycles in application of event after arrival */ +#define XRFDC_NCO_UPDT_RST_DLY_MASK 0x0000D000U /**< optional delay on + the NCO phase reset delay */ + +/* @} */ + +/** @name NCO Phase Reset - NCO Slice Phase Reset + * + * This register contains bits to reset the nco phase of the current + * slice phase accumulator. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_NCO_PHASE_RST_MASK 0x00000001U /**< Reset NCO Phase + of current slice */ + +/* @} */ + +/** @name DAC interpolation data + * + * This register contains bits for DAC interpolation data type + * @{ + */ + +#define XRFDC_DAC_INTERP_DATA_MASK 0x00000001U /**< Data type mask */ + +/* @} */ + +/** @name NCO Freq Word[47:32] - NCO Phase increment(nco freq 48-bit) + * + * This register contains bits for frequency control word of the + * NCO. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_NCO_FQWD_UPP_MASK 0x0000FFFFU /**< NCO Phase + increment[47:32] */ +#define XRFDC_NCO_FQWD_UPP_SHIFT 32U /**< Freq Word upper shift */ + +/* @} */ + +/** @name NCO Freq Word[31:16] - NCO Phase increment(nco freq 48-bit) + * + * This register contains bits for frequency control word of the + * NCO. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_NCO_FQWD_MID_MASK 0x0000FFFFU /**< NCO Phase + increment[31:16] */ +#define XRFDC_NCO_FQWD_MID_SHIFT 16U /**< Freq Word Mid shift */ + +/* @} */ + +/** @name NCO Freq Word[15:0] - NCO Phase increment(nco freq 48-bit) + * + * This register contains bits for frequency control word of the + * NCO. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_NCO_FQWD_LOW_MASK 0x0000FFFFU /**< NCO Phase + increment[15:0] */ +#define XRFDC_NCO_FQWD_MASK 0x0000FFFFFFFFFFFFU /**< NCO Freq + offset[48:0] */ + +/* @} */ + +/** @name NCO Phase Offset[17:16] - NCO Phase offset + * + * This register contains bits to set NCO Phase offset(18-bit offset + * added to the phase accumulator). Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_NCO_PHASE_UPP_MASK 0x00000003U /**< NCO Phase + offset[17:16] */ +#define XRFDC_NCO_PHASE_UPP_SHIFT 16U /**< NCO phase upper shift */ + +/* @} */ + +/** @name NCO Phase Offset[15:0] - NCO Phase offset + * + * This register contains bits to set NCO Phase offset(18-bit offset + * added to the phase accumulator). Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_NCO_PHASE_LOW_MASK 0x0000FFFFU /**< NCO Phase + offset[15:0] */ +#define XRFDC_NCO_PHASE_MASK 0x0003FFFFU /**< NCO Phase + offset[17:0] */ + +/* @} */ + +/** @name NCO Phase mode - NCO Control setting mode + * + * This register contains bits to set NCO mode of operation. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_NCO_PHASE_MOD_MASK 0x00000003U /**< NCO mode + of operation mask */ +#define XRFDC_NCO_PHASE_MOD_4PHASE 0x00000003U /**< NCO output + 4 successive phase */ +#define XRFDC_NCO_PHASE_MOD_EVEN 0x00000001U /**< NCO output + even phase */ +#define XRFDC_NCO_PHASE_MODE_ODD 0x00000002U /**< NCO output + odd phase */ +/* @} */ + +/** @name QMC update - QMC update mode + * + * This register contains bits to Select event source and delay. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_QMC_UPDT_MODE_MASK 0x00000007U /**< QMC event source + selection mask */ +#define XRFDC_QMC_UPDT_MODE_GRP 0x00000000U /**< QMC event source + selection is group */ +#define XRFDC_QMC_UPDT_MODE_SLICE 0x00000001U /**< QMC event source + selection is slice */ +#define XRFDC_QMC_UPDT_MODE_TILE 0x00000002U /**< QMC event source + selection is tile */ +#define XRFDC_QMC_UPDT_MODE_SYSREF 0x00000003U /**< QMC event source + selection is Sysref */ +#define XRFDC_QMC_UPDT_MODE_MARKER 0x00000004U /**< QMC event source + selection is Marker */ +#define XRFDC_QMC_UPDT_MODE_FABRIC 0x00000005U /**< QMC event source + selection is fabric */ +#define XRFDC_QMC_UPDT_DLY_MASK 0x00001FF8U /**< delay in clk_dp + cycles in application of event after arrival */ + +/* @} */ + +/** @name QMC Config - QMC Config register + * + * This register contains bits to enable QMC gain and QMC + * Phase correction. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_QMC_CFG_EN_GAIN_MASK 0x00000001U /**< enable QMC gain + correction mask */ +#define XRFDC_QMC_CFG_EN_PHASE_MASK 0x00000002U /**< enable QMC Phase + correction mask */ +#define XRFDC_QMC_CFG_PHASE_SHIFT 1U /**< QMC config phase shift */ + +/* @} */ + +/** @name QMC Offset - QMC offset correction + * + * This register contains bits to set QMC offset correction + * factor. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_QMC_OFFST_CRCTN_MASK 0x00000FFFU /**< QMC offset + correction factor */ + +/* @} */ + +/** @name QMC Gain - QMC Gain correction + * + * This register contains bits to set QMC gain correction + * factor. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_QMC_GAIN_CRCTN_MASK 0x00003FFFU /**< QMC gain + correction factor */ + +/* @} */ + +/** @name QMC Phase - QMC Phase correction + * + * This register contains bits to set QMC phase correction + * factor. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_QMC_PHASE_CRCTN_MASK 0x00000FFFU /**< QMC phase + correction factor */ + +/* @} */ + +/** @name Coarse Delay Update - Coarse delay update mode. + * + * This register contains bits to Select event source and delay. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_CRSEDLY_UPDT_MODE_MASK 0x00000007U /**< Coarse delay event + source selection mask */ +#define XRFDC_CRSEDLY_UPDT_MODE_GRP 0x00000000U /**< Coarse delay event + source selection is group */ +#define XRFDC_CRSEDLY_UPDT_MODE_SLICE 0x00000001U/**< Coarse delay event + source selection is slice */ +#define XRFDC_CRSEDLY_UPDT_MODE_TILE 0x00000002U /**< Coarse delay event + source selection is tile */ +#define XRFDC_CRSEDLY_UPDT_MODE_SYSREF 0x00000003U /**< Coarse delay event + source selection is sysref */ +#define XRFDC_CRSEDLY_UPDT_MODE_MARKER 0x00000004U /**< Coarse delay event + source selection is Marker */ +#define XRFDC_CRSEDLY_UPDT_MODE_FABRIC 0x00000005U /**< Coarse delay event + source selection is fabric */ +#define XRFDC_CRSEDLY_UPDT_DLY_MASK 0x00001FF8U /**< delay in clk_dp + cycles in application of event after arrival */ + +/* @} */ + +/** @name Coarse delay Config - Coarse delay select + * + * This register contains bits to select coarse delay. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_CRSE_DLY_CFG_MASK 0x00000007U /**< Coarse delay select */ +#define XRFDC_CRSE_DLY_CFG_MASK_EXT 0x0000003FU /**< Extended coarse delay select*/ + +/* @} */ + +/** @name Data Scaling Config - Data Scaling enable + * + * This register contains bits to enable data scaling. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_DAT_SCALE_CFG_MASK 0x00000001U /**< Enable data scaling */ + +/* @} */ + +/** @name Data Scaling Config - Data Scaling enable + * + * This register contains bits to enable data scaling. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_DAT_SCALE_CFG_MASK 0x00000001U /**< Enable data scaling */ + +/* @} */ + +/** @name Switch Matrix Config + * + * This register contains bits to control crossbar switch that select + * data to mixer block. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_SWITCH_MTRX_MASK 0x0000003FU /**< Switch matrix mask */ +#define XRFDC_SEL_CB_TO_MIX1_MASK 0x00000003U /**< Control crossbar + switch that select the data to mixer block mux1 */ +#define XRFDC_SEL_CB_TO_MIX0_MASK 0x0000000CU /**< Control crossbar + switch that select the data to mixer block mux0 */ +#define XRFDC_SEL_CB_TO_QMC_MASK 0x00000010U /**< Control crossbar + switch that select the data to QMC */ +#define XRFDC_SEL_CB_TO_DECI_MASK 0x00000020U /**< Control crossbar + switch that select the data to decimation filter */ +#define XRFDC_SEL_CB_TO_MIX0_SHIFT 2U /**< Crossbar Mixer0 shift */ + +/* @} */ + +/** @name Threshold0 Config + * + * This register contains bits to select mode, clear mode and to + * clear sticky bit. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TRSHD0_EN_MOD_MASK 0x00000003U /**< Enable Threshold0 + block */ +#define XRFDC_TRSHD0_CLR_MOD_MASK 0x00000004U /**< Clear mode */ +#define XRFDC_TRSHD0_STIKY_CLR_MASK 0x00000008U /**< Clear sticky bit */ + +/* @} */ + +/** @name Threshold0 Average[31:16] + * + * This register contains bits to select Threshold0 under averaging. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TRSHD0_AVG_UPP_MASK 0x0000FFFFU /**< Threshold0 under + Averaging[31:16] */ +#define XRFDC_TRSHD0_AVG_UPP_SHIFT 16U /**< Threshold0 Avg upper shift */ +/* @} */ + +/** @name Threshold0 Average[15:0] + * + * This register contains bits to select Threshold0 under averaging. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TRSHD0_AVG_LOW_MASK 0x0000FFFFU /**< Threshold0 under + Averaging[15:0] */ + +/* @} */ + +/** @name Threshold0 Under threshold + * + * This register contains bits to select Threshold0 under threshold. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TRSHD0_UNDER_MASK 0x00007FFFU /**< Threshold0 under + Threshold[14:0] */ + +/* @} */ + +/** @name Threshold0 Over threshold + * + * This register contains bits to select Threshold0 over threshold. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TRSHD0_OVER_MASK 0x00007FFFU /**< Threshold0 under + Threshold[14:0] */ + +/* @} */ + +/** @name Threshold1 Config + * + * This register contains bits to select mode, clear mode and to + * clear sticky bit. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TRSHD1_EN_MOD_MASK 0x00000003U /**< Enable Threshold1 + block */ +#define XRFDC_TRSHD1_CLR_MOD_MASK 0x00000004U /**< Clear mode */ +#define XRFDC_TRSHD1_STIKY_CLR_MASK 0x00000008U /**< Clear sticky bit */ + +/* @} */ + +/** @name Threshold1 Average[31:16] + * + * This register contains bits to select Threshold1 under averaging. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TRSHD1_AVG_UPP_MASK 0x0000FFFFU /**< Threshold1 under + Averaging[31:16] */ +#define XRFDC_TRSHD1_AVG_UPP_SHIFT 16U /**< Threshold1 Avg upper shift */ + +/* @} */ + +/** @name Threshold1 Average[15:0] + * + * This register contains bits to select Threshold1 under averaging. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TRSHD1_AVG_LOW_MASK 0x0000FFFFU /**< Threshold1 under + Averaging[15:0] */ + +/* @} */ + +/** @name Threshold1 Under threshold + * + * This register contains bits to select Threshold1 under threshold. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TRSHD1_UNDER_MASK 0x00007FFFU /**< Threshold1 under + Threshold[14:0] */ + +/* @} */ + +/** @name Threshold1 Over threshold + * + * This register contains bits to select Threshold1 over threshold. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TRSHD1_OVER_MASK 0x00007FFFU /**< Threshold1 under + Threshold[14:0] */ + +/* @} */ + +/** @name FrontEnd Data Control + * + * This register contains bits to select raw data and cal coefficient to + * be streamed to memory. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_FEND_DAT_CTRL_MASK 0x000000FFU /**< raw data and cal + coefficient to be streamed to memory */ + +/* @} */ + +/** @name TI Digital Correction Block control0 + * + * This register contains bits for Time Interleaved digital correction + * block gain and offset correction. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_CTRL0_MASK 0x0000FFFFU /**< TI DCB gain and + offset correction */ +#define XRFDC_TI_DCB_MODE_MASK 0x00007800U /**< TI DCB Mode mask */ + +/* @} */ + +/** @name TI Digital Correction Block control1 + * + * This register contains bits for Time Interleaved digital correction + * block gain and offset correction. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_CTRL1_MASK 0x00001FFFU /**< TI DCB gain and + offset correction */ + +/* @} */ + +/** @name TI Digital Correction Block control2 + * + * This register contains bits for Time Interleaved digital correction + * block gain and offset correction. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_CTRL2_MASK 0x00001FFFU /**< TI DCB gain and + offset correction */ + +/* @} */ + +/** @name TI Time Skew control0 + * + * This register contains bits for Time skew correction control bits0(enables, + * mode, multiplier factors, debug). Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TI_TISK_EN_MASK 0x00000001U /**< Block Enable */ +#define XRFDC_TI_TISK_MODE_MASK 0x00000002U /**< Mode (2G/4G) */ +#define XRFDC_TI_TISK_ZONE_MASK 0x00000004U /**< Specifies Nyquist zone */ +#define XRFDC_TI_TISK_CHOP_EN_MASK 0x00000008U /**< enable chopping mode */ +#define XRFDC_TI_TISK_MU_CM_MASK 0x000000F0U /**< Constant mu_cm multiplying + common mode path */ +#define XRFDC_TI_TISK_MU_DF_MASK 0x00000F00U /**< Constant mu_df multiplying + differential path */ +#define XRFDC_TI_TISK_DBG_CTRL_MASK 0x0000F000U /**< Debug control */ +#define XRFDC_TI_TISK_DBG_UPDT_RT_MASK 0x00001000U /**< Debug update rate */ +#define XRFDC_TI_TISK_DITH_DLY_MASK 0x0000E000U /**< Programmable delay on + dither path to match data path */ +#define XRFDC_TISK_ZONE_SHIFT 2U /**< Nyquist zone shift */ + +/* @} */ + +/** @name DAC MC Config0 + * + * This register contains bits for enable/disable shadow logic , Nyquist zone + * selction, enable full speed clock, Programmable delay. + * @{ + */ + +#define XRFDC_MC_CFG0_MIX_MODE_MASK 0x00000002U /**< Enable + Mixing mode */ +#define XRFDC_MC_CFG0_MIX_MODE_SHIFT 1U /**< Mix mode shift */ + +/* @} */ + +/** @name TI Time Skew control0 + * + * This register contains bits for Time skew correction control bits0(enables, + * mode, multiplier factors, debug). Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TISK_EN_MASK 0x00000001U /**< Block Enable */ +#define XRFDC_TISK_MODE_MASK 0x00000002U /**< Mode (2G/4G) */ +#define XRFDC_TISK_ZONE_MASK 0x00000004U /**< Specifies Nyquist zone */ +#define XRFDC_TISK_CHOP_EN_MASK 0x00000008U /**< enable chopping mode */ +#define XRFDC_TISK_MU_CM_MASK 0x000000F0U /**< Constant mu_cm multiplying + common mode path */ +#define XRFDC_TISK_MU_DF_MASK 0x00000F00U /**< Constant mu_df multiplying + differential path */ +#define XRFDC_TISK_DBG_CTRL_MASK 0x0000F000U /**< Debug control */ +#define XRFDC_TISK_DBG_UPDT_RT_MASK 0x00001000U /**< Debug update rate */ +#define XRFDC_TISK_DITH_DLY_MASK 0x0000E000U /**< Programmable delay on + dither path to match data path */ + +/* @} */ + +/** @name TI Time Skew control1 + * + * This register contains bits for Time skew correction control bits1 + * (Deadzone Parameters). Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TISK_DZ_MIN_VAL_MASK 0x000000FFU /**< Deadzone min */ +#define XRFDC_TISK_DZ_MAX_VAL_MASK 0x0000FF00U /**< Deadzone max */ + +/* @} */ + +/** @name TI Time Skew control2 + * + * This register contains bits for Time skew correction control bits2 + * (Filter parameters). Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TISK_MU0_MASK 0x0000000FU /**< Filter0 multiplying factor */ +#define XRFDC_TISK_BYPASS0_MASK 0x00000080U /**< ByPass filter0 */ +#define XRFDC_TISK_MU1_MASK 0x00000F00U /**< Filter1 multiplying factor */ +#define XRFDC_TISK_BYPASS1_MASK 0x00008000U /**< Filter1 multiplying factor */ + +/* @} */ + +/** @name TI Time Skew control3 + * + * This register contains bits for Time skew control settling time + * following code update. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TISK_SETTLE_MASK 0x000000FFU /**< Settling time following + code update */ + +/* @} */ + +/** @name TI Time Skew control4 + * + * This register contains bits for Time skew control setting time + * following code update. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TISK_CAL_PRI_MASK 0x00000001U /**< */ +#define XRFDC_TISK_DITH_INV_MASK 0x00000FF0U /**< */ + +/* @} */ + +/** @name TI Time Skew DAC0 + * + * This register contains bits for Time skew DAC cal code of + * subadc ch0. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TISK_DAC0_CODE_MASK 0x000000FFU /**< Code to correction + DAC of subadc ch0 front end switch0 */ +#define XRFDC_TISK_DAC0_OVRID_EN_MASK 0x00008000U /**< override enable */ + +/* @} */ + +/** @name TI Time Skew DAC1 + * + * This register contains bits for Time skew DAC cal code of + * subadc ch1. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TISK_DAC1_CODE_MASK 0x000000FFU /**< Code to correction + DAC of subadc ch1 front end switch0 */ +#define XRFDC_TISK_DAC1_OVRID_EN_MASK 0x00008000U /**< override enable */ + +/* @} */ + +/** @name TI Time Skew DAC2 + * + * This register contains bits for Time skew DAC cal code of + * subadc ch2. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TISK_DAC2_CODE_MASK 0x000000FFU /**< Code to correction + DAC of subadc ch2 front end switch0 */ +#define XRFDC_TISK_DAC2_OVRID_EN_MASK 0x00008000U /**< override enable */ + +/* @} */ + +/** @name TI Time Skew DAC3 + * + * This register contains bits for Time skew DAC cal code of + * subadc ch3. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TISK_DAC3_CODE_MASK 0x000000FFU /**< Code to correction + DAC of subadc ch3 front end switch0 */ +#define XRFDC_TISK_DAC3_OVRID_EN_MASK 0x00008000U /**< override enable */ + +/* @} */ + +/** @name TI Time Skew DACP0 + * + * This register contains bits for Time skew DAC cal code of + * subadc ch0. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TISK_DACP0_CODE_MASK 0x000000FFU /**< Code to correction + DAC of subadc ch0 front end switch1 */ +#define XRFDC_TISK_DACP0_OVRID_EN_MASK 0x00008000U /**< override enable */ + +/* @} */ + +/** @name TI Time Skew DACP1 + * + * This register contains bits for Time skew DAC cal code of + * subadc ch1. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TISK_DACP1_CODE_MASK 0x000000FFU /**< Code to correction + DAC of subadc ch1 front end switch1 */ +#define XRFDC_TISK_DACP1_OVRID_EN_MASK 0x00008000U /**< override enable */ + +/* @} */ + +/** @name TI Time Skew DACP2 + * + * This register contains bits for Time skew DAC cal code of + * subadc ch2. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TISK_DACP2_CODE_MASK 0x000000FFU /**< Code to correction + DAC of subadc ch2 front end switch1 */ +#define XRFDC_TISK_DACP2_OVRID_EN_MASK 0x00008000U /**< override enable */ + +/* @} */ + +/** @name TI Time Skew DACP3 + * + * This register contains bits for Time skew DAC cal code of + * subadc ch3. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TISK_DACP3_CODE_MASK 0x000000FFU /**< Code to correction + DAC of subadc ch3 front end switch1 */ +#define XRFDC_TISK_DACP3_OVRID_EN_MASK 0x00008000U /**< override enable */ + +/* @} */ + +/** @name SubDRP ADC0 address + * + * This register contains the sub-drp address of the target register. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_SUBDRP_ADC0_ADDR_MASK 0x000000FFU /**< sub-drp0 address */ + +/* @} */ + +/** @name SubDRP ADC0 Data + * + * This register contains the sub-drp data of the target register. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_SUBDRP_ADC0_DAT_MASK 0x0000FFFFU /**< sub-drp0 data + for read or write transaction */ + +/* @} */ + +/** @name SubDRP ADC1 address + * + * This register contains the sub-drp address of the target register. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_SUBDRP_ADC1_ADDR_MASK 0x000000FFU /**< sub-drp1 address */ + +/* @} */ + +/** @name SubDRP ADC1 Data + * + * This register contains the sub-drp data of the target register. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_SUBDRP_ADC1_DAT_MASK 0x0000FFFFU /**< sub-drp1 data + for read or write transaction */ + +/* @} */ + +/** @name SubDRP ADC2 address + * + * This register contains the sub-drp address of the target register. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_SUBDRP_ADC2_ADDR_MASK 0x000000FFU /**< sub-drp2 address */ + +/* @} */ + +/** @name SubDRP ADC2 Data + * + * This register contains the sub-drp data of the target register. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_SUBDRP_ADC2_DAT_MASK 0x0000FFFFU /**< sub-drp2 data + for read or write transaction */ + +/* @} */ + +/** @name SubDRP ADC3 address + * + * This register contains the sub-drp address of the target register. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_SUBDRP_ADC3_ADDR_MASK 0x000000FFU /**< sub-drp3 address */ + +/* @} */ + +/** @name SubDRP ADC3 Data + * + * This register contains the sub-drp data of the target register. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_SUBDRP_ADC3_DAT_MASK 0x0000FFFFU /**< sub-drp3 data + for read or write transaction */ + +/* @} */ + +/** @name RX MC PWRDWN + * + * This register contains the static configuration bits of ADC(RX) analog. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_RX_MC_PWRDWN_MASK 0x0000FFFFU /**< RX MC power down */ + +/* @} */ + +/** @name RX MC Config0 + * + * This register contains the static configuration bits of ADC(RX) analog. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_RX_MC_CFG0_MASK 0x0000FFFFU /**< RX MC config0 */ +#define XRFDC_RX_MC_CFG0_CM_MASK 0x00000040U /**< Coupling mode mask */ +#define XRFDC_RX_MC_CFG0_IM3_DITH_MASK 0x00000020U /**< IM3 Dither Enable mode mask */ +#define XRFDC_RX_MC_CFG0_IM3_DITH_SHIFT 5U /**< IM3 Dither Enable mode shift */ + +/* @} */ + +/** @name RX MC Config1 + * + * This register contains the static configuration bits of ADC(RX) analog. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_RX_MC_CFG1_MASK 0x0000FFFFU /**< RX MC Config1 */ + +/* @} */ + +/** @name RX MC Config2 + * + * This register contains the static configuration bits of ADC(RX) analog. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_RX_MC_CFG2_MASK 0x0000FFFFU /**< RX MC Config2 */ + +/* @} */ + +/** @name RX Pair MC Config0 + * + * This register contains the RX Pair (RX0 and RX1 or RX2 and RX3)static + * configuration bits of ADC(RX) analog. Read/Write apart from the + * reserved bits. + * @{ + */ + +#define XRFDC_RX_PR_MC_CFG0_MASK 0x0000FFFFU /**< RX Pair MC Config0 */ + +/* @} */ + +/** @name RX Pair MC Config1 + * + * This register contains the RX Pair (RX0 and RX1 or RX2 and RX3)static + * configuration bits of ADC(RX) analog. Read/Write apart from the + * reserved bits. + * @{ + */ + +#define XRFDC_RX_PR_MC_CFG1_MASK 0x0000FFFFU /**< RX Pair MC Config1 */ + +/* @} */ + +/** @name TI DCB Status0 BG + * + * This register contains the subadc ch0 ocb1 BG offset correction factor + * value. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_STS0_BG_MASK 0x0000FFFFU /**< DCB Status0 BG */ + +/* @} */ + +/** @name TI DCB Status0 FG + * + * This register contains the subadc ch0 ocb2 FG offset correction factor + * value(read and write). Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_STS0_FG_MASK 0x0000FFFFU /**< DCB Status0 FG */ + +/* @} */ + +/** @name TI DCB Status1 BG + * + * This register contains the subadc ch1 ocb1 BG offset correction factor + * value. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_STS1_BG_MASK 0x0000FFFFU /**< DCB Status1 BG */ + +/* @} */ + +/** @name TI DCB Status1 FG + * + * This register contains the subadc ch1 ocb2 FG offset correction factor + * value(read and write). Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_STS1_FG_MASK 0x0000FFFFU /**< DCB Status1 FG */ + +/* @} */ + +/** @name TI DCB Status2 BG + * + * This register contains the subadc ch2 ocb1 BG offset correction factor + * value. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_STS2_BG_MASK 0x0000FFFFU /**< DCB Status2 BG */ + +/* @} */ + +/** @name TI DCB Status2 FG + * + * This register contains the subadc ch2 ocb2 FG offset correction factor + * value(read and write). Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_STS2_FG_MASK 0x0000FFFFU /**< DCB Status2 FG */ + +/* @} */ + +/** @name TI DCB Status3 BG + * + * This register contains the subadc ch3 ocb1 BG offset correction factor + * value. Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_STS3_BG_MASK 0x0000FFFFU /**< DCB Status3 BG */ + +/* @} */ + +/** @name TI DCB Status3 FG + * + * This register contains the subadc ch3 ocb2 FG offset correction factor + * value(read and write). Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_STS3_FG_MASK 0x0000FFFFU /**< DCB Status3 FG */ + +/* @} */ + +/** @name TI DCB Status4 MSB + * + * This register contains the DCB status. Read/Write apart from the + * reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_STS4_MSB_MASK 0x0000FFFFU /**< read the status of + gcb acc0 msb bits(subadc chan0) */ + +/* @} */ + +/** @name TI DCB Status4 LSB + * + * This register contains the DCB Status. Read/Write apart from the + * reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_STS4_LSB_MASK 0x0000FFFFU /**< read the status of + gcb acc0 lsb bits(subadc chan0) */ + +/* @} */ + +/** @name TI DCB Status5 MSB + * + * This register contains the DCB status. Read/Write apart from the + * reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_STS5_MSB_MASK 0x0000FFFFU /**< read the status of + gcb acc1 msb bits(subadc chan1) */ + +/* @} */ + +/** @name TI DCB Status5 LSB + * + * This register contains the DCB Status. Read/Write apart from the + * reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_STS5_LSB_MASK 0x0000FFFFU /**< read the status of + gcb acc1 lsb bits(subadc chan1) */ + +/* @} */ + +/** @name TI DCB Status6 MSB + * + * This register contains the DCB status. Read/Write apart from the + * reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_STS6_MSB_MASK 0x0000FFFFU /**< read the status of + gcb acc2 msb bits(subadc chan2) */ + +/* @} */ + +/** @name TI DCB Status6 LSB + * + * This register contains the DCB Status. Read/Write apart from the + * reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_STS6_LSB_MASK 0x0000FFFFU /**< read the status of + gcb acc2 lsb bits(subadc chan2) */ + +/* @} */ + +/** @name TI DCB Status7 MSB + * + * This register contains the DCB status. Read/Write apart from the + * reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_STS7_MSB_MASK 0x0000FFFFU /**< read the status of + gcb acc3 msb bits(subadc chan3) */ + +/* @} */ + +/** @name TI DCB Status7 LSB + * + * This register contains the DCB Status. Read/Write apart from the + * reserved bits. + * @{ + */ + +#define XRFDC_TI_DCB_STS7_LSB_MASK 0x0000FFFFU /**< read the status of + gcb acc3 lsb bits(subadc chan3) */ + +/* @} */ + +/** @name PLL_REFDIV + * + * This register contains the bits for Reference Clock Divider + * @{ + */ + +#define XRFDC_REFCLK_DIV_MASK 0x1FU +#define XRFDC_REFCLK_DIV_1_MASK 0x10U /**< Mask for Div1 */ +#define XRFDC_REFCLK_DIV_2_MASK 0x0U /**< Mask for Div2 */ +#define XRFDC_REFCLK_DIV_3_MASK 0x1U /**< Mask for Div3 */ +#define XRFDC_REFCLK_DIV_4_MASK 0x2U /**< Mask for Div4 */ + +/* @} */ + +/** @name FIFO Latency + * + * This register contains bits for result, key and done flag. + * Read/Write apart from the reserved bits. + * @{ + */ + +#define XRFDC_FIFO_LTNCY_RES_MASK 0x00000FFFU /**< Latency + measurement result */ +#define XRFDC_FIFO_LTNCY_KEY_MASK 0x00004000U /**< Latency + measurement result identification key */ +#define XRFDC_FIFO_LTNCY_DONE_MASK 0x00008000U /**< Latency + measurement done flag */ + +/* @} */ + +/** @name Decoder Control + * + * This register contains Unary Decoder/Randomizer settings to use. + * @{ + */ + +#define XRFDC_DEC_CTRL_MODE_MASK 0x00000007U /**< Decoder mode */ + +/* @} */ + +/** @name HSCOM Power state mask + * + * This register contains HSCOM_PWR to check powerup_state. + * @{ + */ + +#define XRFDC_HSCOM_PWR_STATE_MASK 0x0000FFFFU /**< powerup state mask */ + +/* @} */ + +/** @name Interpolation Control + * + * This register contains Interpolation filter modes. + * @{ + */ + +#define XRFDC_INTERP_MODE_MASK 0x00000077U /**< Interp filter mask */ +#define XRFDC_INTERP_MODE_I_MASK 0x00000007U /**< Interp filter I */ +#define XRFDC_INTERP_MODE_Q_SHIFT 4U /**< Interp mode Q shift */ +#define XRFDC_INTERP_MODE_MASK_EXT 0x00003F3FU /**< Interp filter mask */ +#define XRFDC_INTERP_MODE_I_MASK_EXT 0x0000003FU /**< Interp filter I */ +#define XRFDC_INTERP_MODE_Q_SHIFT_EXT 8U /**< Interp mode Q shift */ + + +/* @} */ + +/** @name Tile Reset + * + * This register contains Tile reset bit. + * @{ + */ + +#define XRFDC_TILE_RESET_MASK 0x00000001U /**< Tile reset mask */ + +/* @} */ + +/** @name Status register + * + * This register contains common status bits. + * @{ + */ + +#define XRFDC_PWR_UP_STAT_MASK 0x00000004U /**< Power Up state mask */ +#define XRFDC_PWR_UP_STAT_SHIFT 2U /**< PowerUp status shift */ +#define XRFDC_PLL_LOCKED_MASK 0x00000008U /**< PLL Locked mask */ +#define XRFDC_PLL_LOCKED_SHIFT 3U /**< PLL locked shift */ + +/* @} */ + +/** @name Restart State register + * + * This register contains Start and End state bits. + * @{ + */ + +#define XRFDC_PWR_STATE_MASK 0x0000FFFFU /**< State mask */ +#define XRFDC_RSR_START_SHIFT 8U /**< Start state shift */ + + +/* @} */ + +/** @name Clock Detect register + * + * This register contains Start and End state bits. + * @{ + */ + +#define XRFDC_CLOCK_DETECT_MASK 0x0000FFFFU /**< Clock detect mask */ + + +/* @} */ + +/** @name Common interrupt enable register + * + * This register contains bits to enable interrupt for + * ADC and DAC tiles. + * @{ + */ + +#define XRFDC_EN_INTR_DAC_TILE0_MASK 0x00000001U /**< DAC Tile0 + interrupt enable mask */ +#define XRFDC_EN_INTR_DAC_TILE1_MASK 0x00000002U /**< DAC Tile1 + interrupt enable mask */ +#define XRFDC_EN_INTR_DAC_TILE2_MASK 0x00000004U /**< DAC Tile2 + interrupt enable mask */ +#define XRFDC_EN_INTR_DAC_TILE3_MASK 0x00000008U /**< DAC Tile3 + interrupt enable mask */ +#define XRFDC_EN_INTR_ADC_TILE0_MASK 0x00000010U /**< ADC Tile0 + interrupt enable mask */ +#define XRFDC_EN_INTR_ADC_TILE1_MASK 0x00000020U /**< ADC Tile1 + interrupt enable mask */ +#define XRFDC_EN_INTR_ADC_TILE2_MASK 0x00000040U /**< ADC Tile2 + interrupt enable mask */ +#define XRFDC_EN_INTR_ADC_TILE3_MASK 0x00000080U /**< ADC Tile3 + interrupt enable mask */ +/* @} */ + + +/** @name interrupt enable register + * + * This register contains bits to enable interrupt for blocks. + * @{ + */ + +#define XRFDC_EN_INTR_SLICE_MASK 0x0000000FU /**< Slice intr mask */ +#define XRFDC_EN_INTR_SLICE0_MASK 0x00000001U /**< slice0 + interrupt enable mask */ +#define XRFDC_EN_INTR_SLICE1_MASK 0x00000002U /**< slice1 + interrupt enable mask */ +#define XRFDC_EN_INTR_SLICE2_MASK 0x00000004U /**< slice2 + interrupt enable mask */ +#define XRFDC_EN_INTR_SLICE3_MASK 0x00000008U /**< slice3 + interrupt enable mask */ +/* @} */ + +/** @name Converter(X) interrupt register + * + * This register contains bits to enable different interrupts for block X. + * @{ + */ + +#define XRFDC_INTR_OVR_RANGE_MASK 0x00000008U /**< Over Range + interrupt mask */ +#define XRFDC_INTR_OVR_VOLTAGE_MASK 0x00000004U /**< Over Voltage + interrupt mask */ +#define XRFDC_INTR_FIFO_OVR_MASK 0x00008000U /**< FIFO OF mask */ +#define XRFDC_INTR_DAT_OVR_MASK 0x00004000U /**< Data OF mask */ +#define XRFDC_INTR_CMODE_OVR_MASK 0x00040000U /**< Common mode OV mask */ +#define XRFDC_INTR_CMODE_UNDR_MASK 0x00080000U /**< Common mode UV mask */ +/* @} */ + +/** @name Multiband config register + * + * This register contains bits to configure multiband. + * @{ + */ + +#define XRFDC_EN_MB_MASK 0x00000008U /**< multi-band adder mask */ +#define XRFDC_EN_MB_SHIFT 3U /** <Enable Multiband shift */ +#define XRFDC_ALT_BOND_MASK 0x0100 /** <Alt bondout mask */ +#define XRFDC_ALT_BOND_SHIFT 8U /** <Alt bondout shift */ + +/* @} */ + +/** @name Invsinc control register + * + * This register contains bits to configure Invsinc. + * @{ + */ +#define XRFDC_EN_INVSINC_MASK 0x00000001U /**< invsinc enable mask */ +#define XRFDC_MODE_INVSINC_MASK 0x00000003U /**< invsinc mode mask */ +/* @} */ + +/* @} */ + +/** @name Signal Detector control register + * + * This register contains bits to configure Signal Detector. + * @{ + */ +#define XRFDC_ADC_SIG_DETECT_MASK 0xFF /**< signal detector mask */ +#define XRFDC_ADC_SIG_DETECT_THRESH_MASK 0xFF /**< signal detector thresholds mask */ +#define XRFDC_ADC_SIG_DETECT_INTG_MASK 0x01 /**< leaky integrator enable mask */ +#define XRFDC_ADC_SIG_DETECT_FLUSH_MASK 0x02 /**< leaky integrator flush mask */ +#define XRFDC_ADC_SIG_DETECT_TCONST_MASK 0x1C /**< time constant mask */ +#define XRFDC_ADC_SIG_DETECT_MODE_MASK 0x60 /**< mode mask */ +#define XRFDC_ADC_SIG_DETECT_HYST_MASK 0x80 /**< hysteresis enable mask */ +#define XRFDC_ADC_SIG_DETECT_INTG_SHIFT 0 /**< leaky integrator enable shift */ +#define XRFDC_ADC_SIG_DETECT_FLUSH_SHIFT 1 /**< leaky integrator flush shift */ +#define XRFDC_ADC_SIG_DETECT_TCONST_SHIFT 2 /**< time constant shift */ +#define XRFDC_ADC_SIG_DETECT_MODE_WRITE_SHIFT 5 /**< mode shift fror writing */ +#define XRFDC_ADC_SIG_DETECT_MODE_READ_SHIFT 6 /**< mode shift fror reading */ +#define XRFDC_ADC_SIG_DETECT_HYST_SHIFT 7 /**< hysteresis enable shift */ +/* @} */ + +/** @name CLK_DIV register + * + * This register contains the bits to control the clock + * divider providing the clock fabric out. + * @{ + */ + +#define XRFDC_FAB_CLK_DIV_MASK 0x0000000FU /**< clk div mask */ + +/* @} */ + +/** @name Multiband Config + * + * This register contains bits to configure multiband for DAC. + * @{ + */ + +#define XRFDC_MB_CFG_MASK 0x000001FFU /**< MB config mask */ +#define XRFDC_MB_EN_4X_MASK 0x00000100U /**< Enable 4X MB mask */ + +/* @} */ + +/** @name Multi Tile Sync + * + * Multi-Tile Sync bit masks. + * @{ + */ + +#define XRFDC_MTS_SRCAP_PLL_M 0x0100U +#define XRFDC_MTS_SRCAP_DIG_M 0x0100U +#define XRFDC_MTS_SRCAP_EN_TRX_M 0x0400U +#define XRFDC_MTS_SRCAP_INIT_M 0x8200U +#define XRFDC_MTS_SRCLR_T1_M 0x2000U +#define XRFDC_MTS_SRCLR_PLL_M 0x0200U +#define XRFDC_MTS_PLLEN_M 0x0001U +#define XRFDC_MTS_SRCOUNT_M 0x00FFU +#define XRFDC_MTS_DELAY_VAL_M 0x041FU +#define XRFDC_MTS_AMARK_CNT_M 0x00FFU +#define XRFDC_MTS_AMARK_LOC_M 0x0F0000U +#define XRFDC_MTS_AMARK_DONE_M 0x100000U + +/* @} */ + +/** @name Output divider LSB register + * + * This register contains bits to configure output divisor + * @{ + */ + +#define XRFDC_PLL_DIVIDER0_MASK 0x00FFU +#define XRFDC_PLL_DIVIDER0_MODE_MASK 0x00C0U +#define XRFDC_PLL_DIVIDER0_VALUE_MASK 0x003FU +#define XRFDC_PLL_DIVIDER0_SHIFT 6U + +/* @} */ + +/** @name Multi-tile sync and clock source control register + * + * This register contains bits to Multi-tile sync and clock source control + * @{ + */ +#define XRFDC_CLK_NETWORK_CTRL1_USE_PLL_MASK 0x1U /**< PLL clock mask */ + +/* @} */ + +/** @name PLL_CRS1 - PLL CRS1 register + * + * This register contains bits for VCO sel_auto, VCO band selection etc., + * @{ + */ + +#define XRFDC_PLL_CRS1_VCO_SEL_MASK 0x00008001U /**< VCO SEL Mask */ +#define XRFDC_PLL_VCO_SEL_AUTO_MASK 0x00008000U /**< VCO Auto SEL Mask */ + +/* @} */ + +/** Register bits Shift, Width Masks + * + * @{ + */ +#define XRFDC_DIGI_ANALOG_SHIFT4 4U +#define XRFDC_DIGI_ANALOG_SHIFT8 8U +#define XRFDC_DIGI_ANALOG_SHIFT12 12U + +/* @} */ + +#define XRFDC_IXR_FIFOUSRDAT_MASK 0x0000000FU +#define XRFDC_IXR_FIFOUSRDAT_OF_MASK 0x00000001U +#define XRFDC_IXR_FIFOUSRDAT_UF_MASK 0x00000002U +#define XRFDC_IXR_FIFOMRGNIND_OF_MASK 0x00000004U +#define XRFDC_IXR_FIFOMRGNIND_UF_MASK 0x00000008U +#define XRFDC_ADC_IXR_DATAPATH_MASK 0x00000FF0U +#define XRFDC_ADC_IXR_DMON_STG_MASK 0x000003F0U +#define XRFDC_DAC_IXR_DATAPATH_MASK 0x00001FF0U +#define XRFDC_DAC_IXR_INTP_STG_MASK 0x000003F0U +#define XRFDC_DAC_IXR_INTP_I_STG0_MASK 0x00000010U +#define XRFDC_DAC_IXR_INTP_I_STG1_MASK 0x00000020U +#define XRFDC_DAC_IXR_INTP_I_STG2_MASK 0x00000040U +#define XRFDC_DAC_IXR_INTP_Q_STG0_MASK 0x00000080U +#define XRFDC_DAC_IXR_INTP_Q_STG1_MASK 0x00000100U +#define XRFDC_DAC_IXR_INTP_Q_STG2_MASK 0x00000200U +#define XRFDC_ADC_IXR_DMON_I_STG0_MASK 0x00000010U +#define XRFDC_ADC_IXR_DMON_I_STG1_MASK 0x00000020U +#define XRFDC_ADC_IXR_DMON_I_STG2_MASK 0x00000040U +#define XRFDC_ADC_IXR_DMON_Q_STG0_MASK 0x00000080U +#define XRFDC_ADC_IXR_DMON_Q_STG1_MASK 0x00000100U +#define XRFDC_ADC_IXR_DMON_Q_STG2_MASK 0x00000200U +#define XRFDC_IXR_QMC_GAIN_PHASE_MASK 0x00000400U +#define XRFDC_IXR_QMC_OFFST_MASK 0x00000800U +#define XRFDC_DAC_IXR_INVSNC_OF_MASK 0x00001000U +#define XRFDC_SUBADC_IXR_DCDR_MASK 0x00FF0000U +#define XRFDC_SUBADC0_IXR_DCDR_OF_MASK 0x00010000U +#define XRFDC_SUBADC0_IXR_DCDR_UF_MASK 0x00020000U +#define XRFDC_SUBADC1_IXR_DCDR_OF_MASK 0x00040000U +#define XRFDC_SUBADC1_IXR_DCDR_UF_MASK 0x00080000U +#define XRFDC_SUBADC2_IXR_DCDR_OF_MASK 0x00100000U +#define XRFDC_SUBADC2_IXR_DCDR_UF_MASK 0x00200000U +#define XRFDC_SUBADC3_IXR_DCDR_OF_MASK 0x00400000U +#define XRFDC_SUBADC3_IXR_DCDR_UF_MASK 0x00800000U +#define XRFDC_ADC_OVR_VOLTAGE_MASK 0x04000000U +#define XRFDC_ADC_OVR_RANGE_MASK 0x08000000U +#define XRFDC_ADC_CMODE_OVR_MASK 0x10000000U +#define XRFDC_ADC_CMODE_UNDR_MASK 0x20000000U +#define XRFDC_ADC_DAT_OVR_MASK 0x40000000U +#define XRFDC_ADC_FIFO_OVR_MASK 0x80000000U +#define XRFDC_DAC_MC_CFG2_OPCSCAS_MASK 0x0000F8F8U +#define XRFDC_DAC_MC_CFG3_CSGAIN_MASK 0x0000FFC0U +#define XRFDC_DAC_MC_CFG2_OPCSCAS_20MA 0x00004858U +#define XRFDC_DAC_MC_CFG3_CSGAIN_20MA 0x000087C0U +#define XRFDC_DAC_MC_CFG2_OPCSCAS_32MA 0x0000A0D8U +#define XRFDC_DAC_MC_CFG3_CSGAIN_32MA 0x0000FFC0U + +#define XRFDC_ADC_OVR_VOL_RANGE_SHIFT 24U +#define XRFDC_ADC_DAT_FIFO_OVR_SHIFT 16U +#define XRFDC_ADC_SUBADC_DCDR_SHIFT 16U +#define XRFDC_DATA_PATH_SHIFT 4U +#define XRFDC_ADC_CMODE_SHIFT 10U + +#define XRFDC_DAC_TILE_DRP_ADDR(X) (0x6000U + (X * 0x4000U)) +#define XRFDC_DAC_TILE_CTRL_STATS_ADDR(X) (0x4000U + (X * 0x4000U)) +#define XRFDC_ADC_TILE_DRP_ADDR(X) (0x16000U + (X * 0x4000U)) +#define XRFDC_ADC_TILE_CTRL_STATS_ADDR(X) (0x14000U + (X * 0x4000U)) +#define XRFDC_CTRL_STATS_OFFSET 0x0U +#define XRFDC_HSCOM_ADDR 0x1C00U +#define XRFDC_BLOCK_ADDR_OFFSET(X) (X * 0x400U) +#define XRFDC_TILE_DRP_OFFSET 0x2000U + + +/***************** Macros (Inline Functions) Definitions *********************/ +#define XRFdc_In64 metal_io_read64 +#define XRFdc_Out64 metal_io_write64 + +#define XRFdc_In32 metal_io_read32 +#define XRFdc_Out32 metal_io_write32 + +#define XRFdc_In16 metal_io_read16 +#define XRFdc_Out16 metal_io_write16 + +#define XRFdc_In8 metal_io_read8 +#define XRFdc_Out8 metal_io_write8 + +/****************************************************************************/ +/** +* Read a register. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param BaseAddress contains the base address of the device. +* @param RegOffset contains the offset from the 1st register of the +* device to the target register. +* +* @return The value read from the register. +* +* @note C-Style signature: +* u32 XRFdc_ReadReg64(XRFdc *InstancePtr. u32 BaseAddress, s32 RegOffset) +* +******************************************************************************/ +#define XRFdc_ReadReg64(InstancePtr, BaseAddress, RegOffset) \ + XRFdc_In64(InstancePtr->io, ((u32)RegOffset + (u32)BaseAddress)) + +/***************************************************************************/ +/** +* Write to a register. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param BaseAddress contains the base address of the device. +* @param RegOffset contains the offset from the 1st register of the +* device to target register. +* @param RegisterValue is the value to be written to the register. +* +* @return None. +* +* @note C-Style signature: +* void XRFdc_WriteReg64(XRFdc *InstancePtr, u32 BaseAddress, s32 RegOffset, +* u64 RegisterValue) +* +******************************************************************************/ +#define XRFdc_WriteReg64(InstancePtr, BaseAddress, RegOffset, RegisterValue) \ + XRFdc_Out64((InstancePtr->io), ((u32)RegOffset + (u32)BaseAddress), \ + (u32)(RegisterValue)) + +/****************************************************************************/ +/** +* Read a register. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param BaseAddress contains the base address of the device. +* @param RegOffset contains the offset from the 1st register of the +* device to the target register. +* +* @return The value read from the register. +* +* @note C-Style signature: +* u32 XRFdc_ReadReg(XRFdc *InstancePtr, u32 BaseAddress. int RegOffset) +* +******************************************************************************/ +#define XRFdc_ReadReg(InstancePtr, BaseAddress, RegOffset) \ + XRFdc_In32((InstancePtr->io), ((u32)BaseAddress + (u32)RegOffset)) + +/***************************************************************************/ +/** +* Write to a register. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param BaseAddress contains the base address of the device. +* @param RegOffset contains the offset from the 1st register of the +* device to target register. +* @param RegisterValue is the value to be written to the register. +* +* @return None. +* +* @note C-Style signature: +* void XRFdc_WriteReg(XRFdc *InstancePtr, u32 BaseAddress, int RegOffset, +* u32 RegisterValue) +* +******************************************************************************/ +#define XRFdc_WriteReg(InstancePtr, BaseAddress, RegOffset, RegisterValue) \ + XRFdc_Out32((InstancePtr->io), ((u32)RegOffset + (u32)BaseAddress), \ + (u32)(RegisterValue)) + +/****************************************************************************/ +/** +* Read a register. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param BaseAddress contains the base address of the device. +* @param RegOffset contains the offset from the 1st register of the +* device to the target register. +* +* @return The value read from the register. +* +* @note C-Style signature: +* u16 XRFdc_ReadReg16(XRFdc *InstancePtr, u32 BaseAddress. int RegOffset) +* +******************************************************************************/ +#define XRFdc_ReadReg16(InstancePtr, BaseAddress, RegOffset) \ + XRFdc_In16((InstancePtr->io), ((u32)RegOffset + (u32)BaseAddress)) + +/***************************************************************************/ +/** +* Write to a register. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param BaseAddress contains the base address of the device. +* @param RegOffset contains the offset from the 1st register of the +* device to target register. +* @param RegisterValue is the value to be written to the register. +* +* @return None. +* +* @note C-Style signature: +* void XRFdc_WriteReg16(XRFdc *InstancePtr, u32 BaseAddress, int RegOffset, +* u16 RegisterValue) +* +******************************************************************************/ +#define XRFdc_WriteReg16(InstancePtr, BaseAddress, RegOffset, RegisterValue) \ + XRFdc_Out16((InstancePtr->io), ((u32)RegOffset + (u32)BaseAddress), \ + (u32)(RegisterValue)) + +/****************************************************************************/ +/** +* Read a register. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param BaseAddress contains the base address of the device. +* @param RegOffset contains the offset from the 1st register of the +* device to the target register. +* +* @return The value read from the register. +* +* @note C-Style signature: +* u8 XRFdc_ReadReg8(XRFdc *InstancePtr, u32 BaseAddress. int RegOffset) +* +******************************************************************************/ +#define XRFdc_ReadReg8(InstancePtr, BaseAddress, RegOffset) \ + XRFdc_In8((InstancePtr->io), ((u32)RegOffset + (u32)BaseAddress)) + +/***************************************************************************/ +/** +* Write to a register. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param BaseAddress contains the base address of the device. +* @param RegOffset contains the offset from the 1st register of the +* device to target register. +* @param RegisterValue is the value to be written to the register. +* +* @return None. +* +* @note C-Style signature: +* void XRFdc_WriteReg8(XRFdc *InstancePtr, u32 BaseAddress, int RegOffset, +* u8 RegisterValue) +* +******************************************************************************/ +#define XRFdc_WriteReg8(InstancePtr, BaseAddress, RegOffset, RegisterValue) \ + XRFdc_Out8((InstancePtr->io), ((u32)RegOffset + (u32)BaseAddress), \ + (u32)(RegisterValue)) + +#ifdef __cplusplus +} +#endif + +#endif /* RFDC_HW_H_ */ +/** @} */ diff --git a/mpm/include/mpm/rfdc/xrfdc_mts.h b/mpm/include/mpm/rfdc/xrfdc_mts.h new file mode 100644 index 000000000..ef56a6193 --- /dev/null +++ b/mpm/include/mpm/rfdc/xrfdc_mts.h @@ -0,0 +1,166 @@ +/****************************************************************************** +* +* Copyright (C) 2018-2019 Xilinx, Inc. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* XILINX BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Except as contained in this notice, the name of the Xilinx shall not be used +* in advertising or otherwise to promote the sale, use or other dealings in +* this Software without prior written authorization from Xilinx. +* +******************************************************************************/ +/*****************************************************************************/ +/** +* +* @file xrfdc_mts.c +* @addtogroup xrfdc_v6_0 +* @{ +* +* Contains the multi tile sync related structures, Macros of the XRFdc driver. +* +* <pre> +* MODIFICATION HISTORY: +* +* Ver Who Date Changes +* ----- --- -------- ----------------------------------------------- +* 3.1 jm 01/24/18 Initial release +* 3.2 jm 03/12/18 Fixed DAC latency calculation. +* jm 03/12/18 Added support for reloading DTC scans. +* jm 03/12/18 Add option to configure sysref capture after MTS. +* 4.0 sk 04/09/18 Added API to enable/disable the sysref. +* rk 04/17/18 Adjust calculated latency by sysref period, where doing +* so results in closer alignment to the target latency. +* 5.0 sk 08/03/18 Fixed MISRAC warnings. +* sk 08/03/18 Check for Block0 enable for tiles participating in MTS. +* sk 08/24/18 Reorganize the code to improve readability and +* optimization. +* 6.0 cog 02/17/19 Added XRFdc_GetMTSEnable API. +* +* </pre> +* +******************************************************************************/ +#ifndef RFDC_MTS_H_ +#define RFDC_MTS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/***************************** Include Files *********************************/ + +#include "xrfdc.h" + +/************************** Constant Definitions *****************************/ + +#define XRFDC_MTS_RMW(read, mask, data) (((read) & ~(mask)) | ((data) & (mask))) +#define XRFDC_MTS_FIELD(data, mask, shift) (((data) & (mask)) >> (shift)) + +/**************************** Type Definitions *******************************/ + +typedef struct { + u32 RefTile; + u32 IsPLL; + int Target[4]; + int Scan_Mode; + int DTC_Code[4]; + int Num_Windows[4]; + int Max_Gap[4]; + int Min_Gap[4]; + int Max_Overlap[4]; +} XRFdc_MTS_DTC_Settings; + +typedef struct { + u32 RefTile; + u32 Tiles; + int Target_Latency; + int Offset[4]; + int Latency[4]; + int Marker_Delay; + int SysRef_Enable; + XRFdc_MTS_DTC_Settings DTC_Set_PLL; + XRFdc_MTS_DTC_Settings DTC_Set_T1; +} XRFdc_MultiConverter_Sync_Config; + +typedef struct { + u32 Count[4]; + u32 Loc[4]; +} XRFdc_MTS_Marker; + +/***************** Macros (Inline Functions) Definitions *********************/ + +#define XRFDC_MTS_SYSREF_DISABLE 0U +#define XRFDC_MTS_SYSREF_ENABLE 1U + +#define XRFDC_MTS_NUM_DTC 128U +#define XRFDC_MTS_REF_TARGET 64U +#define XRFDC_MTS_MAX_CODE 16U +#define XRFDC_MTS_MIN_GAP_T1 10U +#define XRFDC_MTS_MIN_GAP_PLL 5U +#define XRFDC_MTS_SR_TIMEOUT 4096U +#define XRFDC_MTS_DTC_COUNT 10U +#define XRFDC_MTS_MARKER_COUNT 4U +#define XRFDC_MTS_SCAN_INIT 0U +#define XRFDC_MTS_SCAN_RELOAD 1U +#define XRFDC_MTS_SRCOUNT_TIMEOUT 1000U +#define XRFDC_MTS_DELAY_MAX 31U +#define XRFDC_MTS_CHECK_ALL_FIFOS 0U + +#define XRFDC_MTS_SRCAP_T1_EN 0x4000U +#define XRFDC_MTS_SRCAP_T1_RST 0x0800U +#define XRFDC_MTS_SRFLAG_T1 0x4U +#define XRFDC_MTS_SRFLAG_PLL 0x2U +#define XRFDC_MTS_FIFO_DEFAULT 0x0000U +#define XRFDC_MTS_FIFO_ENABLE 0x0003U +#define XRFDC_MTS_FIFO_DISABLE 0x0002U +#define XRFDC_MTS_AMARK_LOC_S 0x10U +#define XRFDC_MTS_AMARK_DONE_S 0x14U +#define XRFDC_MTS_DLY_ALIGNER 0x28U + +/* Error Codes */ +#define XRFDC_MTS_OK 0U +#define XRFDC_MTS_NOT_SUPPORTED 1U +#define XRFDC_MTS_TIMEOUT 2U +#define XRFDC_MTS_MARKER_RUN 4U +#define XRFDC_MTS_MARKER_MISM 8U +#define XRFDC_MTS_DELAY_OVER 16U +#define XRFDC_MTS_TARGET_LOW 32U +#define XRFDC_MTS_IP_NOT_READY 64U +#define XRFDC_MTS_DTC_INVALID 128U +#define XRFDC_MTS_NOT_ENABLED 512U +#define XRFDC_MTS_SYSREF_GATE_ERROR 2048U +#define XRFDC_MTS_SYSREF_FREQ_NDONE 4096U + +/************************** Function Prototypes ******************************/ + +u32 XRFdc_MultiConverter_Sync(XRFdc *InstancePtr, u32 Type, + XRFdc_MultiConverter_Sync_Config *ConfigPtr); +void XRFdc_MultiConverter_Init(XRFdc_MultiConverter_Sync_Config *ConfigPtr, + int *PLL_CodesPtr, int *T1_CodesPtr); +u32 XRFdc_MTS_Sysref_Config(XRFdc *InstancePtr, + XRFdc_MultiConverter_Sync_Config *DACSyncConfigPtr, + XRFdc_MultiConverter_Sync_Config *ADCSyncConfigPtr, u32 SysRefEnable); +u32 XRFdc_GetMTSEnable(XRFdc *InstancePtr, u32 Type,u32 Tile, u32 *EnablePtr); + + +#ifdef __cplusplus +} +#endif + +#endif /* RFDC_MTS_H_ */ +/** @} */ diff --git a/mpm/lib/CMakeLists.txt b/mpm/lib/CMakeLists.txt index 940a23138..f127af0a6 100644 --- a/mpm/lib/CMakeLists.txt +++ b/mpm/lib/CMakeLists.txt @@ -18,6 +18,10 @@ elseif(ENABLE_E320 OR ENABLE_E300) add_subdirectory(catalina) endif(ENABLE_MYKONOS) +if(ENABLE_X400) + add_subdirectory(rfdc) +endif(ENABLE_X400) + USRP_PERIPHS_ADD_OBJECT(periphs exception.cpp ${UHD_HOST_ROOT}/lib/exception.cpp diff --git a/mpm/lib/i2c/i2cdev_iface.cpp b/mpm/lib/i2c/i2cdev_iface.cpp index 9d2c6b4b2..b723dea06 100644 --- a/mpm/lib/i2c/i2cdev_iface.cpp +++ b/mpm/lib/i2c/i2cdev_iface.cpp @@ -64,23 +64,29 @@ public: return ret; } - int transfer(std::vector<uint8_t>* tx, std::vector<uint8_t>* rx, bool do_close) + std::vector<uint8_t> transfer( + std::vector<uint8_t>& tx, size_t num_rx_bytes, bool do_close) { uint8_t *tx_data = NULL, *rx_data = NULL; size_t tx_len = 0, rx_len = 0; + std::vector<uint8_t> rx(num_rx_bytes); - if (tx) { - tx_data = tx->data(); - tx_len = tx->size(); + if (!tx.empty()) { + tx_data = tx.data(); + tx_len = tx.size(); } - if (rx) { - rx_data = rx->data(); - rx_len = rx->size(); + if (num_rx_bytes) { + rx_data = rx.data(); + rx_len = rx.size(); } - int ret = transfer(tx_data, tx_len, rx_data, rx_len, do_close); - return ret; + const int err = transfer(tx_data, tx_len, rx_data, num_rx_bytes, do_close); + if (err) { + throw mpm::runtime_error("I2C Transaction failed!"); + } + + return rx; } private: diff --git a/mpm/lib/rfdc/CMakeLists.txt b/mpm/lib/rfdc/CMakeLists.txt new file mode 100644 index 000000000..a7401fd7b --- /dev/null +++ b/mpm/lib/rfdc/CMakeLists.txt @@ -0,0 +1,19 @@ +# +# Copyright 2019 Ettus Research, National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0 +# +set(RFDC_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/rfdc_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rfdc_throw.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/xrfdc_clock.c + ${CMAKE_CURRENT_SOURCE_DIR}/xrfdc_g.c + ${CMAKE_CURRENT_SOURCE_DIR}/xrfdc_intr.c + ${CMAKE_CURRENT_SOURCE_DIR}/xrfdc_mb.c + ${CMAKE_CURRENT_SOURCE_DIR}/xrfdc_mixer.c + ${CMAKE_CURRENT_SOURCE_DIR}/xrfdc_mts.c + ${CMAKE_CURRENT_SOURCE_DIR}/xrfdc_sinit.c + ${CMAKE_CURRENT_SOURCE_DIR}/xrfdc.c +) + +USRP_PERIPHS_ADD_OBJECT(rfdc ${RFDC_SOURCES}) diff --git a/mpm/lib/rfdc/README.md b/mpm/lib/rfdc/README.md new file mode 100644 index 000000000..ecf4fc054 --- /dev/null +++ b/mpm/lib/rfdc/README.md @@ -0,0 +1,6 @@ +The `xrfdc*.c` files here (and the files `mpm/include/mpm/rfdc/xrfdc*.h`) are derived from files in the `xilinx-v2019.1` tag of [embeddedsw](https://github.com/Xilinx/embeddedsw/), with the following changes: +* Include paths +* `Xil_Assert` macros in `xrfdc.h` were replaced with custom functionality. + * See `patches/xrfdc.h.patch` for the patch applied to that file. +* Call `closedir` in `xrfdc_sinit.c` + * See `patches/xrfdc_sinic.c.patch` for the patch diff --git a/mpm/lib/rfdc/patches/xrfdc.h.patch b/mpm/lib/rfdc/patches/xrfdc.h.patch new file mode 100644 index 000000000..d2fceb13a --- /dev/null +++ b/mpm/lib/rfdc/patches/xrfdc.h.patch @@ -0,0 +1,53 @@ +--- embeddedsw/XilinxProcessorIPLib/drivers/rfdc/src/xrfdc.h 2020-07-16 16:23:14.839402600 -0500 ++++ uhddev/mpm/include/mpm/rfdc/xrfdc.h 2020-08-17 12:31:24.477432400 -0500 +@@ -235,6 +235,7 @@ + + /***************************** Include Files *********************************/ + ++#include "rfdc_throw.h" + #include <stdlib.h> + #include <stdint.h> + +@@ -650,24 +651,24 @@ + + /***************** Macros (Inline Functions) Definitions *********************/ + +-#ifndef __BAREMETAL__ +-#define Xil_AssertNonvoid(Expression) \ +-{ \ +- if (!(Expression)) { \ +- while (1); \ +- } \ +-} +-#define Xil_AssertVoid(Expression) \ +-{ \ +- if (!(Expression)) { \ +- while (1); \ +- } \ +-} +-#define Xil_AssertVoidAlways() \ +-{ \ +- while (1); \ +-} +-#endif ++# ifndef __BAREMETAL__ ++# define Xil_AssertNonvoid(Expression) \ ++ { \ ++ if (!(Expression)) { \ ++ rfdc_throw(#Expression); \ ++ } \ ++ } ++# define Xil_AssertVoid(Expression) \ ++ { \ ++ if (!(Expression)) { \ ++ rfdc_throw(#Expression); \ ++ } \ ++ } ++# define Xil_AssertVoidAlways() \ ++ { \ ++ rfdc_throw("Assert false"); \ ++ } ++# endif + + #define MAX(x,y) (x>y)?x:y + #define MIN(x,y) (x<y)?x:y diff --git a/mpm/lib/rfdc/patches/xrfdc_sinit.c.patch b/mpm/lib/rfdc/patches/xrfdc_sinit.c.patch new file mode 100644 index 000000000..69a97e5df --- /dev/null +++ b/mpm/lib/rfdc/patches/xrfdc_sinit.c.patch @@ -0,0 +1,10 @@ +--- ../embeddedsw/XilinxProcessorIPLib/drivers/rfdc/src/xrfdc_sinit.c 2021-01-14 10:22:52.195957400 -0600 ++++ mpm/lib/rfdc/xrfdc_sinit.c 2021-01-14 10:24:59.611972600 -0600 +@@ -180,6 +180,7 @@ + metal_device_close(DevicePtr); + } + } ++ closedir(DirPtr); + } + return Status; + } diff --git a/mpm/lib/rfdc/rfdc_ctrl.cpp b/mpm/lib/rfdc/rfdc_ctrl.cpp new file mode 100644 index 000000000..94cd85c73 --- /dev/null +++ b/mpm/lib/rfdc/rfdc_ctrl.cpp @@ -0,0 +1,745 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include "mpm/rfdc/rfdc_ctrl.hpp" +#include <mpm/exception.hpp> +#include <set> + +#define BUS_NAME "platform" + +namespace mpm { namespace rfdc { + +rfdc_ctrl::rfdc_ctrl() +{ + rfdc_inst_ptr = &rfdc_inst; + rfdc_inst_ptr->device = nullptr; + rfdc_inst_ptr->io = nullptr; + + // Populates default values to the struct + XRFdc_MultiConverter_Init(&rfdc_dac_sync_config, nullptr, nullptr); + XRFdc_MultiConverter_Init(&rfdc_adc_sync_config, nullptr, nullptr); +} + +rfdc_ctrl::~rfdc_ctrl() +{ + if (rfdc_inst_ptr && rfdc_inst_ptr->device) { + metal_device_close(rfdc_inst_ptr->device); + } + if (metal_init_complete) { + metal_finish(); + } +} + +void rfdc_ctrl::init(uint16_t rfdc_device_id) +{ + XRFdc_Config* config_ptr; + char device_name[NAME_MAX]; + + this->rfdc_device_id = rfdc_device_id; + + struct metal_init_params init_param = METAL_INIT_DEFAULTS; + + if (metal_init(&init_param)) { + throw mpm::runtime_error("Failed to run metal initialization for rfdc.\n"); + } + metal_init_complete = true; + + /* Get configuration data for te RFdc device */ + /* config_ptr is an entry of the XRFdc_ConfigTablePtr array managed by xrfdc_sinit.c + * This memory is not explicitly freed because we do not have access to + * XRFdc_ConfigTablePtr in this scope. */ + config_ptr = XRFdc_LookupConfig(rfdc_device_id); + if (config_ptr == NULL) { + throw mpm::runtime_error("Rfdc config lookup failed.\n"); + } + + /* Initializes the controller with loaded config information */ + if (XRFdc_CfgInitialize(rfdc_inst_ptr, config_ptr) != XRFDC_SUCCESS) { + throw mpm::runtime_error("Rfdc controller init failed.\n"); + } + + /* Set UpdateMixerScale into valid state. For some reason the + XRFdc config functions do not set this value. It will be + overwritten when XRFdc_SetMixerSettings is called next. */ + rfdc_inst_ptr->UpdateMixerScale = 0; + + if (XRFdc_GetDeviceNameByDeviceId(device_name, rfdc_device_id) < 0) { + throw mpm::runtime_error("Failed to find rfdc device with device id \n"); + } + + if (metal_device_open(BUS_NAME, device_name, &rfdc_inst_ptr->device)) { + throw mpm::runtime_error("Failed to open device.\n"); + } + + /* Map RFDC device IO region. 0 is the IO region index on the device. */ + rfdc_inst_ptr->io = metal_device_io_region(rfdc_inst_ptr->device, 0); + if (!rfdc_inst_ptr->io) { + throw mpm::runtime_error("Failed to map RFDC regio\n"); + } + + /* Set all gain threshold stickies to manual clear mode */ + for (int tile_id = 0; tile_id <= XRFDC_TILE_ID_MAX; tile_id++) { + for (int block_id = 0; block_id <= XRFDC_BLOCK_ID_MAX; block_id++) { + for (int threshold_id = 0; threshold_id < THRESHOLDS_PER_BLOCK; + threshold_id++) { + threshold_clr_modes[tile_id][block_id][threshold_id] = + THRESHOLD_CLRMD_UNKNOWN; + } + set_threshold_clr_mode( + tile_id, block_id, THRESHOLD_BOTH, THRESHOLD_CLRMD_MANUAL); + } + } +} + +bool rfdc_ctrl::startup_tile(int tile_id, bool is_dac) +{ + return XRFdc_StartUp(rfdc_inst_ptr, is_dac, tile_id) == XRFDC_SUCCESS; +} + +bool rfdc_ctrl::shutdown_tile(int tile_id, bool is_dac) +{ + return XRFdc_Shutdown(rfdc_inst_ptr, is_dac, tile_id) == XRFDC_SUCCESS; +} + +bool rfdc_ctrl::reset_tile(int tile_id, bool is_dac) +{ + return XRFdc_Reset(rfdc_inst_ptr, is_dac, tile_id) == XRFDC_SUCCESS; +} + +bool rfdc_ctrl::trigger_update_event( + uint32_t tile_id, uint32_t block_id, bool is_dac, event_type_options event_type) +{ + return XRFdc_UpdateEvent(rfdc_inst_ptr, is_dac, tile_id, block_id, event_type) + == XRFDC_SUCCESS; +} + +bool rfdc_ctrl::reset_mixer_settings(uint32_t tile_id, uint32_t block_id, bool is_dac) +{ + XRFdc_Mixer_Settings mixer_settings; + + mixer_settings.Freq = 200; + mixer_settings.PhaseOffset = 0; + mixer_settings.EventSource = XRFDC_EVNT_SRC_SYSREF; + mixer_settings.CoarseMixFreq = 16; + mixer_settings.MixerMode = is_dac ? MIXER_MODE_C2R : MIXER_MODE_R2C; + mixer_settings.FineMixerScale = 0; + mixer_settings.MixerType = XRFDC_MIXER_TYPE_FINE; + + return (XRFdc_SetMixerSettings( + rfdc_inst_ptr, is_dac, tile_id, block_id, &mixer_settings) + == XRFDC_SUCCESS); +} + +bool rfdc_ctrl::set_gain_enable( + uint32_t tile_id, uint32_t block_id, bool is_dac, bool enable) +{ + XRFdc_QMC_Settings qmc_settings; + + // Get current QMC settings for the values that will not be changed + if (XRFdc_GetQMCSettings(rfdc_inst_ptr, is_dac, tile_id, block_id, &qmc_settings) + != XRFDC_SUCCESS) { + return false; + } + + qmc_settings.EnableGain = enable; + // Update the setting on a SYSREF trigger + qmc_settings.EventSource = XRFDC_EVNT_SRC_SYSREF; + + return (XRFdc_SetQMCSettings(rfdc_inst_ptr, is_dac, tile_id, block_id, &qmc_settings) + == XRFDC_SUCCESS); +} + +bool rfdc_ctrl::set_gain(uint32_t tile_id, uint32_t block_id, bool is_dac, double gain) +{ + XRFdc_QMC_Settings qmc_settings; + + // Get current QMC settings for the values that will not be changed + if (XRFdc_GetQMCSettings(rfdc_inst_ptr, is_dac, tile_id, block_id, &qmc_settings) + != XRFDC_SUCCESS) { + return false; + } + + qmc_settings.EnableGain = 1; + qmc_settings.GainCorrectionFactor = gain; + // Update the setting on a SYSREF trigger + qmc_settings.EventSource = XRFDC_EVNT_SRC_SYSREF; + + return (XRFdc_SetQMCSettings(rfdc_inst_ptr, is_dac, tile_id, block_id, &qmc_settings) + == XRFDC_SUCCESS); +} + +bool rfdc_ctrl::set_threshold_settings(uint32_t tile_id, + uint32_t block_id, + threshold_id_options threshold_id, + threshold_mode_options mode, + uint32_t average_val, + uint32_t under_val, + uint32_t over_val) +{ + XRFdc_Threshold_Settings threshold_settings; + + // Get current threshold settings for the values that will not be changed + if (XRFdc_GetThresholdSettings(rfdc_inst_ptr, tile_id, block_id, &threshold_settings) + != XRFDC_SUCCESS) { + return false; + } + threshold_settings.UpdateThreshold = threshold_id; + + // Index 0 and 1 of the threshold settings struct correspond to threshold 0 and 1. + if (threshold_id == THRESHOLD_0 || threshold_id == THRESHOLD_BOTH) { + threshold_settings.ThresholdMode[0] = mode; + threshold_settings.ThresholdAvgVal[0] = average_val; + threshold_settings.ThresholdUnderVal[0] = under_val; + threshold_settings.ThresholdOverVal[0] = over_val; + } + if (threshold_id == THRESHOLD_1 || threshold_id == THRESHOLD_BOTH) { + threshold_settings.ThresholdMode[1] = mode; + threshold_settings.ThresholdAvgVal[1] = average_val; + threshold_settings.ThresholdUnderVal[1] = under_val; + threshold_settings.ThresholdOverVal[1] = over_val; + } + + return XRFdc_SetThresholdSettings( + rfdc_inst_ptr, tile_id, block_id, &threshold_settings) + == XRFDC_SUCCESS; +} + +bool rfdc_ctrl::clear_threshold_sticky( + uint32_t tile_id, uint32_t block_id, threshold_id_options threshold_id) +{ + bool result; + threshold_clr_mode_options old_clear_mode_0 = THRESHOLD_CLRMD_UNKNOWN, + old_clear_mode_1 = THRESHOLD_CLRMD_UNKNOWN; + + // Check current threshold clear mode + old_clear_mode_0 = get_threshold_clr_mode(tile_id, block_id, THRESHOLD_0); + old_clear_mode_1 = get_threshold_clr_mode(tile_id, block_id, THRESHOLD_1); + + // Set the clear mode to manual + if (!set_threshold_clr_mode( + tile_id, block_id, threshold_id, THRESHOLD_CLRMD_MANUAL)) { + return false; + } + + // Clear the sticky + // Do not return on a failure as the clear mode still needs to be returned to the + // previous value. + result = (XRFdc_ThresholdStickyClear(rfdc_inst_ptr, tile_id, block_id, threshold_id) + == XRFDC_SUCCESS); + + // Set the threshold clear mode back to the original setting + // If the old setting is the same or UNKNOWN this will do nothing. + result = result + && set_threshold_clr_mode(tile_id, block_id, THRESHOLD_0, old_clear_mode_0); + result = result + && set_threshold_clr_mode(tile_id, block_id, THRESHOLD_1, old_clear_mode_1); + return result; +} + +bool rfdc_ctrl::set_threshold_clr_mode(uint32_t tile_id, + uint32_t block_id, + threshold_id_options threshold_id, + threshold_clr_mode_options clear_mode) +{ + bool result; + bool mode_matches = false; + uint32_t old_clear_mode_0 = THRESHOLD_CLRMD_UNKNOWN, + old_clear_mode_1 = THRESHOLD_CLRMD_UNKNOWN; + + if ((tile_id > XRFDC_TILE_ID_MAX) || (block_id > XRFDC_BLOCK_ID_MAX)) { + return false; + } + // Do not change the clear mode to UNKNOWN + if (clear_mode == THRESHOLD_CLRMD_UNKNOWN) { + return false; + } + + // Check current threshold clear mode + switch (threshold_id) { + case THRESHOLD_0: + old_clear_mode_0 = get_threshold_clr_mode(tile_id, block_id, THRESHOLD_0); + mode_matches = (old_clear_mode_0 == clear_mode); + break; + case THRESHOLD_1: + old_clear_mode_1 = get_threshold_clr_mode(tile_id, block_id, THRESHOLD_1); + mode_matches = (old_clear_mode_1 == clear_mode); + break; + case THRESHOLD_BOTH: + old_clear_mode_0 = get_threshold_clr_mode(tile_id, block_id, THRESHOLD_0); + old_clear_mode_1 = get_threshold_clr_mode(tile_id, block_id, THRESHOLD_1); + mode_matches = + ((old_clear_mode_0 == clear_mode) && (old_clear_mode_1 == clear_mode)); + break; + } + // Do not change the clear mode if the new value matches the existing value + if (mode_matches) { + return true; + } + + result = (XRFdc_SetThresholdClrMode( + rfdc_inst_ptr, tile_id, block_id, threshold_id, clear_mode) + == XRFDC_SUCCESS); + // If the setting was not successful, save the clear mode as unknown + if (!result) { + clear_mode = THRESHOLD_CLRMD_UNKNOWN; + } + + // Set the new threshold clear mode + switch (threshold_id) { + case THRESHOLD_0: + threshold_clr_modes[tile_id][block_id][0] = clear_mode; + break; + case THRESHOLD_1: + threshold_clr_modes[tile_id][block_id][1] = clear_mode; + break; + case THRESHOLD_BOTH: + threshold_clr_modes[tile_id][block_id][0] = clear_mode; + threshold_clr_modes[tile_id][block_id][1] = clear_mode; + break; + } + return result; +} + +rfdc_ctrl::threshold_clr_mode_options rfdc_ctrl::get_threshold_clr_mode( + uint32_t tile_id, uint32_t block_id, threshold_id_options threshold_id) +{ + int threshold_index; + + // The XRFdc Threshold ID values (1-2) do not match the array indexes (0-1) + if (threshold_id == THRESHOLD_0) { + threshold_index = 0; + } else if (threshold_id == THRESHOLD_1) { + threshold_index = 1; + } + // An invalid Threshold ID was given + else { + return THRESHOLD_CLRMD_UNKNOWN; + } + if ((tile_id > XRFDC_TILE_ID_MAX) || (block_id > XRFDC_BLOCK_ID_MAX) + || (threshold_index >= THRESHOLDS_PER_BLOCK)) { + return THRESHOLD_CLRMD_UNKNOWN; + } + + return threshold_clr_modes[tile_id][block_id][threshold_index]; +} + +bool rfdc_ctrl::set_decoder_mode( + uint32_t tile_id, uint32_t block_id, decoder_mode_options decoder_mode) +{ + return XRFdc_SetDecoderMode(rfdc_inst_ptr, tile_id, block_id, decoder_mode) + == XRFDC_SUCCESS; +} + +bool rfdc_ctrl::reset_nco_phase(uint32_t tile_id, uint32_t block_id, bool is_dac) +{ + XRFdc_Mixer_Settings mixer_settings; + + // Get current mixer settings for the values that will not be changed + if (XRFdc_GetMixerSettings(rfdc_inst_ptr, is_dac, tile_id, block_id, &mixer_settings) + != XRFDC_SUCCESS) { + return false; + } + + // Reset the phase on a SYSREF trigger + mixer_settings.EventSource = XRFDC_EVNT_SRC_SYSREF; + + // Set the mixer settings to set the NCO event source + if (XRFdc_SetMixerSettings(rfdc_inst_ptr, is_dac, tile_id, block_id, &mixer_settings) + != XRFDC_SUCCESS) { + return false; + } + + return (XRFdc_ResetNCOPhase(rfdc_inst_ptr, is_dac, tile_id, block_id) + == XRFDC_SUCCESS); +} + +bool rfdc_ctrl::set_nco_freq( + uint32_t tile_id, uint32_t block_id, bool is_dac, double freq) +{ + XRFdc_Mixer_Settings mixer_settings; + + // Get current mixer settings for the values that will not be changed + if (XRFdc_GetMixerSettings(rfdc_inst_ptr, is_dac, tile_id, block_id, &mixer_settings) + != XRFDC_SUCCESS) { + return false; + } + + // The XRFdc API expects the NCO frequency in MHz + mixer_settings.Freq = freq / 1e6; + // Only the fine mixer uses an NCO to shift the data frequency + mixer_settings.MixerType = XRFDC_MIXER_TYPE_FINE; + // Update the setting on a tile-wide event trigger + mixer_settings.EventSource = XRFDC_EVNT_SRC_TILE; + + return (XRFdc_SetMixerSettings( + rfdc_inst_ptr, is_dac, tile_id, block_id, &mixer_settings) + == XRFDC_SUCCESS) + && trigger_update_event(tile_id, block_id, is_dac, MIXER_EVENT); +} + +double rfdc_ctrl::get_nco_freq(uint32_t tile_id, uint32_t block_id, bool is_dac) +{ + XRFdc_Mixer_Settings mixer_settings; + + if (XRFdc_GetMixerSettings(rfdc_inst_ptr, is_dac, tile_id, block_id, &mixer_settings) + != XRFDC_SUCCESS) { + throw mpm::runtime_error("Error in RFDC code: Failed to get mixer settings"); + } + // The XRFdc API returns the frequency in MHz + return mixer_settings.Freq * 1e6; +} + +bool rfdc_ctrl::set_nco_event_src(uint32_t tile_id, uint32_t block_id, bool is_dac) +{ + XRFdc_Mixer_Settings mixer_settings; + + // Get current mixer settings for the values that will not be changed + if (XRFdc_GetMixerSettings(rfdc_inst_ptr, is_dac, tile_id, block_id, &mixer_settings) + != XRFDC_SUCCESS) { + return false; + } + + // Reset the phase on a SYSREF trigger + mixer_settings.EventSource = XRFDC_EVNT_SRC_SYSREF; + + // Set the mixer settings to set the NCO event source + return (XRFdc_SetMixerSettings(rfdc_inst_ptr, is_dac, tile_id, block_id, &mixer_settings) + == XRFDC_SUCCESS); +} + +bool rfdc_ctrl::set_mixer_mode( + uint32_t tile_id, uint32_t block_id, bool is_dac, mixer_mode_options mixer_mode) +{ + XRFdc_Mixer_Settings mixer_settings; + + // Get current mixer settings for the values that will not be changed + if (XRFdc_GetMixerSettings(rfdc_inst_ptr, is_dac, tile_id, block_id, &mixer_settings) + != XRFDC_SUCCESS) { + return false; + } + + mixer_settings.MixerMode = mixer_mode; + // Update the setting on a tile-wide event trigger + mixer_settings.EventSource = XRFDC_EVNT_SRC_TILE; + + return (XRFdc_SetMixerSettings( + rfdc_inst_ptr, is_dac, tile_id, block_id, &mixer_settings) + == XRFDC_SUCCESS) + && trigger_update_event(tile_id, block_id, is_dac, MIXER_EVENT); +} + +bool rfdc_ctrl::set_nyquist_zone( + uint32_t tile_id, uint32_t block_id, bool is_dac, nyquist_zone_options nyquist_zone) +{ + return XRFdc_SetNyquistZone(rfdc_inst_ptr, is_dac, tile_id, block_id, nyquist_zone) + == XRFDC_SUCCESS; +} + +bool rfdc_ctrl::set_calibration_mode( + uint32_t tile_id, uint32_t block_id, calibration_mode_options calibration_mode) +{ + return XRFdc_SetCalibrationMode(rfdc_inst_ptr, tile_id, block_id, calibration_mode) + == XRFDC_SUCCESS; +} + +bool rfdc_ctrl::enable_inverse_sinc_filter( + uint32_t tile_id, uint32_t block_id, bool enable) +{ + return XRFdc_SetInvSincFIR(rfdc_inst_ptr, tile_id, block_id, enable) == XRFDC_SUCCESS; +} + +bool rfdc_ctrl::set_sample_rate(uint32_t tile_id, bool is_dac, double sample_rate) +{ + // The XRFdc API expects the sample rate in MHz + double sample_rate_mhz = sample_rate / 1e6; + return XRFdc_DynamicPLLConfig(rfdc_inst_ptr, + is_dac, + tile_id, + XRFDC_EXTERNAL_CLK, + sample_rate_mhz, + sample_rate_mhz) + == XRFDC_SUCCESS; +} + +double rfdc_ctrl::get_sample_rate(uint32_t tile_id, uint32_t block_id, bool is_dac) +{ + XRFdc_BlockStatus block_status; + + if (XRFdc_GetBlockStatus(rfdc_inst_ptr, is_dac, tile_id, block_id, &block_status) + != XRFDC_SUCCESS) { + throw mpm::runtime_error("Error in RFDC code: Failed to get Block status"); + } + // The XRFdc API returns the sampling frequency in GHz + return block_status.SamplingFreq * 1e9; +} + +bool rfdc_ctrl::set_if(uint32_t tile_id, uint32_t block_id, bool is_dac, double if_freq) +{ + nyquist_zone_options nyquist_zone; + mixer_mode_options mixer_mode; + bool enable_inverse_sinc; + double nco_freq; + + double nyquist_cutoff = get_sample_rate(tile_id, block_id, is_dac) / 2; + + if (if_freq <= nyquist_cutoff) { // First Nyquist Zone + nyquist_zone = ODD_NYQUIST_ZONE; + mixer_mode = is_dac ? MIXER_MODE_C2R : MIXER_MODE_R2C; + enable_inverse_sinc = true; + } else { // Second Nyquist Zone + nyquist_zone = EVEN_NYQUIST_ZONE; + mixer_mode = is_dac ? MIXER_MODE_C2R : MIXER_MODE_R2C; + enable_inverse_sinc = false; + } + + return set_nyquist_zone(tile_id, block_id, is_dac, nyquist_zone) + && set_mixer_mode(tile_id, block_id, is_dac, mixer_mode) + && (is_dac ? enable_inverse_sinc_filter(tile_id, block_id, enable_inverse_sinc) + : true) + && set_nco_freq(tile_id, block_id, is_dac, if_freq) + && set_nco_event_src(tile_id, block_id, is_dac); +} + +bool rfdc_ctrl::set_decimation_factor( + uint32_t tile_id, uint32_t block_id, interp_decim_options decimation_factor) +{ + return XRFdc_SetDecimationFactor(rfdc_inst_ptr, tile_id, block_id, decimation_factor) + == XRFDC_SUCCESS; +} + +rfdc_ctrl::interp_decim_options rfdc_ctrl::get_decimation_factor( + uint32_t tile_id, uint32_t block_id) +{ + uint32_t decimation_factor; + if (XRFdc_GetDecimationFactor(rfdc_inst_ptr, tile_id, block_id, &decimation_factor) + != XRFDC_SUCCESS) { + throw mpm::runtime_error("Error in RFDC code: Failed to get decimation factor"); + } + return (interp_decim_options)decimation_factor; +} + +bool rfdc_ctrl::set_interpolation_factor( + uint32_t tile_id, uint32_t block_id, interp_decim_options interpolation_factor) +{ + return XRFdc_SetInterpolationFactor( + rfdc_inst_ptr, tile_id, block_id, interpolation_factor) + == XRFDC_SUCCESS; +} + +rfdc_ctrl::interp_decim_options rfdc_ctrl::get_interpolation_factor( + uint32_t tile_id, uint32_t block_id) +{ + uint32_t interpolation_factor; + if (XRFdc_GetInterpolationFactor( + rfdc_inst_ptr, tile_id, block_id, &interpolation_factor) + != XRFDC_SUCCESS) { + throw mpm::runtime_error( + "Error in RFDC code: Failed to get interpolation factor"); + } + return (interp_decim_options)interpolation_factor; +} + +bool rfdc_ctrl::set_data_read_rate( + uint32_t tile_id, uint32_t block_id, uint32_t valid_read_words) +{ + return XRFdc_SetFabRdVldWords(rfdc_inst_ptr, tile_id, block_id, valid_read_words) + == XRFDC_SUCCESS; +} + +uint32_t rfdc_ctrl::get_data_read_rate(uint32_t tile_id, uint32_t block_id, bool is_dac) +{ + uint32_t valid_read_words; + if (XRFdc_GetFabRdVldWords( + rfdc_inst_ptr, is_dac, tile_id, block_id, &valid_read_words) + != XRFDC_SUCCESS) { + throw mpm::runtime_error("Error in RFDC code: Failed to get data read rate"); + } + return valid_read_words; +} + +bool rfdc_ctrl::set_data_write_rate( + uint32_t tile_id, uint32_t block_id, uint32_t valid_write_words) +{ + return XRFdc_SetFabWrVldWords(rfdc_inst_ptr, tile_id, block_id, valid_write_words) + == XRFDC_SUCCESS; +} + +uint32_t rfdc_ctrl::get_data_write_rate(uint32_t tile_id, uint32_t block_id, bool is_dac) +{ + uint32_t valid_write_words; + if (XRFdc_GetFabWrVldWords( + rfdc_inst_ptr, is_dac, tile_id, block_id, &valid_write_words) + != XRFDC_SUCCESS) { + throw mpm::runtime_error("Error in RFDC code: Failed to get data write rate"); + } + return valid_write_words; +} + +bool rfdc_ctrl::set_fabric_clk_div( + uint32_t tile_id, bool is_dac, fabric_clk_div_options divider) +{ + return XRFdc_SetFabClkOutDiv(rfdc_inst_ptr, is_dac, tile_id, divider) + == XRFDC_SUCCESS; +} + +rfdc_ctrl::fabric_clk_div_options rfdc_ctrl::get_fabric_clk_div( + uint32_t tile_id, bool is_dac) +{ + uint16_t divider; + if (XRFdc_GetFabClkOutDiv(rfdc_inst_ptr, is_dac, tile_id, ÷r) + != XRFDC_SUCCESS) { + throw mpm::runtime_error( + "Error in RFDC code: Failed to get fabric clock divider"); + } + return (fabric_clk_div_options)divider; +} + +bool rfdc_ctrl::set_data_fifo_state(uint32_t tile_id, bool is_dac, bool enable) +{ + return XRFdc_SetupFIFO(rfdc_inst_ptr, is_dac, tile_id, enable) == XRFDC_SUCCESS; +} + +bool rfdc_ctrl::get_data_fifo_state(uint32_t tile_id, bool is_dac) +{ + uint8_t enabled; + if (XRFdc_GetFIFOStatus(rfdc_inst_ptr, is_dac, tile_id, &enabled) != XRFDC_SUCCESS) { + throw mpm::runtime_error("Error in RFDC code: Failed to get FIFO status"); + } + return (bool)enabled; +} + +void rfdc_ctrl::clear_data_fifo_interrupts( + const uint32_t tile_id, const uint32_t block_id, const bool is_dac) +{ + if (XRFdc_IntrClr(rfdc_inst_ptr, + static_cast<u32>(is_dac), + tile_id, + block_id, + XRFDC_IXR_FIFOUSRDAT_MASK) + != XRFDC_SUCCESS) { + throw mpm::runtime_error( + "Error in RFDC code: Failed to clear data FIFO interrupts"); + } +} + +bool rfdc_ctrl::sync_tiles(const std::vector<uint32_t>& tiles, bool is_dac, uint32_t latency) +{ + XRFdc_MultiConverter_Sync_Config* sync_config = is_dac ? &rfdc_dac_sync_config + : &rfdc_adc_sync_config; + sync_config->Tiles = 0; + sync_config->Target_Latency = latency; + + for (auto tile = tiles.begin(); tile != tiles.end(); ++tile) { + // sync_config->Tiles is a bitmask, we need to "bump" each bit (0->1) + // that corresponds to the specified indices + sync_config->Tiles |= (1 << *tile); + } + + return XRFDC_MTS_OK + == XRFdc_MultiConverter_Sync( + &rfdc_inst, is_dac ? XRFDC_DAC_TILE : XRFDC_ADC_TILE, sync_config); +} + +uint32_t rfdc_ctrl::get_tile_latency(uint32_t tile_index, bool is_dac) +{ + XRFdc_MultiConverter_Sync_Config* sync_config = is_dac ? &rfdc_dac_sync_config + : &rfdc_adc_sync_config; + // If user has called sync with this tile_index, this + // attribute should be populated in our sync config + if ((1 << tile_index) & sync_config->Tiles) { + return sync_config->Latency[tile_index]; + } + if (is_dac) { + throw mpm::runtime_error("rfdc_ctrl: Failed to get DAC Tile Latency"); + } else { + throw mpm::runtime_error("rfdc_ctrl: Failed to get ADC Tile Latency"); + } +} + +uint32_t rfdc_ctrl::get_tile_offset(uint32_t tile_index, bool is_dac) +{ + XRFdc_MultiConverter_Sync_Config* sync_config = is_dac ? &rfdc_dac_sync_config + : &rfdc_adc_sync_config; + // If user has called sync with this tile_index, this + // attribute should be populated in our sync config + if ((1 << tile_index) & sync_config->Tiles) { + return sync_config->Offset[tile_index]; + } + if (is_dac) { + throw mpm::runtime_error("rfdc_ctrl: Failed to get DAC Tile Offset"); + } else { + throw mpm::runtime_error("rfdc_ctrl: Failed to get ADC Tile Offset"); + } +} + +void rfdc_ctrl::set_cal_frozen( + const uint32_t tile_id, const uint32_t block_id, const bool frozen) +{ + XRFdc_Cal_Freeze_Settings cal_freeze_settings; + cal_freeze_settings.CalFrozen = false; + cal_freeze_settings.DisableFreezePin = true; + cal_freeze_settings.FreezeCalibration = frozen; + if (XRFdc_SetCalFreeze(&rfdc_inst, tile_id, block_id, &cal_freeze_settings) + != XRFDC_SUCCESS) { + throw mpm::runtime_error( + "Error in RFDC code: Failed to set calibration freeze status"); + } +} + +bool rfdc_ctrl::get_cal_frozen(const uint32_t tile_id, const uint32_t block_id) +{ + XRFdc_Cal_Freeze_Settings cal_freeze_settings; + if (XRFdc_GetCalFreeze(&rfdc_inst, tile_id, block_id, &cal_freeze_settings) + != XRFDC_SUCCESS) { + throw mpm::runtime_error( + "Error in RFDC code: Failed to get calibration freeze status"); + } + return cal_freeze_settings.CalFrozen; +} + +void rfdc_ctrl::set_adc_cal_coefficients(uint32_t tile_id, uint32_t block_id, uint32_t cal_block, std::vector<uint32_t> coefs) +{ + if (coefs.size() != 8) + { + throw mpm::runtime_error("set_adc_cal_coefficients requires that exactly 8 coefficients be passed"); + } + + XRFdc_Calibration_Coefficients cs; + cs.Coeff0 = coefs[0]; + cs.Coeff1 = coefs[1]; + cs.Coeff2 = coefs[2]; + cs.Coeff3 = coefs[3]; + cs.Coeff4 = coefs[4]; + cs.Coeff5 = coefs[5]; + cs.Coeff6 = coefs[6]; + cs.Coeff7 = coefs[7]; + + if (XRFdc_SetCalCoefficients(&rfdc_inst, tile_id, block_id, cal_block, &cs) != XRFDC_SUCCESS) { + throw mpm::runtime_error("Error returned from XRFdc_SetCalCoefficients"); + } +} + +std::vector<uint32_t> rfdc_ctrl::get_adc_cal_coefficients(uint32_t tile_id, uint32_t block_id, uint32_t cal_block) +{ + std::vector<uint32_t> result; + XRFdc_Calibration_Coefficients cs; + if (XRFdc_GetCalCoefficients(&rfdc_inst, tile_id, block_id, cal_block, &cs) != XRFDC_SUCCESS) { + throw mpm::runtime_error("Error returned from XRFdc_GetCalCoefficients"); + } + + result.push_back(cs.Coeff0); + result.push_back(cs.Coeff1); + result.push_back(cs.Coeff2); + result.push_back(cs.Coeff3); + result.push_back(cs.Coeff4); + result.push_back(cs.Coeff5); + result.push_back(cs.Coeff6); + result.push_back(cs.Coeff7); + + return result; +} + +}} // namespace mpm::rfdc diff --git a/mpm/lib/rfdc/rfdc_throw.cpp b/mpm/lib/rfdc/rfdc_throw.cpp new file mode 100644 index 000000000..a50fe7c96 --- /dev/null +++ b/mpm/lib/rfdc/rfdc_throw.cpp @@ -0,0 +1,24 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +extern "C" { +#include "mpm/rfdc/rfdc_throw.h" +} +#include <mpm/exception.hpp> +#include <string> + +/** + * A function to throw MPM exceptions from within the Xilinx RFdc API + */ +void rfdc_throw(const char* msg) +{ + if (msg) { + std::string error_msg(msg); + throw mpm::assertion_error("Error in RFDC code: " + error_msg); + } else { + throw mpm::assertion_error("Error in RFDC code."); + } +} diff --git a/mpm/lib/rfdc/xrfdc.c b/mpm/lib/rfdc/xrfdc.c new file mode 100644 index 000000000..6261db774 --- /dev/null +++ b/mpm/lib/rfdc/xrfdc.c @@ -0,0 +1,5345 @@ +/****************************************************************************** +* +* Copyright (C) 2017-2019 Xilinx, Inc. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* XILINX BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Except as contained in this notice, the name of the Xilinx shall not be used +* in advertising or otherwise to promote the sale, use or other dealings in +* this Software without prior written authorization from Xilinx. +* +******************************************************************************/ +/*****************************************************************************/ +/** +* +* @file xrfdc.c +* @addtogroup xrfdc_v6_0 +* @{ +* +* Contains the interface functions of the XRFdc driver. +* See xrfdc.h for a detailed description of the device and driver. +* +* <pre> +* MODIFICATION HISTORY: +* +* Ver Who Date Changes +* ----- --- -------- ----------------------------------------------- +* 1.0 sk 05/16/17 Initial release +* 2.0 sk 08/09/17 Fixed coarse Mixer configuration settings +* CR# 977266, 977872. +* Return error for Slice Event on 4G ADC Block. +* 08/16/17 Add support for SYSREF and PL event sources. +* 08/18/17 Add API to enable and disable FIFO. +* 08/23/17 Add API to configure Nyquist zone. +* 08/30/17 Add additional info to BlockStatus. +* 08/30/17 Add support for Coarse Mixer BYPASS mode. +* 08/31/17 Removed Tile Reset Assert and Deassert. +* 09/07/17 Add support for negative NCO freq. +* 09/15/17 Fixed NCO freq precision issue. +* 09/15/17 Fixed Immediate Event source issue and also +* updated the Immediate Macro value to 0. +* 2.1 sk 09/15/17 Remove Libmetal library dependency for MB. +* sk 09/25/17 Modified XRFdc_GetBlockStatus API to give +* correct information and also updates the +* description for Vector Param in intr handler +* Add API to get Output current and removed +* GetTermVoltage and GetOutputCurr inline functions. +* 2.2 sk 10/05/17 Fixed XRFdc_GetNoOfADCBlocks API for 4GSPS. +* Enable the decoder clock based on decoder mode. +* Add API to get the current FIFO status. +* Updated XRFdc_DumpRegs API for better readability +* of output register dump. +* Add support for 4GSPS CoarseMixer frequency. +* 10/11/17 Modify float types to double to increase precision. +* 10/12/17 Update BlockStatus API to give current status. +* In BYPASS mode, input datatype can be Real or IQ, +* hence checked both while reading the mixer mode. +* 10/17/17 Fixed Set Threshold API Issue. +* 2.3 sk 11/06/17 Fixed PhaseOffset truncation issue. +* Provide user configurability for FineMixerScale. +* 11/08/17 Return error for DAC R2C mode and ADC C2R mode. +* 11/20/17 Fixed StartUp, Shutdown and Reset API for Tile_Id -1. +* 11/20/17 Remove unwanted ADC block checks in 4GSPS mode. +* 3.0 sk 12/11/17 Added DDC and DUC support. +* 12/13/17 Add CoarseMixMode field in Mixer_Settings structure. +* 12/15/17 Add support to switch calibration modes. +* 12/15/17 Add support for mixer frequencies > Fs/2 and < -Fs/2. +* sg 13/01/18 Added PLL and external clock switch support. +* Added API to get PLL lock status. +* Added API to get clock source. +* 3.1 jm 01/24/18 Add Multi-tile sync support. +* sk 01/25/18 Updated Set and Get Interpolation/Decimation factor +* API's to consider the actual factor value. +* 3.2 sk 02/02/18 Add API's to configure inverse-sinc. +* sk 02/27/18 Add API's to configure Multiband. +* sk 03/09/18 Update PLL structure in XRFdc_DynamicPLLConfig API. +* sk 03/09/18 Update ADC and DAC datatypes in Mixer API and use +* input datatype for ADC in threshold and QMC APIs. +* sk 03/09/18 Removed FIFO disable check in DDC and DUC APIs. +* sk 03/09/18 Add support for Marker event source for DAC block. +* sk 03/22/18 Updated PLL settings based on latest IP values. +* 4.0 sk 04/17/18 Corrected Set/Get MixerSettings API description for +* FineMixerScale parameter. +* sk 04/19/18 Enable VCO Auto selection while configuring the clock. +* sk 04/24/18 Add API to get PLL Configurations. +* sk 04/24/18 Add API to get the Link Coupling mode. +* sk 04/28/18 Implement timeouts for PLL Lock, Startup and shutdown. +* sk 05/30/18 Removed CalibrationMode check for DAC. +* sk 06/05/18 Updated minimum Ref clock value to 102.40625MHz. +* 5.0 sk 06/25/18 Update DAC min sampling rate to 500MHz and also update +* VCO Range, PLL_DIVIDER and PLL_FPDIV ranges. +* sk 06/25/18 Add XRFdc_GetFabClkOutDiv() API to read fabric clk div. +* Add Inline APIs XRFdc_CheckBlockEnabled(), +* XRFdc_CheckTileEnabled(). +* sk 07/06/18 Add support to dump HSCOM regs in XRFdc_DumpRegs() API +* sk 07/12/18 Fixed Multiband crossbar settings in C2C mode. +* sk 07/19/18 Add MixerType member to MixerSettings structure and +* Update Mixer Settings APIs to consider the MixerType +* variable. +* sk 07/19/18 Add XRFdc_GetMultibandConfig() API to read Multiband +* configuration. +* sk 07/20/18 Update the APIs to check the corresponding section +* (Digital/Analog)enable/disable. +* sk 07/26/18 Fixed Doxygen, coverity warnings. +* sk 08/03/18 Fixed MISRAC warnings. +* sk 08/24/18 Move mixer related APIs to xrfdc_mixer.c file. +* Define asserts for Linux, Re-arranged XRFdc_RestartIPSM, +* XRFdc_CfgInitialize() and XRFdc_MultiBand() APIs. +* Reorganize the code to improve readability and +* optimization. +* sk 09/24/18 Update powerup-state value based on PLL mode in +* XRFdc_DynamicPLLConfig() API. +* sk 10/10/18 Check for DigitalPath enable in XRFdc_GetNyquistZone() +* and XRFdc_GetCalibrationMode() APIs for Multiband. +* sk 10/13/18 Add support to read the REFCLKDIV param from design. +* Update XRFdc_SetPLLConfig() API to support range of +* REF_CLK_DIV values(1 to 4). +* 5.1 cog 01/29/19 Replace structure reference ADC checks with +* function. +* cog 01/29/19 Added XRFdc_SetDither() and XRFdc_GetDither() APIs. +* cog 01/29/19 Rename DataType for mixer input to MixerInputDataType +* for readability. +* cog 01/29/19 Refactoring of interpolation and decimation APIs and +* changed fabric rate for decimation X8 for non-high speed ADCs. +* cog 01/29/19 New inline functions to determine max & min sampling rates +* rates in PLL range checking. +* 6.0 cog 02/17/19 Added decimation & interpolation modes +* 02/17/19 Added Inverse-Sinc Second Nyquist Zone Support +* cog 02/17/19 Added new clock Distribution functionality. +* cog 02/17/19 Refactored to improve delay balancing in clock +* distribution. +* cog 02/17/19 Added delay calculation & metal log messages. +* cog 02/17/19 Added intratile clock settings. +* cog 02/17/19 Moved multiband to a new file xrfdc_mb.c +* cog 02/17/19 Moved clocking functionality to a new file xrfdc_clock.c +* cog 02/17/19 Added XRFdc_SetIMRPassMode() and XRFdc_SetIMRPassMode() APIs +* cog 02/17/19 Added XRFdc_SetDACMode() and XRFdc_GetDACMode() APIs +* cog 02/17/19 Added XRFdc_SetSignalDetector() and XRFdc_GetSignalDetector() APIs. +* cog 02/17/19 Added XRFdc_DisableCoefficientsOverride(), XRFdc_SetCalCoefficients +* and XRFdc_GetCalCoefficients APIs. +* cog 02/21/19 Added XRFdc_SetCalFreeze() and XRFdc_GetCalFreeze() APIs. +* cog 04/09/19 Changed Calibrtation coefficient override control register for OCB1. +* cog 04/15/19 Rename XRFdc_SetDACMode() and XRFdc_GetDACMode() APIs to +* XRFdc_SetDataPathMode() and XRFdc_GetDataPathMode() respectively. +* cog 04/30/19 Made Changes to the bypass calibration functionality to support Gen2 +* and below. +* +* </pre> +* +******************************************************************************/ + +/***************************** Include Files *********************************/ +#include "mpm/rfdc/xrfdc.h" + +/************************** Constant Definitions *****************************/ +#define XRFDC_PLL_LOCK_DLY_CNT 1000U + +/**************************** Type Definitions *******************************/ + +/***************** Macros (Inline Functions) Definitions *********************/ +static u32 XRFdc_RestartIPSM(XRFdc *InstancePtr, u32 Type, int Tile_Id, + u32 Start, u32 End); +static void StubHandler(void *CallBackRefPtr, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 StatusEvent); +static void XRFdc_ADCInitialize(XRFdc *InstancePtr); +static void XRFdc_DACInitialize(XRFdc *InstancePtr); +static void XRFdc_DACMBConfigInit(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id); +static void XRFdc_ADCMBConfigInit(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id); +static void XRFdc_UpdatePLLStruct(XRFdc *InstancePtr, u32 Type, u32 Tile_Id); +static u32 XRFdc_GetADCBlockStatus(XRFdc *InstancePtr, u32 BaseAddr, + u32 Tile_Id, u32 Block_Id, XRFdc_BlockStatus *BlockStatusPtr); +static u32 XRFdc_GetDACBlockStatus(XRFdc *InstancePtr, u32 BaseAddr, + u32 Tile_Id, u32 Block_Id, XRFdc_BlockStatus *BlockStatusPtr); +static void XRFdc_DumpHSCOMRegs(XRFdc *InstancePtr, u32 Type, int Tile_Id); +static void XRFdc_DumpDACRegs(XRFdc *InstancePtr, int Tile_Id); +static void XRFdc_DumpADCRegs(XRFdc *InstancePtr, int Tile_Id); +static u32 XRFdc_WaitForRestartClr(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 BaseAddr, u32 End); + +/************************** Function Prototypes ******************************/ + +/*****************************************************************************/ +/** +* +* Initializes a specific XRFdc instance such that the driver is ready to use. +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param ConfigPtr is a reference to a structure containing information +* about xrfdc. This function initializes an InstancePtr object +* for a specific device specified by the contents of Config. +* +* @return +* - XRFDC_SUCCESS if successful. +* +* @note The user needs to first call the XRFdc_LookupConfig() API +* which returns the Configuration structure pointer which is +* passed as a parameter to the XRFdc_CfgInitialize() API. +* +******************************************************************************/ +u32 XRFdc_CfgInitialize(XRFdc *InstancePtr, XRFdc_Config *ConfigPtr) +{ + u32 Status; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(ConfigPtr != NULL); + + InstancePtr->io = (struct metal_io_region *) + metal_allocate_memory(sizeof(struct metal_io_region)); + metal_io_init(InstancePtr->io, (void *)(metal_phys_addr_t)ConfigPtr->BaseAddr, + &ConfigPtr->BaseAddr, XRFDC_REGION_SIZE, (unsigned)(-1), 0, NULL); + + /* + * Set the values read from the device config and the base address. + */ + InstancePtr->BaseAddr = ConfigPtr->BaseAddr; + InstancePtr->RFdc_Config = *ConfigPtr; + InstancePtr->ADC4GSPS = ConfigPtr->ADCType; + InstancePtr->StatusHandler = StubHandler; + + /* Initialize ADC */ + XRFdc_ADCInitialize(InstancePtr); + + /* Initialize DAC */ + XRFdc_DACInitialize(InstancePtr); + + /* + * Indicate the instance is now ready to use and + * initialized without error. + */ + InstancePtr->IsReady = XRFDC_COMPONENT_IS_READY; + + Status = XRFDC_SUCCESS; + return Status; +} + +/*****************************************************************************/ +/** +* +* Initialize ADC Tiles. +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* +* @return +* - None. +* +* @note Static API used to initialize ADC Tiles +* +******************************************************************************/ +static void XRFdc_ADCInitialize(XRFdc *InstancePtr) +{ + u32 Tile_Id; + u32 Block_Id; + u8 MixerType; + + for (Tile_Id = XRFDC_TILE_ID0; Tile_Id < XRFDC_TILE_ID4; Tile_Id++) { + InstancePtr->ADC_Tile[Tile_Id].NumOfADCBlocks = 0U; + for (Block_Id = XRFDC_BLK_ID0; Block_Id < XRFDC_BLK_ID4; Block_Id++) { + if (XRFdc_IsADCBlockEnabled(InstancePtr, Tile_Id, Block_Id) != 0U) { + InstancePtr->ADC_Tile[Tile_Id].NumOfADCBlocks += 1U; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Analog_Datapath[Block_Id]. + AnalogPathEnabled = XRFDC_ANALOGPATH_ENABLE; + } + /* Initialize Data Type */ + if (InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id]. + ADCBlock_Analog_Config[Block_Id].MixMode == XRFDC_MIXER_MODE_BYPASS) { + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[Block_Id].MixerInputDataType = + InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id].ADCBlock_Digital_Config[Block_Id].MixerInputDataType; + } else { + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[Block_Id].MixerInputDataType = + InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id]. + ADCBlock_Analog_Config[Block_Id].MixMode; + } + /* Initialize MixerType */ + MixerType = InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id]. + ADCBlock_Digital_Config[Block_Id].MixerType; + InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Block_Id].Mixer_Settings.MixerType = MixerType; + + InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Block_Id].ConnectedIData = -1; + InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Block_Id].ConnectedQData = -1; + InstancePtr->ADC_Tile[Tile_Id].MultibandConfig = + InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id].MultibandConfig; + if (XRFdc_IsADCDigitalPathEnabled(InstancePtr, Tile_Id, Block_Id) != 0U) { + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[Block_Id].DigitalPathAvailable = + XRFDC_DIGITALPATH_ENABLE; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[Block_Id].DigitalPathEnabled = + XRFDC_DIGITALPATH_ENABLE; + /* Initialize ConnectedI/QData, MB Config */ + XRFdc_ADCMBConfigInit(InstancePtr, Tile_Id, Block_Id); + } + } + + /* Initialize PLL Structure */ + XRFdc_UpdatePLLStruct(InstancePtr, XRFDC_ADC_TILE, Tile_Id); + } +} + +/*****************************************************************************/ +/** +* +* Initialize DAC Tiles. +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* +* @return +* - None. +* +* @note Static API used to initialize DAC Tiles +* +******************************************************************************/ +static void XRFdc_DACInitialize(XRFdc *InstancePtr) +{ + u32 Tile_Id; + u32 Block_Id; + u8 MixerType; + + for (Tile_Id = XRFDC_TILE_ID0; Tile_Id < XRFDC_TILE_ID4; Tile_Id++) { + InstancePtr->DAC_Tile[Tile_Id].NumOfDACBlocks = 0U; + for (Block_Id = XRFDC_BLK_ID0; Block_Id < XRFDC_BLK_ID4; Block_Id++) { + if (XRFdc_IsDACBlockEnabled(InstancePtr, Tile_Id, Block_Id) != 0U) { + InstancePtr->DAC_Tile[Tile_Id].NumOfDACBlocks += 1U; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Analog_Datapath[Block_Id].AnalogPathEnabled = + XRFDC_ANALOGPATH_ENABLE; + } + /* Initialize Data Type */ + if (InstancePtr->RFdc_Config.DACTile_Config[Tile_Id]. + DACBlock_Analog_Config[Block_Id].MixMode == XRFDC_MIXER_MODE_BYPASS) { + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[Block_Id].MixerInputDataType = + InstancePtr->RFdc_Config.DACTile_Config[Tile_Id].DACBlock_Digital_Config[Block_Id].MixerInputDataType; + } else { + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[Block_Id].MixerInputDataType = + XRFDC_DATA_TYPE_IQ; + } + /* Initialize MixerType */ + MixerType = InstancePtr->RFdc_Config.DACTile_Config[Tile_Id]. + DACBlock_Digital_Config[Block_Id].MixerType; + InstancePtr->DAC_Tile[Tile_Id]. + DACBlock_Digital_Datapath[Block_Id].Mixer_Settings.MixerType = MixerType; + + InstancePtr->DAC_Tile[Tile_Id]. + DACBlock_Digital_Datapath[Block_Id].ConnectedIData = -1; + InstancePtr->DAC_Tile[Tile_Id]. + DACBlock_Digital_Datapath[Block_Id].ConnectedQData = -1; + InstancePtr->DAC_Tile[Tile_Id].MultibandConfig = + InstancePtr->RFdc_Config.DACTile_Config[Tile_Id].MultibandConfig; + if (XRFdc_IsDACDigitalPathEnabled(InstancePtr, Tile_Id, Block_Id) != 0U) { + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[Block_Id].DigitalPathAvailable = + XRFDC_DIGITALPATH_ENABLE; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[Block_Id].DigitalPathEnabled = + XRFDC_DIGITALPATH_ENABLE; + /* Initialize ConnectedI/QData, MB Config */ + XRFdc_DACMBConfigInit(InstancePtr, Tile_Id, Block_Id); + } + } + /* Initialize PLL Structure */ + XRFdc_UpdatePLLStruct(InstancePtr, XRFDC_DAC_TILE, Tile_Id); + } +} + +/*****************************************************************************/ +/** +* +* Initialize Multiband Configuration for DAC Tiles. +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3, and -1. +* @param Block_Id is DAC block number inside the tile. Valid values +* are 0-3. +* +* @return +* - None. +* +* @note Static API used to initialize DAC MB Config +* +******************************************************************************/ +static void XRFdc_DACMBConfigInit(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id) +{ + if (InstancePtr->RFdc_Config.DACTile_Config[Tile_Id]. + DACBlock_Analog_Config[Block_Id].MixMode == XRFDC_MIXER_MODE_C2C) { + /* Mixer Mode is C2C */ + switch (InstancePtr->DAC_Tile[Tile_Id].MultibandConfig) { + case XRFDC_MB_MODE_4X: + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id, XRFDC_BLK_ID0, XRFDC_BLK_ID1); + break; + case XRFDC_MB_MODE_2X_BLK01_BLK23: + case XRFDC_MB_MODE_2X_BLK01: + case XRFDC_MB_MODE_2X_BLK23: + if ((Block_Id == XRFDC_BLK_ID0) || (Block_Id == XRFDC_BLK_ID1)) { + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id, XRFDC_BLK_ID0, XRFDC_BLK_ID1); + } else { + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id, XRFDC_BLK_ID2, XRFDC_BLK_ID3); + } + break; + default: + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id, Block_Id, Block_Id + 1U); + break; + } + } else if (InstancePtr->RFdc_Config.DACTile_Config[Tile_Id]. + DACBlock_Analog_Config[Block_Id].MixMode == 0x0) { + /* Mixer Mode is C2R */ + switch (InstancePtr->DAC_Tile[Tile_Id].MultibandConfig) { + case XRFDC_MB_MODE_4X: + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id, XRFDC_BLK_ID0, -1); + break; + case XRFDC_MB_MODE_2X_BLK01_BLK23: + case XRFDC_MB_MODE_2X_BLK01: + case XRFDC_MB_MODE_2X_BLK23: + if ((Block_Id == XRFDC_BLK_ID0) || (Block_Id == XRFDC_BLK_ID1)) { + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id, XRFDC_BLK_ID0, -1); + } else { + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id, XRFDC_BLK_ID2, -1); + } + break; + default: + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id, Block_Id, -1); + break; + } + } else { + /* Mixer Mode is BYPASS */ + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id, Block_Id, -1); + } +} +/*****************************************************************************/ +/** +* +* Initialize Multiband Configuration for ADC Tiles. +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3, and -1. +* @param Block_Id is ADC block number inside the tile. Valid values +* are 0-3. +* +* @return +* - None. +* +* @note Static API used to initialize ADC MB Config +* +******************************************************************************/ +static void XRFdc_ADCMBConfigInit(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id) +{ + if (InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id]. + ADCBlock_Analog_Config[Block_Id].MixMode == XRFDC_MIXER_MODE_C2C) { + /* Mixer mode is C2C */ + switch (InstancePtr->ADC_Tile[Tile_Id].MultibandConfig) { + case XRFDC_MB_MODE_4X: + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id, XRFDC_BLK_ID0, XRFDC_BLK_ID1); + break; + case XRFDC_MB_MODE_2X_BLK01_BLK23: + case XRFDC_MB_MODE_2X_BLK01: + case XRFDC_MB_MODE_2X_BLK23: + if ((Block_Id == XRFDC_BLK_ID0) || (Block_Id == XRFDC_BLK_ID1)) { + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id, XRFDC_BLK_ID0, XRFDC_BLK_ID1); + } else { + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id, XRFDC_BLK_ID2, XRFDC_BLK_ID3); + } + break; + default: + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id, Block_Id, Block_Id + 1U); + break; + } + } else if (InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id]. + ADCBlock_Analog_Config[Block_Id].MixMode == 0x0) { + /* Mixer mode is R2C */ + switch (InstancePtr->ADC_Tile[Tile_Id].MultibandConfig) { + case XRFDC_MB_MODE_4X: + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id, XRFDC_BLK_ID0, -1); + break; + case XRFDC_MB_MODE_2X_BLK01_BLK23: + case XRFDC_MB_MODE_2X_BLK01: + case XRFDC_MB_MODE_2X_BLK23: + if ((Block_Id == XRFDC_BLK_ID0) || (Block_Id == XRFDC_BLK_ID1)) { + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id, XRFDC_BLK_ID0, -1); + } else { + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id, XRFDC_BLK_ID2, -1); + } + break; + default: + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id, Block_Id, -1); + break; + } + } else { + /* Mixer mode is BYPASS */ + XRFdc_SetConnectedIQData(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id, Block_Id, -1); + } +} + +/*****************************************************************************/ +/** +* +* This API updates PLL Structure. +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC. +* @param Tile_Id Valid values are 0-3, and -1. +* +* @return +* - None. +* +* @note Static API used to initialize PLL Settings for ADC and DAC +* +******************************************************************************/ +static void XRFdc_UpdatePLLStruct(XRFdc *InstancePtr, u32 Type, u32 Tile_Id) +{ + if (Type == XRFDC_ADC_TILE) { + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.SampleRate = + InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id].SamplingRate; + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.RefClkFreq = + InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id].RefClkFreq; + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.Enabled = + InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id].PLLEnable; + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.FeedbackDivider = + InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id].FeedbackDiv; + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.OutputDivider = + InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id].OutputDiv; + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.RefClkDivider = + InstancePtr->RFdc_Config.ADCTile_Config[Tile_Id].RefClkDiv; + } else { + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.SampleRate = + InstancePtr->RFdc_Config.DACTile_Config[Tile_Id].SamplingRate; + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.RefClkFreq = + InstancePtr->RFdc_Config.DACTile_Config[Tile_Id].RefClkFreq; + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.Enabled = + InstancePtr->RFdc_Config.DACTile_Config[Tile_Id].PLLEnable; + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.FeedbackDivider = + InstancePtr->RFdc_Config.DACTile_Config[Tile_Id].FeedbackDiv; + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.OutputDivider = + InstancePtr->RFdc_Config.DACTile_Config[Tile_Id].OutputDiv; + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.RefClkDivider = + InstancePtr->RFdc_Config.DACTile_Config[Tile_Id].RefClkDiv; + } +} + +/*****************************************************************************/ +/** +* +* The API Restarts the requested tile. It can restart a single tile and +* alternatively can restart all the tiles. Existing register settings are not +* lost or altered in the process. It just starts the requested tile(s). +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3, and -1. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if tile is not enabled or available +* +* @note None +* +******************************************************************************/ +u32 XRFdc_StartUp(XRFdc *InstancePtr, u32 Type, int Tile_Id) +{ + u32 Status; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_RestartIPSM(InstancePtr, Type, Tile_Id, XRFDC_SM_STATE1, + XRFDC_SM_STATE15); + return Status; + +} + +/*****************************************************************************/ +/** +* +* The API stops the tile as requested. It can also stop all the tiles if +* asked for. It does not clear any of the existing register settings. It just +* stops the requested tile(s). +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3, and -1. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if tile is not enabled or available +* +* @note None +* +******************************************************************************/ +u32 XRFdc_Shutdown(XRFdc *InstancePtr, u32 Type, int Tile_Id) +{ + u32 Status; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_RestartIPSM(InstancePtr, Type, Tile_Id, XRFDC_SM_STATE1, + XRFDC_SM_STATE1); + return Status; + +} + +/*****************************************************************************/ +/** +* +* The API resets the requested tile. It can reset all the tiles as well. In +* the process, all existing register settings are cleared and are replaced +* with the settings initially configured (through the GUI). +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3, and -1. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if tile is not enabled or available +* +* @note None +******************************************************************************/ +u32 XRFdc_Reset(XRFdc *InstancePtr, u32 Type, int Tile_Id) +{ + u32 Status; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_RestartIPSM(InstancePtr, Type, Tile_Id, XRFDC_SM_STATE0, + XRFDC_SM_STATE15); + return Status; + +} + +/*****************************************************************************/ +/** +* +* This Static API will be used to wait for restart bit clears and also check +* for PLL Lock if clock source is internal PLL. +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param End is end state of State Machine. +* +* @return - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if timeout occurs. +* +* @note None +* +******************************************************************************/ +static u32 XRFdc_WaitForRestartClr(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 BaseAddr, u32 End) +{ + u32 ClkSrc = 0U; + u32 DelayCount; + u32 LockStatus = 0U; + u32 Status; + + /* + * Get Tile clock source information + */ + if (XRFdc_GetClockSource(InstancePtr, Type, Tile_Id, &ClkSrc) + != XRFDC_SUCCESS) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + if ((ClkSrc == XRFDC_INTERNAL_PLL_CLK) && (End == XRFDC_SM_STATE15)) { + /* + * Wait for internal PLL to lock + */ + if (XRFdc_GetPLLLockStatus(InstancePtr, Type, Tile_Id, + &LockStatus) != XRFDC_SUCCESS) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + DelayCount = 0U; + while (LockStatus != XRFDC_PLL_LOCKED) { + if (DelayCount == XRFDC_PLL_LOCK_DLY_CNT) { + metal_log(METAL_LOG_ERROR, "\n PLL Lock timeout " + "error in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } else { + /* Wait for 1 msec */ +#ifdef __BAREMETAL__ + usleep(1000); +#else + metal_sleep_usec(1000); +#endif + DelayCount++; + (void)XRFdc_GetPLLLockStatus(InstancePtr, Type, Tile_Id, + &LockStatus); + } + } + } + + /* Wait till restart bit clear */ + DelayCount = 0U; + while (XRFdc_ReadReg16(InstancePtr, BaseAddr, XRFDC_RESTART_OFFSET) != 0U) { + if (DelayCount == XRFDC_PLL_LOCK_DLY_CNT) { + metal_log(METAL_LOG_ERROR, "\n Failed to clear " + "the restart bit in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } else { + /* Wait for 1 msec */ +#ifdef __BAREMETAL__ + usleep(1000); +#else + metal_sleep_usec(1000); +#endif + DelayCount++; + } + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/*****************************************************************************/ +/** +* +* Restarts a requested the tile and ensures that starts from a defined start +* state and reaches the requested or defined end state. +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3, and -1. +* @param Start is start state of State Machine +* @param End is end state of State Machine. +* +* @return - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if tile is not enabled or available +* +* @note None +* +******************************************************************************/ +static u32 XRFdc_RestartIPSM(XRFdc *InstancePtr, u32 Type, int Tile_Id, + u32 Start, u32 End) +{ + u32 Status; + u32 BaseAddr; + u16 NoOfTiles; + u16 Index; + + /* An input tile if of -1 selects all tiles */ + if (Tile_Id == XRFDC_SELECT_ALL_TILES) { + NoOfTiles = XRFDC_NUM_OF_TILES4; + Index = XRFDC_TILE_ID0; + } else { + NoOfTiles = Tile_Id + 1; + Index = Tile_Id; + } + + for (; Index < NoOfTiles; Index++) { + BaseAddr = XRFDC_CTRL_STS_BASE(Type, Index); + Status = XRFdc_CheckTileEnabled(InstancePtr, Type, Index); + if ((Status != XRFDC_SUCCESS) && (Tile_Id != XRFDC_SELECT_ALL_TILES)) { + metal_log(METAL_LOG_ERROR, "\n Requested tile%d not " + "available in %s\r\n", Index, __func__); + goto RETURN_PATH; + } else if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_DEBUG, "\n Tile%d not " + "available in %s\r\n", Index, __func__); + continue; + } else { + /* Write Start and End states */ + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_RESTART_STATE_OFFSET, + XRFDC_PWR_STATE_MASK, (Start << XRFDC_RSR_START_SHIFT) | End); + + /* Trigger restart */ + XRFdc_WriteReg(InstancePtr, BaseAddr, XRFDC_RESTART_OFFSET, + XRFDC_RESTART_MASK); + + /* Wait for restart bit clear */ + Status = XRFdc_WaitForRestartClr(InstancePtr, Type, Index, + BaseAddr, End); + if (Status != XRFDC_SUCCESS) { + goto RETURN_PATH; + } + } + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* The API returns the IP status. +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param IPStatusPtr Pointer to the XRFdc_IPStatus structure through +* which the status is returned. +* +* @return +* - XRFDC_SUCCESS if successful. +* +* @note None. +* +******************************************************************************/ +u32 XRFdc_GetIPStatus(XRFdc *InstancePtr, XRFdc_IPStatus *IPStatusPtr) +{ + u32 Tile_Id; + u32 Block_Id; + u32 BaseAddr; + u16 ReadReg; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(IPStatusPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + for (Tile_Id = XRFDC_TILE_ID0; Tile_Id < XRFDC_TILE_ID4; Tile_Id++) { + IPStatusPtr->ADCTileStatus[Tile_Id].BlockStatusMask = 0x0; + IPStatusPtr->DACTileStatus[Tile_Id].BlockStatusMask = 0x0; + for (Block_Id = XRFDC_BLK_ID0; Block_Id < XRFDC_BLK_ID4; Block_Id++) { + if (XRFdc_IsADCBlockEnabled(InstancePtr, Tile_Id, + Block_Id) != 0U) { + IPStatusPtr->ADCTileStatus[Tile_Id].IsEnabled = 1; + IPStatusPtr->ADCTileStatus[Tile_Id].BlockStatusMask |= + (1U << Block_Id); + BaseAddr = XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id); + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_STATUS_OFFSET); + IPStatusPtr->ADCTileStatus[Tile_Id].PowerUpState = (ReadReg & + XRFDC_PWR_UP_STAT_MASK) >> XRFDC_PWR_UP_STAT_SHIFT; + IPStatusPtr->ADCTileStatus[Tile_Id].PLLState = (ReadReg & + XRFDC_PLL_LOCKED_MASK) >> XRFDC_PLL_LOCKED_SHIFT; + IPStatusPtr->ADCTileStatus[Tile_Id].TileState = + XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_CURRENT_STATE_OFFSET); + } + if (XRFdc_IsDACBlockEnabled(InstancePtr, Tile_Id, + Block_Id) != 0U) { + IPStatusPtr->DACTileStatus[Tile_Id].IsEnabled = 1; + IPStatusPtr->DACTileStatus[Tile_Id].BlockStatusMask |= + (1U << Block_Id); + BaseAddr = XRFDC_DAC_TILE_CTRL_STATS_ADDR(Tile_Id); + IPStatusPtr->DACTileStatus[Tile_Id].TileState = + XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_CURRENT_STATE_OFFSET); + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_STATUS_OFFSET); + IPStatusPtr->DACTileStatus[Tile_Id].PowerUpState = (ReadReg & + XRFDC_PWR_UP_STAT_MASK) >> XRFDC_PWR_UP_STAT_SHIFT; + IPStatusPtr->DACTileStatus[Tile_Id].PLLState = (ReadReg & + XRFDC_PLL_LOCKED_MASK) >> XRFDC_PLL_LOCKED_SHIFT; + } + } + } + + /*TODO IP state*/ + + return XRFDC_SUCCESS; +} + +/*****************************************************************************/ +/** +* +* The API returns the requested block status. +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. XRFdc_BlockStatus. +* @param BlockStatusPtr is Pointer to the XRFdc_BlockStatus structure through +* which the ADC/DAC block status is returned. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Common API for ADC/DAC blocks. +* +******************************************************************************/ +u32 XRFdc_GetBlockStatus(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, XRFdc_BlockStatus *BlockStatusPtr) +{ + u32 Status; + u32 Block; + u16 ReadReg; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(BlockStatusPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, Type, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + Block = Block_Id; + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Block_Id == XRFDC_BLK_ID1) && (Type == XRFDC_ADC_TILE)) { + Block_Id = XRFDC_BLK_ID2; + } + + BaseAddr = XRFDC_BLOCK_BASE(Type, Tile_Id, Block_Id); + if (Type == XRFDC_ADC_TILE) { + Status = XRFdc_GetADCBlockStatus(InstancePtr, BaseAddr, Tile_Id, + Block, BlockStatusPtr); + } else { + Status = XRFdc_GetDACBlockStatus(InstancePtr, BaseAddr, Tile_Id, + Block, BlockStatusPtr); + } + if (Status != XRFDC_SUCCESS) { + goto RETURN_PATH; + } + + ReadReg = XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CLK_EN_OFFSET, + XRFDC_DAT_CLK_EN_MASK); + if (ReadReg == XRFDC_DAT_CLK_EN_MASK) { + BlockStatusPtr->DataPathClocksStatus = 0x1U; + } else { + BlockStatusPtr->DataPathClocksStatus = 0x0U; + } + + Status = XRFDC_SUCCESS; + +RETURN_PATH: + return Status; + +} + +/*****************************************************************************/ +/** +* +* The API returns the requested block status for ADC block +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. XRFdc_BlockStatus. +* @param BlockStatus is Pointer to the XRFdc_BlockStatus structure through +* which the ADC/DAC block status is returned. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Static API for ADC blocks. +* +******************************************************************************/ +static u32 XRFdc_GetADCBlockStatus(XRFdc *InstancePtr, u32 BaseAddr, + u32 Tile_Id, u32 Block_Id, XRFdc_BlockStatus *BlockStatusPtr) +{ + u8 FIFOEnable = 0U; + u32 DecimationFactor = 0U; + u8 MixerMode; + u16 ReadReg; + u32 Status; + + BlockStatusPtr->SamplingFreq = InstancePtr->ADC_Tile[Tile_Id]. + PLL_Settings.SampleRate; + + /* DigitalDataPathStatus */ + (void)XRFdc_GetFIFOStatus(InstancePtr, XRFDC_ADC_TILE, + Tile_Id, &FIFOEnable); + BlockStatusPtr->DigitalDataPathStatus = FIFOEnable; + (void)XRFdc_GetDecimationFactor(InstancePtr, Tile_Id, + Block_Id, &DecimationFactor); + BlockStatusPtr->DigitalDataPathStatus |= + (DecimationFactor << XRFDC_DIGI_ANALOG_SHIFT4); + + ReadReg = XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_MXR_MODE_OFFSET, + (XRFDC_EN_I_IQ_MASK | XRFDC_EN_Q_IQ_MASK)); + switch (ReadReg) { + case XRFDC_MIXER_MODE_C2C_MASK: + MixerMode = XRFDC_MIXER_MODE_C2C; + break; + case XRFDC_MIXER_MODE_R2C_MASK: + MixerMode = XRFDC_MIXER_MODE_R2C; + break; + case XRFDC_MIXER_MODE_OFF_MASK: + MixerMode = XRFDC_MIXER_MODE_OFF; + break; + default: + metal_log(METAL_LOG_ERROR, "\n Invalid MixerMode " + "for ADC in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + BlockStatusPtr->DigitalDataPathStatus |= + (MixerMode << XRFDC_DIGI_ANALOG_SHIFT8); + + /* + * Checking ADC block enable for ADC AnalogPath. + * This can be changed later, + */ + BlockStatusPtr->AnalogDataPathStatus = + XRFdc_IsADCBlockEnabled(InstancePtr, Tile_Id, Block_Id); + BlockStatusPtr->IsFIFOFlagsEnabled = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_FABRIC_IMR_OFFSET, XRFDC_FAB_IMR_USRDAT_MASK); + BlockStatusPtr->IsFIFOFlagsAsserted = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_FABRIC_ISR_OFFSET, XRFDC_FAB_ISR_USRDAT_MASK); + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* The API returns the requested block status for DAC block +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. XRFdc_BlockStatus. +* @param BlockStatus is Pointer to the XRFdc_BlockStatus structure through +* which the DAC block status is returned. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Static API for DAC blocks. +* +******************************************************************************/ +static u32 XRFdc_GetDACBlockStatus(XRFdc *InstancePtr, u32 BaseAddr, + u32 Tile_Id, u32 Block_Id, XRFdc_BlockStatus *BlockStatusPtr) +{ + u32 InterpolationFactor = 0U; + u32 DecoderMode = 0U; + u8 MixerMode; + u16 ReadReg; + u32 Status; + u8 FIFOEnable = 0U; + + BlockStatusPtr->SamplingFreq = InstancePtr->DAC_Tile[Tile_Id]. + PLL_Settings.SampleRate; + + /* DigitalDataPathStatus */ + (void)XRFdc_GetFIFOStatus(InstancePtr, XRFDC_DAC_TILE, + Tile_Id, &FIFOEnable); + BlockStatusPtr->DigitalDataPathStatus = FIFOEnable; + (void)XRFdc_GetInterpolationFactor(InstancePtr, Tile_Id, + Block_Id, &InterpolationFactor); + BlockStatusPtr->DigitalDataPathStatus |= + (InterpolationFactor << XRFDC_DIGI_ANALOG_SHIFT4); + /* Adder Enable */ + ReadReg = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_DAC_MB_CFG_OFFSET, XRFDC_EN_MB_MASK); + ReadReg = ReadReg >> XRFDC_EN_MB_SHIFT; + BlockStatusPtr->DigitalDataPathStatus |= + (ReadReg << XRFDC_DIGI_ANALOG_SHIFT8); + ReadReg = XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_MXR_MODE_OFFSET, + (XRFDC_EN_I_IQ_MASK | XRFDC_EN_Q_IQ_MASK)); + switch (ReadReg) { + case XRFDC_MIXER_MODE_C2C_MASK: + MixerMode = XRFDC_MIXER_MODE_C2C; + break; + case XRFDC_MIXER_MODE_C2R_MASK: + MixerMode = XRFDC_MIXER_MODE_C2R; + break; + case XRFDC_MIXER_MODE_OFF_MASK: + MixerMode = XRFDC_MIXER_MODE_OFF; + break; + default: + metal_log(METAL_LOG_ERROR, "\n Invalid MixerMode " + "for ADC in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + BlockStatusPtr->DigitalDataPathStatus |= + (MixerMode << XRFDC_DIGI_ANALOG_SHIFT12); + + /* AnalogDataPathStatus */ + BlockStatusPtr->AnalogDataPathStatus = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_DAC_INVSINC_OFFSET, (InstancePtr->RFdc_Config.IPType < 2)?XRFDC_EN_INVSINC_MASK:XRFDC_MODE_INVSINC_MASK); + (void)XRFdc_GetDecoderMode(InstancePtr, Tile_Id, Block_Id, + &DecoderMode); + BlockStatusPtr->AnalogDataPathStatus |= + (DecoderMode << XRFDC_DIGI_ANALOG_SHIFT4); + + /* FIFO Flags status */ + BlockStatusPtr->IsFIFOFlagsEnabled = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_DAC_FABRIC_IMR_OFFSET, XRFDC_FAB_IMR_USRDAT_MASK); + BlockStatusPtr->IsFIFOFlagsAsserted = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_DAC_FABRIC_ISR_OFFSET, XRFDC_FAB_ISR_USRDAT_MASK); + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* This API is used to update various QMC settings, eg gain, phase, offset etc. +* QMC settings passed are used to update the corresponding +* block level registers. Driver structure is updated with the new values. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param QMCSettingsPtr is Pointer to the XRFdc_QMC_Settings structure +* in which the QMC settings are passed. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note None +* +******************************************************************************/ +u32 XRFdc_SetQMCSettings(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, XRFdc_QMC_Settings *QMCSettingsPtr) +{ + u32 Status; + XRFdc_QMC_Settings *QMCConfigPtr; + u32 BaseAddr; + s32 PhaseCorrectionFactor; + u32 GainCorrectionFactor; + u32 Index; + u32 NoOfBlocks; + u32 Offset; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(QMCSettingsPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, Type, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + Index = Block_Id; + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE)) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + if (InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[Index]. + MixerInputDataType == XRFDC_DATA_TYPE_IQ) { + Index = Block_Id; + NoOfBlocks = XRFDC_NUM_OF_BLKS3; + if (Block_Id == XRFDC_BLK_ID1) { + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + for (; Index < NoOfBlocks;) { + if (Type == XRFDC_ADC_TILE) { + /* ADC */ + QMCConfigPtr = &InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Analog_Datapath[Index].QMC_Settings; + } else { + QMCConfigPtr = &InstancePtr->DAC_Tile[Tile_Id]. + DACBlock_Analog_Datapath[Index].QMC_Settings; + } + + BaseAddr = XRFDC_BLOCK_BASE(Type, Tile_Id, Index); + + if ((QMCSettingsPtr->EnableGain != 0U) && + (QMCSettingsPtr->EnableGain != 1U)) { + metal_log(METAL_LOG_ERROR, "\n Invalid QMC gain option " + "in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if ((QMCSettingsPtr->EnablePhase != 0U) && + (QMCSettingsPtr->EnablePhase != 1U)) { + metal_log(METAL_LOG_ERROR, "\n Invalid QMC phase option " + "in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if ((QMCSettingsPtr->PhaseCorrectionFactor <= XRFDC_MIN_PHASE_CORR_FACTOR) || + (QMCSettingsPtr->PhaseCorrectionFactor >= XRFDC_MAX_PHASE_CORR_FACTOR)) { + metal_log(METAL_LOG_ERROR, "\n Invalid QMC Phase Correction " + "factor in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if ((QMCSettingsPtr->GainCorrectionFactor < XRFDC_MIN_GAIN_CORR_FACTOR) || + (QMCSettingsPtr->GainCorrectionFactor >= XRFDC_MAX_GAIN_CORR_FACTOR)) { + metal_log(METAL_LOG_ERROR, "\n Invalid QMC Gain Correction " + "factor in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + if ((QMCSettingsPtr->EventSource > XRFDC_EVNT_SRC_PL) || + ((QMCSettingsPtr->EventSource == XRFDC_EVNT_SRC_MARKER) && + (Type == XRFDC_ADC_TILE))) { + metal_log(METAL_LOG_ERROR, "\n Invalid event source selection " + "in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE) && + ((QMCSettingsPtr->EventSource == XRFDC_EVNT_SRC_SLICE) || + (QMCSettingsPtr->EventSource == + XRFDC_EVNT_SRC_IMMEDIATE))) { + metal_log(METAL_LOG_ERROR, "\n Invalid Event Source, " + "event source is not supported in 4GSPS ADC %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_QMC_CFG_OFFSET, + XRFDC_QMC_CFG_EN_GAIN_MASK, QMCSettingsPtr->EnableGain); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_QMC_CFG_OFFSET, + XRFDC_QMC_CFG_EN_PHASE_MASK, + (QMCSettingsPtr->EnablePhase << XRFDC_QMC_CFG_PHASE_SHIFT)); + + /* Phase Correction factor is applicable to ADC/DAC IQ mode only */ + if (((Type == XRFDC_ADC_TILE) && + (InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[Index]. + MixerInputDataType == XRFDC_DATA_TYPE_IQ)) || + ((Type == XRFDC_DAC_TILE) && + (InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[Index]. + MixerInputDataType == XRFDC_DATA_TYPE_IQ))) { + PhaseCorrectionFactor = + ((QMCSettingsPtr->PhaseCorrectionFactor / XRFDC_MAX_PHASE_CORR_FACTOR) * + XRFDC_QMC_PHASE_MULT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_QMC_PHASE_OFFSET, + XRFDC_QMC_PHASE_CRCTN_MASK, PhaseCorrectionFactor); + } + + /* Gain Correction factor */ + GainCorrectionFactor = ((QMCSettingsPtr->GainCorrectionFactor * + XRFDC_QMC_GAIN_MULT) / XRFDC_MAX_GAIN_CORR_FACTOR); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_QMC_GAIN_OFFSET, + XRFDC_QMC_GAIN_CRCTN_MASK, GainCorrectionFactor); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_QMC_OFF_OFFSET, + XRFDC_QMC_OFFST_CRCTN_MASK, QMCSettingsPtr->OffsetCorrectionFactor); + + /* Event Source */ + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_QMC_UPDT_OFFSET, + XRFDC_QMC_UPDT_MODE_MASK, QMCSettingsPtr->EventSource); + + if (QMCSettingsPtr->EventSource == XRFDC_EVNT_SRC_IMMEDIATE) { + if (Type == XRFDC_ADC_TILE) { + Offset = XRFDC_ADC_UPDATE_DYN_OFFSET; + } else { + Offset = XRFDC_DAC_UPDATE_DYN_OFFSET; + } + XRFdc_ClrSetReg(InstancePtr, BaseAddr, Offset, + XRFDC_UPDT_EVNT_MASK, XRFDC_UPDT_EVNT_QMC_MASK); + } + /* Update the instance with new values */ + QMCConfigPtr->EventSource = QMCSettingsPtr->EventSource; + QMCConfigPtr->PhaseCorrectionFactor = + QMCSettingsPtr->PhaseCorrectionFactor; + QMCConfigPtr->GainCorrectionFactor = + QMCSettingsPtr->GainCorrectionFactor; + QMCConfigPtr->OffsetCorrectionFactor = + QMCSettingsPtr->OffsetCorrectionFactor; + QMCConfigPtr->EnablePhase = QMCSettingsPtr->EnablePhase; + QMCConfigPtr->EnableGain = QMCSettingsPtr->EnableGain; + if ((Type == XRFDC_ADC_TILE) && + (InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Index].MixerInputDataType == + XRFDC_DATA_TYPE_IQ) && (XRFdc_IsHighSpeedADC(InstancePtr, + Tile_Id) == 1)) { + Index += XRFDC_BLK_ID2; + } else { + Index += XRFDC_BLK_ID1; + } + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* QMC settings are returned back to the caller through this API. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param QMCSettingsPtr Pointer to the XRFdc_QMC_Settings structure +* in which the QMC settings are passed. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note None +* +******************************************************************************/ +u32 XRFdc_GetQMCSettings(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, XRFdc_QMC_Settings *QMCSettingsPtr) +{ + u32 Status; + u32 BaseAddr; + s32 PhaseCorrectionFactor; + u32 GainCorrectionFactor; + s32 OffsetCorrectionFactor; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(QMCSettingsPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, Type, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Block_Id == XRFDC_BLK_ID1) && (Type == XRFDC_ADC_TILE) && + (InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Block_Id].MixerInputDataType != + XRFDC_DATA_TYPE_IQ)) { + Block_Id = XRFDC_BLK_ID2; + } + + BaseAddr = XRFDC_BLOCK_BASE(Type, Tile_Id, Block_Id); + + QMCSettingsPtr->EnableGain = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_QMC_CFG_OFFSET, XRFDC_QMC_CFG_EN_GAIN_MASK); + QMCSettingsPtr->EnablePhase = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_QMC_CFG_OFFSET, XRFDC_QMC_CFG_EN_PHASE_MASK) >> + XRFDC_QMC_CFG_PHASE_SHIFT; + + /* Phase Correction factor */ + if (((Type == XRFDC_ADC_TILE) && + (InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[Block_Id]. + MixerInputDataType == XRFDC_DATA_TYPE_IQ)) || + ((Type == XRFDC_DAC_TILE) && + (InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[Block_Id]. + MixerInputDataType == XRFDC_DATA_TYPE_IQ))) { + PhaseCorrectionFactor = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_QMC_PHASE_OFFSET, XRFDC_QMC_PHASE_CRCTN_MASK); + PhaseCorrectionFactor = (PhaseCorrectionFactor >> + 11) == 0 ? PhaseCorrectionFactor : + ((-1 ^ 0xFFF) | PhaseCorrectionFactor); + QMCSettingsPtr->PhaseCorrectionFactor = + ((PhaseCorrectionFactor * XRFDC_MAX_PHASE_CORR_FACTOR) / + XRFDC_QMC_PHASE_MULT); + } else { + QMCSettingsPtr->PhaseCorrectionFactor = 0U; + } + + /* Gain Correction factor */ + GainCorrectionFactor = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_QMC_GAIN_OFFSET, XRFDC_QMC_GAIN_CRCTN_MASK); + QMCSettingsPtr->GainCorrectionFactor = ((GainCorrectionFactor * + XRFDC_MAX_GAIN_CORR_FACTOR) / XRFDC_QMC_GAIN_MULT); + OffsetCorrectionFactor = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_QMC_OFF_OFFSET, XRFDC_QMC_OFFST_CRCTN_MASK); + QMCSettingsPtr->OffsetCorrectionFactor = + (OffsetCorrectionFactor >> 11) == 0 ? OffsetCorrectionFactor : + ((-1 ^ 0xFFF) | OffsetCorrectionFactor); + + /* Event Source */ + QMCSettingsPtr->EventSource = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_QMC_UPDT_OFFSET, XRFDC_QMC_UPDT_MODE_MASK); + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Coarse delay settings passed are used to update the corresponding +* block level registers. Driver structure is updated with the new values. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param CoarseDelaySettingsPtr is Pointer to the XRFdc_CoarseDelay_Settings +* structure in which the CoarseDelay settings are passed. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note None +* +******************************************************************************/ +u32 XRFdc_SetCoarseDelaySettings(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, XRFdc_CoarseDelay_Settings *CoarseDelaySettingsPtr) +{ + u32 Status; + u32 BaseAddr; + u32 Index; + u32 NoOfBlocks; + u16 Mask; + u16 MaxDelay; + XRFdc_CoarseDelay_Settings *CoarseDelayConfigPtr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(CoarseDelaySettingsPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Mask = (InstancePtr->RFdc_Config.IPType < 2)?XRFDC_CRSE_DLY_CFG_MASK:XRFDC_CRSE_DLY_CFG_MASK_EXT; + MaxDelay = (InstancePtr->RFdc_Config.IPType < 2)?XRFDC_CRSE_DLY_MAX:XRFDC_CRSE_DLY_MAX_EXT; + Status = XRFdc_CheckBlockEnabled(InstancePtr, Type, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + Index = Block_Id; + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE)) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + for (; Index < NoOfBlocks; Index++) { + if (Type == XRFDC_ADC_TILE) { + CoarseDelayConfigPtr = &InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Analog_Datapath[Index]. + CoarseDelay_Settings; + } else { + CoarseDelayConfigPtr = &InstancePtr->DAC_Tile[Tile_Id]. + DACBlock_Analog_Datapath[Index]. + CoarseDelay_Settings; + } + + BaseAddr = XRFDC_BLOCK_BASE(Type, Tile_Id, Index); + + if (CoarseDelaySettingsPtr->CoarseDelay > MaxDelay) { + metal_log(METAL_LOG_ERROR, "\n Requested coarse " + "delay not valid in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if ((CoarseDelaySettingsPtr->EventSource > XRFDC_EVNT_SRC_PL) || + ((CoarseDelaySettingsPtr->EventSource == XRFDC_EVNT_SRC_MARKER) && + (Type == XRFDC_ADC_TILE))) { + metal_log(METAL_LOG_ERROR, "\n Invalid event " + "source selection in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE) && + ((CoarseDelaySettingsPtr->EventSource == + XRFDC_EVNT_SRC_SLICE) || + (CoarseDelaySettingsPtr->EventSource == + XRFDC_EVNT_SRC_IMMEDIATE))) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, "\n Invalid Event " + "Source, event source is not supported in " + "4GSPS ADC %s\r\n", __func__); + goto RETURN_PATH; + } + if (Type == XRFDC_ADC_TILE) { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_CRSE_DLY_CFG_OFFSET, Mask, CoarseDelaySettingsPtr->CoarseDelay); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, + XRFDC_ADC_CRSE_DLY_UPDT_OFFSET, XRFDC_QMC_UPDT_MODE_MASK, + CoarseDelaySettingsPtr->EventSource); + if (CoarseDelaySettingsPtr->EventSource == + XRFDC_EVNT_SRC_IMMEDIATE) { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, + XRFDC_ADC_UPDATE_DYN_OFFSET, XRFDC_UPDT_EVNT_MASK, + XRFDC_ADC_UPDT_CRSE_DLY_MASK); + } + } else { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, + XRFDC_DAC_CRSE_DLY_CFG_OFFSET, Mask, + CoarseDelaySettingsPtr->CoarseDelay); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, + XRFDC_DAC_CRSE_DLY_UPDT_OFFSET, XRFDC_QMC_UPDT_MODE_MASK, + CoarseDelaySettingsPtr->EventSource); + if (CoarseDelaySettingsPtr->EventSource == + XRFDC_EVNT_SRC_IMMEDIATE) { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, + XRFDC_DAC_UPDATE_DYN_OFFSET, XRFDC_UPDT_EVNT_MASK, + XRFDC_DAC_UPDT_CRSE_DLY_MASK); + } + } + /* Update the instance with new values */ + CoarseDelayConfigPtr->CoarseDelay = + CoarseDelaySettingsPtr->CoarseDelay; + CoarseDelayConfigPtr->EventSource = + CoarseDelaySettingsPtr->EventSource; + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Coarse delay settings are returned back to the caller. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param CoarseDelaySettingsPtr Pointer to the XRFdc_CoarseDelay_Settings +* structure in which the Coarse Delay settings are passed. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note None. +* +******************************************************************************/ +u32 XRFdc_GetCoarseDelaySettings(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, XRFdc_CoarseDelay_Settings *CoarseDelaySettingsPtr) +{ + u32 Status; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(CoarseDelaySettingsPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, Type, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Block_Id == XRFDC_BLK_ID1) && (Type == XRFDC_ADC_TILE)) { + Block_Id = XRFDC_BLK_ID2; + } + + BaseAddr = XRFDC_BLOCK_BASE(Type, Tile_Id, Block_Id); + + if (Type == XRFDC_ADC_TILE) { + CoarseDelaySettingsPtr->CoarseDelay = + XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_CRSE_DLY_CFG_OFFSET); + CoarseDelaySettingsPtr->EventSource = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_CRSE_DLY_UPDT_OFFSET, XRFDC_QMC_UPDT_MODE_MASK); + } else { + CoarseDelaySettingsPtr->CoarseDelay = + XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_DAC_CRSE_DLY_CFG_OFFSET); + CoarseDelaySettingsPtr->EventSource = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_DAC_CRSE_DLY_UPDT_OFFSET, XRFDC_QMC_UPDT_MODE_MASK); + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* This function will trigger the update event for an event. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param Event is for which dynamic update event will trigger. +* XRFDC_EVENT_* defines the different events. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Common API for ADC/DAC blocks +* +******************************************************************************/ +u32 XRFdc_UpdateEvent(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, u32 Block_Id, + u32 Event) +{ + u32 Status; + u32 BaseAddr; + u32 EventSource; + u32 NoOfBlocks; + u32 Index; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Index = Block_Id; + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE)) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + if ((Event == XRFDC_EVENT_QMC) && (InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Index].MixerInputDataType == + XRFDC_DATA_TYPE_IQ)) { + Index = Block_Id; + NoOfBlocks = XRFDC_NUM_OF_BLKS3; + if (Block_Id == XRFDC_BLK_ID1) { + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + if ((Event != XRFDC_EVENT_MIXER) && (Event != XRFDC_EVENT_QMC) && + (Event != XRFDC_EVENT_CRSE_DLY)) { + metal_log(METAL_LOG_ERROR, "\n Invalid Event " + "value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + for (; Index < NoOfBlocks;) { + /* Identify the Event Source */ + BaseAddr = XRFDC_BLOCK_BASE(Type, Tile_Id, Index); + if (Event == XRFDC_EVENT_MIXER) { + Status = XRFdc_CheckDigitalPathEnabled(InstancePtr, Type, + Tile_Id, Block_Id); + EventSource = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_NCO_UPDT_OFFSET, XRFDC_NCO_UPDT_MODE_MASK); + } else if (Event == XRFDC_EVENT_CRSE_DLY) { + Status = XRFdc_CheckBlockEnabled(InstancePtr, Type, + Tile_Id, Block_Id); + EventSource = XRFdc_RDReg(InstancePtr, BaseAddr, + (Type == XRFDC_ADC_TILE) ? XRFDC_ADC_CRSE_DLY_UPDT_OFFSET : + XRFDC_DAC_CRSE_DLY_UPDT_OFFSET, XRFDC_QMC_UPDT_MODE_MASK); + } else { + Status = XRFdc_CheckBlockEnabled(InstancePtr, Type, + Tile_Id, Block_Id); + EventSource = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_QMC_UPDT_OFFSET, XRFDC_QMC_UPDT_MODE_MASK); + } + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + if ((EventSource == XRFDC_EVNT_SRC_SYSREF) || + (EventSource == XRFDC_EVNT_SRC_PL) || + (EventSource == XRFDC_EVNT_SRC_MARKER)) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, "\n Invalid Event " + "Source, this should be issued external to " + "the driver %s\r\n", __func__); + goto RETURN_PATH; + } + if (Type == XRFDC_ADC_TILE) { + if (EventSource == XRFDC_EVNT_SRC_SLICE) { + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_UPDATE_DYN_OFFSET, 0x1); + } else { + BaseAddr = XRFDC_ADC_TILE_DRP_ADDR(Tile_Id) + + XRFDC_HSCOM_ADDR; + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_HSCOM_UPDT_DYN_OFFSET, 0x1); + } + } else { + if (EventSource == XRFDC_EVNT_SRC_SLICE) { + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_DAC_UPDATE_DYN_OFFSET, 0x1); + } else { + BaseAddr = XRFDC_DAC_TILE_DRP_ADDR(Tile_Id) + + XRFDC_HSCOM_ADDR; + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_HSCOM_UPDT_DYN_OFFSET, 0x1); + } + } + if ((Event == XRFDC_EVENT_QMC) && (InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Index].MixerInputDataType == + XRFDC_DATA_TYPE_IQ) && (XRFdc_IsHighSpeedADC(InstancePtr, + Tile_Id) == 1) && (Type == XRFDC_ADC_TILE)) { + Index += XRFDC_BLK_ID2; + } else { + Index += XRFDC_BLK_ID1; + } + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* This API is to set the decimation factor and also update the FIFO write +* words w.r.t to decimation factor. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param DecimationFactor to be set for DAC block. +* XRFDC_INTERP_DECIM_* defines the valid values. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Only ADC blocks +* +******************************************************************************/ +u32 XRFdc_SetDecimationFactor(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 DecimationFactor) +{ + u32 Status; + u32 BaseAddr; + u32 Index; + u32 NoOfBlocks; + u16 FabricRate; + u8 DataType; + u32 Factor; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckDigitalPathEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block digital path not " + "enabled in %s\r\n", __func__); + goto RETURN_PATH; + } + + if ((DecimationFactor != XRFDC_INTERP_DECIM_OFF) && + (DecimationFactor != XRFDC_INTERP_DECIM_1X) && + (DecimationFactor != XRFDC_INTERP_DECIM_2X) && + (DecimationFactor != XRFDC_INTERP_DECIM_4X) && + (DecimationFactor != XRFDC_INTERP_DECIM_8X) && + ((InstancePtr->RFdc_Config.IPType < 2) || + ((DecimationFactor != XRFDC_INTERP_DECIM_3X) && + (DecimationFactor != XRFDC_INTERP_DECIM_5X) && + (DecimationFactor != XRFDC_INTERP_DECIM_6X) && + (DecimationFactor != XRFDC_INTERP_DECIM_10X) && + (DecimationFactor != XRFDC_INTERP_DECIM_12X) && + (DecimationFactor != XRFDC_INTERP_DECIM_16X) && + (DecimationFactor != XRFDC_INTERP_DECIM_20X) && + (DecimationFactor != XRFDC_INTERP_DECIM_24X) && + (DecimationFactor != XRFDC_INTERP_DECIM_40X)))) { + metal_log(METAL_LOG_ERROR, "\n Invalid Decimation factor " + "value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + Index = Block_Id; + if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + for (; Index < NoOfBlocks; Index++) { + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Index); + DataType = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_DECI_CONFIG_OFFSET, XRFDC_DEC_CFG_MASK); + + /* Decimation factor */ + Factor = DecimationFactor; + if (InstancePtr->RFdc_Config.IPType < 2) { + if (DecimationFactor == XRFDC_INTERP_DECIM_4X) { + Factor = 0x3; + } + if (DecimationFactor == XRFDC_INTERP_DECIM_8X) { + Factor = 0x4; + } + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_DECI_MODE_OFFSET, + XRFDC_DEC_MOD_MASK, Factor); + } else { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_DECI_MODE_OFFSET, + XRFDC_DEC_MOD_MASK_EXT, Factor); + } + + + + /* Fabric rate */ + FabricRate = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_FABRIC_RATE_OFFSET, XRFDC_ADC_FAB_RATE_WR_MASK); + if ((DataType == XRFDC_DECIM_2G_IQ_DATA_TYPE) || + (DataType == XRFDC_DECIM_4G_DATA_TYPE) || + (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1)) { + switch (DecimationFactor) { + case XRFDC_INTERP_DECIM_1X: + FabricRate = XRFDC_FAB_RATE_8; + break; + case XRFDC_INTERP_DECIM_2X: + case XRFDC_INTERP_DECIM_3X: + FabricRate = XRFDC_FAB_RATE_4; + break; + case XRFDC_INTERP_DECIM_4X: + case XRFDC_INTERP_DECIM_5X: + case XRFDC_INTERP_DECIM_6X: + FabricRate = XRFDC_FAB_RATE_2; + break; + case XRFDC_INTERP_DECIM_10X: + case XRFDC_INTERP_DECIM_12X: + case XRFDC_INTERP_DECIM_8X: + FabricRate = XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) ? + XRFDC_FAB_RATE_1:XRFDC_FAB_RATE_2; + break; + case XRFDC_INTERP_DECIM_16X: + case XRFDC_INTERP_DECIM_20X: + case XRFDC_INTERP_DECIM_24X: + case XRFDC_INTERP_DECIM_40X: + FabricRate = XRFDC_FAB_RATE_1; + break; + default: + metal_log(METAL_LOG_DEBUG, "\n Decimation block " + "is OFF in %s\r\n", __func__); + break; + } + } else { + switch (DecimationFactor) { + case XRFDC_INTERP_DECIM_1X: + FabricRate = XRFDC_FAB_RATE_4; + break; + case XRFDC_INTERP_DECIM_2X: + case XRFDC_INTERP_DECIM_3X: + FabricRate = XRFDC_FAB_RATE_2; + break; + case XRFDC_INTERP_DECIM_4X: + case XRFDC_INTERP_DECIM_8X: + case XRFDC_INTERP_DECIM_5X: + case XRFDC_INTERP_DECIM_6X: + case XRFDC_INTERP_DECIM_10X: + case XRFDC_INTERP_DECIM_12X: + case XRFDC_INTERP_DECIM_16X: + case XRFDC_INTERP_DECIM_20X: + case XRFDC_INTERP_DECIM_24X: + case XRFDC_INTERP_DECIM_40X: + FabricRate = XRFDC_FAB_RATE_1; + break; + default: + metal_log(METAL_LOG_DEBUG, "\n Decimation block " + "is OFF in %s\r\n", __func__); + break; + } + } + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_FABRIC_RATE_OFFSET, + XRFDC_ADC_FAB_RATE_WR_MASK, FabricRate); + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* This API is to set the divider for clock fabric out. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param FabClkDiv to be set for a tile. +* XRFDC_FAB_CLK_* defines the valid divider values. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note ADC and DAC Tiles +* +******************************************************************************/ +u32 XRFdc_SetFabClkOutDiv(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u16 FabClkDiv) +{ + u32 Status; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckTileEnabled(InstancePtr, Type, Tile_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested tile not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + if ((FabClkDiv != XRFDC_FAB_CLK_DIV1) && + (FabClkDiv != XRFDC_FAB_CLK_DIV2) && + (FabClkDiv != XRFDC_FAB_CLK_DIV4) && + (FabClkDiv != XRFDC_FAB_CLK_DIV8) && + (FabClkDiv != XRFDC_FAB_CLK_DIV16)) { + metal_log(METAL_LOG_ERROR, "\n Invalid Fabric clock out " + "divider value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + BaseAddr = XRFDC_DRP_BASE(Type, Tile_Id) + XRFDC_HSCOM_ADDR; + + if ((Type == XRFDC_ADC_TILE) && + (FabClkDiv == XRFDC_FAB_CLK_DIV1)) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, "\n Invalid clock divider " + "in %s \r\n", __func__); + goto RETURN_PATH; + } else { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_HSCOM_CLK_DIV_OFFSET, + XRFDC_FAB_CLK_DIV_MASK, FabClkDiv); + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* This API is to get the divider for clock fabric out. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param FabClkDivPtr is a pointer to get fabric clock for a tile. +* XRFDC_FAB_CLK_* defines the valid divider values. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note API is applicable for both ADC and DAC Tiles +* +******************************************************************************/ +u32 XRFdc_GetFabClkOutDiv(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u16 *FabClkDivPtr) +{ + u32 Status; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(FabClkDivPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckTileEnabled(InstancePtr, Type, Tile_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested tile not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + BaseAddr = XRFDC_DRP_BASE(Type, Tile_Id) + XRFDC_HSCOM_ADDR; + + *FabClkDivPtr = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_HSCOM_CLK_DIV_OFFSET, XRFDC_FAB_CLK_DIV_MASK); + + if ((*FabClkDivPtr < XRFDC_FAB_CLK_DIV1) || + (*FabClkDivPtr > XRFDC_FAB_CLK_DIV16)) { + *FabClkDivPtr = XRFDC_FAB_CLK_DIV16; + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* This API is to set the interpolation factor and also update the FIFO read +* words w.r.t to interpolation factor. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param InterpolationFactor to be set for DAC block. +* XRFDC_INTERP_DECIM_* defines the valid values. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Only DAC blocks +* +******************************************************************************/ +u32 XRFdc_SetInterpolationFactor(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 InterpolationFactor) +{ + u32 Status; + u32 BaseAddr; + u16 FabricRate; + u8 DataType; + u32 Factor; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckDigitalPathEnabled(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block digital path not " + "enabled in %s\r\n", __func__); + goto RETURN_PATH; + } + + if ((InterpolationFactor != XRFDC_INTERP_DECIM_OFF) && + (InterpolationFactor != XRFDC_INTERP_DECIM_1X) && + (InterpolationFactor != XRFDC_INTERP_DECIM_2X) && + (InterpolationFactor != XRFDC_INTERP_DECIM_4X) && + (InterpolationFactor != XRFDC_INTERP_DECIM_8X) && + ((InstancePtr->RFdc_Config.IPType < 2) || + ((InterpolationFactor != XRFDC_INTERP_DECIM_3X) && + (InterpolationFactor != XRFDC_INTERP_DECIM_5X) && + (InterpolationFactor != XRFDC_INTERP_DECIM_6X) && + (InterpolationFactor != XRFDC_INTERP_DECIM_10X) && + (InterpolationFactor != XRFDC_INTERP_DECIM_12X) && + (InterpolationFactor != XRFDC_INTERP_DECIM_16X) && + (InterpolationFactor != XRFDC_INTERP_DECIM_20X) && + (InterpolationFactor != XRFDC_INTERP_DECIM_24X) && + (InterpolationFactor != XRFDC_INTERP_DECIM_40X)))) { + metal_log(METAL_LOG_ERROR, "\n Invalid Interpolation factor " + "divider value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_DAC_TILE, Tile_Id, Block_Id); + + DataType = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_DAC_ITERP_DATA_OFFSET); + if ((DataType == XRFDC_ADC_MIXER_MODE_IQ) && + (InterpolationFactor == + XRFDC_INTERP_DECIM_1X)) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, "\n Invalid interpolation " + "factor in %s\r\n", __func__); + goto RETURN_PATH; + } + + /* Interpolation factor */ + Factor = InterpolationFactor; + + if (InstancePtr->RFdc_Config.IPType < 2) { + if (InterpolationFactor == XRFDC_INTERP_DECIM_4X) { + Factor = 0x3; + } + if (InterpolationFactor == XRFDC_INTERP_DECIM_8X) { + Factor = 0x4; + } + } + if (DataType == XRFDC_ADC_MIXER_MODE_IQ) { + Factor |= Factor << ((InstancePtr->RFdc_Config.IPType < 2)?XRFDC_INTERP_MODE_Q_SHIFT:XRFDC_INTERP_MODE_Q_SHIFT_EXT); + } + + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_DAC_INTERP_CTRL_OFFSET, + (InstancePtr->RFdc_Config.IPType < 2)?XRFDC_INTERP_MODE_MASK:XRFDC_INTERP_MODE_MASK_EXT, Factor); + + /* Fabric rate */ + FabricRate = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_FABRIC_RATE_OFFSET, XRFDC_FAB_RATE_RD_MASK); + FabricRate = FabricRate >> XRFDC_FAB_RATE_RD_SHIFT; + if (DataType == XRFDC_ADC_MIXER_MODE_IQ) { + switch (InterpolationFactor) { + case XRFDC_INTERP_DECIM_2X: + case XRFDC_INTERP_DECIM_3X: + FabricRate = XRFDC_FAB_RATE_8; + break; + case XRFDC_INTERP_DECIM_4X: + case XRFDC_INTERP_DECIM_5X: + case XRFDC_INTERP_DECIM_6X: + FabricRate = XRFDC_FAB_RATE_4; + break; + case XRFDC_INTERP_DECIM_8X: + case XRFDC_INTERP_DECIM_10X: + case XRFDC_INTERP_DECIM_12X: + FabricRate = XRFDC_FAB_RATE_2; + break; + case XRFDC_INTERP_DECIM_16X: + case XRFDC_INTERP_DECIM_20X: + case XRFDC_INTERP_DECIM_24X: + case XRFDC_INTERP_DECIM_40X: + FabricRate = XRFDC_FAB_RATE_1; + break; + default: + metal_log(METAL_LOG_DEBUG, "\n Interpolation block " + "is OFF in %s\r\n", __func__); + break; + } + } else { + switch (InterpolationFactor) { + case XRFDC_INTERP_DECIM_1X: + FabricRate = XRFDC_FAB_RATE_8; + break; + case XRFDC_INTERP_DECIM_2X: + case XRFDC_INTERP_DECIM_3X: + FabricRate = XRFDC_FAB_RATE_4; + break; + case XRFDC_INTERP_DECIM_4X: + case XRFDC_INTERP_DECIM_5X: + case XRFDC_INTERP_DECIM_6X: + FabricRate = XRFDC_FAB_RATE_2; + break; + case XRFDC_INTERP_DECIM_8X: + case XRFDC_INTERP_DECIM_10X: + case XRFDC_INTERP_DECIM_12X: + case XRFDC_INTERP_DECIM_16X: + case XRFDC_INTERP_DECIM_20X: + case XRFDC_INTERP_DECIM_24X: + case XRFDC_INTERP_DECIM_40X: + FabricRate = XRFDC_FAB_RATE_1; + break; + default: + metal_log(METAL_LOG_DEBUG, "\n Interpolation block " + "is OFF in %s\r\n", __func__); + break; + } + } + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_FABRIC_RATE_OFFSET, + XRFDC_FAB_RATE_RD_MASK, (FabricRate << XRFDC_FAB_RATE_RD_SHIFT)); + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Interpolation factor are returned back to the caller. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param InterpolationFactorPtr Pointer to return the interpolation factor +* for DAC blocks. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Only for DAC blocks +* +******************************************************************************/ +u32 XRFdc_GetInterpolationFactor(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id, u32 *InterpolationFactorPtr) +{ + u32 Status; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InterpolationFactorPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckDigitalPathEnabled(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block digital path not " + "enabled in %s\r\n", __func__); + goto RETURN_PATH; + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_DAC_TILE, Tile_Id, Block_Id); + + if (InstancePtr->RFdc_Config.IPType < 2) { + *InterpolationFactorPtr = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_DAC_INTERP_CTRL_OFFSET, XRFDC_INTERP_MODE_I_MASK); + if (*InterpolationFactorPtr == 0x3U) { + *InterpolationFactorPtr = XRFDC_INTERP_DECIM_4X; + } else if (*InterpolationFactorPtr == 0x4U) { + *InterpolationFactorPtr = XRFDC_INTERP_DECIM_8X; + } + } else { + *InterpolationFactorPtr = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_DAC_INTERP_CTRL_OFFSET, XRFDC_INTERP_MODE_I_MASK_EXT); + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Decimation factor are returned back to the caller. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param DecimationFactorPtr Pointer to return the Decimation factor +* for DAC blocks. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Only for ADC blocks +* +******************************************************************************/ +u32 XRFdc_GetDecimationFactor(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 *DecimationFactorPtr) +{ + u32 Status; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(DecimationFactorPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckDigitalPathEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block digital path not " + "enabled in %s\r\n", __func__); + goto RETURN_PATH; + } + + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Block_Id == XRFDC_BLK_ID1)) { + Block_Id = XRFDC_BLK_ID2; + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Block_Id); + + + + if (InstancePtr->RFdc_Config.IPType < 2) { + *DecimationFactorPtr = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_DECI_MODE_OFFSET, XRFDC_DEC_MOD_MASK); + if (*DecimationFactorPtr == 0x3U) { + *DecimationFactorPtr = XRFDC_INTERP_DECIM_4X; + } else if (*DecimationFactorPtr == 0x4U) { + *DecimationFactorPtr = XRFDC_INTERP_DECIM_8X; + } + } else { + *DecimationFactorPtr = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_DECI_MODE_OFFSET, XRFDC_DEC_MOD_MASK_EXT); + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Fabric data rate for the requested DAC block is set by writing to the +* corresponding register. The function writes the number of valid write words +* for the requested DAC block. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param FabricWrVldWords is write fabric rate to be set for DAC block. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Only for DAC blocks +* +******************************************************************************/ +u32 XRFdc_SetFabWrVldWords(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 FabricWrVldWords) +{ + u32 Status; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckDigitalPathEnabled(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block digital path not " + "enabled in %s\r\n", __func__); + goto RETURN_PATH; + } + + if (FabricWrVldWords > XRFDC_DAC_MAX_WR_FAB_RATE) { + metal_log(METAL_LOG_ERROR, "\n Requested write valid words " + "is Invalid in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_DAC_TILE, Tile_Id, Block_Id); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_FABRIC_RATE_OFFSET, + XRFDC_DAC_FAB_RATE_WR_MASK, FabricWrVldWords); + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; + +} + +/*****************************************************************************/ +/** +* +* Fabric data rate for the requested ADC block is set by writing to the +* corresponding register. The function writes the number of valid read words +* for the requested ADC block. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC block number inside the tile. Valid values +* are 0-3. +* @param FabricRdVldWords is Read fabric rate to be set for ADC block. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Only for ADC blocks +* +******************************************************************************/ +u32 XRFdc_SetFabRdVldWords(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 FabricRdVldWords) +{ + u32 Status; + u32 BaseAddr; + u32 Index; + u32 NoOfBlocks; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckDigitalPathEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block digital path not " + "enabled in %s\r\n", __func__); + goto RETURN_PATH; + } + + if (FabricRdVldWords > XRFDC_ADC_MAX_RD_FAB_RATE) { + metal_log(METAL_LOG_ERROR, "\n Requested read " + "valid words is Invalid in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + Index = Block_Id; + if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + for (; Index < NoOfBlocks; Index++) { + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Index); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, + XRFDC_ADC_FABRIC_RATE_OFFSET, XRFDC_FAB_RATE_RD_MASK, + (FabricRdVldWords << XRFDC_FAB_RATE_RD_SHIFT)); + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; + +} + +/*****************************************************************************/ +/** +* +* This API returns the the number of fabric write valid words requested +* for the block. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param FabricWrVldWordsPtr Pointer to return the fabric data rate for +* DAC block +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note ADC/DAC blocks +* +******************************************************************************/ +u32 XRFdc_GetFabWrVldWords(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 *FabricWrVldWordsPtr) +{ + u32 Status; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(FabricWrVldWordsPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckDigitalPathEnabled(InstancePtr, Type, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block digital path not " + "enabled in %s\r\n", __func__); + goto RETURN_PATH; + } + + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Block_Id == XRFDC_BLK_ID1) && (Type == XRFDC_ADC_TILE)) { + Block_Id = XRFDC_BLK_ID2; + } + + BaseAddr = XRFDC_BLOCK_BASE(Type, Tile_Id, Block_Id); + + *FabricWrVldWordsPtr = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_FABRIC_RATE_OFFSET); + if (Type == XRFDC_ADC_TILE) { + *FabricWrVldWordsPtr &= XRFDC_ADC_FAB_RATE_WR_MASK; + } else { + *FabricWrVldWordsPtr &= XRFDC_DAC_FAB_RATE_WR_MASK; + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* This API returns the the number of fabric read valid words requested +* for the block. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param FabricRdVldWordsPtr Pointer to return the fabric data rate for +* ADC/DAC block +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note ADC/DAC blocks +* +******************************************************************************/ +u32 XRFdc_GetFabRdVldWords(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 *FabricRdVldWordsPtr) +{ + u32 Status; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(FabricRdVldWordsPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckDigitalPathEnabled(InstancePtr, Type, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block digital path not " + "enabled in %s\r\n", __func__); + goto RETURN_PATH; + } + + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Block_Id == XRFDC_BLK_ID1) && (Type == XRFDC_ADC_TILE)) { + Block_Id = XRFDC_BLK_ID2; + } + BaseAddr = XRFDC_BLOCK_BASE(Type, Tile_Id, Block_Id); + + *FabricRdVldWordsPtr = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_FABRIC_RATE_OFFSET, XRFDC_FAB_RATE_RD_MASK); + *FabricRdVldWordsPtr = (*FabricRdVldWordsPtr) >> XRFDC_FAB_RATE_RD_SHIFT; + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* This API is to clear the Sticky bit in threshold config registers. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param ThresholdToUpdate Select which Threshold (Threshold0 or +* Threshold1 or both) to update. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Only ADC blocks +* +******************************************************************************/ +u32 XRFdc_ThresholdStickyClear(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 ThresholdToUpdate) +{ + u32 Status; + u32 BaseAddr; + u32 Index; + u32 NoOfBlocks; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + if ((ThresholdToUpdate != XRFDC_UPDATE_THRESHOLD_0) && + (ThresholdToUpdate != XRFDC_UPDATE_THRESHOLD_1) && + (ThresholdToUpdate != XRFDC_UPDATE_THRESHOLD_BOTH)) { + metal_log(METAL_LOG_ERROR, "\n Invalid ThresholdToUpdate " + "value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + Index = Block_Id; + if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + if (InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Index].MixerInputDataType == + XRFDC_DATA_TYPE_IQ) { + Index = Block_Id; + NoOfBlocks = XRFDC_NUM_OF_BLKS3; + if (Block_Id == XRFDC_BLK_ID1) { + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + for (; Index < NoOfBlocks;) { + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Index); + /* Update for Threshold0 */ + if ((ThresholdToUpdate == XRFDC_UPDATE_THRESHOLD_0) || + (ThresholdToUpdate == XRFDC_UPDATE_THRESHOLD_BOTH)) { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD0_CFG_OFFSET, XRFDC_TRSHD0_STIKY_CLR_MASK, + XRFDC_TRSHD0_STIKY_CLR_MASK); + } + /* Update for Threshold1 */ + if ((ThresholdToUpdate == XRFDC_UPDATE_THRESHOLD_1) || + (ThresholdToUpdate == XRFDC_UPDATE_THRESHOLD_BOTH)) { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD1_CFG_OFFSET, XRFDC_TRSHD1_STIKY_CLR_MASK, + XRFDC_TRSHD1_STIKY_CLR_MASK); + } + + if ((InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Index].MixerInputDataType == + XRFDC_DATA_TYPE_IQ) && (XRFdc_IsHighSpeedADC(InstancePtr, + Tile_Id) == 1)) { + Index += XRFDC_BLK_ID2; + } else { + Index += XRFDC_BLK_ID1; + } + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* This API sets the threshold clear mode. The clear mode can be through +* explicit DRP access (manual) or auto clear (QMC gain update event). +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADCC block number inside the tile. Valid values +* are 0-3. +* @param ThresholdToUpdate Select which Threshold (Threshold0 or +* Threshold1 or both) to update. +* @param ClrMode can be DRP access (manual) or auto clear (QMC gain +* update event). +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Only ADC blocks +* +******************************************************************************/ +u32 XRFdc_SetThresholdClrMode(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id, u32 ThresholdToUpdate, u32 ClrMode) +{ + u32 Status; + u16 ReadReg; + u32 BaseAddr; + u32 Index; + u32 NoOfBlocks; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + if ((ThresholdToUpdate != XRFDC_UPDATE_THRESHOLD_0) && + (ThresholdToUpdate != XRFDC_UPDATE_THRESHOLD_1) && + (ThresholdToUpdate != XRFDC_UPDATE_THRESHOLD_BOTH)) { + metal_log(METAL_LOG_ERROR, "\n Invalid ThresholdToUpdate " + "value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + if ((ClrMode != XRFDC_THRESHOLD_CLRMD_MANUAL_CLR) && + (ClrMode != XRFDC_THRESHOLD_CLRMD_AUTO_CLR)) { + metal_log(METAL_LOG_ERROR, "\n Invalid Clear mode " + "value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + Index = Block_Id; + if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + if (InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Index].MixerInputDataType == + XRFDC_DATA_TYPE_IQ) { + Index = Block_Id; + NoOfBlocks = XRFDC_NUM_OF_BLKS3; + if (Block_Id == XRFDC_BLK_ID1) { + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + for (; Index < NoOfBlocks;) { + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Index); + /* Update for Threshold0 */ + if ((ThresholdToUpdate == XRFDC_UPDATE_THRESHOLD_0) || + (ThresholdToUpdate == XRFDC_UPDATE_THRESHOLD_BOTH)) { + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD0_CFG_OFFSET); + if (ClrMode == XRFDC_THRESHOLD_CLRMD_MANUAL_CLR) { + ReadReg &= ~XRFDC_TRSHD0_CLR_MOD_MASK; + } else { + ReadReg |= XRFDC_TRSHD0_CLR_MOD_MASK; + } + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD0_CFG_OFFSET, ReadReg); + } + /* Update for Threshold1 */ + if ((ThresholdToUpdate == XRFDC_UPDATE_THRESHOLD_1) || + (ThresholdToUpdate == XRFDC_UPDATE_THRESHOLD_BOTH)) { + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD1_CFG_OFFSET); + if (ClrMode == XRFDC_THRESHOLD_CLRMD_MANUAL_CLR) { + ReadReg &= ~XRFDC_TRSHD1_CLR_MOD_MASK; + } else { + ReadReg |= XRFDC_TRSHD1_CLR_MOD_MASK; + } + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD1_CFG_OFFSET, ReadReg); + } + if ((InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Index].MixerInputDataType == + XRFDC_DATA_TYPE_IQ) && (XRFdc_IsHighSpeedADC(InstancePtr, + Tile_Id) == 1)) { + Index += XRFDC_BLK_ID2; + } else { + Index += XRFDC_BLK_ID1; + } + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Threshold settings are updated into the relevant registers. Driver structure +* is updated with the new values. There can be two threshold settings: +* threshold0 and threshold1. Both of them are independent of each other. +* The function returns the requested threshold (which can be threshold0, +* threshold1, or both. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param ThresholdSettingsPtr Pointer through which the register settings for +* thresholds are passed to the API. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Only ADC blocks +* +******************************************************************************/ +u32 XRFdc_SetThresholdSettings(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + XRFdc_Threshold_Settings *ThresholdSettingsPtr) +{ + u32 Status; + u32 BaseAddr; + u32 Index; + u32 NoOfBlocks; + XRFdc_Threshold_Settings *ThresholdConfigPtr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(ThresholdSettingsPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + Index = Block_Id; + if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + if (InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Index].MixerInputDataType == + XRFDC_DATA_TYPE_IQ) { + Index = Block_Id; + NoOfBlocks = XRFDC_NUM_OF_BLKS3; + if (Block_Id == XRFDC_BLK_ID1) { + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + for (; Index < NoOfBlocks;) { + ThresholdConfigPtr = &InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Analog_Datapath[Index].Threshold_Settings; + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Index); + + if ((ThresholdSettingsPtr->UpdateThreshold != XRFDC_UPDATE_THRESHOLD_0) && + (ThresholdSettingsPtr->UpdateThreshold != XRFDC_UPDATE_THRESHOLD_1) && + (ThresholdSettingsPtr->UpdateThreshold != XRFDC_UPDATE_THRESHOLD_BOTH)) { + metal_log(METAL_LOG_ERROR, "\n Invalid UpdateThreshold " + "value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if (((ThresholdSettingsPtr->UpdateThreshold == + XRFDC_UPDATE_THRESHOLD_0) || + (ThresholdSettingsPtr->UpdateThreshold == + XRFDC_UPDATE_THRESHOLD_BOTH)) && + (ThresholdSettingsPtr->ThresholdMode[0] > XRFDC_TRSHD_HYSTERISIS)) { + metal_log(METAL_LOG_ERROR, "\n Requested threshold " + "mode for threshold0 is invalid " + "in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if (((ThresholdSettingsPtr->UpdateThreshold == + XRFDC_UPDATE_THRESHOLD_1) || + (ThresholdSettingsPtr->UpdateThreshold == + XRFDC_UPDATE_THRESHOLD_BOTH)) && + (ThresholdSettingsPtr->ThresholdMode[1] > XRFDC_TRSHD_HYSTERISIS)) { + metal_log(METAL_LOG_ERROR, "\n Requested threshold " + "mode for threshold1 is invalid " + "in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + /* Update for Threshold0 */ + if ((ThresholdSettingsPtr->UpdateThreshold == + XRFDC_UPDATE_THRESHOLD_0) || + (ThresholdSettingsPtr->UpdateThreshold == + XRFDC_UPDATE_THRESHOLD_BOTH)) { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_TRSHD0_CFG_OFFSET, + XRFDC_TRSHD0_EN_MOD_MASK, + ThresholdSettingsPtr->ThresholdMode[0]); + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD0_AVG_LO_OFFSET, + (u16)ThresholdSettingsPtr->ThresholdAvgVal[0]); + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD0_AVG_UP_OFFSET, + (u16)(ThresholdSettingsPtr->ThresholdAvgVal[0] >> + XRFDC_TRSHD0_AVG_UPP_SHIFT)); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD0_UNDER_OFFSET, XRFDC_TRSHD0_UNDER_MASK, + ThresholdSettingsPtr->ThresholdUnderVal[0]); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD0_OVER_OFFSET, XRFDC_TRSHD0_OVER_MASK, + ThresholdSettingsPtr->ThresholdOverVal[0]); + + ThresholdConfigPtr->ThresholdMode[0] = + ThresholdSettingsPtr->ThresholdMode[0]; + ThresholdConfigPtr->ThresholdAvgVal[0] = + ThresholdSettingsPtr->ThresholdAvgVal[0]; + ThresholdConfigPtr->ThresholdUnderVal[0] = + ThresholdSettingsPtr->ThresholdUnderVal[0]; + ThresholdConfigPtr->ThresholdOverVal[0] = + ThresholdSettingsPtr->ThresholdOverVal[0]; + } + + /* Update for Threshold1 */ + if ((ThresholdSettingsPtr->UpdateThreshold == + XRFDC_UPDATE_THRESHOLD_1) || + (ThresholdSettingsPtr->UpdateThreshold == + XRFDC_UPDATE_THRESHOLD_BOTH)) { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD1_CFG_OFFSET, XRFDC_TRSHD1_EN_MOD_MASK, + ThresholdSettingsPtr->ThresholdMode[1]); + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD1_AVG_LO_OFFSET, + (u16)ThresholdSettingsPtr->ThresholdAvgVal[1]); + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD1_AVG_UP_OFFSET, + (u16)(ThresholdSettingsPtr->ThresholdAvgVal[1] >> + XRFDC_TRSHD1_AVG_UPP_SHIFT)); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD1_UNDER_OFFSET, XRFDC_TRSHD1_UNDER_MASK, + ThresholdSettingsPtr->ThresholdUnderVal[1]); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD1_OVER_OFFSET, XRFDC_TRSHD1_OVER_MASK, + ThresholdSettingsPtr->ThresholdOverVal[1]); + + ThresholdConfigPtr->ThresholdMode[1] = + ThresholdSettingsPtr->ThresholdMode[1]; + ThresholdConfigPtr->ThresholdAvgVal[1] = + ThresholdSettingsPtr->ThresholdAvgVal[1]; + ThresholdConfigPtr->ThresholdUnderVal[1] = + ThresholdSettingsPtr->ThresholdUnderVal[1]; + ThresholdConfigPtr->ThresholdOverVal[1] = + ThresholdSettingsPtr->ThresholdOverVal[1]; + } + if ((InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Index].MixerInputDataType == + XRFDC_DATA_TYPE_IQ) && (XRFdc_IsHighSpeedADC(InstancePtr, + Tile_Id) == 1)) { + Index += XRFDC_BLK_ID2; + } else { + Index += XRFDC_BLK_ID1; + } + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Threshold settings are read from the corresponding registers and are passed +* back to the caller. There can be two threshold settings: +* threshold0 and threshold1. Both of them are independent of each other. +* The function returns the requested threshold (which can be threshold0, +* threshold1, or both. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param ThresholdSettingsPtr Pointer through which the register settings +* for thresholds are passed back.. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Only for ADC blocks +* +******************************************************************************/ +u32 XRFdc_GetThresholdSettings(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + XRFdc_Threshold_Settings *ThresholdSettingsPtr) +{ + u32 Status; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(ThresholdSettingsPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Block_Id == XRFDC_BLK_ID1) && (InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Block_Id].MixerInputDataType != + XRFDC_DATA_TYPE_IQ)) { + Block_Id = XRFDC_BLK_ID2; + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Block_Id); + + /* Threshold mode */ + ThresholdSettingsPtr->UpdateThreshold = XRFDC_UPDATE_THRESHOLD_BOTH; + ThresholdSettingsPtr->ThresholdMode[0] = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD0_CFG_OFFSET, XRFDC_TRSHD0_EN_MOD_MASK); + ThresholdSettingsPtr->ThresholdMode[1] = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_TRSHD1_CFG_OFFSET, XRFDC_TRSHD1_EN_MOD_MASK); + + /* Threshold Average Value */ + ThresholdSettingsPtr->ThresholdAvgVal[0] = XRFdc_ReadReg16(InstancePtr, + BaseAddr, XRFDC_ADC_TRSHD0_AVG_LO_OFFSET); + ThresholdSettingsPtr->ThresholdAvgVal[0] |= (XRFdc_ReadReg16(InstancePtr, + BaseAddr, XRFDC_ADC_TRSHD0_AVG_UP_OFFSET) << + XRFDC_TRSHD0_AVG_UPP_SHIFT); + ThresholdSettingsPtr->ThresholdAvgVal[1] = XRFdc_ReadReg16(InstancePtr, + BaseAddr, XRFDC_ADC_TRSHD1_AVG_LO_OFFSET); + ThresholdSettingsPtr->ThresholdAvgVal[1] |= (XRFdc_ReadReg16(InstancePtr, + BaseAddr, XRFDC_ADC_TRSHD1_AVG_UP_OFFSET) << + XRFDC_TRSHD1_AVG_UPP_SHIFT); + + /* Threshold Under Value */ + ThresholdSettingsPtr->ThresholdUnderVal[0] = XRFdc_RDReg(InstancePtr, + BaseAddr, XRFDC_ADC_TRSHD0_UNDER_OFFSET, XRFDC_TRSHD0_UNDER_MASK); + ThresholdSettingsPtr->ThresholdUnderVal[1] = XRFdc_RDReg(InstancePtr, + BaseAddr, XRFDC_ADC_TRSHD1_UNDER_OFFSET, XRFDC_TRSHD1_UNDER_MASK); + + /* Threshold Over Value */ + ThresholdSettingsPtr->ThresholdOverVal[0] = XRFdc_RDReg(InstancePtr, + BaseAddr, XRFDC_ADC_TRSHD0_OVER_OFFSET, XRFDC_TRSHD0_OVER_MASK); + ThresholdSettingsPtr->ThresholdOverVal[1] = XRFdc_RDReg(InstancePtr, + BaseAddr, XRFDC_ADC_TRSHD1_OVER_OFFSET, XRFDC_TRSHD1_OVER_MASK); + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Decoder mode is updated into the relevant registers. Driver structure is +* updated with the new values. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is DAC block number inside the tile. Valid values +* are 0-3. +* @param DecoderMode Valid values are 1 (Maximum SNR, for non- +* randomized decoder), 2 (Maximum Linearity, for randomized decoder) +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Only DAC blocks +* +******************************************************************************/ +u32 XRFdc_SetDecoderMode(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 DecoderMode) +{ + u32 Status; + u32 *DecoderModeConfigPtr; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + DecoderModeConfigPtr = &InstancePtr->DAC_Tile[Tile_Id]. + DACBlock_Analog_Datapath[Block_Id].DecoderMode; + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_DAC_TILE, Tile_Id, Block_Id); + + if ((DecoderMode != XRFDC_DECODER_MAX_SNR_MODE) && + (DecoderMode != XRFDC_DECODER_MAX_LINEARITY_MODE)) { + metal_log(METAL_LOG_ERROR, "\n Invalid decoder mode " + "in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_DAC_DECODER_CTRL_OFFSET, + XRFDC_DEC_CTRL_MODE_MASK, DecoderMode); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_DAC_DECODER_CLK_OFFSET, + XRFDC_DEC_CTRL_MODE_MASK, DecoderMode); + *DecoderModeConfigPtr = DecoderMode; + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Decoder mode is read and returned back. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is DAC block number inside the tile. Valid values +* are 0-3. +* @param DecoderModePtr Valid values are 1 (Maximum SNR, for non-randomized +* decoder), 2 (Maximum Linearity, for randomized decoder) +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Only for DAC blocks +* +******************************************************************************/ +u32 XRFdc_GetDecoderMode(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 *DecoderModePtr) +{ + u32 Status; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(DecoderModePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_DAC_TILE, Tile_Id, Block_Id); + *DecoderModePtr = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_DAC_DECODER_CTRL_OFFSET, XRFDC_DEC_CTRL_MODE_MASK); + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Resets the NCO phase of the current block phase accumulator. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note None +* +******************************************************************************/ +u32 XRFdc_ResetNCOPhase(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id) +{ + u32 Status; + u32 BaseAddr; + u32 Index; + u32 NoOfBlocks; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckDigitalPathEnabled(InstancePtr, Type, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block digital path not " + "enabled in %s\r\n", __func__); + goto RETURN_PATH; + } + + Index = Block_Id; + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE)) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + for (; Index < NoOfBlocks; Index++) { + BaseAddr = XRFDC_BLOCK_BASE(Type, Tile_Id, Index); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_NCO_RST_OFFSET, + XRFDC_NCO_PHASE_RST_MASK, XRFDC_NCO_PHASE_RST_MASK); + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; + +} +/*****************************************************************************/ +/** +* +* Enable and Disable the ADC/DAC FIFO. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Enable valid values are 1 (FIFO enable) and 0 (FIFO Disable) +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note Common API for ADC/DAC blocks +* +******************************************************************************/ +u32 XRFdc_SetupFIFO(XRFdc *InstancePtr, u32 Type, int Tile_Id, u8 Enable) +{ + u32 Status; + u32 BaseAddr; + u16 NoOfTiles; + u16 Index; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + if ((Enable != 0U) && (Enable != 1U)) { + metal_log(METAL_LOG_ERROR, "\n Invalid enable " + "value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + /* An input tile if of -1 selects all tiles */ + if (Tile_Id == XRFDC_SELECT_ALL_TILES) { + NoOfTiles = XRFDC_NUM_OF_TILES4; + Index = XRFDC_TILE_ID0; + } else { + NoOfTiles = Tile_Id + 1; + Index = Tile_Id; + } + + for (; Index < NoOfTiles; Index++) { + BaseAddr = XRFDC_CTRL_STS_BASE(Type, Index); + + Status = XRFdc_CheckTileEnabled(InstancePtr, Type, Index); + if ((Status != XRFDC_SUCCESS) && (Tile_Id != XRFDC_SELECT_ALL_TILES)) { + metal_log(METAL_LOG_ERROR, "\n Requested tile%d not " + "available in %s\r\n", Index, __func__); + goto RETURN_PATH; + } else if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_DEBUG, "\n Tile%d not " + "available in %s\r\n", Index, __func__); + continue; + } else { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_FIFO_ENABLE, + XRFDC_FIFO_EN_MASK, (!Enable)); + } + } + Status = XRFDC_SUCCESS; + +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Current status of ADC/DAC FIFO. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param EnablePtr valid values are 1 (FIFO enable) and 0 (FIFO Disable) +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note Common API for ADC/DAC blocks +* +******************************************************************************/ +u32 XRFdc_GetFIFOStatus(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u8 *EnablePtr) +{ + u32 Status; + u32 BaseAddr; + u32 ReadReg; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(EnablePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckTileEnabled(InstancePtr, Type, Tile_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested tile not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + BaseAddr = XRFDC_CTRL_STS_BASE(Type, Tile_Id); + + ReadReg = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_FIFO_ENABLE, XRFDC_FIFO_EN_MASK); + *EnablePtr = (!ReadReg); + + Status = XRFDC_SUCCESS; + +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Get Output Current for DAC block. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param OutputCurrPtr pointer to return the output current. +* +* @return +* - Return Output Current for DAC block +* +******************************************************************************/ +u32 XRFdc_GetOutputCurr(XRFdc *InstancePtr, u32 Tile_Id, + u32 Block_Id, u32 *OutputCurrPtr) +{ + u32 Status; + u32 BaseAddr; + u16 ReadReg_Cfg2; + u16 ReadReg_Cfg3; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(OutputCurrPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_DAC_TILE, Tile_Id, Block_Id); + + ReadReg_Cfg2 = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_DAC_MC_CFG2_OFFSET, XRFDC_DAC_MC_CFG2_OPCSCAS_MASK); + ReadReg_Cfg3 = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_DAC_MC_CFG3_OFFSET, XRFDC_DAC_MC_CFG3_CSGAIN_MASK); + if ((ReadReg_Cfg2 == XRFDC_DAC_MC_CFG2_OPCSCAS_32MA) && + (ReadReg_Cfg3 == XRFDC_DAC_MC_CFG3_CSGAIN_32MA)) { + *OutputCurrPtr = XRFDC_OUTPUT_CURRENT_32MA; + } else if ((ReadReg_Cfg2 == XRFDC_DAC_MC_CFG2_OPCSCAS_20MA) && + (ReadReg_Cfg3 == XRFDC_DAC_MC_CFG3_CSGAIN_20MA)) { + *OutputCurrPtr = XRFDC_OUTPUT_CURRENT_20MA; + } else if ((ReadReg_Cfg2 == 0x0) && (ReadReg_Cfg3 == 0x0)) { + *OutputCurrPtr = 0x0; + } else { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, "\n Invalid output " + "current value %s\r\n", __func__); + goto RETURN_PATH; + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Set the Nyquist zone. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param NyquistZone valid values are 1 (Odd),2 (Even). +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note Common API for ADC/DAC blocks +* +******************************************************************************/ +u32 XRFdc_SetNyquistZone(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 NyquistZone) +{ + u32 Status; + u16 ReadReg; + u32 BaseAddr; + u32 Index; + u32 NoOfBlocks; + u8 CalibrationMode = 0U; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, Type, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + if ((NyquistZone != XRFDC_ODD_NYQUIST_ZONE) && + (NyquistZone != XRFDC_EVEN_NYQUIST_ZONE)) { + metal_log(METAL_LOG_ERROR, "\n Invalid NyquistZone " + "value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + Index = Block_Id; + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE)) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + for (; Index < NoOfBlocks; Index++) { + BaseAddr = XRFDC_BLOCK_BASE(Type, Tile_Id, Index); + if (Type == XRFDC_ADC_TILE) { + /* Identify calibration mode */ + Status = XRFdc_GetCalibrationMode(InstancePtr, + Tile_Id, Block_Id, &CalibrationMode); + if (Status != XRFDC_SUCCESS) { + return XRFDC_FAILURE; + } + + if (CalibrationMode == XRFDC_CALIB_MODE1) { + if (NyquistZone == + XRFDC_ODD_NYQUIST_ZONE) { + NyquistZone = + XRFDC_EVEN_NYQUIST_ZONE; + } else { + NyquistZone = + XRFDC_ODD_NYQUIST_ZONE; + } + } + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_TI_TISK_CRL0_OFFSET); + if ((NyquistZone % 2U) == 0U) { + ReadReg |= XRFDC_TI_TISK_ZONE_MASK; + } else { + ReadReg &= ~XRFDC_TI_TISK_ZONE_MASK; + } + + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_TI_TISK_CRL0_OFFSET, ReadReg); + InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Analog_Datapath[Index]. + NyquistZone = NyquistZone; + } else { + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_DAC_MC_CFG0_OFFSET); + if ((NyquistZone % 2U) == 0U) { + ReadReg |= XRFDC_MC_CFG0_MIX_MODE_MASK; + } else { + ReadReg &= ~XRFDC_MC_CFG0_MIX_MODE_MASK; + } + + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_DAC_MC_CFG0_OFFSET, ReadReg); + InstancePtr->DAC_Tile[Tile_Id]. + DACBlock_Analog_Datapath[Index]. + NyquistZone = NyquistZone; + } + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Get the Nyquist zone. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param NyquistZonePtr Pointer to return the Nyquist zone. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note Common API for ADC/DAC blocks +* +******************************************************************************/ +u32 XRFdc_GetNyquistZone(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 *NyquistZonePtr) +{ + u32 Status; + u16 ReadReg; + u32 BaseAddr; + u32 Block; + u8 CalibrationMode = 0U; + u8 MultibandConfig; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(NyquistZonePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + if (Type == XRFDC_ADC_TILE) { + MultibandConfig = InstancePtr->ADC_Tile[Tile_Id].MultibandConfig; + } else { + MultibandConfig = InstancePtr->DAC_Tile[Tile_Id].MultibandConfig; + } + + if (MultibandConfig != XRFDC_MB_MODE_SB) { + Status = XRFdc_CheckDigitalPathEnabled(InstancePtr, Type, + Tile_Id, Block_Id); + } else { + Status = XRFdc_CheckBlockEnabled(InstancePtr, Type, + Tile_Id, Block_Id); + } + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + Block = Block_Id; + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Block_Id == XRFDC_BLK_ID1) && (Type == XRFDC_ADC_TILE)) { + Block_Id = XRFDC_BLK_ID2; + } + + BaseAddr = XRFDC_BLOCK_BASE(Type, Tile_Id, Block_Id); + + if (Type == XRFDC_ADC_TILE) { + /* Identify calibration mode */ + Status = XRFdc_GetCalibrationMode(InstancePtr, Tile_Id, + Block, &CalibrationMode); + if (Status != XRFDC_SUCCESS) { + return XRFDC_FAILURE; + } + ReadReg = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_TI_TISK_CRL0_OFFSET, XRFDC_TI_TISK_ZONE_MASK); + *NyquistZonePtr = (ReadReg >> XRFDC_TISK_ZONE_SHIFT); + } else { + ReadReg = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_DAC_MC_CFG0_OFFSET, XRFDC_MC_CFG0_MIX_MODE_MASK); + *NyquistZonePtr = (ReadReg >> XRFDC_MC_CFG0_MIX_MODE_SHIFT); + } + if (*NyquistZonePtr == 0U) { + *NyquistZonePtr = XRFDC_ODD_NYQUIST_ZONE; + } else { + *NyquistZonePtr = XRFDC_EVEN_NYQUIST_ZONE; + } + + if ((Type == XRFDC_ADC_TILE) && + (CalibrationMode == XRFDC_CALIB_MODE1)) { + if (*NyquistZonePtr == XRFDC_EVEN_NYQUIST_ZONE) { + *NyquistZonePtr = XRFDC_ODD_NYQUIST_ZONE; + } else { + *NyquistZonePtr = XRFDC_EVEN_NYQUIST_ZONE; + } + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* This API is to set the DAC Datapath mode. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param Mode valid values are 0-3. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled / out of range. +* +* @note Only for DAC blocks +* +******************************************************************************/ +u32 XRFdc_SetDataPathMode(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 Mode) +{ + u32 Status = XRFDC_SUCCESS; + u32 BaseAddr; + u32 NyquistZone; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_DAC_TILE, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + return Status; + } + + if (Mode > XRFDC_DAC_MODE_MAX) { + metal_log(METAL_LOG_ERROR, "\n Invalid Mode " + "value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + return Status; + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_DAC_TILE, Tile_Id, Block_Id); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_DAC_DATAPATH_OFFSET, + XRFDC_DATAPATH_MODE_MASK, Mode); + + NyquistZone = (Mode == XRFDC_DAC_MODE_7G_NQ2) ? + XRFDC_EVEN_NYQUIST_ZONE : XRFDC_ODD_NYQUIST_ZONE; + XRFdc_SetNyquistZone(InstancePtr, XRFDC_DAC_TILE, Tile_Id, Block_Id, NyquistZone); + return Status; +} + +/*****************************************************************************/ +/** +* +* This API is to get the DAC Datapath mode. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param ModePtr pointer used to return value. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note Only for DAC blocks +* +******************************************************************************/ +u32 XRFdc_GetDataPathMode(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 *ModePtr) +{ + u32 Status = XRFDC_SUCCESS; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + Xil_AssertNonvoid(ModePtr != NULL); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_DAC_TILE, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + return Status; + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_DAC_TILE, Tile_Id, Block_Id); + *ModePtr = XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_DAC_DATAPATH_OFFSET, + XRFDC_DATAPATH_MODE_MASK); + return Status; +} + +/*****************************************************************************/ +/** +* +* This API is to set the DAC Image Reject Filter Pass mode. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param Mode valid values are 0 (for low pass) 1 (for high pass). +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled / bad parameter passed +* +* @note Only for DAC blocks +* +******************************************************************************/ +u32 XRFdc_SetIMRPassMode(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 Mode) +{ + u32 Status = XRFDC_SUCCESS; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_DAC_TILE, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + return Status; + } + + if (Mode > XRFDC_DAC_IMR_MODE_MAX) { + metal_log(METAL_LOG_ERROR, "\n Invalid Mode " + "value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + return Status; + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_DAC_TILE, Tile_Id, Block_Id); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_DAC_DATAPATH_OFFSET, + XRFDC_DATAPATH_IMR_MASK, Mode << 2); + + return Status; +} + +/*****************************************************************************/ +/** +* +* This API is to get the DAC Image Reject Filter Pass mode. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param ModePtr pointer used to return value. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note Only for DAC blocks +* +******************************************************************************/ +u32 XRFdc_GetIMRPassMode(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 *ModePtr) +{ + u32 Status = XRFDC_SUCCESS; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + Xil_AssertNonvoid(ModePtr != NULL); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_DAC_TILE, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + return Status; + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_DAC_TILE, Tile_Id, Block_Id); + *ModePtr = (XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_DAC_DATAPATH_OFFSET, + XRFDC_DATAPATH_IMR_MASK)) >> 2; + return Status; +} +/*****************************************************************************/ +/** +* +* This API is to set the Calibration mode. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param CalibrationMode valid values are 1 and 2. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note Only for ADC blocks +* +******************************************************************************/ +u32 XRFdc_SetCalibrationMode(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u8 CalibrationMode) +{ + u32 Status; + u16 ReadReg; + u32 BaseAddr; + u32 Index; + u32 NoOfBlocks; + XRFdc_Mixer_Settings Mixer_Settings = {0}; + u32 NyquistZone = 0U; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + if ((CalibrationMode != XRFDC_CALIB_MODE1) && + (CalibrationMode != XRFDC_CALIB_MODE2)) { + metal_log(METAL_LOG_ERROR, "\n Invalid Calibration mode " + "value in %s\r\n", __func__); + return XRFDC_FAILURE; + } + + Index = Block_Id; + if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + /* Get Mixer Configurations */ + Status = XRFdc_GetMixerSettings(InstancePtr, XRFDC_ADC_TILE, + Tile_Id, Block_Id, &Mixer_Settings); + if (Status != XRFDC_SUCCESS) { + return XRFDC_FAILURE; + } + + /* Get Nyquist Zone */ + Status = XRFdc_GetNyquistZone(InstancePtr, XRFDC_ADC_TILE, + Tile_Id, Block_Id, &NyquistZone); + if (Status != XRFDC_SUCCESS) { + return XRFDC_FAILURE; + } + + for (; Index < NoOfBlocks; Index++) { + BaseAddr = XRFDC_ADC_TILE_DRP_ADDR(Tile_Id) + + XRFDC_BLOCK_ADDR_OFFSET(Index); + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_TI_DCB_CRL0_OFFSET); + ReadReg &= ~XRFDC_TI_DCB_MODE_MASK; + if (CalibrationMode == XRFDC_CALIB_MODE1) { + if (((Index % 2U) != 0U) && + (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1)) { + ReadReg |= XRFDC_TI_DCB_MODE1_4GSPS; + } else if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 0) { + ReadReg |= XRFDC_TI_DCB_MODE1_2GSPS; + } + } + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_TI_DCB_CRL0_OFFSET, ReadReg); + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Analog_Datapath[Index]. + CalibrationMode = CalibrationMode; + } + + /* Set Nyquist Zone */ + Status = XRFdc_SetNyquistZone(InstancePtr, XRFDC_ADC_TILE, + Tile_Id, Block_Id, NyquistZone); + if (Status != XRFDC_SUCCESS) { + return XRFDC_FAILURE; + } + + /* Set Mixer Configurations */ + Status = XRFdc_SetMixerSettings(InstancePtr, XRFDC_ADC_TILE, + Tile_Id, Block_Id, &Mixer_Settings); + if (Status != XRFDC_SUCCESS) { + return XRFDC_FAILURE; + } + + Status = XRFDC_SUCCESS; + + return Status; +} + +/*****************************************************************************/ +/** +* +* This API is to get the Calibration mode. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param CalibrationModePtr pointer to get the calibration mode. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note Only for ADC blocks +* +******************************************************************************/ +u32 XRFdc_GetCalibrationMode(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u8 *CalibrationModePtr) +{ + u32 Status; + u16 ReadReg; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(CalibrationModePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + if (InstancePtr->ADC_Tile[Tile_Id].MultibandConfig != XRFDC_MB_MODE_SB) { + Status = XRFdc_CheckDigitalPathEnabled(InstancePtr, XRFDC_ADC_TILE, + Tile_Id, Block_Id); + } else { + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_ADC_TILE, + Tile_Id, Block_Id); + } + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) { + if (Block_Id == XRFDC_BLK_ID1) { + Block_Id = XRFDC_BLK_ID3; + } + if (Block_Id == XRFDC_BLK_ID0) { + Block_Id = XRFDC_BLK_ID1; + } + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Block_Id); + ReadReg = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_TI_DCB_CRL0_OFFSET, XRFDC_TI_DCB_MODE_MASK); + if (ReadReg != 0U) { + *CalibrationModePtr = XRFDC_CALIB_MODE1; + } else { + *CalibrationModePtr = XRFDC_CALIB_MODE2; + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* This API is used to set the mode for the Inverse-Sinc filter. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is DAC block number inside the tile. Valid values +* are 0-3. +* @param Mode valid values are 0(disable), 1(1st Nyquist zone) + and 2(2nd Nyquist zone). +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled/invalid mode. +* +* @note Only DAC blocks +* +******************************************************************************/ +u32 XRFdc_SetInvSincFIR(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u16 Mode) +{ + u32 Status; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + if (Mode > ((InstancePtr->RFdc_Config.IPType < 2)?XRFDC_INV_SYNC_EN_MAX:XRFDC_INV_SYNC_MODE_MAX)) { + metal_log(METAL_LOG_ERROR, "\n Invalid mode " + "value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_DAC_TILE, Tile_Id, Block_Id); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_DAC_INVSINC_OFFSET, + (InstancePtr->RFdc_Config.IPType < 2)?XRFDC_EN_INVSINC_MASK:XRFDC_MODE_INVSINC_MASK, Mode); + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* This API is used to get the Inverse-Sinc filter mode. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is DAC block number inside the tile. Valid values +* are 0-3. +* @param ModePtr is a pointer to get the inv-sinc status. valid values +* are 0(disable), 1(1st Nyquist zone) and 2(2nd Nyquist zone). +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Only DAC blocks +* +******************************************************************************/ +u32 XRFdc_GetInvSincFIR(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u16 *ModePtr) +{ + u32 Status; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + Xil_AssertNonvoid(ModePtr != NULL); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_DAC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_DAC_TILE, Tile_Id, Block_Id); + *ModePtr = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_DAC_INVSINC_OFFSET, (InstancePtr->RFdc_Config.IPType < 2)?XRFDC_EN_INVSINC_MASK:XRFDC_MODE_INVSINC_MASK); + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* Static API to dump ADC registers. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3, and -1. +* +* @return +* None +* +* @note None +* +******************************************************************************/ +static void XRFdc_DumpADCRegs(XRFdc *InstancePtr, int Tile_Id) +{ + u32 BlockId; + u32 Block; + u32 IsBlockAvail; + u32 Offset; + u32 BaseAddr; + u32 ReadReg = 0U; + + for (BlockId = XRFDC_BLK_ID0; BlockId < XRFDC_BLK_ID4; BlockId++) { + Block = BlockId; + if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) { + if (BlockId == XRFDC_BLK_ID1) { + Block = XRFDC_BLK_ID0; + } + if ((BlockId == XRFDC_BLK_ID3) || (BlockId == XRFDC_BLK_ID2)) { + Block = XRFDC_BLK_ID1; + } + } + IsBlockAvail = XRFdc_IsADCBlockEnabled(InstancePtr, Tile_Id, + Block); + if (IsBlockAvail == 0U) { + IsBlockAvail = XRFdc_IsADCDigitalPathEnabled(InstancePtr, Tile_Id, + Block); + if (IsBlockAvail == 0U) { + continue; + } + } + metal_log(METAL_LOG_DEBUG, "\n ADC%d%d:: \r\n", Tile_Id, BlockId); + BaseAddr = XRFDC_ADC_TILE_DRP_ADDR(Tile_Id) + + XRFDC_BLOCK_ADDR_OFFSET(BlockId); + for (Offset = 0x0U; Offset <= 0x284U; Offset += 0x4U) { + if ((Offset >= 0x24U && Offset <= 0x2CU) || + (Offset >= 0x48U && Offset <= 0x7CU) || + (Offset >= 0xACU && Offset <= 0xC4U) || + (Offset >= 0x114U && Offset <= 0x13CU) || + (Offset >= 0x188U && Offset <= 0x194U) || + (Offset >= 0x1B8U && Offset <= 0x1BCU) || + (Offset >= 0x1D8U && Offset <= 0x1FCU) || + (Offset >= 0x240U && Offset <= 0x27CU)) { + continue; + } + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, Offset); + metal_log(METAL_LOG_DEBUG, + "\n offset = 0x%x and Value = 0x%x \t", + Offset, ReadReg); + } + } + (void)ReadReg; +} + +/*****************************************************************************/ +/** +* +* Static API to dump DAC registers. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id Valid values are 0-3, and -1. +* +* @return +* None +* +* @note None +* +******************************************************************************/ +static void XRFdc_DumpDACRegs(XRFdc *InstancePtr, int Tile_Id) +{ + u32 BlockId; + u32 IsBlockAvail; + u32 Offset; + u32 BaseAddr; + u32 ReadReg = 0U; + + for (BlockId = XRFDC_BLK_ID0; BlockId < XRFDC_BLK_ID4; BlockId++) { + IsBlockAvail = XRFdc_IsDACBlockEnabled(InstancePtr, Tile_Id, + BlockId); + if (IsBlockAvail == 0U) { + IsBlockAvail = XRFdc_IsDACDigitalPathEnabled(InstancePtr, Tile_Id, + BlockId); + if (IsBlockAvail == 0U) { + continue; + } + } + metal_log(METAL_LOG_DEBUG, "\n DAC%d%d:: \r\n", Tile_Id, BlockId); + BaseAddr = XRFDC_DAC_TILE_DRP_ADDR(Tile_Id) + + XRFDC_BLOCK_ADDR_OFFSET(BlockId); + for (Offset = 0x0U; Offset <= 0x24CU; Offset += 0x4U) { + if ((Offset >= 0x28U && Offset <= 0x34U) || + (Offset >= 0x48U && Offset <= 0x7CU) || + (Offset >= 0xA8U && Offset <= 0xBCU) || + (Offset >= 0xE4U && Offset <= 0xFCU) || + (Offset >= 0x16CU && Offset <= 0x17CU) || + (Offset >= 0x198U && Offset <= 0x1BCU) || + (Offset >= 0x1ECU && Offset <= 0x1FCU) || + (Offset >= 0x204U && Offset <= 0x23CU)) { + continue; + } + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, Offset); + metal_log(METAL_LOG_DEBUG, + "\n offset = 0x%x and Value = 0x%x \t", + Offset, ReadReg); + } + } + (void)ReadReg; +} + +/*****************************************************************************/ +/** +* +* Static API to dump HSCOM registers. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3, and -1. +* +* @return +* None +* +* @note None +* +******************************************************************************/ +static void XRFdc_DumpHSCOMRegs(XRFdc *InstancePtr, u32 Type, int Tile_Id) +{ + u32 Offset; + u32 BaseAddr; + u32 ReadReg = 0U; + + if (Type == XRFDC_ADC_TILE) { + metal_log(METAL_LOG_DEBUG, "\n ADC%d HSCOM:: \r\n", Tile_Id); + BaseAddr = XRFDC_ADC_TILE_DRP_ADDR(Tile_Id) + XRFDC_HSCOM_ADDR; + + } else { + metal_log(METAL_LOG_DEBUG, "\n DAC%d HSCOM:: \r\n", Tile_Id); + BaseAddr = XRFDC_DAC_TILE_DRP_ADDR(Tile_Id) + XRFDC_HSCOM_ADDR; + } + metal_log(METAL_LOG_DEBUG, "\n Offset\tValue \r\n"); + for (Offset = 0x0U; Offset <= 0x148U; Offset += 0x4U) { + if ((Offset >= 0x60U && Offset <= 0x88U) || + (Offset == 0xBCU) || + (Offset >= 0xC4U && Offset <= 0xFCU) || + (Offset >= 0x110U && Offset <= 0x11CU) || + (Offset >= 0x12CU && Offset <= 0x13CU)) { + continue; + } + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, Offset); + metal_log(METAL_LOG_DEBUG, "\n 0x%x \t 0x%x \t", + Offset, ReadReg); + } + (void)ReadReg; +} + +/*****************************************************************************/ +/** +* +* This Prints the offset of the register along with the content. This API is +* meant to be used for debug purposes. It prints to the console the contents +* of registers for the passed Tile_Id. If -1 is passed, it prints the contents +* of the registers for all the tiles for the respective ADC or DAC +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3, and -1. +* +* @return +* None +* +* @note None +* +******************************************************************************/ +void XRFdc_DumpRegs(XRFdc *InstancePtr, u32 Type, int Tile_Id) +{ + u16 NoOfTiles; + u16 Index; + + Xil_AssertVoid(InstancePtr != NULL); + Xil_AssertVoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + if (Tile_Id == XRFDC_SELECT_ALL_TILES) { + NoOfTiles = XRFDC_NUM_OF_TILES4; + } else { + NoOfTiles = XRFDC_NUM_OF_TILES1; + } + for (Index = XRFDC_TILE_ID0; Index < NoOfTiles; Index++) { + if (NoOfTiles == XRFDC_NUM_OF_TILES4) { + Tile_Id = Index; + } + if (Type == XRFDC_ADC_TILE) { + XRFdc_DumpADCRegs(InstancePtr, Tile_Id); + } else { + XRFdc_DumpDACRegs(InstancePtr, Tile_Id); + } + XRFdc_DumpHSCOMRegs(InstancePtr, Type, Tile_Id); + } +} + +/*****************************************************************************/ +/** +* +* This is a stub for the status callback. The stub is here in case the upper +* layers forget to set the handler. +* +* @param CallBackRefPtr is a pointer to the upper layer callback reference. +* @param Type indicates ADC/DAC. +* @param Tile_Id indicates Tile number (0-3). +* @param Block_Id indicates Block number (0-3). +* @param StatusEvent indicates one or more interrupt occurred. +* +* @return None. +* +* @note None. +* +******************************************************************************/ +static void StubHandler(void *CallBackRefPtr, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 StatusEvent) +{ + (void) ((void *)CallBackRefPtr); + (void) Type; + (void) Tile_Id; + (void) Block_Id; + (void) StatusEvent; + + Xil_AssertVoidAlways(); + +} +/*****************************************************************************/ +/** +* +* This function is used to get the Link Coupling mode. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id indicates Tile number (0-3). +* @param Block_Id indicates Block number(0-3 for 2G, 0-1 for 4G). +* @param ModePtr pointer to get link coupling mode. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note Only for ADC blocks +* +******************************************************************************/ +u32 XRFdc_GetLinkCoupling(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 *ModePtr) +{ + u32 Status; + u16 ReadReg; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(ModePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Block_Id == XRFDC_BLK_ID1)) { + Block_Id = XRFDC_BLK_ID2; + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Block_Id); + + ReadReg = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_RXPR_MC_CFG0_OFFSET, XRFDC_RX_MC_CFG0_CM_MASK); + if (ReadReg != 0U) { + *ModePtr = XRFDC_LINK_COUPLING_AC; + } else { + *ModePtr = XRFDC_LINK_COUPLING_DC; + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* This function is used to set the IM3 Dither mode. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id indicates Tile number (0-3). +* @param Block_Id indicates Block number(0-3 for LS, 0-1 for HS). +* @param Mode 0: Disable +* 1: Enable +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note Only for ADC blocks +* +******************************************************************************/ +u32 XRFdc_SetDither(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 Mode) +{ + u32 Status; + u32 BaseAddr; + u32 Index; + u32 NoOfBlocks; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + if (Mode > XRFDC_DITH_ENABLE) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, "\n Invalid Dither Mode " + "in %s\r\n", __func__); + goto RETURN_PATH; + } + Index = Block_Id; + if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + for (; Index < NoOfBlocks; Index++) { + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Index); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, + XRFDC_ADC_DAC_MC_CFG0_OFFSET, XRFDC_RX_MC_CFG0_IM3_DITH_MASK, + (Mode << XRFDC_RX_MC_CFG0_IM3_DITH_SHIFT)); + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* This function is used to get the IM3 Dither mode. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id indicates Tile number (0-3). +* @param Block_Id indicates Block number(0-3 for LS, 0-1 for HS). +* @param ModePtr pointer to get link coupling mode. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note Only for ADC blocks +* +******************************************************************************/ +u32 XRFdc_GetDither(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, + u32 *ModePtr) +{ + u32 Status; + u16 ReadReg; + u32 BaseAddr; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(ModePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Block_Id == XRFDC_BLK_ID1)) { + Block_Id = XRFDC_BLK_ID2; + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Block_Id); + + ReadReg = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_DAC_MC_CFG0_OFFSET, XRFDC_RX_MC_CFG0_IM3_DITH_MASK); + if (ReadReg != 0U) { + *ModePtr = XRFDC_DITH_ENABLE; + } else { + *ModePtr = XRFDC_DITH_DISABLE; + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/*****************************************************************************/ +/** +* +* This function is used to set the ADC Signal Detector Settings. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id indicates Tile number (0-3). +* @param Block_Id indicates Block number(0-3 for LS, 0-1 for HS). +* @param SettingsPtr pointer to the XRFdc_Signal_Detector_Settings structure +* to set the signal detector configurations +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled, or invaid values. +* +* @note Only for ADC blocks +* +******************************************************************************/ +u32 XRFdc_SetSignalDetector(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, XRFdc_Signal_Detector_Settings *SettingsPtr) +{ + u32 Status; + u32 BaseAddr; + u32 Index; + u32 NoOfBlocks; + u16 SignalDetCtrlReg = 0; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(SettingsPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + if (InstancePtr->RFdc_Config.IPType < 2) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Requested fuctionality not " + "available for this IP in %s\r\n", + __func__); + goto RETURN_PATH; + } + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, Block_Id); + + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, + "\n Requested block not " + "available in %s\r\n", + __func__); + goto RETURN_PATH; + } + if (SettingsPtr->Mode > XRFDC_SIGDET_MODE_RNDM) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Signal Detector " + "Mode in %s\r\n", + __func__); + goto RETURN_PATH; + } + if (SettingsPtr->EnableIntegrator > XRFDC_ENABLED) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Signal Detector " + "Integrator Enable in %s\r\n", + __func__); + goto RETURN_PATH; + } + if (SettingsPtr->HysteresisEnable > XRFDC_ENABLED) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Signal Detector " + "Hysteresis Enable in %s\r\n", + __func__); + goto RETURN_PATH; + } + if (SettingsPtr->Flush > XRFDC_ENABLED) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Signal Detector " + "Flush Option in %s\r\n", + __func__); + goto RETURN_PATH; + } + if (SettingsPtr->TimeConstant > XRFDC_SIGDET_TC_2_18) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Signal Detector " + "Time Constant in %s\r\n", + __func__); + goto RETURN_PATH; + } + SignalDetCtrlReg |= SettingsPtr->EnableIntegrator << XRFDC_ADC_SIG_DETECT_INTG_SHIFT; + SignalDetCtrlReg |= SettingsPtr->Flush << XRFDC_ADC_SIG_DETECT_FLUSH_SHIFT; + SignalDetCtrlReg |= SettingsPtr->TimeConstant << XRFDC_ADC_SIG_DETECT_TCONST_SHIFT; + if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) { + SignalDetCtrlReg |= ((SettingsPtr->Mode << 1) | 1) << XRFDC_ADC_SIG_DETECT_MODE_WRITE_SHIFT; + } else { + SignalDetCtrlReg |= (SettingsPtr->Mode << 1) << XRFDC_ADC_SIG_DETECT_MODE_WRITE_SHIFT; + } + SignalDetCtrlReg |= SettingsPtr->HysteresisEnable << XRFDC_ADC_SIG_DETECT_HYST_SHIFT; + + Index = Block_Id; + if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + for (; Index < NoOfBlocks; Index++) { + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Index); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_SIG_DETECT_CTRL_OFFSET, XRFDC_ADC_SIG_DETECT_MASK, + SignalDetCtrlReg); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_SIG_DETECT_THRESHOLD0_LEVEL_OFFSET, + XRFDC_ADC_SIG_DETECT_THRESH_MASK, SettingsPtr->HighThreshold); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_SIG_DETECT_THRESHOLD1_LEVEL_OFFSET, + XRFDC_ADC_SIG_DETECT_THRESH_MASK, SettingsPtr->LowThreshold); + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* This function is used to get the ADC Signal Detector Settings. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id indicates Tile number (0-3). +* @param Block_Id indicates Block number(0-3 for LS, 0-1 for HS). +* @param SettingsPtr pointer to the XRFdc_Signal_Detector_Settings structure +* to get the signal detector configurations +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note Only for ADC blocks +* +******************************************************************************/ +u32 XRFdc_GetSignalDetector(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, XRFdc_Signal_Detector_Settings *SettingsPtr) +{ + u32 Status; + u32 BaseAddr; + u16 SignalDetCtrlReg = 0; + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(SettingsPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + if (InstancePtr->RFdc_Config.IPType < 2) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Requested functionality not " + "available for this IP in %s\r\n", + __func__); + goto RETURN_PATH; + } + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, Block_Id); + + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, + "\n Requested block not " + "available in %s\r\n", + __func__); + goto RETURN_PATH; + } + + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && (Block_Id == XRFDC_BLK_ID1)) { + Block_Id = XRFDC_BLK_ID2; + } + + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Block_Id); + + SignalDetCtrlReg = + XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_ADC_SIG_DETECT_CTRL_OFFSET, XRFDC_ADC_SIG_DETECT_MASK); + SettingsPtr->EnableIntegrator = + (SignalDetCtrlReg & XRFDC_ADC_SIG_DETECT_INTG_MASK) >> XRFDC_ADC_SIG_DETECT_INTG_SHIFT; + SettingsPtr->Flush = (SignalDetCtrlReg & XRFDC_ADC_SIG_DETECT_FLUSH_MASK) >> XRFDC_ADC_SIG_DETECT_FLUSH_SHIFT; + SettingsPtr->TimeConstant = + (SignalDetCtrlReg & XRFDC_ADC_SIG_DETECT_TCONST_MASK) >> XRFDC_ADC_SIG_DETECT_TCONST_SHIFT; + SettingsPtr->Mode = (SignalDetCtrlReg & XRFDC_ADC_SIG_DETECT_MODE_MASK) >> XRFDC_ADC_SIG_DETECT_MODE_READ_SHIFT; + + SettingsPtr->HysteresisEnable = + (SignalDetCtrlReg & XRFDC_ADC_SIG_DETECT_HYST_MASK) >> XRFDC_ADC_SIG_DETECT_HYST_SHIFT; + SettingsPtr->HighThreshold = XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_ADC_SIG_DETECT_THRESHOLD0_LEVEL_OFFSET, + XRFDC_ADC_SIG_DETECT_THRESH_MASK); + SettingsPtr->LowThreshold = XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_ADC_SIG_DETECT_THRESHOLD1_LEVEL_OFFSET, + XRFDC_ADC_SIG_DETECT_THRESH_MASK); + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/*****************************************************************************/ +/** +* +* This function is used to disable Calibration Coefficients override. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id indicates Tile number (0-3). +* @param Block_Id indicates Block number(0-3 for LS, 0-1 for HS). +* @param CalibrationBlock indicates the calibration block. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if error occurs. +* +* @note Only for ADC blocks +* +******************************************************************************/ +u32 XRFdc_DisableCoefficientsOverride(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, u32 CalibrationBlock) +{ + u32 BaseAddr; + u32 Status; + u32 Index; + u32 NoOfBlocks; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, + "\n Requested block not " + "available in %s\r\n", + __func__); + goto RETURN_PATH; + } + + if ((InstancePtr->RFdc_Config.IPType < 2) && (CalibrationBlock == XRFDC_CAL_BLOCK_OCB1)) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Requested functionality not " + "available for this IP in %s\r\n", + __func__); + goto RETURN_PATH; + } + + Index = Block_Id; + if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + for (; Index < NoOfBlocks; Index++) { + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Index); + switch (CalibrationBlock) { + case XRFDC_CAL_BLOCK_OCB1: + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_TI_DCB_CRL1_OFFSET, XRFDC_CAL_OCB_EN_MASK, + XRFDC_DISABLED); + break; + case XRFDC_CAL_BLOCK_OCB2: + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_TI_DCB_CRL3_OFFSET, XRFDC_CAL_OCB_EN_MASK, + XRFDC_DISABLED); + break; + case XRFDC_CAL_BLOCK_GCB: + if (InstancePtr->RFdc_Config.IPType < 2) { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_TI_DCB_CRL1_OFFSET, + XRFDC_CAL_GCB_ENFL_MASK, XRFDC_CAL_GCB_ACEN_MASK); + /*Clear IP Override Coeffs*/ + XRFdc_ClrSetReg(InstancePtr, XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id), + XRFDC_CAL_GCB_COEFF0_FAB(Index), XRFDC_CAL_GCB_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id), + XRFDC_CAL_GCB_COEFF1_FAB(Index), XRFDC_CAL_GCB_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id), + XRFDC_CAL_GCB_COEFF2_FAB(Index), XRFDC_CAL_GCB_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id), + XRFDC_CAL_GCB_COEFF3_FAB(Index), XRFDC_CAL_GCB_MASK, XRFDC_DISABLED); + } else { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_TI_DCB_CRL2_OFFSET, + XRFDC_CAL_GCB_EN_MASK, XRFDC_DISABLED); + } + break; + case XRFDC_CAL_BLOCK_TSCB: + if (InstancePtr->RFdc_Config.IPType < 2) { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF0_ALT, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF1_ALT, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF2_ALT, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF3_ALT, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF4_ALT, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF5_ALT, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF6_ALT, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF7_ALT, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_DISABLED); + } else { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF0, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF1, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF2, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF3, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF4, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF5, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF6, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF7, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_DISABLED); + } + break; + default: + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Calibration " + "Mode in %s\r\n", + __func__); + goto RETURN_PATH; + } + } + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/*****************************************************************************/ +/** +* +* This function is used to set the ADC Calibration Coefficients. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id indicates Tile number (0-3). +* @param Block_Id indicates Block number(0-3 for LS, 0-1 for HS). +* @param CalibrationBlock indicates the block to be written to. +* @param CoeffPtr is pointer to the XRFdc_Calibration_Coefficients structure +* to set the calibration coefficients. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if error occurs. +* +* @note Only for ADC blocks +* +******************************************************************************/ +u32 XRFdc_SetCalCoefficients(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, u32 CalibrationBlock, + XRFdc_Calibration_Coefficients *CoeffPtr) +{ + u32 BaseAddr; + u32 Status; + u32 Index; + u32 NoOfBlocks; + u32 HighSpeed; + u32 Shift; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(CoeffPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + if ((InstancePtr->RFdc_Config.IPType < 2) && (CalibrationBlock == XRFDC_CAL_BLOCK_OCB1)) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Requested functionality not " + "available for this IP in %s\r\n", + __func__); + goto RETURN_PATH; + } + + if (CalibrationBlock == XRFDC_CAL_BLOCK_GCB) { + if ((CoeffPtr->Coeff0 | CoeffPtr->Coeff1 | CoeffPtr->Coeff2 | CoeffPtr->Coeff3) & + ~(XRFDC_CAL_GCB_MASK | (XRFDC_CAL_GCB_MASK << XRFDC_CAL_SLICE_SHIFT))) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Bad Coefficient " + "available for this IP in %s\r\n", + __func__); + goto RETURN_PATH; + } + } + + if (CalibrationBlock == XRFDC_CAL_BLOCK_TSCB) { + if ((CoeffPtr->Coeff0 | CoeffPtr->Coeff1 | CoeffPtr->Coeff2 | CoeffPtr->Coeff3 | CoeffPtr->Coeff4 | + CoeffPtr->Coeff5 | CoeffPtr->Coeff6 | CoeffPtr->Coeff7) & + ~(XRFDC_CAL_TSCB_MASK | (XRFDC_CAL_TSCB_MASK << XRFDC_CAL_SLICE_SHIFT))) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Bad Coefficient " + "available for this IP in %s\r\n", + __func__); + goto RETURN_PATH; + } + } + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, + "\n Requested block not " + "available in %s\r\n", + __func__); + goto RETURN_PATH; + } + + Index = Block_Id; + HighSpeed = XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id); + if (HighSpeed == XRFDC_ENABLED) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } else { + NoOfBlocks = Block_Id + 1U; + } + for (; Index < NoOfBlocks; Index++) { + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Index); + Shift = HighSpeed ? XRFDC_CAL_SLICE_SHIFT * (Index % 2) : 0; + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Index); + switch (CalibrationBlock) { + case XRFDC_CAL_BLOCK_OCB1: + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_TI_DCB_CRL1_OFFSET, XRFDC_CAL_OCB_EN_MASK, + XRFDC_ENABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_OCB1_OFFSET_COEFF0, XRFDC_CAL_OCB_MASK, + CoeffPtr->Coeff0 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_OCB1_OFFSET_COEFF1, XRFDC_CAL_OCB_MASK, + CoeffPtr->Coeff1 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_OCB1_OFFSET_COEFF2, XRFDC_CAL_OCB_MASK, + CoeffPtr->Coeff2 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_OCB1_OFFSET_COEFF3, XRFDC_CAL_OCB_MASK, + CoeffPtr->Coeff3 >> Shift); + break; + case XRFDC_CAL_BLOCK_OCB2: + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_TI_DCB_CRL3_OFFSET, XRFDC_CAL_OCB_EN_MASK, + XRFDC_ENABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_OCB2_OFFSET_COEFF0, XRFDC_CAL_OCB_MASK, + CoeffPtr->Coeff0 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_OCB2_OFFSET_COEFF1, XRFDC_CAL_OCB_MASK, + CoeffPtr->Coeff1 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_OCB2_OFFSET_COEFF2, XRFDC_CAL_OCB_MASK, + CoeffPtr->Coeff2 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_OCB2_OFFSET_COEFF3, XRFDC_CAL_OCB_MASK, + CoeffPtr->Coeff3 >> Shift); + break; + case XRFDC_CAL_BLOCK_GCB: + + if (InstancePtr->RFdc_Config.IPType < 2) { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_TI_DCB_CRL1_OFFSET, + XRFDC_CAL_GCB_ACEN_MASK, XRFDC_DISABLED); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_TI_DCB_CRL1_OFFSET, + XRFDC_CAL_GCB_FLSH_MASK, XRFDC_ENABLED << XRFDC_CAL_GCB_FLSH_SHIFT); + XRFdc_ClrSetReg(InstancePtr, XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id), + XRFDC_CAL_GCB_COEFF0_FAB(Index), XRFDC_CAL_GCB_MASK, + CoeffPtr->Coeff0 >> Shift); + XRFdc_ClrSetReg(InstancePtr, XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id), + XRFDC_CAL_GCB_COEFF1_FAB(Index), XRFDC_CAL_GCB_MASK, + CoeffPtr->Coeff1 >> Shift); + XRFdc_ClrSetReg(InstancePtr, XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id), + XRFDC_CAL_GCB_COEFF2_FAB(Index), XRFDC_CAL_GCB_MASK, + CoeffPtr->Coeff2 >> Shift); + XRFdc_ClrSetReg(InstancePtr, XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id), + XRFDC_CAL_GCB_COEFF3_FAB(Index), XRFDC_CAL_GCB_MASK, + CoeffPtr->Coeff3 >> Shift); + } else { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_TI_DCB_CRL2_OFFSET, + XRFDC_CAL_GCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_GCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_GCB_OFFSET_COEFF0, XRFDC_CAL_GCB_MASK, + CoeffPtr->Coeff0 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_GCB_OFFSET_COEFF1, XRFDC_CAL_GCB_MASK, + CoeffPtr->Coeff1 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_GCB_OFFSET_COEFF2, XRFDC_CAL_GCB_MASK, + CoeffPtr->Coeff2 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_GCB_OFFSET_COEFF3, XRFDC_CAL_GCB_MASK, + CoeffPtr->Coeff3 >> Shift); + } + break; + case XRFDC_CAL_BLOCK_TSCB: + if (InstancePtr->RFdc_Config.IPType < 2) { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF0_ALT, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_TSCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF1_ALT, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_TSCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF2_ALT, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_TSCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF3_ALT, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_TSCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF4_ALT, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_TSCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF5_ALT, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_TSCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF6_ALT, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_TSCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF7_ALT, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_TSCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF0_ALT, + XRFDC_CAL_TSCB_MASK, CoeffPtr->Coeff0 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF1_ALT, + XRFDC_CAL_TSCB_MASK, CoeffPtr->Coeff1); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF2_ALT, + XRFDC_CAL_TSCB_MASK, CoeffPtr->Coeff2 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF3_ALT, + XRFDC_CAL_TSCB_MASK, CoeffPtr->Coeff3 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF4_ALT, + XRFDC_CAL_TSCB_MASK, CoeffPtr->Coeff4 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF5_ALT, + XRFDC_CAL_TSCB_MASK, CoeffPtr->Coeff5 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF6_ALT, + XRFDC_CAL_TSCB_MASK, CoeffPtr->Coeff6 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF7_ALT, + XRFDC_CAL_TSCB_MASK, CoeffPtr->Coeff7 >> Shift); + } else { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF0, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_TSCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF1, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_TSCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF2, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_TSCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF3, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_TSCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF4, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_TSCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF5, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_TSCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF6, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_TSCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF7, + XRFDC_CAL_TSCB_EN_MASK, XRFDC_ENABLED << XRFDC_CAL_TSCB_EN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF0, + XRFDC_CAL_TSCB_MASK, CoeffPtr->Coeff0 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF1, + XRFDC_CAL_TSCB_MASK, CoeffPtr->Coeff1 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF2, + XRFDC_CAL_TSCB_MASK, CoeffPtr->Coeff2 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF3, + XRFDC_CAL_TSCB_MASK, CoeffPtr->Coeff3 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF4, + XRFDC_CAL_TSCB_MASK, CoeffPtr->Coeff4 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF5, + XRFDC_CAL_TSCB_MASK, CoeffPtr->Coeff5 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF6, + XRFDC_CAL_TSCB_MASK, CoeffPtr->Coeff6 >> Shift); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF7, + XRFDC_CAL_TSCB_MASK, CoeffPtr->Coeff7 >> Shift); + } + break; + default: + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Calibration " + "Mode in %s\r\n", + __func__); + goto RETURN_PATH; + } + } + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/*****************************************************************************/ +/** +* +* This function is used to get the ADC Calibration Coefficients. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id indicates Tile number (0-3). +* @param Block_Id indicates Block number(0-3 for LS, 0-1 for HS). +* @param CalibrationBlock indicates the block to be read from +* @param CoeffPtr is pointer to the XRFdc_Calibration_Coefficients structure +* to get the calibration coefficients. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if error occurs. +* +* @note Only for ADC blocks +* +******************************************************************************/ +u32 XRFdc_GetCalCoefficients(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, u32 CalibrationBlock, + XRFdc_Calibration_Coefficients *CoeffPtr) +{ + u32 BaseAddr; + u32 Status; + u32 Index; + u32 HighSpeed; + u32 Shift; + u32 NoOfBlocks; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(CoeffPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, + "\n Requested block not " + "available in %s\r\n", + __func__); + goto RETURN_PATH; + } + + memset(CoeffPtr, 0, sizeof(XRFdc_Calibration_Coefficients)); + Index = Block_Id; + HighSpeed = XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id); + if (HighSpeed == XRFDC_ENABLED) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } else { + NoOfBlocks = Block_Id + 1U; + } + for (; Index < NoOfBlocks; Index++) { + BaseAddr = XRFDC_BLOCK_BASE(XRFDC_ADC_TILE, Tile_Id, Index); + Shift = HighSpeed ? XRFDC_CAL_SLICE_SHIFT * (Index % 2) : 0; + switch (CalibrationBlock) { + case XRFDC_CAL_BLOCK_OCB1: + CoeffPtr->Coeff0 |= + XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_OCB1_OFFSET_COEFF0, XRFDC_CAL_OCB_MASK) + << Shift; + CoeffPtr->Coeff1 |= + XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_OCB1_OFFSET_COEFF1, XRFDC_CAL_OCB_MASK) + << Shift; + CoeffPtr->Coeff2 |= + XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_OCB1_OFFSET_COEFF2, XRFDC_CAL_OCB_MASK) + << Shift; + CoeffPtr->Coeff3 |= + XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_OCB1_OFFSET_COEFF3, XRFDC_CAL_OCB_MASK) + << Shift; + break; + case XRFDC_CAL_BLOCK_OCB2: + CoeffPtr->Coeff0 |= + XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_OCB2_OFFSET_COEFF0, XRFDC_CAL_OCB_MASK) + << Shift; + CoeffPtr->Coeff1 |= + XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_OCB2_OFFSET_COEFF1, XRFDC_CAL_OCB_MASK) + << Shift; + CoeffPtr->Coeff2 |= + XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_OCB2_OFFSET_COEFF2, XRFDC_CAL_OCB_MASK) + << Shift; + CoeffPtr->Coeff3 |= + XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_OCB2_OFFSET_COEFF3, XRFDC_CAL_OCB_MASK) + << Shift; + break; + case XRFDC_CAL_BLOCK_GCB: + if (InstancePtr->RFdc_Config.IPType < 2) { + if (XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_ADC_TI_DCB_CRL1_OFFSET, + XRFDC_CAL_GCB_FLSH_MASK) == XRFDC_DISABLED) { + CoeffPtr->Coeff0 |= + (XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_GCB_OFFSET_COEFF0_ALT, + XRFDC_CAL_GCB_FAB_MASK) >> + 4) + << Shift; + CoeffPtr->Coeff1 |= + (XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_GCB_OFFSET_COEFF1_ALT, + XRFDC_CAL_GCB_FAB_MASK) >> + 4) + << Shift; + CoeffPtr->Coeff2 |= + (XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_GCB_OFFSET_COEFF2_ALT, + XRFDC_CAL_GCB_FAB_MASK) >> + 4) + << Shift; + CoeffPtr->Coeff3 |= + (XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_GCB_OFFSET_COEFF3_ALT, + XRFDC_CAL_GCB_FAB_MASK) >> + 4) + << Shift; + } else { + CoeffPtr->Coeff0 |= + XRFdc_RDReg(InstancePtr, XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id), + XRFDC_CAL_GCB_COEFF0_FAB(Block_Id), XRFDC_CAL_GCB_MASK) + << Shift; + CoeffPtr->Coeff1 |= + XRFdc_RDReg(InstancePtr, XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id), + XRFDC_CAL_GCB_COEFF1_FAB(Block_Id), XRFDC_CAL_GCB_MASK) + << Shift; + CoeffPtr->Coeff2 |= + XRFdc_RDReg(InstancePtr, XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id), + XRFDC_CAL_GCB_COEFF2_FAB(Block_Id), XRFDC_CAL_GCB_MASK) + << Shift; + CoeffPtr->Coeff3 |= + XRFdc_RDReg(InstancePtr, XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id), + XRFDC_CAL_GCB_COEFF3_FAB(Block_Id), XRFDC_CAL_GCB_MASK) + << Shift; + } + } else { + CoeffPtr->Coeff0 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_GCB_OFFSET_COEFF0, + XRFDC_CAL_GCB_MASK) + << Shift; + CoeffPtr->Coeff1 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_GCB_OFFSET_COEFF1, + XRFDC_CAL_GCB_MASK) + << Shift; + CoeffPtr->Coeff2 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_GCB_OFFSET_COEFF2, + XRFDC_CAL_GCB_MASK) + << Shift; + CoeffPtr->Coeff3 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_GCB_OFFSET_COEFF3, + XRFDC_CAL_GCB_MASK) + << Shift; + } + break; + case XRFDC_CAL_BLOCK_TSCB: + if (InstancePtr->RFdc_Config.IPType < 2) { + CoeffPtr->Coeff0 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF0_ALT, + XRFDC_CAL_TSCB_MASK) + << Shift; + CoeffPtr->Coeff1 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF1_ALT, + XRFDC_CAL_TSCB_MASK) + << Shift; + CoeffPtr->Coeff2 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF2_ALT, + XRFDC_CAL_TSCB_MASK) + << Shift; + CoeffPtr->Coeff3 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF3_ALT, + XRFDC_CAL_TSCB_MASK) + << Shift; + CoeffPtr->Coeff4 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF4_ALT, + XRFDC_CAL_TSCB_MASK) + << Shift; + CoeffPtr->Coeff5 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF5_ALT, + XRFDC_CAL_TSCB_MASK) + << Shift; + CoeffPtr->Coeff6 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF6_ALT, + XRFDC_CAL_TSCB_MASK) + << Shift; + CoeffPtr->Coeff7 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF7_ALT, + XRFDC_CAL_TSCB_MASK) + << Shift; + } else { + CoeffPtr->Coeff0 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF0, + XRFDC_CAL_TSCB_MASK) + << Shift; + CoeffPtr->Coeff1 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF1, + XRFDC_CAL_TSCB_MASK) + << Shift; + CoeffPtr->Coeff2 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF2, + XRFDC_CAL_TSCB_MASK) + << Shift; + CoeffPtr->Coeff3 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF3, + XRFDC_CAL_TSCB_MASK) + << Shift; + CoeffPtr->Coeff4 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF4, + XRFDC_CAL_TSCB_MASK) + << Shift; + CoeffPtr->Coeff5 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF5, + XRFDC_CAL_TSCB_MASK) + << Shift; + CoeffPtr->Coeff6 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF6, + XRFDC_CAL_TSCB_MASK) + << Shift; + CoeffPtr->Coeff7 |= XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CAL_TSCB_OFFSET_COEFF7, + XRFDC_CAL_TSCB_MASK) + << Shift; + } + break; + default: + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Calibration " + "Mode in %s\r\n", + __func__); + goto RETURN_PATH; + } + } + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/*****************************************************************************/ +/** +* +* This function is used to set calibration freeze settings. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id indicates Tile number (0-3). +* @param Block_Id indicates Block number(0-3 for LS, 0-1 for HS). +* @param CalFreezePtr pointer to the settings to be applied. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if error occurs. +* +* @note Only for ADC blocks +* +******************************************************************************/ +u32 XRFdc_SetCalFreeze(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, XRFdc_Cal_Freeze_Settings *CalFreezePtr) +{ + u32 BaseAddr; + u32 Status; + u32 Index; + u32 NoOfBlocks; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(CalFreezePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + if (CalFreezePtr->FreezeCalibration > XRFDC_CAL_FREEZE_CALIB) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid FreezeCalibration " + "option in %s\r\n", + __func__); + goto RETURN_PATH; + } + + if (CalFreezePtr->DisableFreezePin > XRFDC_CAL_FRZ_PIN_DISABLE) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid DisableFreezePin " + "option in %s\r\n", + __func__); + goto RETURN_PATH; + } + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, + "\n Requested block not " + "available in %s\r\n", + __func__); + goto RETURN_PATH; + } + + BaseAddr = XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id); + + Index = Block_Id; + if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + for (; Index < NoOfBlocks; Index++) { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CONV_CAL_STGS(Index), XRFDC_CAL_FREEZE_PIN_MASK, + CalFreezePtr->DisableFreezePin << XRFDC_CAL_FREEZE_PIN_SHIFT); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CONV_CAL_STGS(Index), XRFDC_CAL_FREEZE_CAL_MASK, + CalFreezePtr->FreezeCalibration << XRFDC_CAL_FREEZE_CAL_SHIFT); + } + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/*****************************************************************************/ +/** +* +* This function is used to get calibration freeze settings and status. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Tile_Id indicates Tile number (0-3). +* @param Block_Id indicates Block number(0-3 for LS, 0-1 for HS). +* @param CalFreezePtr pointer to be filled the settings/status. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if error occurs. +* +* @note Only for ADC blocks +* +******************************************************************************/ +u32 XRFdc_GetCalFreeze(XRFdc *InstancePtr, u32 Tile_Id, u32 Block_Id, XRFdc_Cal_Freeze_Settings *CalFreezePtr) +{ + u32 BaseAddr; + u32 Status; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(CalFreezePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, XRFDC_ADC_TILE, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, + "\n Requested block not " + "available in %s\r\n", + __func__); + goto RETURN_PATH; + } + + BaseAddr = XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id); + + if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) { + if (Block_Id == XRFDC_BLK_ID1) { + Block_Id = XRFDC_BLK_ID2; + } + } + CalFreezePtr->CalFrozen = + XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CONV_CAL_STGS(Block_Id), XRFDC_CAL_FREEZE_STS_MASK) >> + XRFDC_CAL_FREEZE_STS_SHIFT; + CalFreezePtr->DisableFreezePin = + XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CONV_CAL_STGS(Block_Id), XRFDC_CAL_FREEZE_PIN_MASK) >> + XRFDC_CAL_FREEZE_PIN_SHIFT; + CalFreezePtr->FreezeCalibration = + XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_CONV_CAL_STGS(Block_Id), XRFDC_CAL_FREEZE_CAL_MASK) >> + XRFDC_CAL_FREEZE_CAL_SHIFT; + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/** @} */ diff --git a/mpm/lib/rfdc/xrfdc_clock.c b/mpm/lib/rfdc/xrfdc_clock.c new file mode 100644 index 000000000..73a19fda5 --- /dev/null +++ b/mpm/lib/rfdc/xrfdc_clock.c @@ -0,0 +1,1801 @@ +/****************************************************************************** +* +* Copyright (C) 2019 Xilinx, Inc. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* XILINX BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Except as contained in this notice, the name of the Xilinx shall not be used +* in advertising or otherwise to promote the sale, use or other dealings in +* this Software without prior written authorization from Xilinx. +* +******************************************************************************/ +/*****************************************************************************/ +/** +* +* @file xrfdc_clock.c +* @addtogroup xrfdc_v6_0 +* @{ +* +* Contains the interface functions of the Mixer Settings in XRFdc driver. +* See xrfdc.h for a detailed description of the device and driver. +* +* <pre> +* MODIFICATION HISTORY: +* +* Ver Who Date Changes +* ----- --- -------- ----------------------------------------------- +* 6.0 cog 02/17/19 Initial release. +* cog 03/12/19 Invert clock detection bits to support IP change. +* cog 03/12/19 Fix bug where incorrect FS, RefClk and were output +* divider were being returned. +* cog 04/09/19 Discriminate between Gen 3 IP and lower for checking +* if internal PLL is enabled. +* cog 04/09/19 Fixed issue where tile was not autostarting after PLL +* rate change. +* </pre> +* +******************************************************************************/ + +/***************************** Include Files *********************************/ +#include "mpm/rfdc/xrfdc.h" + +/************************** Constant Definitions *****************************/ +static u32 PllTuningMatrix[8][4][2] = { + {{0x7F8A, 0x3FFF}, {0x7F9C, 0x3FFF}, {0x7FE2, 0x3FFF} }, + {{0x7FE9, 0xFFFF}, {0x7F8E, 0xFFFF}, {0x7F9C, 0xFFFF} }, + {{0x7F95, 0xFFFF}, {0x7F8E, 0xFFFF}, { 0x7F9A, 0xFFFF}, {0x7F8C, 0xFFFF} }, + {{0x7F95, 0x3FFF}, {0x7FEE, 0x3FFF}, { 0x7F9A, 0xFFFF}, {0x7F9C, 0xFFFF} }, + {{0x7F95, 0x3FFF}, {0x7FEE, 0x3FFF}, { 0x7F9A, 0xFFFF}, {0x7F9C, 0xFFFF} }, + {{0x7F95, 0xFFFF}, {0x7F8E, 0xFFFF}, { 0x7FEA, 0xFFFF}, {0x7F9C, 0xFFFF} }, + {{0x7FE9, 0xFFFF}, {0x7F8E, 0xFFFF}, { 0x7F9A, 0xFFFF}, {0x7F9C, 0xFFFF} }, + {{0x7FEC, 0xFFFF}, {0x7FEE, 0x3FFF}, { 0x7F9C, 0xFFFF} } +}; + + +/**************************** Type Definitions *******************************/ + +/***************** Macros (Inline Functions) Definitions *********************/ +static u32 XRFdc_CheckClkDistValid(XRFdc *InstancePtr, XRFdc_Distribution_Settings *DistributionSettingsPtr); +static u32 XRFdc_SetPLLConfig(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + double RefClkFreq, double SamplingRate); + +/************************** Function Prototypes ******************************/ + +/*****************************************************************************/ +/** +* +* This function is used to set the clock settings +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type indicates ADC/DAC. +* @param Tile_Id indicates Tile number (0-3). +* @param SettingsPtr pointer to set the clock settings +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if no valid distribution found. +* +******************************************************************************/ +u32 XRFdc_SetTileClkSettings(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, XRFdc_Tile_Clock_Settings *SettingsPtr) +{ + u32 Status; + u32 BaseAddr; + u16 PLLSource; + u16 NetworkCtrlReg; + u16 DistCtrlReg; + u16 PLLRefDivReg; + u16 PLLOpDivReg; + u32 TileIndex; + u16 DivideMode = 0; + u16 DivideValue = 0; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(SettingsPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + if (InstancePtr->RFdc_Config.IPType < 2) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Requested fuctionality not " + "available for this IP in %s\r\n", + __func__); + goto RETURN_PATH; + } + + TileIndex = (Type == XRFDC_DAC_TILE) ? Tile_Id : Tile_Id + XRFDC_CLK_DST_ADC0; + Status = XRFdc_CheckTileEnabled(InstancePtr, Type, Tile_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, + "\n Requested Tile not " + "available in %s\r\n", + __func__); + goto RETURN_PATH; + } + if (SettingsPtr->SourceTile > XRFDC_CLK_DST_ADC3) { + metal_log(METAL_LOG_ERROR, + "\n Invalid Parameter Value " + "for Source in %s\r\n", + __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if (SettingsPtr->DistributedClock > XRFDC_DIST_OUT_OUTDIV) { + metal_log(METAL_LOG_ERROR, + "\n Invalid Parameter Value " + "for Distribution Out in %s\r\n", + __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if (SettingsPtr->PLLEnable > 1) { + metal_log(METAL_LOG_ERROR, + "\n Invalid Parameter Value " + "for PLLEnable in %s\r\n", + __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if ((SettingsPtr->PLLEnable == XRFDC_ENABLED) && (SettingsPtr->DivisionFactor < 1)) { + metal_log(METAL_LOG_ERROR, "\n Invalid Configuration in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if ((SettingsPtr->DistributedClock == XRFDC_DIST_OUT_OUTDIV) && + ((SettingsPtr->DivisionFactor < 2) && (SettingsPtr->PLLEnable == XRFDC_DISABLED))) { + metal_log(METAL_LOG_ERROR, "\n Invalid Configuration in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if ((SettingsPtr->SourceTile != TileIndex) && (SettingsPtr->DistributedClock != XRFDC_DIST_OUT_NONE)) { + metal_log(METAL_LOG_ERROR, "\n Cannot Redistribute Clock in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + /*configure PLL & divider or just divider*/ + if (SettingsPtr->PLLEnable == XRFDC_ENABLED) { + metal_log(METAL_LOG_WARNING, + "\n Output divider settings may " + "be overridden in %s\r\n", + __func__); + PLLSource = XRFDC_INTERNAL_PLL_CLK; + Status = XRFdc_DynamicPLLConfig(InstancePtr, Type, Tile_Id, PLLSource, + SettingsPtr->PLLSettings.RefClkFreq, + SettingsPtr->PLLSettings.SampleRate); + SettingsPtr->DivisionFactor = SettingsPtr->PLLSettings.OutputDivider; + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, + "\n Could not set up PLL " + "in %s\r\n", + __func__); + goto RETURN_PATH; + } + } else if (SettingsPtr->DivisionFactor > 1) { + if (SettingsPtr->DivisionFactor == 2U) { + DivideMode = XRFDC_PLL_OUTDIV_MODE_2; + } else if (SettingsPtr->DivisionFactor == 3U) { + DivideMode = XRFDC_PLL_OUTDIV_MODE_3; + DivideValue = XRFDC_PLL_OUTDIV_MODE_3_VAL; + } else if (SettingsPtr->DivisionFactor >= 4U) { + DivideMode = XRFDC_PLL_OUTDIV_MODE_N; + DivideValue = ((SettingsPtr->DivisionFactor - 4U) >> 1); + } + XRFdc_ClrSetReg(InstancePtr, XRFDC_DRP_BASE(Type, Tile_Id) + XRFDC_HSCOM_ADDR, XRFDC_PLL_DIVIDER0, + XRFDC_PLL_DIVIDER0_MASK, ((DivideMode << XRFDC_PLL_DIVIDER0_SHIFT) | DivideValue)); + } + DistCtrlReg = 0; + PLLRefDivReg = 0; + PLLOpDivReg = 0; + NetworkCtrlReg = 0; + if (SettingsPtr->SourceTile == TileIndex) { + if (SettingsPtr->DistributedClock == XRFDC_DIST_OUT_NONE) { + if (SettingsPtr->PLLEnable == XRFDC_DISABLED) { + PLLRefDivReg |= XRFDC_PLLREFDIV_INPUT_OFF; + NetworkCtrlReg |= XRFDC_NET_CTRL_CLK_REC_DIST_T1; + if (SettingsPtr->DivisionFactor < 2) { + /* + T1 from Self + No PLL + Do Not Use PLL Output Divider + Do Not Distribute + */ + NetworkCtrlReg |= XRFDC_NET_CTRL_CLK_T1_SRC_LOCAL; + DistCtrlReg |= XRFDC_DIST_CTRL_CLK_T1_SRC_LOCAL; + } else { + /* + T1 from Self + No PLL + Do Not Distribute + */ + PLLOpDivReg |= XRFDC_PLLOPDIV_INPUT_DIST_LOCAL; + } + } else { + /* + T1 from Self + PLL + Use PLL Output Divider + Do Not Distribute + */ + NetworkCtrlReg |= XRFDC_NET_CTRL_CLK_REC_PLL; + } + } else { + NetworkCtrlReg |= XRFDC_NET_CTRL_CLK_T1_SRC_DIST; + DistCtrlReg |= XRFDC_DIST_CTRL_TO_T1; + if (SettingsPtr->PLLEnable == XRFDC_DISABLED) { + NetworkCtrlReg |= XRFDC_NET_CTRL_CLK_REC_DIST_T1; + PLLRefDivReg |= XRFDC_PLLREFDIV_INPUT_OFF; + if (SettingsPtr->DivisionFactor < 2) { + /* + T1 From Distribution + No PLL + Do Not Use PLL Output Divider + Send to Distribution + */ + DistCtrlReg |= XRFDC_DIST_CTRL_DIST_SRC_LOCAL; + } else { + /* + T1 From Distribution + No PLL + Use PLL Output Divider + Send to Distribution + */ + PLLOpDivReg |= XRFDC_PLLOPDIV_INPUT_DIST_LOCAL; + DistCtrlReg |= (SettingsPtr->DistributedClock == XRFDC_DIST_OUT_RX) ? + XRFDC_DIST_CTRL_DIST_SRC_LOCAL : + XRFDC_DIST_CTRL_DIST_SRC_PLL; + } + + } else { + /* + T1 From Distribution + PLL + Use PLL Output Divider + Send to Distribution + */ + DistCtrlReg |= (SettingsPtr->DistributedClock == XRFDC_DIST_OUT_RX) ? + XRFDC_DIST_CTRL_DIST_SRC_LOCAL : + XRFDC_DIST_CTRL_DIST_SRC_PLL; + NetworkCtrlReg |= XRFDC_NET_CTRL_CLK_REC_PLL; + } + } + } else { + if (SettingsPtr->PLLEnable == XRFDC_DISABLED) { + PLLRefDivReg |= XRFDC_PLLREFDIV_INPUT_OFF; + if (SettingsPtr->DivisionFactor > 1) { + /* + Source From Distribution + No PLL + Use PLL Output Divider + Do Not Distribute + */ + + PLLOpDivReg |= XRFDC_PLLOPDIV_INPUT_DIST_LOCAL; + NetworkCtrlReg |= XRFDC_NET_CTRL_CLK_INPUT_DIST; + DistCtrlReg |= XRFDC_DIST_CTRL_TO_PLL_DIV; + } else { + /* + Source From Distribution + No PLL + Do Not Use PLL Output Divider + Do Not Distribute + */ + NetworkCtrlReg |= XRFDC_NET_CTRL_CLK_T1_SRC_DIST; + DistCtrlReg |= XRFDC_DIST_CTRL_TO_T1; + } + } else { + /* + Source From Distribution + PLL + Use PLL Output Divider + Do Not Distribute + */ + PLLRefDivReg |= XRFDC_PLLREFDIV_INPUT_DIST; + DistCtrlReg |= XRFDC_DIST_CTRL_TO_PLL_DIV; + } + } + + /*Write to Registers*/ + if (Type == XRFDC_ADC_TILE) { + BaseAddr = XRFDC_ADC_TILE_DRP_ADDR(Tile_Id); + } else { + BaseAddr = XRFDC_DAC_TILE_DRP_ADDR(Tile_Id); + } + BaseAddr += XRFDC_HSCOM_ADDR; + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_HSCOM_CLK_DSTR_OFFSET, XRFDC_HSCOM_CLK_DSTR_MASK_ALT, DistCtrlReg); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CLK_NETWORK_CTRL1, XRFDC_HSCOM_NETWORK_CTRL1_MASK, NetworkCtrlReg); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_PLL_REFDIV, XRFDC_PLL_REFDIV_MASK, PLLRefDivReg); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_PLL_DIVIDER0, XRFDC_PLL_DIVIDER0_ALT_MASK, PLLOpDivReg); + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/*****************************************************************************/ +/** +* +* This function is used to check the distribution chosen is valid +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param DistributionSettingsPtr pointer to the distribution settings struct +* +* @return +* - XRFDC_SUCCESS if Valid. +* - XRFDC_FAILURE if Not Valid. +* +******************************************************************************/ +static u32 XRFdc_CheckClkDistValid(XRFdc *InstancePtr, XRFdc_Distribution_Settings *DistributionSettingsPtr) +{ + u32 Status; + u8 CurrentTile; + u8 *Source; + u8 Sources[8] = { DistributionSettingsPtr->DAC[0].SourceTile, DistributionSettingsPtr->DAC[1].SourceTile, + DistributionSettingsPtr->DAC[2].SourceTile, DistributionSettingsPtr->DAC[3].SourceTile, + DistributionSettingsPtr->ADC[0].SourceTile, DistributionSettingsPtr->ADC[1].SourceTile, + DistributionSettingsPtr->ADC[2].SourceTile, DistributionSettingsPtr->ADC[3].SourceTile }; + u8 LowBoundary; + u16 EFuse; + XRFdc_Distribution *DistributionPtr; + + /*init for first distribution*/ + DistributionPtr = DistributionSettingsPtr->DistributionStatus; + Source = Sources; + LowBoundary = DistributionSettingsPtr->DAC[0].SourceTile; + DistributionPtr->DistributionSource = DistributionSettingsPtr->DAC[0].SourceTile; + DistributionPtr->Enabled = XRFDC_ENABLED; + DistributionPtr->LowerBound = 0; + + for (CurrentTile = 0; CurrentTile < XRFDC_DIST_MAX; CurrentTile++, Source++) { + if (*Source >= XRFDC_DIST_MAX) { + Status = XRFDC_FAILURE; /*out of range*/ + metal_log(METAL_LOG_ERROR, + "\n Invalid Source " + "value in %s - Out of Range\r\n", + __func__); + goto RETURN_PATH; + } + if (*Source < LowBoundary) { + Status = XRFDC_FAILURE; /*SW: no hopovers*/ + metal_log(METAL_LOG_ERROR, + "\n Invalid Configuration " + "Hopping Over Not Allowed in %s\r\n", + __func__); + goto RETURN_PATH; + } + if (Sources[*Source] != *Source) { /*SW: check source is a distributer*/ + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Source " + "Sourcing from Tile that is not Distributing in %s\r\n", + __func__); + goto RETURN_PATH; + } + if ((*Source == XRFDC_CLK_DST_DAC0) && + (InstancePtr->RFdc_Config.DACTile_Config[XRFDC_CLK_DST_DAC0].NumSlices == 2)) { /*HW: only 2 clks*/ + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Source " + "Sourcing from Tile Without Clock in %s\r\n", + __func__); + goto RETURN_PATH; + } + if ((*Source == XRFDC_CLK_DST_DAC2) && + (InstancePtr->RFdc_Config.DACTile_Config[XRFDC_CLK_DST_DAC2].NumSlices == 2)) { /*HW: only 2 clks*/ + metal_log(METAL_LOG_ERROR, + "\n Invalid Source " + "Sourcing from Tile Without Clock in %s\r\n", + __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if ((CurrentTile < XRFDC_CLK_DST_ADC0) && + (*Source > XRFDC_CLK_DST_DAC3)) { /*Cut between ADC0 MUX8 && DAC3 STH*/ + metal_log(METAL_LOG_ERROR, + "\n Invalid Configuration " + "DAC Cannot Source from ADC in %s\r\n", + __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if (CurrentTile < XRFDC_CLK_DST_ADC0) { /*DAC*/ + EFuse = XRFdc_ReadReg16(InstancePtr, + XRFDC_DRP_BASE(XRFDC_DAC_TILE, CurrentTile) + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_EFUSE_2_OFFSET); + } else { /*ADC*/ + EFuse = XRFdc_ReadReg16(InstancePtr, + XRFDC_DRP_BASE(XRFDC_ADC_TILE, (CurrentTile - XRFDC_CLK_DST_ADC0)) + + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_EFUSE_2_OFFSET); + } + /*if PKG <2*/ + + if ((DistributionSettingsPtr->DAC[0].SourceTile != DistributionSettingsPtr->DAC[1].SourceTile) || + (DistributionSettingsPtr->DAC[0].SourceTile != DistributionSettingsPtr->DAC[2].SourceTile) || + (DistributionSettingsPtr->DAC[0].SourceTile != DistributionSettingsPtr->DAC[3].SourceTile) || + (DistributionSettingsPtr->DAC[0].SourceTile != DistributionSettingsPtr->ADC[0].SourceTile) || + (DistributionSettingsPtr->DAC[0].SourceTile != DistributionSettingsPtr->ADC[1].SourceTile) || + (DistributionSettingsPtr->DAC[0].SourceTile != DistributionSettingsPtr->ADC[2].SourceTile) || + (DistributionSettingsPtr->DAC[0].SourceTile != DistributionSettingsPtr->ADC[3].SourceTile) || + (DistributionSettingsPtr->DAC[0].PLLEnable != XRFDC_ENABLED) || + (DistributionSettingsPtr->DAC[1].PLLEnable != XRFDC_ENABLED) || + (DistributionSettingsPtr->DAC[2].PLLEnable != XRFDC_ENABLED) || + (DistributionSettingsPtr->DAC[3].PLLEnable != XRFDC_ENABLED) || + (DistributionSettingsPtr->ADC[0].PLLEnable != XRFDC_ENABLED) || + (DistributionSettingsPtr->ADC[1].PLLEnable != XRFDC_ENABLED) || + (DistributionSettingsPtr->ADC[2].PLLEnable != XRFDC_ENABLED) || + (DistributionSettingsPtr->ADC[3].PLLEnable != XRFDC_ENABLED)) { /*special case that is allowed.*/ + + /*if PKG <2*/ + if (EFuse & XRFDC_PREMIUMCTRL_CLKDIST) { + if ((CurrentTile > XRFDC_CLK_DST_ADC1) && + (*Source != CurrentTile)) { /*E: no dist past adc1*/ + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Configuration " + "- Licensing - Not Premium in %s\r\n", + __func__); + goto RETURN_PATH; + } + } + /*if PKG <1*/ + if ((EFuse & XRFDC_EXPORTCTRL_CLKDIST) == XRFDC_EXPORTCTRL_CLKDIST) { + if ((CurrentTile > XRFDC_CLK_DST_DAC3) && (*Source != CurrentTile)) { /*E: No ADC Dist*/ + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Configuration " + "- Licensing in %s\r\n", + __func__); + goto RETURN_PATH; + } + if ((CurrentTile == XRFDC_CLK_DST_DAC0) && + (*Source != XRFDC_CLK_DST_DAC1)) { /*E: DAC0 must source from DAC1*/ + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Configuration " + "- Licensing in - Export Control %s\r\n", + __func__); + goto RETURN_PATH; + } + if ((CurrentTile == XRFDC_CLK_DST_DAC2) && + (*Source != XRFDC_CLK_DST_DAC3)) { /*E: DAC2 must source from DAC3*/ + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Configuration " + "- Licensing in %s\r\n", + __func__); + goto RETURN_PATH; + } + } + } + + if (*Source != DistributionPtr->DistributionSource) { /*i.e. if new distribution*/ + DistributionPtr->UpperBound = CurrentTile - 1; + DistributionPtr++; + DistributionPtr->Enabled = XRFDC_ENABLED; + LowBoundary = *Source; + DistributionPtr->DistributionSource = *Source; + DistributionPtr->LowerBound = CurrentTile; + } + } + DistributionPtr->UpperBound = CurrentTile - 1; + Status = XRFDC_SUCCESS; +RETURN_PATH: + if (Status == XRFDC_FAILURE) { + memset(DistributionSettingsPtr, 0, sizeof(XRFdc_Distribution_Settings)); + } + return Status; +} +/*****************************************************************************/ +/** +* +* This function is used to set the clock distribution +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param DistributionSettingsPtr pointer to the distribution settings struct +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if could not set distribution. +* +******************************************************************************/ +u32 XRFdc_SetClkDistribution(XRFdc *InstancePtr, XRFdc_Distribution_Settings *DistributionSettingsPtr) +{ + u32 Status; + u8 DelayLeft; + u8 DelayRight; + s8 Delay; + s8 ClkDetItr; + u8 *Delays[8]; + u8 DelayOutSourceLeft; + u8 DelayOutSourceRight; + XRFdc_Distribution *Distribution; + u8 DistributionCount; + u16 Reg; + u16 ClkDetectReg; + u8 FeedBackForInputRight = 0; + u8 FeedBackForInputLeft = 0; + u8 Tile; + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(DistributionSettingsPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + if (InstancePtr->RFdc_Config.IPType < 2) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Requested fuctionality not " + "available for this IP in %s\r\n", + __func__); + goto RETURN_PATH; + } + + Status = XRFdc_CheckClkDistValid(InstancePtr, DistributionSettingsPtr); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, + "\n Invalid Distribution " + "in %s\r\n", + __func__); + goto RETURN_PATH; + } + Delays[0] = &DistributionSettingsPtr->DAC[0].Delay; + Delays[1] = &DistributionSettingsPtr->DAC[1].Delay; + Delays[2] = &DistributionSettingsPtr->DAC[2].Delay; + Delays[3] = &DistributionSettingsPtr->DAC[3].Delay; + Delays[4] = &DistributionSettingsPtr->ADC[0].Delay; + Delays[5] = &DistributionSettingsPtr->ADC[1].Delay; + Delays[6] = &DistributionSettingsPtr->ADC[2].Delay; + Delays[7] = &DistributionSettingsPtr->ADC[3].Delay; + Status = XRFdc_Shutdown(InstancePtr, XRFDC_ADC_TILE, -1); + if (Status != XRFDC_SUCCESS) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + Status = XRFdc_Shutdown(InstancePtr, XRFDC_DAC_TILE, -1); + if (Status != XRFDC_SUCCESS) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + for (Distribution = DistributionSettingsPtr->DistributionStatus, DistributionCount = 0; + DistributionCount < XRFDC_DIST_MAX; Distribution++, DistributionCount++) { + if (Distribution->Enabled == XRFDC_DISABLED) { + break; + } + DelayLeft = (-Distribution->LowerBound + Distribution->DistributionSource); + DelayRight = (Distribution->UpperBound - Distribution->DistributionSource); + DelayOutSourceLeft = 0; + DelayOutSourceRight = 0; + Distribution->MaxDelay = 0; + Distribution->MinDelay = 255; + Distribution->IsDelayBalanced = 0; + if ((DelayLeft == 0) && (DelayRight == 0)) { /*self contained*/ + Reg = XRFDC_CLK_DISTR_OFF; + } else { + Reg = XRFDC_CLK_DISTR_MUX9_SRC_INT; + if (DelayLeft == 0) { + Reg |= XRFDC_CLK_DISTR_MUX8_SRC_NTH; + } else { + Reg |= XRFDC_CLK_DISTR_MUX8_SRC_INT; + } + if (((Distribution->DistributionSource == XRFDC_CLK_DST_DAC3) || + (Distribution->DistributionSource == XRFDC_CLK_DST_ADC3)) && + ((DelayLeft > 1) || (DelayRight > 1))) /*cases for no FB from tile to right*/ + { + Reg |= XRFDC_CLK_DISTR_MUX4A_SRC_INT | XRFDC_CLK_DISTR_MUX6_SRC_INT | + XRFDC_CLK_DISTR_MUX7_SRC_INT; + FeedBackForInputRight = 0; + FeedBackForInputLeft = 0; + } else { + if (DelayLeft > 1) { + Reg |= XRFDC_CLK_DISTR_MUX4A_SRC_STH | XRFDC_CLK_DISTR_MUX6_SRC_NTH | + XRFDC_CLK_DISTR_MUX7_SRC_INT; + DelayOutSourceRight = 2; + FeedBackForInputRight = 0; + FeedBackForInputLeft = 1; + } else { + Reg |= XRFDC_CLK_DISTR_MUX4A_SRC_INT; + FeedBackForInputRight = 1; + FeedBackForInputLeft = 0; + if ((DelayRight > 1) && + (Distribution->DistributionSource != XRFDC_CLK_DST_DAC2)) { + Reg |= XRFDC_CLK_DISTR_MUX7_SRC_STH; + DelayOutSourceLeft = 2; + } else { + Reg |= XRFDC_CLK_DISTR_MUX7_SRC_INT; + } + if (DelayRight == 0) { + Reg |= XRFDC_CLK_DISTR_MUX6_SRC_OFF; + } else { + Reg |= XRFDC_CLK_DISTR_MUX6_SRC_INT; + } + } + } + } + + *Delays[Distribution->DistributionSource] = + (Reg == XRFDC_CLK_DISTR_OFF) ? 0 : DelayOutSourceLeft + DelayOutSourceRight + 2; + Distribution->MaxDelay = MAX(Distribution->MaxDelay, (*Delays[Distribution->DistributionSource])); + Distribution->MinDelay = MIN(Distribution->MinDelay, (*Delays[Distribution->DistributionSource])); + + /* setup clk detect register */ + ClkDetectReg = (XRFDC_CLOCK_DETECT_CLK << + ((XRFDC_CLK_DST_ADC3 - Distribution->DistributionSource) << 1)); + + if ((Distribution->DistributionSource) < XRFDC_CLK_DST_ADC0) { /*DAC*/ + XRFdc_ClrSetReg(InstancePtr, + XRFDC_DRP_BASE(XRFDC_DAC_TILE, (Distribution->DistributionSource)) + + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_CLK_DSTR_OFFSET, XRFDC_HSCOM_CLK_DSTR_MASK, Reg); + XRFdc_ClrSetReg(InstancePtr, + XRFDC_CTRL_STS_BASE(XRFDC_DAC_TILE, (Distribution->DistributionSource)), + XRFDC_CLOCK_DETECT_OFFSET, XRFDC_CLOCK_DETECT_MASK, ClkDetectReg); + } else { /*ADC*/ + XRFdc_ClrSetReg(InstancePtr, + XRFDC_DRP_BASE(XRFDC_ADC_TILE, + (Distribution->DistributionSource - XRFDC_CLK_DST_ADC0)) + + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_CLK_DSTR_OFFSET, XRFDC_HSCOM_CLK_DSTR_MASK, Reg); + XRFdc_ClrSetReg(InstancePtr, + XRFDC_CTRL_STS_BASE(XRFDC_ADC_TILE, + (Distribution->DistributionSource - XRFDC_CLK_DST_ADC0)), + XRFDC_CLOCK_DETECT_OFFSET, XRFDC_CLOCK_DETECT_MASK, ClkDetectReg); + } + /*Leftmost tile*/ + if (DelayLeft) { + *Delays[Distribution->LowerBound] = DelayOutSourceLeft + (DelayLeft << 1); + Distribution->MaxDelay = MAX(Distribution->MaxDelay, (*Delays[Distribution->LowerBound])); + Distribution->MinDelay = MIN(Distribution->MinDelay, (*Delays[Distribution->LowerBound])); + Reg = XRFDC_CLK_DISTR_MUX4A_SRC_STH | XRFDC_CLK_DISTR_MUX6_SRC_OFF | + XRFDC_CLK_DISTR_MUX7_SRC_OFF | XRFDC_CLK_DISTR_MUX8_SRC_NTH | + XRFDC_CLK_DISTR_MUX9_SRC_INT; + + /* setup clk detect register */ + ClkDetectReg = (XRFDC_CLOCK_DETECT_BOTH << + ((XRFDC_CLK_DST_ADC3 - Distribution->DistributionSource) << 1)); + for (ClkDetItr = DelayLeft - 1; ClkDetItr > 0; ClkDetItr--) { + ClkDetectReg |= (XRFDC_CLOCK_DETECT_DIST << + ((XRFDC_CLK_DST_ADC3 - (Distribution->DistributionSource - ClkDetItr)) << 1)); + } + + if ((Distribution->DistributionSource - DelayLeft) < XRFDC_CLK_DST_ADC0) { /*DAC*/ + XRFdc_ClrSetReg(InstancePtr, + XRFDC_DRP_BASE(XRFDC_DAC_TILE, + (Distribution->DistributionSource - DelayLeft)) + + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_CLK_DSTR_OFFSET, XRFDC_HSCOM_CLK_DSTR_MASK, Reg); + XRFdc_ClrSetReg(InstancePtr, + XRFDC_CTRL_STS_BASE(XRFDC_DAC_TILE, (Distribution->DistributionSource - DelayLeft)), + XRFDC_CLOCK_DETECT_OFFSET, XRFDC_CLOCK_DETECT_MASK, ClkDetectReg); + } else { /*ADC*/ + XRFdc_ClrSetReg(InstancePtr, + XRFDC_DRP_BASE(XRFDC_ADC_TILE, (Distribution->DistributionSource - + DelayLeft - XRFDC_CLK_DST_ADC0)) + + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_CLK_DSTR_OFFSET, XRFDC_HSCOM_CLK_DSTR_MASK, Reg); + XRFdc_ClrSetReg(InstancePtr, + XRFDC_CTRL_STS_BASE(XRFDC_ADC_TILE, + (Distribution->DistributionSource - DelayLeft - XRFDC_CLK_DST_ADC0)), + XRFDC_CLOCK_DETECT_OFFSET, XRFDC_CLOCK_DETECT_MASK, ClkDetectReg); + } + } + /*Rest of tiles left of Distribution->DistributionSource*/ + for (Delay = 1; Delay < DelayLeft; Delay++) { + Reg = XRFDC_CLK_DISTR_MUX6_SRC_OFF | XRFDC_CLK_DISTR_MUX7_SRC_STH | + XRFDC_CLK_DISTR_MUX8_SRC_INT | XRFDC_CLK_DISTR_MUX9_SRC_INT; + if (FeedBackForInputLeft == 0) { + Reg |= XRFDC_CLK_DISTR_MUX4A_SRC_STH; + } else { + Reg |= XRFDC_CLK_DISTR_MUX4A_SRC_INT; + } + *Delays[Distribution->DistributionSource - Delay] = + DelayOutSourceLeft + ((Delay + FeedBackForInputLeft) << 1); + Distribution->MaxDelay = + MAX(Distribution->MaxDelay, (*Delays[Distribution->DistributionSource - Delay])); + Distribution->MinDelay = + MIN(Distribution->MinDelay, (*Delays[Distribution->DistributionSource - Delay])); + FeedBackForInputLeft = !FeedBackForInputLeft; + + /* setup clk detect register */ + ClkDetectReg = (XRFDC_CLOCK_DETECT_BOTH << + ((XRFDC_CLK_DST_ADC3 - Distribution->DistributionSource) << 1)); + for (ClkDetItr = Delay - 1; ClkDetItr > 0; ClkDetItr--) { + ClkDetectReg |= (XRFDC_CLOCK_DETECT_DIST << + ((XRFDC_CLK_DST_ADC3 - (Distribution->DistributionSource - ClkDetItr)) << 1)); + } + + if ((Distribution->DistributionSource - Delay) < XRFDC_CLK_DST_ADC0) { /*DAC*/ + XRFdc_ClrSetReg(InstancePtr, + XRFDC_DRP_BASE(XRFDC_DAC_TILE, + (Distribution->DistributionSource - Delay)) + + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_CLK_DSTR_OFFSET, XRFDC_HSCOM_CLK_DSTR_MASK, Reg); + XRFdc_ClrSetReg(InstancePtr, + XRFDC_CTRL_STS_BASE(XRFDC_DAC_TILE, (Distribution->DistributionSource - Delay)), + XRFDC_CLOCK_DETECT_OFFSET, XRFDC_CLOCK_DETECT_MASK, ClkDetectReg); + } else { /*ADC*/ + XRFdc_ClrSetReg(InstancePtr, + XRFDC_DRP_BASE(XRFDC_ADC_TILE, (Distribution->DistributionSource - + Delay - XRFDC_CLK_DST_ADC0)) + + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_CLK_DSTR_OFFSET, XRFDC_HSCOM_CLK_DSTR_MASK, Reg); + XRFdc_ClrSetReg(InstancePtr, + XRFDC_CTRL_STS_BASE(XRFDC_ADC_TILE, + (Distribution->DistributionSource - Delay - XRFDC_CLK_DST_ADC0)), + XRFDC_CLOCK_DETECT_OFFSET, XRFDC_CLOCK_DETECT_MASK, ClkDetectReg); + } + } + /*Rightmost tile*/ + if (DelayRight) { + Reg = XRFDC_CLK_DISTR_MUX4A_SRC_INT | XRFDC_CLK_DISTR_MUX6_SRC_OFF | + XRFDC_CLK_DISTR_MUX7_SRC_OFF | XRFDC_CLK_DISTR_MUX8_SRC_NTH | + XRFDC_CLK_DISTR_MUX9_SRC_NTH; + *Delays[Distribution->UpperBound] = DelayOutSourceRight + (DelayRight << 1); + Distribution->MaxDelay = MAX(Distribution->MaxDelay, (*Delays[Distribution->UpperBound])); + Distribution->MinDelay = MIN(Distribution->MinDelay, (*Delays[Distribution->UpperBound])); + + /* setup clk detect register */ + ClkDetectReg = (XRFDC_CLOCK_DETECT_BOTH << + ((XRFDC_CLK_DST_ADC3 - Distribution->DistributionSource) << 1)); + for (ClkDetItr = DelayRight - 1; ClkDetItr > 0; ClkDetItr--) { + ClkDetectReg |= (XRFDC_CLOCK_DETECT_DIST << + ((XRFDC_CLK_DST_ADC3 - (Distribution->DistributionSource + ClkDetItr)) << 1)); + } + + if ((Distribution->DistributionSource + DelayRight) < XRFDC_CLK_DST_ADC0) { /*DAC*/ + XRFdc_ClrSetReg(InstancePtr, + XRFDC_DRP_BASE(XRFDC_DAC_TILE, + (Distribution->DistributionSource + DelayRight)) + + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_CLK_DSTR_OFFSET, XRFDC_HSCOM_CLK_DSTR_MASK, Reg); + XRFdc_ClrSetReg(InstancePtr, + XRFDC_CTRL_STS_BASE(XRFDC_DAC_TILE, (Distribution->DistributionSource + DelayRight)), + XRFDC_CLOCK_DETECT_OFFSET, XRFDC_CLOCK_DETECT_MASK, ClkDetectReg); + } else { /*ADC*/ + XRFdc_ClrSetReg(InstancePtr, + XRFDC_DRP_BASE(XRFDC_ADC_TILE, (Distribution->DistributionSource + + DelayRight - XRFDC_CLK_DST_ADC0)) + + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_CLK_DSTR_OFFSET, XRFDC_HSCOM_CLK_DSTR_MASK, Reg); + XRFdc_ClrSetReg(InstancePtr, + XRFDC_CTRL_STS_BASE(XRFDC_ADC_TILE, + (Distribution->DistributionSource + DelayRight - XRFDC_CLK_DST_ADC0)), + XRFDC_CLOCK_DETECT_OFFSET, XRFDC_CLOCK_DETECT_MASK, ClkDetectReg); + } + } + /*rest of tiles to right*/ + for (Delay = 1; Delay < DelayRight; Delay++) { + if (((Delay + Distribution->DistributionSource) == 3) || (FeedBackForInputRight == 0)) { + FeedBackForInputRight = 0; + Reg = XRFDC_CLK_DISTR_MUX4A_SRC_INT; + *Delays[Distribution->DistributionSource + Delay] = DelayOutSourceRight + (Delay << 1); + } else { + Reg = XRFDC_CLK_DISTR_MUX4A_SRC_STH; + *Delays[Distribution->DistributionSource + Delay] = + DelayOutSourceRight + ((Delay + 1) << 1); + } + Distribution->MaxDelay = + MAX(Distribution->MaxDelay, (*Delays[Distribution->DistributionSource + Delay])); + Distribution->MinDelay = + MIN(Distribution->MinDelay, (*Delays[Distribution->DistributionSource + Delay])); + FeedBackForInputRight = !FeedBackForInputRight; + Reg |= XRFDC_CLK_DISTR_MUX6_SRC_NTH | XRFDC_CLK_DISTR_MUX7_SRC_OFF | + XRFDC_CLK_DISTR_MUX8_SRC_NTH | XRFDC_CLK_DISTR_MUX9_SRC_NTH; + + /* setup clk detect register */ + ClkDetectReg = (XRFDC_CLOCK_DETECT_BOTH << + ((XRFDC_CLK_DST_ADC3 - Distribution->DistributionSource) << 1)); + for (ClkDetItr = Delay - 1; ClkDetItr > 0; ClkDetItr--) { + ClkDetectReg |= (XRFDC_CLOCK_DETECT_DIST << + ((XRFDC_CLK_DST_ADC3 - (Distribution->DistributionSource + ClkDetItr)) << 1)); + } + + if ((Distribution->DistributionSource + Delay) < XRFDC_CLK_DST_ADC0) { /*DAC*/ + XRFdc_ClrSetReg(InstancePtr, + XRFDC_DRP_BASE(XRFDC_DAC_TILE, + (Distribution->DistributionSource + Delay)) + + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_CLK_DSTR_OFFSET, XRFDC_HSCOM_CLK_DSTR_MASK, Reg); + XRFdc_ClrSetReg(InstancePtr, + XRFDC_CTRL_STS_BASE(XRFDC_DAC_TILE, (Distribution->DistributionSource + Delay)), + XRFDC_CLOCK_DETECT_OFFSET, XRFDC_CLOCK_DETECT_MASK, ClkDetectReg); + } else { /*ADC*/ + XRFdc_ClrSetReg(InstancePtr, + XRFDC_DRP_BASE(XRFDC_ADC_TILE, (Distribution->DistributionSource + + Delay - XRFDC_CLK_DST_ADC0)) + + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_CLK_DSTR_OFFSET, XRFDC_HSCOM_CLK_DSTR_MASK, Reg); + XRFdc_ClrSetReg(InstancePtr, + XRFDC_CTRL_STS_BASE(XRFDC_ADC_TILE, + (Distribution->DistributionSource + Delay - XRFDC_CLK_DST_ADC0)), + XRFDC_CLOCK_DETECT_OFFSET, XRFDC_CLOCK_DETECT_MASK, ClkDetectReg); + } + } + Distribution->IsDelayBalanced = (Distribution->MaxDelay == Distribution->MinDelay) ? 1 : 0; + } + for (Tile = 0; Tile < XRFDC_NUM_OF_TILES4; Tile++) { + XRFdc_SetTileClkSettings(InstancePtr, XRFDC_ADC_TILE, Tile, &DistributionSettingsPtr->ADC[Tile]); + XRFdc_SetTileClkSettings(InstancePtr, XRFDC_DAC_TILE, Tile, &DistributionSettingsPtr->DAC[Tile]); + } + Status = XRFdc_StartUp(InstancePtr, XRFDC_ADC_TILE, -1); + if (Status != XRFDC_SUCCESS) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + Status = XRFdc_StartUp(InstancePtr, XRFDC_DAC_TILE, -1); + if (Status != XRFDC_SUCCESS) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/*****************************************************************************/ +/** +* +* This function is used to get the clock distribution +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param DistributionSettingsPtr pointer to get the distribution settings +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if no valid distribution found. +* +******************************************************************************/ +u32 XRFdc_GetClkDistribution(XRFdc *InstancePtr, XRFdc_Distribution_Settings *DistributionSettingsPtr) +{ + u32 Status; + u16 ReadReg; + u8 *Tile[8]; + u8 CurrentTile; + s8 AdjacentTile; + u8 DelayOutSourceLeft; + u8 DelayOutSourceRight; + u8 *Delays[8]; + XRFdc_Distribution *Distribution; + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(DistributionSettingsPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + if (InstancePtr->RFdc_Config.IPType < 2) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Requested fuctionality not " + "available for this IP in %s\r\n", + __func__); + goto RETURN_PATH; + } + + Distribution = DistributionSettingsPtr->DistributionStatus; + Delays[0] = &DistributionSettingsPtr->DAC[0].Delay; + Delays[1] = &DistributionSettingsPtr->DAC[1].Delay; + Delays[2] = &DistributionSettingsPtr->DAC[2].Delay; + Delays[3] = &DistributionSettingsPtr->DAC[3].Delay; + Delays[4] = &DistributionSettingsPtr->ADC[0].Delay; + Delays[5] = &DistributionSettingsPtr->ADC[1].Delay; + Delays[6] = &DistributionSettingsPtr->ADC[2].Delay; + Delays[7] = &DistributionSettingsPtr->ADC[3].Delay; + Tile[0] = &DistributionSettingsPtr->DAC[0].SourceTile; + Tile[1] = &DistributionSettingsPtr->DAC[1].SourceTile; + Tile[2] = &DistributionSettingsPtr->DAC[2].SourceTile; + Tile[3] = &DistributionSettingsPtr->DAC[3].SourceTile; + Tile[4] = &DistributionSettingsPtr->ADC[0].SourceTile; + Tile[5] = &DistributionSettingsPtr->ADC[1].SourceTile; + Tile[6] = &DistributionSettingsPtr->ADC[2].SourceTile; + Tile[7] = &DistributionSettingsPtr->ADC[3].SourceTile; + memset(DistributionSettingsPtr, XRFDC_CLK_DST_INVALID, sizeof(XRFdc_Distribution_Settings)); + + for (CurrentTile = 0; CurrentTile < XRFDC_DIST_MAX; CurrentTile++) { + DelayOutSourceLeft = 0; + DelayOutSourceRight = 0; + if (*Tile[CurrentTile] != XRFDC_CLK_DST_INVALID) { + continue; + } + + if (CurrentTile < XRFDC_CLK_DST_ADC0) { /*DAC*/ + ReadReg = XRFdc_ReadReg16(InstancePtr, + XRFDC_DRP_BASE(XRFDC_DAC_TILE, CurrentTile) + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_CLK_DSTR_OFFSET) & + XRFDC_HSCOM_CLK_DSTR_MASK; + } else { /*ADC*/ + ReadReg = XRFdc_ReadReg16(InstancePtr, + XRFDC_DRP_BASE(XRFDC_ADC_TILE, (CurrentTile - XRFDC_CLK_DST_ADC0)) + + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_CLK_DSTR_OFFSET) & + XRFDC_HSCOM_CLK_DSTR_MASK; + } + + if (ReadReg == XRFDC_CLK_DISTR_OFF) { /*it is its own source no dist*/ + *Tile[CurrentTile] = CurrentTile; + *Delays[CurrentTile] = 0; + Distribution->MaxDelay = 0; + Distribution->MinDelay = 0; + Distribution->IsDelayBalanced = 1; + Distribution++; + } else if (ReadReg & (XRFDC_CLK_DISTR_MUX6_SRC_INT | + XRFDC_CLK_DISTR_MUX7_SRC_INT)) { /*it is its own source, distributes its clk*/ + Distribution->MaxDelay = 0; + Distribution->MinDelay = 255; + Distribution->IsDelayBalanced = 0; + *Tile[CurrentTile] = CurrentTile; + if (ReadReg & XRFDC_CLK_DISTR_MUX7_SRC_STH) { + DelayOutSourceLeft = 2; + } else if (ReadReg & XRFDC_CLK_DISTR_MUX6_SRC_NTH) { + DelayOutSourceRight = 2; + } + *Delays[CurrentTile] = DelayOutSourceLeft + DelayOutSourceRight + 2; + Distribution->MaxDelay = MAX(Distribution->MaxDelay, (*Delays[CurrentTile])); + Distribution->MinDelay = MIN(Distribution->MinDelay, (*Delays[CurrentTile])); + /*work right*/ + for (AdjacentTile = CurrentTile + 1; AdjacentTile <= XRFDC_CLK_DST_ADC3; AdjacentTile++) { + if (AdjacentTile < XRFDC_CLK_DST_ADC0) { /*DAC*/ + ReadReg = XRFdc_ReadReg16(InstancePtr, + XRFDC_DRP_BASE(XRFDC_DAC_TILE, AdjacentTile) + + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_CLK_DSTR_OFFSET) & + XRFDC_HSCOM_CLK_DSTR_MASK; + } else { /*ADC*/ + ReadReg = XRFdc_ReadReg16(InstancePtr, + XRFDC_DRP_BASE(XRFDC_ADC_TILE, + (AdjacentTile - XRFDC_CLK_DST_ADC0)) + + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_CLK_DSTR_OFFSET) & + XRFDC_HSCOM_CLK_DSTR_MASK; + } + if ((ReadReg == XRFDC_CLK_DISTR_CONT_RIGHT_EVEN) && + (AdjacentTile != XRFDC_CLK_DST_DAC3)) { + *Tile[AdjacentTile] = CurrentTile; + *Delays[AdjacentTile] = + DelayOutSourceRight + ((AdjacentTile - CurrentTile) << 1) + 2; + Distribution->MaxDelay = MAX(Distribution->MaxDelay, (*Delays[AdjacentTile])); + Distribution->MinDelay = MIN(Distribution->MinDelay, (*Delays[AdjacentTile])); + } else if (ReadReg == XRFDC_CLK_DISTR_CONT_RIGHT_HWL_ODD) { + *Tile[AdjacentTile] = CurrentTile; + *Delays[AdjacentTile] = + DelayOutSourceRight + ((AdjacentTile - CurrentTile) << 1); + Distribution->MaxDelay = MAX(Distribution->MaxDelay, (*Delays[AdjacentTile])); + Distribution->MinDelay = MIN(Distribution->MinDelay, (*Delays[AdjacentTile])); + } else if (ReadReg == XRFDC_CLK_DISTR_RIGHTMOST_TILE) { + *Tile[AdjacentTile] = CurrentTile; + *Delays[AdjacentTile] = + DelayOutSourceRight + ((AdjacentTile - CurrentTile) << 1); + Distribution->MaxDelay = MAX(Distribution->MaxDelay, (*Delays[AdjacentTile])); + Distribution->MinDelay = MIN(Distribution->MinDelay, (*Delays[AdjacentTile])); + break; + } else { + break; + } + } + /*work left*/ + for (AdjacentTile = CurrentTile - 1; AdjacentTile >= XRFDC_CLK_DST_DAC0; AdjacentTile--) { + if (*Tile[AdjacentTile] != XRFDC_CLK_DST_INVALID) { + break; + } + if (AdjacentTile < XRFDC_CLK_DST_ADC0) { /*DAC*/ + ReadReg = XRFdc_ReadReg16(InstancePtr, + XRFDC_DRP_BASE(XRFDC_DAC_TILE, AdjacentTile) + + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_CLK_DSTR_OFFSET) & + XRFDC_HSCOM_CLK_DSTR_MASK; + } else { /*ADC*/ + ReadReg = XRFdc_ReadReg16(InstancePtr, + XRFDC_DRP_BASE(XRFDC_ADC_TILE, + (AdjacentTile - XRFDC_CLK_DST_ADC0)) + + XRFDC_HSCOM_ADDR, + XRFDC_HSCOM_CLK_DSTR_OFFSET) & + XRFDC_HSCOM_CLK_DSTR_MASK; + } + if (ReadReg == XRFDC_CLK_DISTR_LEFTMOST_TILE) { + *Tile[AdjacentTile] = CurrentTile; + *Delays[AdjacentTile] = + DelayOutSourceLeft + ((CurrentTile - AdjacentTile) << 1); + break; + } else if (ReadReg == XRFDC_CLK_DISTR_CONT_LEFT_EVEN) { + *Tile[AdjacentTile] = CurrentTile; + *Delays[AdjacentTile] = + DelayOutSourceLeft + ((CurrentTile - AdjacentTile) << 1) + 2; + } else if (ReadReg == XRFDC_CLK_DISTR_CONT_LEFT_ODD) { + *Delays[AdjacentTile] = + DelayOutSourceLeft + ((CurrentTile - AdjacentTile) << 1); + *Tile[AdjacentTile] = CurrentTile; + } else { + break; + } + Distribution->MaxDelay = MAX(Distribution->MaxDelay, (*Delays[AdjacentTile])); + Distribution->MinDelay = MIN(Distribution->MinDelay, (*Delays[AdjacentTile])); + } + Distribution->IsDelayBalanced = (Distribution->MaxDelay == Distribution->MinDelay) ? 1 : 0; + Distribution++; + } + } + Status = XRFdc_CheckClkDistValid(InstancePtr, DistributionSettingsPtr); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, + "\n Invalid Distribution " + "in %s\r\n", + __func__); + goto RETURN_PATH; + } + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/*****************************************************************************/ +/** +* +* This function gets Clock source +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type indicates ADC/DAC. +* @param Tile_Id indicates Tile number (0-3). +* @param ClockSourcePtr Pointer to return the clock source +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note None. +* +******************************************************************************/ +u32 XRFdc_GetClockSource(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 *ClockSourcePtr) +{ + u32 BaseAddr; + u32 Status; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(ClockSourcePtr != NULL); + + Status = XRFdc_CheckTileEnabled(InstancePtr, Type, Tile_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested tile not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + BaseAddr = XRFDC_DRP_BASE(Type, Tile_Id) + XRFDC_HSCOM_ADDR; + + *ClockSourcePtr = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_CLK_NETWORK_CTRL1, XRFDC_CLK_NETWORK_CTRL1_USE_PLL_MASK); + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* This function gets PLL lock status +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type indicates ADC/DAC. +* @param Tile_Id indicates Tile number (0-3). +* @param LockStatusPtr Pointer to return the PLL lock status +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note None. +* +******************************************************************************/ +u32 XRFdc_GetPLLLockStatus(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 *LockStatusPtr) +{ + u32 BaseAddr; + u32 ReadReg; + u32 ClkSrc = 0U; + u32 Status; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(LockStatusPtr != NULL); + + /* + * Get Tile clock source information + */ + if (XRFdc_GetClockSource(InstancePtr, Type, Tile_Id, &ClkSrc) + != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Get clock source request Tile %d " + "failed in %s\r\n", Tile_Id, __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + if (ClkSrc == XRFDC_EXTERNAL_CLK) { + metal_log(METAL_LOG_DEBUG, "\n Requested Tile %d " + "uses external clock source in %s\r\n", Tile_Id, __func__); + *LockStatusPtr = XRFDC_PLL_LOCKED; + } else { + if (Type == XRFDC_ADC_TILE) { + BaseAddr = XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id); + } else { + BaseAddr = XRFDC_DAC_TILE_CTRL_STATS_ADDR(Tile_Id); + } + + ReadReg = XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_STATUS_OFFSET, + XRFDC_PLL_LOCKED_MASK); + if (ReadReg != 0U) { + *LockStatusPtr = XRFDC_PLL_LOCKED; + } else { + *LockStatusPtr = XRFDC_PLL_UNLOCKED; + } + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* +* This function used for configuring the internal PLL registers +* based on reference clock and sampling rate +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type indicates ADC/DAC. +* @param Tile_Id indicates Tile number (0-3). +* @param RefClkFreq Reference Clock Frequency MHz(50MHz - 1.2GHz) +* @param SamplingRate Sampling Rate in MHz(0.5- 4 GHz) +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note None. +* +******************************************************************************/ +static u32 XRFdc_SetPLLConfig(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + double RefClkFreq, double SamplingRate) +{ + u32 BaseAddr; + u32 Status; + u32 FeedbackDiv; + u32 OutputDiv; + double CalcSamplingRate; + double PllFreq; + double SamplingError; + u32 Best_FeedbackDiv = 0x0U; + u32 Best_OutputDiv = 0x2U; + double Best_Error = 0xFFFFFFFFU; + u32 DivideMode = 0x0U; + u32 DivideValue = 0x0U; + u32 PllFreqIndex = 0x0U; + u32 FbDivIndex = 0x0U; + u32 RefClkDiv = 0x1; + u16 ReadReg; + + if (Type == XRFDC_ADC_TILE) { + BaseAddr = XRFDC_ADC_TILE_DRP_ADDR(Tile_Id); + } else { + BaseAddr = XRFDC_DAC_TILE_DRP_ADDR(Tile_Id); + } + + BaseAddr += XRFDC_HSCOM_ADDR; + + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, XRFDC_PLL_REFDIV); + if (ReadReg & XRFDC_REFCLK_DIV_1_MASK) { + RefClkDiv = XRFDC_REF_CLK_DIV_1; + } else { + switch (ReadReg & XRFDC_REFCLK_DIV_MASK) { + case XRFDC_REFCLK_DIV_2_MASK: + RefClkDiv = XRFDC_REF_CLK_DIV_2; + break; + case XRFDC_REFCLK_DIV_3_MASK: + RefClkDiv = XRFDC_REF_CLK_DIV_3; + break; + case XRFDC_REFCLK_DIV_4_MASK: + RefClkDiv = XRFDC_REF_CLK_DIV_4; + break; + default: + /* + * IP currently supporting 1 to 4 divider values. This + * error condition might change in future based on IP update. + */ + metal_log(METAL_LOG_ERROR, "\n Unsupported Reference " + "clock Divider value in %s\r\n", __func__); + return XRFDC_FAILURE; + } + } + + RefClkFreq /= RefClkDiv; + + /* + * Sweep valid integer values of FeedbackDiv(N) and record a list + * of values that fall in the valid VCO range 8.5GHz - 12.8GHz + */ + for (FeedbackDiv = PLL_FPDIV_MIN; FeedbackDiv <= PLL_FPDIV_MAX; + FeedbackDiv++) { + + PllFreq = FeedbackDiv * RefClkFreq; + + if ((PllFreq >= VCO_RANGE_MIN) && (PllFreq <= VCO_RANGE_MAX)) { + /* + * Sweep values of OutputDiv(M) to find the output frequency + * that best matches the user requested value + */ + + for (OutputDiv = PLL_DIVIDER_MIN; OutputDiv <= PLL_DIVIDER_MAX; + OutputDiv += 2U) { + + CalcSamplingRate = (PllFreq / OutputDiv); + + if (SamplingRate > CalcSamplingRate) { + SamplingError = SamplingRate - CalcSamplingRate; + } else { + SamplingError = CalcSamplingRate - SamplingRate; + } + + if (Best_Error > SamplingError) { + Best_FeedbackDiv = FeedbackDiv; + Best_OutputDiv = OutputDiv; + Best_Error = SamplingError; + } + } + + OutputDiv = 3U; + CalcSamplingRate = (PllFreq / OutputDiv); + + if (SamplingRate > CalcSamplingRate) { + SamplingError = SamplingRate - CalcSamplingRate; + } else { + SamplingError = CalcSamplingRate - SamplingRate; + } + + if (Best_Error > SamplingError) { + Best_FeedbackDiv = FeedbackDiv; + Best_OutputDiv = OutputDiv; + Best_Error = SamplingError; + } + } + + /* + * PLL Static configuration + */ + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_PLL_SDM_CFG0, 0x80U); + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_PLL_SDM_SEED0, 0x111U); + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_PLL_SDM_SEED1, 0x11U); + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_PLL_VREG, 0x45U); + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_PLL_VCO0, 0x5800U); + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_PLL_VCO1, 0x08U); + + /* + * Set Feedback divisor value + */ + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_PLL_FPDIV, + Best_FeedbackDiv - 2U); + + /* + * Set Output divisor value + */ + if (Best_OutputDiv == 2U) { + DivideMode = 0x1U; + } else if (Best_OutputDiv == 3U) { + DivideMode = 0x2U; + DivideValue = 0x1U; + } else if (Best_OutputDiv >= 4U) { + DivideMode = 0x3U; + DivideValue = ((Best_OutputDiv - 4U)/2U); + } + + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_PLL_DIVIDER0, + XRFDC_PLL_DIVIDER0_MASK, ((DivideMode << XRFDC_PLL_DIVIDER0_SHIFT) | DivideValue)); + + /* + * Enable fine sweep + */ + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_PLL_CRS2, XRFDC_PLL_CRS2_VAL); + + /* + * Set default PLL spare inputs LSB + */ + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_PLL_SPARE0, 0x507U); + + /* + * Set PLL spare inputs MSB + */ + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_PLL_SPARE1, 0x0U); + + PllFreq = RefClkFreq * Best_FeedbackDiv; + + if (PllFreq < 9400U) { + PllFreqIndex = 0U; + FbDivIndex = 2U; + if (Best_FeedbackDiv < 21U) { + FbDivIndex = 0U; + } else if (Best_FeedbackDiv < 30U) { + FbDivIndex = 1U; + } + } else if (PllFreq < 10070U) { + PllFreqIndex = 1U; + FbDivIndex = 2U; + if (Best_FeedbackDiv < 18U) { + FbDivIndex = 0U; + } else if (Best_FeedbackDiv < 30U) { + FbDivIndex = 1U; + } + } else if (PllFreq < 10690U) { + PllFreqIndex = 2U; + FbDivIndex = 3U; + if (Best_FeedbackDiv < 18U) { + FbDivIndex = 0U; + } else if (Best_FeedbackDiv < 25U) { + FbDivIndex = 1U; + } else if (Best_FeedbackDiv < 35U) { + FbDivIndex = 2U; + } + } else if (PllFreq < 10990U) { + PllFreqIndex = 3U; + FbDivIndex = 3U; + if (Best_FeedbackDiv < 19U) { + FbDivIndex = 0U; + } else if (Best_FeedbackDiv < 27U) { + FbDivIndex = 1U; + } else if (Best_FeedbackDiv < 38U) { + FbDivIndex = 2U; + } + } else if (PllFreq < 11430U) { + PllFreqIndex = 4U; + FbDivIndex = 3U; + if (Best_FeedbackDiv < 19U) { + FbDivIndex = 0U; + } else if (Best_FeedbackDiv < 27U) { + FbDivIndex = 1U; + } else if (Best_FeedbackDiv < 38U) { + FbDivIndex = 2U; + } + } else if (PllFreq < 12040U) { + PllFreqIndex = 5U; + FbDivIndex = 3U; + if (Best_FeedbackDiv < 20U) { + FbDivIndex = 0U; + } else if (Best_FeedbackDiv < 28U) { + FbDivIndex = 1U; + } else if (Best_FeedbackDiv < 40U) { + FbDivIndex = 2U; + } + } else if (PllFreq < 12530U) { + PllFreqIndex = 6U; + FbDivIndex = 3U; + if (Best_FeedbackDiv < 23U) { + FbDivIndex = 0U; + } else if (Best_FeedbackDiv < 30U) { + FbDivIndex = 1U; + } else if (Best_FeedbackDiv < 42U) { + FbDivIndex = 2U; + } + } else if (PllFreq < 20000U) { + PllFreqIndex = 7U; + FbDivIndex = 2U; + if (Best_FeedbackDiv < 20U) { + FbDivIndex = 0U; + /* + * Set PLL spare inputs LSB + */ + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_PLL_SPARE0, 0x577); + } else if (Best_FeedbackDiv < 39U) { + FbDivIndex = 1U; + } + } + + /* + * Enable automatic selection of the VCO, this will work with the + * IP version 2.0.1 and above and using older version of IP is + * not likely to work. + */ + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_PLL_CRS1, + XRFDC_PLL_VCO_SEL_AUTO_MASK, XRFDC_PLL_VCO_SEL_AUTO_MASK); + + /* + * PLL bits for loop filters LSB + */ + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_PLL_LPF0, + PllTuningMatrix[PllFreqIndex][FbDivIndex][0]); + + /* + * PLL bits for loop filters MSB + */ + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_PLL_LPF1, XRFDC_PLL_LPF1_VAL); + + /* + * Set PLL bits for charge pumps + */ + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_PLL_CHARGEPUMP, + PllTuningMatrix[PllFreqIndex][FbDivIndex][1]); + } + + CalcSamplingRate = (Best_FeedbackDiv * RefClkFreq) / Best_OutputDiv; + CalcSamplingRate /= XRFDC_MILLI; + + if (Type == XRFDC_ADC_TILE) { + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.SampleRate = + CalcSamplingRate; + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.RefClkDivider = RefClkDiv; + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.FeedbackDivider = Best_FeedbackDiv; + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.OutputDivider = Best_OutputDiv; + } else { + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.SampleRate = + CalcSamplingRate; + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.RefClkDivider = RefClkDiv; + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.FeedbackDivider = Best_FeedbackDiv; + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.OutputDivider = Best_OutputDiv; + } + + Status = XRFDC_SUCCESS; + + return Status; +} +/*****************************************************************************/ +/** +* +* This API is used to get the PLL Configurations. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type represents ADC or DAC. +* @param Tile_Id Valid values are 0-3. +* @param PLLSettings pointer to the XRFdc_PLL_Settings structure to get +* the PLL configurations +* +* @return None +* +******************************************************************************/ +u32 XRFdc_GetPLLConfig(XRFdc *InstancePtr, u32 Type, + u32 Tile_Id, XRFdc_PLL_Settings *PLLSettings) +{ + u32 Status; + u32 BaseAddr; + u16 ReadReg; + double RefClkFreq; + double SampleRate; + u32 FeedbackDivider; + u8 OutputDivider; + u32 RefClkDivider; + u32 Enabled; + u8 DivideMode; + u32 PLLFreq; + u32 PLLFS; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + Xil_AssertNonvoid(PLLSettings != NULL); + + Status = XRFdc_CheckTileEnabled(InstancePtr, Type, Tile_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested tile not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + BaseAddr = XRFDC_CTRL_STS_BASE(Type, Tile_Id); + PLLFreq = XRFdc_ReadReg(InstancePtr, BaseAddr, XRFDC_PLL_FREQ); + + RefClkFreq = ((double)PLLFreq)/1000; + PLLFS = XRFdc_ReadReg(InstancePtr, BaseAddr, XRFDC_PLL_FS); + SampleRate = ((double)PLLFS)/1000000; + if (PLLFS == 0) { + /*This code is here to support the old IPs.*/ + if (Type == XRFDC_ADC_TILE) { + PLLSettings->Enabled = + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.Enabled; + PLLSettings->FeedbackDivider = + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.FeedbackDivider; + PLLSettings->OutputDivider = + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.OutputDivider; + PLLSettings->RefClkDivider = + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.RefClkDivider; + PLLSettings->RefClkFreq = + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.RefClkFreq; + PLLSettings->SampleRate = + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.SampleRate; + Status = XRFDC_SUCCESS; + goto RETURN_PATH; + } else { + PLLSettings->Enabled = + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.Enabled; + PLLSettings->FeedbackDivider = + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.FeedbackDivider; + PLLSettings->OutputDivider = + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.OutputDivider; + PLLSettings->RefClkDivider = + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.RefClkDivider; + PLLSettings->RefClkFreq = + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.RefClkFreq; + PLLSettings->SampleRate = + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.SampleRate; + Status = XRFDC_SUCCESS; + goto RETURN_PATH; + } + } else { + if (Type == XRFDC_ADC_TILE) { + BaseAddr = XRFDC_ADC_TILE_DRP_ADDR(Tile_Id); + } else { + BaseAddr = XRFDC_DAC_TILE_DRP_ADDR(Tile_Id); + } + + BaseAddr += XRFDC_HSCOM_ADDR; + + FeedbackDivider = XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_PLL_FPDIV, 0x00FF) + 2; + + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, XRFDC_PLL_REFDIV); + if (ReadReg & XRFDC_REFCLK_DIV_1_MASK) { + RefClkDivider = XRFDC_REF_CLK_DIV_1; + } else { + switch (ReadReg & XRFDC_REFCLK_DIV_MASK) { + case XRFDC_REFCLK_DIV_2_MASK: + RefClkDivider = XRFDC_REF_CLK_DIV_2; + break; + case XRFDC_REFCLK_DIV_3_MASK: + RefClkDivider = XRFDC_REF_CLK_DIV_3; + break; + case XRFDC_REFCLK_DIV_4_MASK: + RefClkDivider = XRFDC_REF_CLK_DIV_4; + break; + default: + /* + * IP currently supporting 1 to 4 divider values. This + * error condition might change in future based on IP update. + */ + metal_log(METAL_LOG_ERROR, "\n Unsupported Reference " + "clock Divider value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + } + if (InstancePtr->RFdc_Config.IPType < 2) { + if (XRFdc_GetClockSource(InstancePtr, Type, Tile_Id, &Enabled) + != XRFDC_SUCCESS) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + } else { + Enabled = (ReadReg & XRFDC_PLLREFDIV_INPUT_OFF)?XRFDC_DISABLED:XRFDC_ENABLED; + } + + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, XRFDC_PLL_DIVIDER0); + DivideMode = (ReadReg & XRFDC_PLL_DIVIDER0_MODE_MASK) >> XRFDC_PLL_DIVIDER0_SHIFT; + + switch(DivideMode) { + case XRFDC_PLL_OUTDIV_MODE_1: + OutputDivider = 1; + break; + case XRFDC_PLL_OUTDIV_MODE_2: + OutputDivider = 2; + break; + case XRFDC_PLL_OUTDIV_MODE_3: + OutputDivider = 3; + break; + case XRFDC_PLL_OUTDIV_MODE_N: + OutputDivider = ((ReadReg & XRFDC_PLL_DIVIDER0_VALUE_MASK) + 2) << 1; + break; + default: + metal_log(METAL_LOG_ERROR, "\n Unsupported Output " + "clock Divider value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + break; + } + PLLSettings->Enabled = Enabled; + PLLSettings->FeedbackDivider = FeedbackDivider; + PLLSettings->OutputDivider = OutputDivider; + PLLSettings->RefClkDivider = RefClkDivider; + PLLSettings->RefClkFreq = RefClkFreq; + PLLSettings->SampleRate = SampleRate; + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + + return Status; +} + +/*****************************************************************************/ +/** +* +* This function used for dynamically switch between internal PLL and +* external clcok source and configuring the internal PLL +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type indicates ADC/DAC +* @param Tile_Id indicates Tile number (0-3) +* @param Source Clock source internal PLL or external clock source +* @param RefClkFreq Reference Clock Frequency in MHz(102.40625MHz - 1.2GHz) +* @param SamplingRate Sampling Rate in MHz(0.1- 6.554GHz for DAC and +* 0.5/1.0 - 2.058/4.116GHz for ADC based on the device package). +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Tile not enabled. +* +* @note This API enables automatic selection of the VCO which will work in +* IP version 2.0.1 and above. Using older version of IP this API is +* not likely to work. +* +******************************************************************************/ +u32 XRFdc_DynamicPLLConfig(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u8 Source, double RefClkFreq, double SamplingRate) +{ + u32 ClkSrc = 0U; + u32 Status; + u32 BaseAddr; + u32 PLLEnable = 0x0; + u32 InitialPowerUpState; + double MaxSampleRate; + double MinSampleRate; + u32 PLLFreq; + u32 PLLFS; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + if ((Source != XRFDC_INTERNAL_PLL_CLK) && + (Source != XRFDC_EXTERNAL_CLK)) { + metal_log(METAL_LOG_ERROR, "\n Invalid Source " + "value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + Status = XRFdc_CheckTileEnabled(InstancePtr, Type, Tile_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested tile not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + /* + * Get Tile clock source information + */ + if (XRFdc_GetClockSource(InstancePtr, Type, Tile_Id, &ClkSrc) + != XRFDC_SUCCESS) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + if (XRFdc_GetMaxSampleRate(InstancePtr, Type, Tile_Id, &MaxSampleRate) != XRFDC_SUCCESS) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if (XRFdc_GetMinSampleRate(InstancePtr, Type, Tile_Id, &MinSampleRate) != XRFDC_SUCCESS) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if ((SamplingRate < MinSampleRate) || + (SamplingRate > MaxSampleRate)) { + metal_log(METAL_LOG_ERROR, "\n Invalid sampling " + "rate value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + BaseAddr = XRFDC_CTRL_STS_BASE(Type, Tile_Id); + + if (Source == XRFDC_INTERNAL_PLL_CLK) { + if ((RefClkFreq < XRFDC_REFFREQ_MIN) || + (RefClkFreq > XRFDC_REFFREQ_MAX)) { + metal_log(METAL_LOG_ERROR, "\n Input reference clock " + "frequency does not respect the specifications " + "for internal PLL usage. Please use a different " + "frequency or bypass the internal PLL", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + } + + PLLFreq = (u32)(RefClkFreq*1000); + PLLFS = (u32)(SamplingRate*1000); + XRFdc_WriteReg(InstancePtr, BaseAddr, XRFDC_PLL_FREQ, PLLFreq); + XRFdc_WriteReg(InstancePtr, BaseAddr, XRFDC_PLL_FS, PLLFS); + + if ((Source != XRFDC_INTERNAL_PLL_CLK) && + (ClkSrc != XRFDC_INTERNAL_PLL_CLK)) { + metal_log(METAL_LOG_DEBUG, "\n Requested Tile %d " + "uses external clock source in %s\r\n", Tile_Id, __func__); + if (Type == XRFDC_ADC_TILE) { + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.SampleRate = + (double)(SamplingRate/1000); + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.RefClkFreq = RefClkFreq; + } else { + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.SampleRate = + (double)(SamplingRate/1000); + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.RefClkFreq = RefClkFreq; + } + Status = XRFDC_SUCCESS; + goto RETURN_PATH; + } + + if (Type == XRFDC_ADC_TILE) { + BaseAddr = XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id); + InitialPowerUpState = XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_STATUS_OFFSET, XRFDC_PWR_UP_STAT_MASK) >> XRFDC_PWR_UP_STAT_SHIFT; + BaseAddr = XRFDC_ADC_TILE_DRP_ADDR(Tile_Id) + XRFDC_HSCOM_ADDR; + } else { + BaseAddr = XRFDC_DAC_TILE_CTRL_STATS_ADDR(Tile_Id); + InitialPowerUpState = XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_STATUS_OFFSET, XRFDC_PWR_UP_STAT_MASK) >> XRFDC_PWR_UP_STAT_SHIFT; + BaseAddr = XRFDC_DAC_TILE_DRP_ADDR(Tile_Id) + XRFDC_HSCOM_ADDR; + } + + /* + * Stop the ADC or DAC tile by putting tile in reset state if not stopped already + */ + if (InitialPowerUpState != XRFDC_DISABLED) { + Status = XRFdc_Shutdown(InstancePtr, Type, Tile_Id) ; + if (Status != XRFDC_SUCCESS) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + } + if (Source == XRFDC_INTERNAL_PLL_CLK) { + PLLEnable = 0x1; + /* + * Configure the PLL + */ + if (XRFdc_SetPLLConfig(InstancePtr, Type, Tile_Id, RefClkFreq, + SamplingRate) != XRFDC_SUCCESS) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + XRFdc_ClrSetReg(InstancePtr, BaseAddr, + XRFDC_CLK_NETWORK_CTRL1, XRFDC_CLK_NETWORK_CTRL1_USE_PLL_MASK, + XRFDC_CLK_NETWORK_CTRL1_USE_PLL_MASK); + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_HSCOM_PWR_STATE_OFFSET, + XRFDC_HSCOM_PWR_STATS_PLL); + } else { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_CLK_NETWORK_CTRL1, + XRFDC_CLK_NETWORK_CTRL1_USE_PLL_MASK, 0x0); + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_HSCOM_PWR_STATE_OFFSET, + XRFDC_HSCOM_PWR_STATS_EXTERNAL); + SamplingRate /= XRFDC_MILLI; + + if (Type == XRFDC_ADC_TILE) { + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.SampleRate = + SamplingRate; + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.RefClkDivider = 0x0U; + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.FeedbackDivider = 0x0U; + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.OutputDivider = 0x0U; + } else { + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.SampleRate = + SamplingRate; + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.RefClkDivider = 0x0U; + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.FeedbackDivider = 0x0U; + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.OutputDivider = 0x0U; + } + } + + /* + * Re-start the ADC or DAC tile if tile was shut down in this function + */ + if (InitialPowerUpState != XRFDC_DISABLED) { + Status = XRFdc_StartUp(InstancePtr, Type, Tile_Id) ; + if (Status != XRFDC_SUCCESS) { + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + } + + if (Type == XRFDC_ADC_TILE) { + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.RefClkFreq = RefClkFreq; + InstancePtr->ADC_Tile[Tile_Id].PLL_Settings.Enabled = PLLEnable; + } else { + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.RefClkFreq = RefClkFreq; + InstancePtr->DAC_Tile[Tile_Id].PLL_Settings.Enabled = PLLEnable; + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/** @} */ diff --git a/mpm/lib/rfdc/xrfdc_g.c b/mpm/lib/rfdc/xrfdc_g.c new file mode 100644 index 000000000..857fb1ffd --- /dev/null +++ b/mpm/lib/rfdc/xrfdc_g.c @@ -0,0 +1,619 @@ +/******************************************************************* +* Copyright (C) 2017-2019 Xilinx, Inc. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* XILINX BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Except as contained in this notice, the name of the Xilinx shall not be used +* in advertising or otherwise to promote the sale, use or other dealings in +* this Software without prior written authorization from Xilinx. +* + +*******************************************************************************/ + +/*****************************************************************************/ +/** +* +* @file xrfdc_g.c +* @addtogroup rfdc_v6_0 +* @{ +* +* This file contains a configuration table that specifies the configuration of +* RFdc devices in the system. +* +* <pre> +* MODIFICATION HISTORY: +* +* Ver Who Date Changes +* ----- --- -------- ----------------------------------------------- +* 1.0 sk 05/16/17 Initial release +* 5.1 cog 01/29/19 Added FSMax, NumSlice & IP_Type. +* +* </pre> +* +******************************************************************************/ +#ifdef __BAREMETAL__ +/***************************** Include Files ********************************/ +#include "xparameters.h" +#include "mpm/rfdc/xrfdc.h" +/************************** Constant Definitions ****************************/ + +/**************************** Type Definitions ******************************/ + +/***************** Macros (Inline Functions) Definitions ********************/ + +/************************** Variable Definitions ****************************/ +/** + * The configuration table for devices + */ + +XRFdc_Config XRFdc_ConfigTable[XPAR_XRFDC_NUM_INSTANCES] = +{ + { + XPAR_USP_RF_DATA_CONVERTER_0_DEVICE_ID, + XPAR_USP_RF_DATA_CONVERTER_0_BASEADDR, + XPAR_USP_RF_DATA_CONVERTER_0_HIGH_SPEED_ADC, + XPAR_USP_RF_DATA_CONVERTER_0_SYSREF_MASTER, + XPAR_USP_RF_DATA_CONVERTER_0_SYSREF_MASTER, + XPAR_USP_RF_DATA_CONVERTER_0_SYSREF_SOURCE, + XPAR_USP_RF_DATA_CONVERTER_0_SYSREF_SOURCE, + XPAR_USP_RF_DATA_CONVERTER_0_IP_TYPE, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC0_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC0_PLL_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC0_SAMPLING_RATE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC0_REFCLK_FREQ, + XPAR_USP_RF_DATA_CONVERTER_0_DAC0_FABRIC_FREQ, + XPAR_USP_RF_DATA_CONVERTER_0_DAC0_FBDIV, + XPAR_USP_RF_DATA_CONVERTER_0_DAC0_OUTDIV, + XPAR_USP_RF_DATA_CONVERTER_0_DAC0_REFCLK_DIV, + XPAR_USP_RF_DATA_CONVERTER_0_DAC0_BAND, + XPAR_USP_RF_DATA_CONVERTER_0_DAC0_FS_MAX, + XPAR_USP_RF_DATA_CONVERTER_0_DAC0_SLICES, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_SLICE00_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INVSINC_CTRL00, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_MODE00, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DECODER_MODE00, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_SLICE01_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INVSINC_CTRL01, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_MODE01, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DECODER_MODE01, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_SLICE02_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INVSINC_CTRL02, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_MODE02, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DECODER_MODE02, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_SLICE03_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INVSINC_CTRL03, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_MODE03, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DECODER_MODE03, + }, + }, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_TYPE00, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_WIDTH00, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INTERPOLATION_MODE00, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_FIFO00_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_ADDER00_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_TYPE00, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_TYPE01, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_WIDTH01, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INTERPOLATION_MODE01, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_FIFO01_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_ADDER01_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_TYPE01, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_TYPE02, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_WIDTH02, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INTERPOLATION_MODE02, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_FIFO02_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_ADDER02_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_TYPE02, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_TYPE03, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_WIDTH03, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INTERPOLATION_MODE03, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_FIFO03_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_ADDER03_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_TYPE03, + }, + }, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC1_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC1_PLL_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC1_SAMPLING_RATE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC1_REFCLK_FREQ, + XPAR_USP_RF_DATA_CONVERTER_0_DAC1_FABRIC_FREQ, + XPAR_USP_RF_DATA_CONVERTER_0_DAC1_FBDIV, + XPAR_USP_RF_DATA_CONVERTER_0_DAC1_OUTDIV, + XPAR_USP_RF_DATA_CONVERTER_0_DAC1_REFCLK_DIV, + XPAR_USP_RF_DATA_CONVERTER_0_DAC1_BAND, + XPAR_USP_RF_DATA_CONVERTER_0_DAC1_FS_MAX, + XPAR_USP_RF_DATA_CONVERTER_0_DAC1_SLICES, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_SLICE10_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INVSINC_CTRL10, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_MODE10, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DECODER_MODE10, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_SLICE11_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INVSINC_CTRL11, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_MODE11, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DECODER_MODE11, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_SLICE12_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INVSINC_CTRL12, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_MODE12, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DECODER_MODE12, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_SLICE13_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INVSINC_CTRL13, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_MODE13, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DECODER_MODE13, + }, + }, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_TYPE10, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_WIDTH10, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INTERPOLATION_MODE10, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_FIFO10_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_ADDER10_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_TYPE10, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_TYPE11, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_WIDTH11, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INTERPOLATION_MODE11, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_FIFO11_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_ADDER11_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_TYPE11, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_TYPE12, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_WIDTH12, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INTERPOLATION_MODE12, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_FIFO12_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_ADDER12_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_TYPE12, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_TYPE13, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_WIDTH13, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INTERPOLATION_MODE13, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_FIFO13_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_ADDER13_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_TYPE13, + }, + }, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC2_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC2_PLL_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC2_SAMPLING_RATE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC2_REFCLK_FREQ, + XPAR_USP_RF_DATA_CONVERTER_0_DAC2_FABRIC_FREQ, + XPAR_USP_RF_DATA_CONVERTER_0_DAC2_FBDIV, + XPAR_USP_RF_DATA_CONVERTER_0_DAC2_OUTDIV, + XPAR_USP_RF_DATA_CONVERTER_0_DAC2_REFCLK_DIV, + XPAR_USP_RF_DATA_CONVERTER_0_DAC2_BAND, + XPAR_USP_RF_DATA_CONVERTER_0_DAC2_FS_MAX, + XPAR_USP_RF_DATA_CONVERTER_0_DAC2_SLICES, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_SLICE20_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INVSINC_CTRL20, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_MODE20, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DECODER_MODE20, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_SLICE21_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INVSINC_CTRL21, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_MODE21, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DECODER_MODE21, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_SLICE22_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INVSINC_CTRL22, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_MODE22, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DECODER_MODE22, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_SLICE23_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INVSINC_CTRL23, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_MODE23, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DECODER_MODE23, + }, + }, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_TYPE20, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_WIDTH20, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INTERPOLATION_MODE20, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_FIFO20_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_ADDER20_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_TYPE20, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_TYPE21, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_WIDTH21, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INTERPOLATION_MODE21, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_FIFO21_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_ADDER21_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_TYPE21, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_TYPE22, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_WIDTH22, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INTERPOLATION_MODE22, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_FIFO22_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_ADDER22_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_TYPE22, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_TYPE23, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_WIDTH23, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INTERPOLATION_MODE23, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_FIFO23_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_ADDER23_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_TYPE23, + }, + }, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC3_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC3_PLL_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC3_SAMPLING_RATE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC3_REFCLK_FREQ, + XPAR_USP_RF_DATA_CONVERTER_0_DAC3_FABRIC_FREQ, + XPAR_USP_RF_DATA_CONVERTER_0_DAC3_FBDIV, + XPAR_USP_RF_DATA_CONVERTER_0_DAC3_OUTDIV, + XPAR_USP_RF_DATA_CONVERTER_0_DAC3_REFCLK_DIV, + XPAR_USP_RF_DATA_CONVERTER_0_DAC3_BAND, + XPAR_USP_RF_DATA_CONVERTER_0_DAC3_FS_MAX, + XPAR_USP_RF_DATA_CONVERTER_0_DAC3_SLICES, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_SLICE30_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INVSINC_CTRL30, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_MODE30, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DECODER_MODE30, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_SLICE31_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INVSINC_CTRL31, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_MODE31, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DECODER_MODE31, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_SLICE32_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INVSINC_CTRL32, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_MODE32, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DECODER_MODE32, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_SLICE33_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INVSINC_CTRL33, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_MODE33, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DECODER_MODE33, + }, + }, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_TYPE30, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_WIDTH30, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INTERPOLATION_MODE30, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_FIFO30_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_ADDER30_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_TYPE30, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_TYPE31, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_WIDTH31, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INTERPOLATION_MODE31, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_FIFO31_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_ADDER31_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_TYPE31, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_TYPE32, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_WIDTH32, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INTERPOLATION_MODE32, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_FIFO32_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_ADDER32_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_TYPE32, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_TYPE33, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_DATA_WIDTH33, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_INTERPOLATION_MODE33, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_FIFO33_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_ADDER33_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_DAC_MIXER_TYPE33, + }, + }, + }, + }, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC0_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC0_PLL_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC0_SAMPLING_RATE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC0_REFCLK_FREQ, + XPAR_USP_RF_DATA_CONVERTER_0_ADC0_FABRIC_FREQ, + XPAR_USP_RF_DATA_CONVERTER_0_ADC0_FBDIV, + XPAR_USP_RF_DATA_CONVERTER_0_ADC0_OUTDIV, + XPAR_USP_RF_DATA_CONVERTER_0_ADC0_REFCLK_DIV, + XPAR_USP_RF_DATA_CONVERTER_0_ADC0_BAND, + XPAR_USP_RF_DATA_CONVERTER_0_ADC0_FS_MAX, + XPAR_USP_RF_DATA_CONVERTER_0_ADC0_SLICES, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_SLICE00_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_MODE00, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_SLICE01_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_MODE01, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_SLICE02_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_MODE02, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_SLICE03_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_MODE03, + }, + }, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_TYPE00, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_WIDTH00, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DECIMATION_MODE00, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_FIFO00_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_TYPE00, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_TYPE01, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_WIDTH01, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DECIMATION_MODE01, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_FIFO01_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_TYPE01, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_TYPE02, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_WIDTH02, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DECIMATION_MODE02, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_FIFO02_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_TYPE02, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_TYPE03, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_WIDTH03, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DECIMATION_MODE03, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_FIFO03_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_TYPE03, + }, + }, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC1_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC1_PLL_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC1_SAMPLING_RATE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC1_REFCLK_FREQ, + XPAR_USP_RF_DATA_CONVERTER_0_ADC1_FABRIC_FREQ, + XPAR_USP_RF_DATA_CONVERTER_0_ADC1_FBDIV, + XPAR_USP_RF_DATA_CONVERTER_0_ADC1_OUTDIV, + XPAR_USP_RF_DATA_CONVERTER_0_ADC1_REFCLK_DIV, + XPAR_USP_RF_DATA_CONVERTER_0_ADC1_BAND, + XPAR_USP_RF_DATA_CONVERTER_0_ADC1_FS_MAX, + XPAR_USP_RF_DATA_CONVERTER_0_ADC1_SLICES, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_SLICE10_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_MODE10, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_SLICE11_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_MODE11, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_SLICE12_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_MODE12, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_SLICE13_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_MODE13, + }, + }, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_TYPE10, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_WIDTH10, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DECIMATION_MODE10, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_FIFO10_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_TYPE10, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_TYPE11, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_WIDTH11, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DECIMATION_MODE11, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_FIFO11_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_TYPE11, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_TYPE12, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_WIDTH12, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DECIMATION_MODE12, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_FIFO12_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_TYPE12, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_TYPE13, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_WIDTH13, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DECIMATION_MODE13, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_FIFO13_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_TYPE13, + }, + }, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC2_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC2_PLL_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC2_SAMPLING_RATE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC2_REFCLK_FREQ, + XPAR_USP_RF_DATA_CONVERTER_0_ADC2_FABRIC_FREQ, + XPAR_USP_RF_DATA_CONVERTER_0_ADC2_FBDIV, + XPAR_USP_RF_DATA_CONVERTER_0_ADC2_OUTDIV, + XPAR_USP_RF_DATA_CONVERTER_0_ADC2_REFCLK_DIV, + XPAR_USP_RF_DATA_CONVERTER_0_ADC2_BAND, + XPAR_USP_RF_DATA_CONVERTER_0_ADC2_FS_MAX, + XPAR_USP_RF_DATA_CONVERTER_0_ADC2_SLICES, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_SLICE20_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_MODE20, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_SLICE21_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_MODE21, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_SLICE22_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_MODE22, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_SLICE23_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_MODE23, + }, + }, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_TYPE20, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_WIDTH20, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DECIMATION_MODE20, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_FIFO20_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_TYPE20, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_TYPE21, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_WIDTH21, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DECIMATION_MODE21, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_FIFO21_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_TYPE21, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_TYPE22, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_WIDTH22, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DECIMATION_MODE22, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_FIFO22_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_TYPE22, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_TYPE23, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_WIDTH23, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DECIMATION_MODE23, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_FIFO23_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_TYPE23, + }, + }, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC3_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC3_PLL_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC3_SAMPLING_RATE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC3_REFCLK_FREQ, + XPAR_USP_RF_DATA_CONVERTER_0_ADC3_FABRIC_FREQ, + XPAR_USP_RF_DATA_CONVERTER_0_ADC3_FBDIV, + XPAR_USP_RF_DATA_CONVERTER_0_ADC3_OUTDIV, + XPAR_USP_RF_DATA_CONVERTER_0_ADC3_REFCLK_DIV, + XPAR_USP_RF_DATA_CONVERTER_0_ADC3_BAND, + XPAR_USP_RF_DATA_CONVERTER_0_ADC3_FS_MAX, + XPAR_USP_RF_DATA_CONVERTER_0_ADC3_SLICES, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_SLICE30_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_MODE30, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_SLICE31_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_MODE31, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_SLICE32_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_MODE32, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_SLICE33_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_MODE33, + }, + }, + { + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_TYPE30, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_WIDTH30, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DECIMATION_MODE30, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_FIFO30_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_TYPE30, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_TYPE31, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_WIDTH31, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DECIMATION_MODE31, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_FIFO31_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_TYPE31, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_TYPE32, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_WIDTH32, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DECIMATION_MODE32, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_FIFO32_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_TYPE32, + }, + { + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_TYPE33, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DATA_WIDTH33, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_DECIMATION_MODE33, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_FIFO33_ENABLE, + XPAR_USP_RF_DATA_CONVERTER_0_ADC_MIXER_TYPE33, + }, + }, + }, + } + } +}; +#endif +/** @} */ diff --git a/mpm/lib/rfdc/xrfdc_intr.c b/mpm/lib/rfdc/xrfdc_intr.c new file mode 100644 index 000000000..7b8bd0927 --- /dev/null +++ b/mpm/lib/rfdc/xrfdc_intr.c @@ -0,0 +1,771 @@ +/****************************************************************************** +* +* Copyright (C) 2017-2019 Xilinx, Inc. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* XILINX BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Except as contained in this notice, the name of the Xilinx shall not be used +* in advertising or otherwise to promote the sale, use or other dealings in +* this Software without prior written authorization from Xilinx. +* +******************************************************************************/ +/*****************************************************************************/ +/** +* +* @file xrfdc_intr.c +* @addtogroup rfdc_v6_0 +* @{ +* +* This file contains functions related to RFdc interrupt handling. +* +* <pre> +* MODIFICATION HISTORY: +* +* Ver Who Date Changes +* ----- ----- -------- ----------------------------------------------- +* 1.0 sk 05/16/17 First release +* 2.1 sk 09/15/17 Remove Libmetal library dependency for MB. +* 09/18/17 Add API to clear the interrupts. +* sk 09/21/17 Add support for Over voltage and Over +* Range interrupts. +* 2.2 sk 10/18/17 Add support for FIFO and DATA overflow interrupt +* 5.0 sk 08/24/18 Reorganize the code to improve readability and +* optimization. +* 5.1 cog 01/29/19 Replace structure reference ADC checks with +* function. +* 6.0 cog 02/20/19 Added handling for new ADC common mode over/under +* voltage interrupts. +* cog 02/20/19 XRFdc_GetIntrStatus now populates a pointer with the +* status and returns an error code. +* cog 02/20/19 XRFdc_IntrClr, XRFdc_IntrDisable and XRFdc_IntrEnable +* now return error codes. +* cog 03/25/19 The new common mode over/under voltage interrupts mask +* bits were clashing with other interrupt bits. +* </pre> +* +******************************************************************************/ + +/***************************** Include Files *********************************/ + +#include "mpm/rfdc/xrfdc.h" + +/************************** Constant Definitions *****************************/ + +/**************************** Type Definitions *******************************/ + +/***************** Macros (Inline Functions) Definitions *********************/ + +/************************** Variable Definitions *****************************/ + +/************************** Function Prototypes ******************************/ + +/************************** Variable Definitions ****************************/ + +/****************************************************************************/ +/** +* +* This function sets the interrupt mask. +* +* @param InstancePtr is a pointer to the XRFdc instance +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3, and -1. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param IntrMask contains the interrupts to be enabled. +* '1' enables an interrupt, and '0' disables. +* +* @return - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not available. +* +* @note None. +* +*****************************************************************************/ +u32 XRFdc_IntrEnable(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 IntrMask) +{ + u32 BaseAddr; + u32 ReadReg; + u32 Index; + u32 NoOfBlocks; + u32 Status; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, Type, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + Index = Block_Id; + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE)) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + for (; Index < NoOfBlocks; Index++) { + ReadReg = XRFdc_ReadReg16(InstancePtr, 0x0, + XRFDC_COMMON_INTR_ENABLE); + if (Type == XRFDC_ADC_TILE) { + ReadReg |= (1U << (Tile_Id + 4)); + XRFdc_WriteReg16(InstancePtr, 0x0, + XRFDC_COMMON_INTR_ENABLE, ReadReg); + BaseAddr = XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_INTR_ENABLE, + (1U << Index), (1U << Index)); + /* Enable Converter interrupts */ + ReadReg = XRFdc_ReadReg(InstancePtr, BaseAddr, + XRFDC_CONV_INTR_EN(Index)); + if ((IntrMask & XRFDC_ADC_OVR_VOLTAGE_MASK) != 0U) { + ReadReg |= (XRFDC_ADC_OVR_VOLTAGE_MASK >> + XRFDC_ADC_OVR_VOL_RANGE_SHIFT); + } + if ((IntrMask & XRFDC_ADC_OVR_RANGE_MASK) != 0U) { + ReadReg |= (XRFDC_ADC_OVR_RANGE_MASK >> + XRFDC_ADC_OVR_VOL_RANGE_SHIFT); + } + if ((IntrMask & XRFDC_ADC_FIFO_OVR_MASK) != 0U) { + ReadReg |= + (XRFDC_ADC_FIFO_OVR_MASK >> XRFDC_ADC_DAT_FIFO_OVR_SHIFT); + } + if ((IntrMask & XRFDC_ADC_DAT_OVR_MASK) != 0U) { + ReadReg |= + (XRFDC_ADC_DAT_OVR_MASK >> XRFDC_ADC_DAT_FIFO_OVR_SHIFT); + } + if ((IntrMask & XRFDC_ADC_CMODE_OVR_MASK) != 0U) { + ReadReg |= + (XRFDC_ADC_CMODE_OVR_MASK >> XRFDC_ADC_CMODE_SHIFT); + } + if ((IntrMask & XRFDC_ADC_CMODE_UNDR_MASK) != 0U) { + ReadReg |= + (XRFDC_ADC_CMODE_UNDR_MASK >> XRFDC_ADC_CMODE_SHIFT); + } + + XRFdc_WriteReg(InstancePtr, BaseAddr, + XRFDC_CONV_INTR_EN(Index), ReadReg); + + BaseAddr = XRFDC_ADC_TILE_DRP_ADDR(Tile_Id) + + XRFDC_BLOCK_ADDR_OFFSET(Index); + + /* Check for FIFO interface interrupts */ + if ((IntrMask & XRFDC_IXR_FIFOUSRDAT_MASK) != 0U) { + ReadReg = (IntrMask & XRFDC_IXR_FIFOUSRDAT_MASK); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_FABRIC_IMR_OFFSET, + XRFDC_IXR_FIFOUSRDAT_MASK, ReadReg); + } + /* Check for SUBADC interrupts */ + if ((IntrMask & XRFDC_SUBADC_IXR_DCDR_MASK) != 0U) { + ReadReg = (IntrMask & XRFDC_SUBADC_IXR_DCDR_MASK) >> + XRFDC_ADC_SUBADC_DCDR_SHIFT; + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_DEC_IMR_OFFSET, + XRFDC_DEC_IMR_MASK, ReadReg); + } + /* Check for DataPath interrupts */ + if ((IntrMask & XRFDC_ADC_IXR_DATAPATH_MASK) != 0U) { + ReadReg = (IntrMask & XRFDC_ADC_IXR_DATAPATH_MASK) >> + XRFDC_DATA_PATH_SHIFT; + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_DATPATH_IMR_OFFSET, + XRFDC_ADC_DAT_IMR_MASK, ReadReg); + } + } else { + ReadReg |= (1U << Tile_Id); + XRFdc_WriteReg16(InstancePtr, 0x0, + XRFDC_COMMON_INTR_ENABLE, ReadReg); + BaseAddr = XRFDC_DAC_TILE_CTRL_STATS_ADDR(Tile_Id); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, + XRFDC_INTR_ENABLE, (1U << Index), (1U << Index)); + BaseAddr = XRFDC_DAC_TILE_DRP_ADDR(Tile_Id) + + XRFDC_BLOCK_ADDR_OFFSET(Index); + /* Check for FIFO interface interrupts */ + if ((IntrMask & XRFDC_IXR_FIFOUSRDAT_MASK) != 0U) { + ReadReg = (IntrMask & XRFDC_IXR_FIFOUSRDAT_MASK); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_DAC_FABRIC_IMR_OFFSET, + XRFDC_IXR_FIFOUSRDAT_MASK, ReadReg); + } + + if ((IntrMask & XRFDC_DAC_IXR_DATAPATH_MASK) != 0U) { + ReadReg = (IntrMask & XRFDC_DAC_IXR_DATAPATH_MASK) >> + XRFDC_DATA_PATH_SHIFT; + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_DATPATH_IMR_OFFSET, + XRFDC_DAC_DAT_IMR_MASK, ReadReg); + } + } + } + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/****************************************************************************/ +/** +* +* This function clears the interrupt mask. +* +* @param InstancePtr is a pointer to the XRFdc instance +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3, and -1. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param IntrMask contains the interrupts to be disabled. +* '1' disables an interrupt, and '0' remains no change. +* +* @return - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not available. +* +* @note None. +* +*****************************************************************************/ +u32 XRFdc_IntrDisable(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 IntrMask) +{ + u32 BaseAddr; + u32 ReadReg; + u32 Status; + u32 Index; + u32 NoOfBlocks; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckBlockEnabled(InstancePtr, Type, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + Index = Block_Id; + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE)) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + for (; Index < NoOfBlocks; Index++) { + if (Type == XRFDC_ADC_TILE) { + BaseAddr = XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id); + /* Check for Over Voltage and Over Range */ + ReadReg = XRFdc_ReadReg(InstancePtr, BaseAddr, + XRFDC_CONV_INTR_EN(Index)); + if ((IntrMask & XRFDC_ADC_OVR_VOLTAGE_MASK) != 0U) { + ReadReg &= ~(XRFDC_ADC_OVR_VOLTAGE_MASK >> + XRFDC_ADC_OVR_VOL_RANGE_SHIFT); + } + if ((IntrMask & XRFDC_ADC_OVR_RANGE_MASK) != 0U) { + ReadReg &= ~(XRFDC_ADC_OVR_RANGE_MASK >> + XRFDC_ADC_OVR_VOL_RANGE_SHIFT); + } + /* Disable Converter interrupts */ + if ((IntrMask & XRFDC_ADC_FIFO_OVR_MASK) != 0U) { + ReadReg &= + ~(XRFDC_ADC_FIFO_OVR_MASK >> XRFDC_ADC_DAT_FIFO_OVR_SHIFT); + } + if ((IntrMask & XRFDC_ADC_DAT_OVR_MASK) != 0U) { + ReadReg &= + ~(XRFDC_ADC_DAT_OVR_MASK >> XRFDC_ADC_DAT_FIFO_OVR_SHIFT); + } + if ((IntrMask & XRFDC_ADC_CMODE_OVR_MASK) != 0U) { + ReadReg &= + ~(XRFDC_ADC_CMODE_OVR_MASK >> XRFDC_ADC_CMODE_SHIFT); + } + if ((IntrMask & XRFDC_ADC_CMODE_UNDR_MASK) != 0U) { + ReadReg &= + ~(XRFDC_ADC_CMODE_UNDR_MASK >> XRFDC_ADC_CMODE_SHIFT); + } + + XRFdc_WriteReg(InstancePtr, BaseAddr, + XRFDC_CONV_INTR_EN(Index), ReadReg); + BaseAddr = XRFDC_ADC_TILE_DRP_ADDR(Tile_Id) + + XRFDC_BLOCK_ADDR_OFFSET(Index); + /* Check for FIFO interface interrupts */ + if ((IntrMask & XRFDC_IXR_FIFOUSRDAT_MASK) != 0U) { + ReadReg = IntrMask & XRFDC_IXR_FIFOUSRDAT_MASK; + XRFdc_ClrReg(InstancePtr, BaseAddr, XRFDC_ADC_FABRIC_IMR_OFFSET, + ReadReg); + } + /* Check for SUBADC interrupts */ + if ((IntrMask & XRFDC_SUBADC_IXR_DCDR_MASK) != 0U) { + ReadReg = ((IntrMask & XRFDC_SUBADC_IXR_DCDR_MASK) >> + XRFDC_ADC_SUBADC_DCDR_SHIFT); + XRFdc_ClrReg(InstancePtr, BaseAddr, XRFDC_ADC_DEC_IMR_OFFSET, + ReadReg); + } + /* Check for DataPath interrupts */ + if ((IntrMask & XRFDC_ADC_IXR_DATAPATH_MASK) != 0U) { + ReadReg = ((IntrMask & + XRFDC_ADC_IXR_DATAPATH_MASK) >> XRFDC_DATA_PATH_SHIFT); + XRFdc_ClrReg(InstancePtr, BaseAddr, XRFDC_DATPATH_IMR_OFFSET, + ReadReg); + } + } else { + BaseAddr = XRFDC_DAC_TILE_DRP_ADDR(Tile_Id) + + XRFDC_BLOCK_ADDR_OFFSET(Index); + /* Check for FIFO interface interrupts */ + if ((IntrMask & XRFDC_IXR_FIFOUSRDAT_MASK) != 0U) { + ReadReg = (IntrMask & XRFDC_IXR_FIFOUSRDAT_MASK); + XRFdc_ClrReg(InstancePtr, BaseAddr, XRFDC_DAC_FABRIC_IMR_OFFSET, + ReadReg); + } + /* Check for FIFO DataPath interrupts */ + if ((IntrMask & XRFDC_DAC_IXR_DATAPATH_MASK) != 0U) { + ReadReg = ((IntrMask & + XRFDC_DAC_IXR_DATAPATH_MASK) >> XRFDC_DATA_PATH_SHIFT); + XRFdc_ClrReg(InstancePtr, BaseAddr, XRFDC_DATPATH_IMR_OFFSET, + ReadReg); + } + } + } + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/****************************************************************************/ +/** +* +* This function returns the interrupt status read from Interrupt Status +* Register(ISR). +* +* @param InstancePtr is a pointer to the XRFdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3, and -1. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param IntrStsPtr is pointer to a32-bit value representing the contents of +* the Interrupt Status Registers (FIFO interface, Decoder interface, +* Data Path Interface). +* +* @return - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not available. +* +* @note None. +* +*****************************************************************************/ +u32 XRFdc_GetIntrStatus(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 *IntrStsPtr) +{ + u32 BaseAddr; + u32 ReadReg; + u32 Status; + u32 Block; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(IntrStsPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Block = Block_Id; + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE)) { + if ((Block_Id == XRFDC_BLK_ID2) || (Block_Id == XRFDC_BLK_ID3)) { + Block = XRFDC_BLK_ID1; + } + if (Block_Id == XRFDC_BLK_ID1) { + Block = XRFDC_BLK_ID0; + } + } + Status = XRFdc_CheckBlockEnabled(InstancePtr, Type, Tile_Id, Block); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + *IntrStsPtr = 0; + + if (Type == XRFDC_ADC_TILE) { + BaseAddr = XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id); + /* Check for Over Voltage and Over Range */ + ReadReg = XRFdc_ReadReg(InstancePtr, BaseAddr, + XRFDC_CONV_INTR_STS(Block_Id)); + *IntrStsPtr |= ((ReadReg & XRFDC_INTR_OVR_VOLTAGE_MASK) << + XRFDC_ADC_OVR_VOL_RANGE_SHIFT); + *IntrStsPtr |= ((ReadReg & XRFDC_INTR_OVR_RANGE_MASK) << + XRFDC_ADC_OVR_VOL_RANGE_SHIFT); + *IntrStsPtr |= ((ReadReg & XRFDC_INTR_FIFO_OVR_MASK) << + XRFDC_ADC_DAT_FIFO_OVR_SHIFT); + *IntrStsPtr |= ((ReadReg & XRFDC_INTR_DAT_OVR_MASK) << + XRFDC_ADC_DAT_FIFO_OVR_SHIFT); + + /* Check for Common Mode Over/Under Voltage */ + *IntrStsPtr |= ((ReadReg & XRFDC_INTR_CMODE_OVR_MASK) << + XRFDC_ADC_CMODE_SHIFT); + *IntrStsPtr |= ((ReadReg & XRFDC_INTR_CMODE_UNDR_MASK) << + XRFDC_ADC_CMODE_SHIFT); + + BaseAddr = XRFDC_ADC_TILE_DRP_ADDR(Tile_Id) + + XRFDC_BLOCK_ADDR_OFFSET(Block_Id); + /* Check for FIFO interface interrupts */ + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_FABRIC_ISR_OFFSET); + *IntrStsPtr |= (ReadReg & XRFDC_IXR_FIFOUSRDAT_MASK); + /* Check for SUBADC interrupts */ + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_DEC_ISR_OFFSET); + *IntrStsPtr |= ((ReadReg & XRFDC_DEC_ISR_SUBADC_MASK) << + XRFDC_ADC_SUBADC_DCDR_SHIFT); + /* Check for DataPath interrupts */ + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_DATPATH_ISR_OFFSET); + *IntrStsPtr |= ((ReadReg & XRFDC_ADC_DAT_PATH_ISR_MASK) << + XRFDC_DATA_PATH_SHIFT); + } else { + BaseAddr = XRFDC_DAC_TILE_DRP_ADDR(Tile_Id) + + XRFDC_BLOCK_ADDR_OFFSET(Block_Id); + /* Check for FIFO interface interrupts */ + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_DAC_FABRIC_ISR_OFFSET); + *IntrStsPtr |= (ReadReg & XRFDC_IXR_FIFOUSRDAT_MASK); + /* Check for DataPath interrupts */ + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_DATPATH_ISR_OFFSET); + *IntrStsPtr |= ((ReadReg & XRFDC_DAC_DAT_PATH_ISR_MASK) << + XRFDC_DATA_PATH_SHIFT); + } + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/****************************************************************************/ +/** +* +* This function clear the interrupts. +* +* @param InstancePtr is a pointer to the XRFdc instance +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3, and -1. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param IntrMask contains the interrupts to be cleared. +* +* @return - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not available. +* +* @note None. +* +*****************************************************************************/ +u32 XRFdc_IntrClr(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, u32 IntrMask) +{ + u32 BaseAddr; + u32 Status; + u32 Block; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Block = Block_Id; + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE)) { + if ((Block_Id == XRFDC_BLK_ID2) || (Block_Id == XRFDC_BLK_ID3)) { + Block = XRFDC_BLK_ID1; + } + if (Block_Id == XRFDC_BLK_ID1) { + Block = XRFDC_BLK_ID0; + } + } + Status = XRFdc_CheckBlockEnabled(InstancePtr, Type, Tile_Id, Block); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + + if (Type == XRFDC_ADC_TILE) { + /* ADC */ + BaseAddr = XRFDC_ADC_TILE_CTRL_STATS_ADDR(Tile_Id); + /* Check for Converter interrupts */ + if ((IntrMask & XRFDC_ADC_OVR_VOLTAGE_MASK) != 0U) { + XRFdc_WriteReg(InstancePtr, BaseAddr, + XRFDC_CONV_INTR_STS(Block_Id), (IntrMask & + XRFDC_ADC_OVR_VOLTAGE_MASK) >> XRFDC_ADC_OVR_VOL_RANGE_SHIFT); + } + if ((IntrMask & XRFDC_ADC_OVR_RANGE_MASK) != 0U) { + XRFdc_WriteReg(InstancePtr, BaseAddr, + XRFDC_CONV_INTR_STS(Block_Id), (IntrMask & + XRFDC_ADC_OVR_RANGE_MASK) >> XRFDC_ADC_OVR_VOL_RANGE_SHIFT); + } + if ((IntrMask & XRFDC_ADC_FIFO_OVR_MASK) != 0U) { + XRFdc_WriteReg(InstancePtr, BaseAddr, + XRFDC_CONV_INTR_STS(Block_Id), (IntrMask & + XRFDC_ADC_FIFO_OVR_MASK) >> XRFDC_ADC_DAT_FIFO_OVR_SHIFT); + } + if ((IntrMask & XRFDC_ADC_DAT_OVR_MASK) != 0U) { + XRFdc_WriteReg(InstancePtr, BaseAddr, + XRFDC_CONV_INTR_STS(Block_Id), (IntrMask & + XRFDC_ADC_DAT_OVR_MASK) >> XRFDC_ADC_DAT_FIFO_OVR_SHIFT); + } + if ((IntrMask & XRFDC_ADC_CMODE_OVR_MASK) != 0U) { + XRFdc_WriteReg(InstancePtr, BaseAddr, + XRFDC_CONV_INTR_STS(Block_Id), (IntrMask & + XRFDC_ADC_CMODE_OVR_MASK) >> XRFDC_ADC_CMODE_SHIFT); + } + if ((IntrMask & XRFDC_ADC_CMODE_UNDR_MASK) != 0U) { + XRFdc_WriteReg(InstancePtr, BaseAddr, + XRFDC_CONV_INTR_STS(Block_Id), (IntrMask & + XRFDC_ADC_CMODE_UNDR_MASK) >> XRFDC_ADC_CMODE_SHIFT); + } + BaseAddr = XRFDC_ADC_TILE_DRP_ADDR(Tile_Id) + + XRFDC_BLOCK_ADDR_OFFSET(Block_Id); + /* Check for FIFO interface interrupts */ + if ((IntrMask & XRFDC_IXR_FIFOUSRDAT_MASK) != 0U) { + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_FABRIC_ISR_OFFSET, + (IntrMask & XRFDC_IXR_FIFOUSRDAT_MASK)); + } + /* Check for SUBADC interrupts */ + if ((IntrMask & XRFDC_SUBADC_IXR_DCDR_MASK) != 0U) { + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_DEC_ISR_OFFSET, (u16)((IntrMask & + XRFDC_SUBADC_IXR_DCDR_MASK) >> XRFDC_ADC_SUBADC_DCDR_SHIFT)); + } + /* Check for DataPath interrupts */ + if ((IntrMask & XRFDC_ADC_IXR_DATAPATH_MASK) != 0U) { + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_DATPATH_ISR_OFFSET, (u16)(IntrMask & + XRFDC_ADC_IXR_DATAPATH_MASK) >> XRFDC_DATA_PATH_SHIFT); + } + } else { + /* DAC */ + BaseAddr = XRFDC_DAC_TILE_DRP_ADDR(Tile_Id) + + XRFDC_BLOCK_ADDR_OFFSET(Block_Id); + /* Check for FIFO interface interrupts */ + if ((IntrMask & XRFDC_IXR_FIFOUSRDAT_MASK) != 0U) { + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_DAC_FABRIC_ISR_OFFSET, + (u16)(IntrMask & XRFDC_IXR_FIFOUSRDAT_MASK)); + } + /* Check for DataPath interrupts */ + if ((IntrMask & XRFDC_DAC_IXR_DATAPATH_MASK) != 0U) { + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_DATPATH_ISR_OFFSET, (u16)(IntrMask & + XRFDC_DAC_IXR_DATAPATH_MASK) >> XRFDC_DATA_PATH_SHIFT); + } + } + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} + +/****************************************************************************/ +/** +* +* This function is the interrupt handler for the driver. +* It must be connected to an interrupt system by the application such that it +* can be called when an interrupt occurs. +* +* @param Vector is interrupt vector number. Libmetal status handler +* expects two parameters in the handler prototype, hence +* kept this parameter. This is not used inside +* the interrupt handler API. +* @param XRFdcPtr contains a pointer to the driver instance +* +* @return None. +* +* @note Vector param is not useful inside the interrupt handler, hence +* typecast with void to remove compilation warning. +* +******************************************************************************/ +u32 XRFdc_IntrHandler(u32 Vector, void *XRFdcPtr) +{ + XRFdc *InstancePtr = (XRFdc *)XRFdcPtr; + u32 Intrsts = 0x0U; + u32 Tile_Id = XRFDC_TILE_ID4; + u32 Block_Id = XRFDC_BLK_ID4; + u32 ReadReg; + u16 Type = 0U; + u32 BaseAddr; + u32 IntrMask = 0x0U; + u32 Block; + + Xil_AssertNonvoid(InstancePtr != NULL); + + (void)Vector; + /* + * Read the interrupt ID register to determine which + * interrupt is active + */ + ReadReg = XRFdc_ReadReg16(InstancePtr, 0x0, + XRFDC_COMMON_INTR_STS); + if ((ReadReg & XRFDC_EN_INTR_DAC_TILE0_MASK) != 0U) { + Type = XRFDC_DAC_TILE; + Tile_Id = XRFDC_TILE_ID0; + } else if ((ReadReg & XRFDC_EN_INTR_DAC_TILE1_MASK) != 0U) { + Type = XRFDC_DAC_TILE; + Tile_Id = XRFDC_TILE_ID1; + } else if ((ReadReg & XRFDC_EN_INTR_DAC_TILE2_MASK) != 0U) { + Type = XRFDC_DAC_TILE; + Tile_Id = XRFDC_TILE_ID2; + } else if ((ReadReg & XRFDC_EN_INTR_DAC_TILE3_MASK) != 0U) { + Type = XRFDC_DAC_TILE; + Tile_Id = XRFDC_TILE_ID3; + } else if ((ReadReg & XRFDC_EN_INTR_ADC_TILE0_MASK) != 0U) { + Type = XRFDC_ADC_TILE; + Tile_Id = XRFDC_TILE_ID0; + } else if ((ReadReg & XRFDC_EN_INTR_ADC_TILE1_MASK) != 0U) { + Type = XRFDC_ADC_TILE; + Tile_Id = XRFDC_TILE_ID1; + } else if ((ReadReg & XRFDC_EN_INTR_ADC_TILE2_MASK) != 0U) { + Type = XRFDC_ADC_TILE; + Tile_Id = XRFDC_TILE_ID2; + } else if ((ReadReg & XRFDC_EN_INTR_ADC_TILE3_MASK) != 0U) { + Type = XRFDC_ADC_TILE; + Tile_Id = XRFDC_TILE_ID3; + } else { + metal_log(METAL_LOG_DEBUG, "\n Invalid Tile_Id \r\n"); + } + + BaseAddr = XRFDC_CTRL_STS_BASE(Type, Tile_Id); + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, XRFDC_INTR_STS); + if ((ReadReg & XRFDC_EN_INTR_SLICE0_MASK) != 0U) { + Block_Id = XRFDC_BLK_ID0; + } else if ((ReadReg & XRFDC_EN_INTR_SLICE1_MASK) != 0U) { + Block_Id = XRFDC_BLK_ID1; + } else if ((ReadReg & XRFDC_EN_INTR_SLICE2_MASK) != 0U) { + Block_Id = XRFDC_BLK_ID2; + } else if ((ReadReg & XRFDC_EN_INTR_SLICE3_MASK) != 0U) { + Block_Id = XRFDC_BLK_ID3; + } else { + metal_log(METAL_LOG_DEBUG, "\n Invalid ADC Block_Id \r\n"); + } + (void)XRFdc_GetIntrStatus(InstancePtr, Type, Tile_Id, Block_Id, &Intrsts); + if (Type == XRFDC_ADC_TILE) { + /* ADC */ + if ((Intrsts & XRFDC_ADC_OVR_VOLTAGE_MASK) != 0U) { + IntrMask |= Intrsts & XRFDC_ADC_OVR_VOLTAGE_MASK; + metal_log(METAL_LOG_DEBUG, "\n ADC Over Voltage interrupt \r\n"); + } + if ((Intrsts & XRFDC_ADC_OVR_RANGE_MASK) != 0U) { + IntrMask |= Intrsts & XRFDC_ADC_OVR_RANGE_MASK; + metal_log(METAL_LOG_DEBUG, "\n ADC Over Range interrupt \r\n"); + } + if ((Intrsts & XRFDC_ADC_FIFO_OVR_MASK) != 0U) { + IntrMask |= Intrsts & XRFDC_ADC_FIFO_OVR_MASK; + metal_log(METAL_LOG_DEBUG, "\n ADC FIFO OF interrupt \r\n"); + } + if ((Intrsts & XRFDC_ADC_DAT_OVR_MASK) != 0U) { + IntrMask |= Intrsts & XRFDC_ADC_DAT_OVR_MASK; + metal_log(METAL_LOG_DEBUG, "\n ADC DATA OF interrupt \r\n"); + } + if ((Intrsts & XRFDC_ADC_CMODE_OVR_MASK) != 0U) { + IntrMask |= XRFDC_ADC_CMODE_OVR_MASK; + metal_log(METAL_LOG_DEBUG, "\n ADC CMODE OV interrupt \r\n"); + } + if ((Intrsts & XRFDC_ADC_CMODE_UNDR_MASK) != 0U) { + IntrMask |= XRFDC_ADC_CMODE_UNDR_MASK; + metal_log(METAL_LOG_DEBUG, "\n ADC CMODE UV interrupt \r\n"); + } + if ((Intrsts & XRFDC_IXR_FIFOUSRDAT_MASK) != 0U) { + IntrMask |= Intrsts & XRFDC_IXR_FIFOUSRDAT_MASK; + metal_log(METAL_LOG_DEBUG, "\n ADC FIFO interface interrupt \r\n"); + } + if ((Intrsts & XRFDC_SUBADC_IXR_DCDR_MASK) != 0U) { + IntrMask |= Intrsts & XRFDC_SUBADC_IXR_DCDR_MASK; + metal_log(METAL_LOG_DEBUG, + "\n ADC Decoder interface interrupt \r\n"); + } + if ((Intrsts & XRFDC_ADC_IXR_DATAPATH_MASK) != 0U) { + IntrMask |= Intrsts & XRFDC_ADC_IXR_DATAPATH_MASK; + metal_log(METAL_LOG_DEBUG, + "\n ADC Data Path interface interrupt \r\n"); + } + } else { + /* DAC */ + if ((Intrsts & XRFDC_IXR_FIFOUSRDAT_MASK) != 0U) { + IntrMask |= Intrsts & XRFDC_IXR_FIFOUSRDAT_MASK; + metal_log(METAL_LOG_DEBUG, "\n DAC FIFO interface interrupt \r\n"); + } + if ((Intrsts & XRFDC_DAC_IXR_DATAPATH_MASK) != 0U) { + IntrMask |= Intrsts & XRFDC_DAC_IXR_DATAPATH_MASK; + metal_log(METAL_LOG_DEBUG, "\n DAC Data Path interface interrupt \r\n"); + } + } + Block = Block_Id; + + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE)) { + if ((Block_Id == XRFDC_BLK_ID0) || (Block_Id == XRFDC_BLK_ID1)) { + Block = XRFDC_BLK_ID0; + } else { + Block = XRFDC_BLK_ID1; + } + } + InstancePtr->StatusHandler(InstancePtr->CallBackRef, Type, Tile_Id, + Block, IntrMask); + + /* Clear the interrupt */ + if (Type == XRFDC_ADC_TILE) { + /* ADC */ + XRFdc_IntrClr(InstancePtr, XRFDC_ADC_TILE, Tile_Id, Block_Id, Intrsts); + + } else { + /* DAC */ + XRFdc_IntrClr(InstancePtr, XRFDC_DAC_TILE, Tile_Id, Block_Id, Intrsts); + } + + return (u32)METAL_IRQ_HANDLED; +} + +/*****************************************************************************/ +/** +* +* This function sets the status callback function, the status handler, which the +* driver calls when it encounters conditions that should be reported to the +* higher layer software. The handler executes in an interrupt context, so +* the amount of processing should be minimized +* +* +* @param InstancePtr is a pointer to the XRFdc instance. +* @param CallBackRef is the upper layer callback reference passed back +* when the callback function is invoked. +* @param FunctionPtr is the pointer to the callback function. +* +* @return None. +* +* @note +* +* The handler is called within interrupt context, so it should finish its +* work quickly. +* +******************************************************************************/ +void XRFdc_SetStatusHandler(XRFdc *InstancePtr, void *CallBackRef, + XRFdc_StatusHandler FunctionPtr) +{ + Xil_AssertVoid(InstancePtr != NULL); + Xil_AssertVoid(FunctionPtr != NULL); + Xil_AssertVoid(InstancePtr->IsReady == (u32)XRFDC_COMPONENT_IS_READY); + + InstancePtr->StatusHandler = FunctionPtr; + InstancePtr->CallBackRef = CallBackRef; +} + +/** @} */ diff --git a/mpm/lib/rfdc/xrfdc_mb.c b/mpm/lib/rfdc/xrfdc_mb.c new file mode 100644 index 000000000..c7b563d40 --- /dev/null +++ b/mpm/lib/rfdc/xrfdc_mb.c @@ -0,0 +1,786 @@ +/****************************************************************************** +* +* Copyright (C) 2019 Xilinx, Inc. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* XILINX BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Except as contained in this notice, the name of the Xilinx shall not be used +* in advertising or otherwise to promote the sale, use or other dealings in +* this Software without prior written authorization from Xilinx. +* +******************************************************************************/ +/*****************************************************************************/ +/** +* +* @file xrfdc_mb.c +* @addtogroup xrfdc_v6_0 +* @{ +* +* Contains the interface functions of the Mixer Settings in XRFdc driver. +* See xrfdc.h for a detailed description of the device and driver. +* +* <pre> +* MODIFICATION HISTORY: +* +* Ver Who Date Changes +* ----- --- -------- ----------------------------------------------- +* 6.0 cog 02/17/18 Initial release/handle alternate bound out. +* +* </pre> +* +******************************************************************************/ + +/***************************** Include Files *********************************/ +#include "mpm/rfdc/xrfdc.h" + +/************************** Constant Definitions *****************************/ + +/**************************** Type Definitions *******************************/ + +/***************** Macros (Inline Functions) Definitions *********************/ +static void XRFdc_SetSignalFlow(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Mode, u32 DigitalDataPathId, u32 MixerInOutDataType, + int ConnectIData, int ConnectQData); +static void XRFdc_MB_R2C_C2R(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u8 NoOfDataPaths, u32 MixerInOutDataType, u32 Mode, + u32 DataPathIndex[], u32 BlockIndex[]); +static void XRFdc_MB_C2C(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u8 NoOfDataPaths, u32 MixerInOutDataType, u32 Mode, + u32 DataPathIndex[], u32 BlockIndex[]); +static void XRFdc_SB_R2C_C2R(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 MixerInOutDataType, u32 Mode, u32 DataPathIndex[], u32 BlockIndex[]); +static void XRFdc_SB_C2C(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 MixerInOutDataType, u32 Mode, u32 DataPathIndex[], u32 BlockIndex[]); +/************************** Function Prototypes ******************************/ + +/*****************************************************************************/ +/** +* +* Static API to setup Singleband configuration for C2C MixerInOutDataType +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param MixerInOutDataType is mixer data type, valid values are XRFDC_MB_DATATYPE_* +* @param Mode is connection mode SB/MB_2X/MB_4X. +* @param DataPathIndex is the array that represents the blocks enabled in +* DigitalData Path. +* @param BlockIndex is the array that represents the blocks enabled in +* Analog Path(Data Converters). +* +* @return +* - None +* +* @note Static API for ADC/DAC blocks +* +******************************************************************************/ +static void XRFdc_SB_C2C(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 MixerInOutDataType, u32 Mode, u32 DataPathIndex[], u32 BlockIndex[]) +{ + u32 Block_Id; + + if ((Type == XRFDC_ADC_TILE) && (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1)) { + /* Update ConnectedIData and ConnectedQData for ADC 4GSPS */ + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedQData = BlockIndex[1U]; + Block_Id = (DataPathIndex[0] == 0U ? 1U : 0U); + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[Block_Id]. + ConnectedIData = -1; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[Block_Id]. + ConnectedQData = -1; + + if (DataPathIndex[0] == XRFDC_BLK_ID1) { + DataPathIndex[0] = XRFDC_BLK_ID2; + } + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0], + MixerInOutDataType, BlockIndex[0U], BlockIndex[0U]+1U); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0]+1U, + MixerInOutDataType, BlockIndex[1U]+1U, BlockIndex[1U]+2U); + Block_Id = (DataPathIndex[0] == XRFDC_BLK_ID2 ? XRFDC_BLK_ID0 : + XRFDC_BLK_ID2); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, Block_Id, + MixerInOutDataType, -1, -1); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, Block_Id+1U, + MixerInOutDataType, -1, -1); + } else { + DataPathIndex[1] = BlockIndex[0] + BlockIndex[1] - DataPathIndex[0]; + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0], + MixerInOutDataType, BlockIndex[0], BlockIndex[1]); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[1], + MixerInOutDataType, -1, -1); + + /* Update ConnectedIData and ConnectedQData for DAC and ADC 2GSPS */ + if (Type == XRFDC_ADC_TILE) { + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedIData = BlockIndex[0]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedQData = BlockIndex[1]; + + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedIData = -1; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedQData = -1; + } else { + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedIData = BlockIndex[0]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedQData = BlockIndex[1]; + + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedIData = -1; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedQData = -1; + } + } +} + +/*****************************************************************************/ +/** +* +* Static API to setup Singleband configuration for C2R and R2C MultiBandDataTypes +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param MixerInOutDataType is mixer data type, valid values are XRFDC_MB_DATATYPE_* +* @param Mode is connection mode SB/MB_2X/MB_4X. +* @param DataPathIndex is the array that represents the blocks enabled in +* DigitalData Path. +* @param BlockIndex is the array that represents the blocks enabled in +* Analog Path(Data Converters). +* +* @return +* - None +* +* @note Static API for ADC/DAC blocks +* +******************************************************************************/ +static void XRFdc_SB_R2C_C2R(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 MixerInOutDataType, u32 Mode, u32 DataPathIndex[], u32 BlockIndex[]) +{ + if (Type == XRFDC_ADC_TILE) { + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedQData = -1; + } else { + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedQData = -1; + } + if ((Type == XRFDC_ADC_TILE) && (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1)) { + if (DataPathIndex[0] == XRFDC_BLK_ID1) { + DataPathIndex[0] = XRFDC_BLK_ID2; + } + if (BlockIndex[0] == XRFDC_BLK_ID1) { + BlockIndex[0] = XRFDC_BLK_ID2; + } + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0]+1U, + MixerInOutDataType, BlockIndex[0U]+1U, -1); + } + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0], + MixerInOutDataType, BlockIndex[0U], -1); +} + +/*****************************************************************************/ +/** +* +* Static API to setup Multiband configuration for C2C MixerInOutDataType +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param MixerInOutDataType is mixer data type, valid values are XRFDC_MB_DATATYPE_* +* @param Mode is connection mode SB/MB_2X/MB_4X. +* @param DataPathIndex is the array that represents the blocks enabled in +* DigitalData Path. +* @param BlockIndex is the array that represents the blocks enabled in +* Analog Path(Data Converters). +* +* @return +* - None +* +* @note Static API for ADC/DAC blocks +* +******************************************************************************/ +static void XRFdc_MB_C2C(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u8 NoOfDataPaths, u32 MixerInOutDataType, u32 Mode, + u32 DataPathIndex[], u32 BlockIndex[]) +{ + if ((Type == XRFDC_ADC_TILE) && (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1)) { + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0], + MixerInOutDataType, BlockIndex[0U], BlockIndex[0U]+1U); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0]+1U, + MixerInOutDataType, BlockIndex[0U]+2U, BlockIndex[0U]+3U); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0]+2U, + MixerInOutDataType, BlockIndex[0U], BlockIndex[0U]+1U); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0]+3U, + MixerInOutDataType, BlockIndex[0U]+2U, BlockIndex[0U]+3U); + + /* Update ConnectedIData and ConnectedQData for ADC 4GSPS */ + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedQData = BlockIndex[1U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedQData = BlockIndex[1U]; + } else if (NoOfDataPaths == 2U) { + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0], + MixerInOutDataType, BlockIndex[0U], BlockIndex[1U]); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[1], + MixerInOutDataType, BlockIndex[0U], BlockIndex[1U]); + + /* Update ConnectedIData and ConnectedQData for DAC and ADC 2GSPS */ + if (Type == XRFDC_ADC_TILE) { + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedQData = BlockIndex[1U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedQData = BlockIndex[1U]; + } else { + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedQData = BlockIndex[1U]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedQData = BlockIndex[1U]; + } + } + if (NoOfDataPaths == 4U) { + if (Type == XRFDC_ADC_TILE) { + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0], + MixerInOutDataType, BlockIndex[0U], BlockIndex[1U]); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[1], + MixerInOutDataType, BlockIndex[0U], BlockIndex[1U]); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[2], + MixerInOutDataType, BlockIndex[0U], BlockIndex[1U]); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[3], + MixerInOutDataType, BlockIndex[0U], BlockIndex[1U]); + + /* Update ConnectedIData and ConnectedQData for ADC 4GSPS */ + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedQData = BlockIndex[1U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedQData = BlockIndex[1U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[2]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[2]]. + ConnectedQData = BlockIndex[1U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[3]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[3]]. + ConnectedQData = BlockIndex[1U]; + } else { + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0], + MixerInOutDataType, DataPathIndex[0], DataPathIndex[1U]); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[1], + MixerInOutDataType, DataPathIndex[0U], DataPathIndex[1U]); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[2], + MixerInOutDataType, DataPathIndex[2U], DataPathIndex[3U]); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[3], + MixerInOutDataType, DataPathIndex[2U], DataPathIndex[3U]); + + /* Update ConnectedIData and ConnectedQData for DAC and ADC 2GSPS */ + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedQData = BlockIndex[1U]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedQData = BlockIndex[1U]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[2]]. + ConnectedIData = DataPathIndex[0U]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[2]]. + ConnectedQData = DataPathIndex[1U]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[3]]. + ConnectedIData = DataPathIndex[0U]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[3]]. + ConnectedQData = DataPathIndex[1U]; + } + } +} + +/*****************************************************************************/ +/** +* +* Static API to setup Multiband configuration for C2C MixerInOutDataType +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param MixerInOutDataType is mixer data type, valid values are XRFDC_MB_DATATYPE_* +* @param Mode is connection mode SB/MB_2X/MB_4X. +* @param DataPathIndex is the array that represents the blocks enabled in +* DigitalData Path. +* @param BlockIndex is the array that represents the blocks enabled in +* Analog Path(Data Converters). +* +* @return +* - None +* +* @note Static API for ADC/DAC blocks +* +******************************************************************************/ +static void XRFdc_MB_R2C_C2R(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u8 NoOfDataPaths, u32 MixerInOutDataType, u32 Mode, + u32 DataPathIndex[], u32 BlockIndex[]) +{ + if ((Type == XRFDC_ADC_TILE) && (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1)) { + /* Update ConnectedIData and ConnectedQData */ + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedQData = -1; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedQData = -1; + if (BlockIndex[0] == XRFDC_BLK_ID1) { + BlockIndex[0] = XRFDC_BLK_ID2; + } + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0], + MixerInOutDataType, BlockIndex[0U], -1); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[1], + MixerInOutDataType, BlockIndex[0U]+1U, -1); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0]+2U, + MixerInOutDataType, BlockIndex[0U], -1); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[1]+2U, + MixerInOutDataType, BlockIndex[0U]+1U, -1); + } else if (NoOfDataPaths == 2U) { + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0], + MixerInOutDataType, BlockIndex[0], -1); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[1], + MixerInOutDataType, BlockIndex[0], -1); + + /* Update ConnectedIData and ConnectedQData */ + if (Type == XRFDC_ADC_TILE) { + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedQData = -1; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedQData = -1; + } else { + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedQData = -1; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedQData = -1; + } + + } + if (NoOfDataPaths == 4U) { + if (Type == XRFDC_ADC_TILE) { + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0], + MixerInOutDataType, BlockIndex[0], -1); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[1], + MixerInOutDataType, BlockIndex[0], -1); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[2], + MixerInOutDataType, BlockIndex[0], -1); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[3], + MixerInOutDataType, BlockIndex[0], -1); + + /* Update ConnectedIData and ConnectedQData */ + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedQData = -1; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedQData = -1; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[2]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[2]]. + ConnectedQData = -1; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[3]]. + ConnectedIData = BlockIndex[0U]; + InstancePtr->ADC_Tile[Tile_Id].ADCBlock_Digital_Datapath[DataPathIndex[3]]. + ConnectedQData = -1; + + } else { + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[0], + MixerInOutDataType, DataPathIndex[0], -1); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[1], + MixerInOutDataType, DataPathIndex[0], -1); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[2], + MixerInOutDataType, DataPathIndex[2], -1); + XRFdc_SetSignalFlow(InstancePtr, Type, Tile_Id, Mode, DataPathIndex[3], + MixerInOutDataType, DataPathIndex[2], -1); + + /* Update ConnectedIData and ConnectedQData */ + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedIData = DataPathIndex[0]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[0]]. + ConnectedQData = -1; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedIData = DataPathIndex[0]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[1]]. + ConnectedQData = -1; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[2]]. + ConnectedIData = DataPathIndex[0]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[2]]. + ConnectedQData = -1; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[3]]. + ConnectedIData = DataPathIndex[0]; + InstancePtr->DAC_Tile[Tile_Id].DACBlock_Digital_Datapath[DataPathIndex[3]]. + ConnectedQData = -1; + } + } +} + +/*****************************************************************************/ +/** +* +* Static API to update mode and MultibandConfig +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param NoOfDataPaths is number of DataPaths enabled. +* @param ModePtr is a pointer to connection mode SB/MB_2X/MB_4X. +* @param DataPathIndex is the array that represents the blocks enabled in +* DigitalData Path. +* +* @return +* - None +* +* @note Static API for ADC/DAC blocks +* +******************************************************************************/ +static u32 XRFdc_UpdateMBConfig(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u8 NoOfDataPaths, u32 *ModePtr, u32 DataPathIndex[]) +{ + u8 MultibandConfig; + u32 Status; + + if (Type == XRFDC_ADC_TILE) { + MultibandConfig = InstancePtr->ADC_Tile[Tile_Id].MultibandConfig; + } else { + MultibandConfig = InstancePtr->DAC_Tile[Tile_Id].MultibandConfig; + } + + if (NoOfDataPaths == 1U) { + *ModePtr = XRFDC_SINGLEBAND_MODE; + if (((DataPathIndex[0] == XRFDC_BLK_ID2) || + (DataPathIndex[0] == XRFDC_BLK_ID3)) && + ((MultibandConfig == XRFDC_MB_MODE_2X_BLK01_BLK23) || + (MultibandConfig == XRFDC_MB_MODE_4X))) { + MultibandConfig = XRFDC_MB_MODE_2X_BLK01; + } else if (((DataPathIndex[0] == XRFDC_BLK_ID0) || + (DataPathIndex[0] == XRFDC_BLK_ID1)) && + ((MultibandConfig == XRFDC_MB_MODE_2X_BLK01_BLK23) || + (MultibandConfig == XRFDC_MB_MODE_4X))) { + MultibandConfig = XRFDC_MB_MODE_2X_BLK23; + } else if ((MultibandConfig == XRFDC_MB_MODE_2X_BLK01) && + ((DataPathIndex[0] == XRFDC_BLK_ID0) || + (DataPathIndex[0] == XRFDC_BLK_ID1))) { + MultibandConfig = XRFDC_MB_MODE_SB; + } else if ((MultibandConfig == XRFDC_MB_MODE_2X_BLK23) && + ((DataPathIndex[0] == XRFDC_BLK_ID2) || + (DataPathIndex[0] == XRFDC_BLK_ID3))) { + MultibandConfig = XRFDC_MB_MODE_SB; + } + } else if (NoOfDataPaths == 2U) { + *ModePtr = XRFDC_MULTIBAND_MODE_2X; + if (((MultibandConfig == XRFDC_MB_MODE_2X_BLK01) && + (DataPathIndex[0] == XRFDC_BLK_ID2) && (DataPathIndex[1] == XRFDC_BLK_ID3)) || + ((MultibandConfig == XRFDC_MB_MODE_2X_BLK23) && (DataPathIndex[0] == XRFDC_BLK_ID0) && + (DataPathIndex[1] == XRFDC_BLK_ID1)) || (MultibandConfig == XRFDC_MB_MODE_4X)) { + MultibandConfig = XRFDC_MB_MODE_2X_BLK01_BLK23; + } else if (((DataPathIndex[0] == XRFDC_BLK_ID2) && (DataPathIndex[1] == XRFDC_BLK_ID3)) && + (MultibandConfig == XRFDC_MB_MODE_SB)) { + MultibandConfig = XRFDC_MB_MODE_2X_BLK23; + } else if (((DataPathIndex[0] == XRFDC_BLK_ID0) && (DataPathIndex[1] == XRFDC_BLK_ID1)) && + (MultibandConfig == XRFDC_MB_MODE_SB)) { + MultibandConfig = XRFDC_MB_MODE_2X_BLK01; + } + } else if (NoOfDataPaths == 4U) { + *ModePtr = XRFDC_MULTIBAND_MODE_4X; + MultibandConfig = XRFDC_MB_MODE_4X; + } else { + metal_log(METAL_LOG_ERROR, "\n Invalid DigitalDataPathMask " + "value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + /* Update Multiband Config member */ + if (Type == XRFDC_ADC_TILE) { + InstancePtr->ADC_Tile[Tile_Id].MultibandConfig = MultibandConfig; + } else { + InstancePtr->DAC_Tile[Tile_Id].MultibandConfig = MultibandConfig; + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/*****************************************************************************/ +/** +* +* User-level API to setup multiband configuration. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param DigitalDataPathMask is the DataPath mask. First 4 bits represent +* 4 data paths, 1 means enabled and 0 means disabled. +* @param MixerInOutDataType is mixer data type, valid values are XRFDC_MB_DATATYPE_* +* @param DataConverterMask is block enabled mask (input/output driving +* blocks). 1 means enabled and 0 means disabled. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note Common API for ADC/DAC blocks +* +******************************************************************************/ +u32 XRFdc_MultiBand(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u8 DigitalDataPathMask, u32 MixerInOutDataType, u32 DataConverterMask) +{ + u32 Status; + u32 Block_Id; + u8 NoOfDataPaths = 0U; + u32 BlockIndex[XRFDC_NUM_OF_BLKS4] = {XRFDC_BLK_ID4}; + u32 DataPathIndex[XRFDC_NUM_OF_BLKS4] = {XRFDC_BLK_ID4}; + u32 NoOfDataConverters = 0U; + u32 Mode = 0x0; + u32 NoOfBlocks = XRFDC_BLK_ID4; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + if ((DigitalDataPathMask == 0U) || (DigitalDataPathMask > 0xFU)) { + metal_log(METAL_LOG_ERROR, "\n Invalid DigitalDataPathMask " + "value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + if ((DataConverterMask == 0U) || (DataConverterMask > 0xFU)) { + metal_log(METAL_LOG_ERROR, "\n Invalid DataConverterMask " + "value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + if ((MixerInOutDataType != XRFDC_MB_DATATYPE_C2C) && + (MixerInOutDataType != XRFDC_MB_DATATYPE_R2C) && + (MixerInOutDataType != XRFDC_MB_DATATYPE_C2R)) { + metal_log(METAL_LOG_ERROR, "\n Invalid MixerInOutDataType " + "value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE)) { + NoOfBlocks = XRFDC_BLK_ID2; + } + /* Identify DataPathIndex and BlockIndex */ + for (Block_Id = XRFDC_BLK_ID0; Block_Id < NoOfBlocks; Block_Id++) { + if ((DataConverterMask & (1U << Block_Id)) != 0U) { + BlockIndex[NoOfDataConverters] = Block_Id; + NoOfDataConverters += 1U; + Status = XRFdc_CheckBlockEnabled(InstancePtr, Type, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block not " + "available in %s\r\n", __func__); + goto RETURN_PATH; + } + } + if ((DigitalDataPathMask & (1U << Block_Id)) != 0U) { + DataPathIndex[NoOfDataPaths] = Block_Id; + NoOfDataPaths += 1U; + Status = XRFdc_CheckDigitalPathEnabled(InstancePtr, Type, Tile_Id, + Block_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Requested block digital path " + "not enabled in %s\r\n", __func__); + goto RETURN_PATH; + } + } + } + + /* rerouting & configuration for alternative bonding. */ + if ((Type == XRFDC_DAC_TILE) && (DataConverterMask & 0x05) && (MixerInOutDataType == XRFDC_MB_DATATYPE_C2C) && + (InstancePtr->RFdc_Config.DACTile_Config[Tile_Id].NumSlices == 2)) { + BlockIndex[XRFDC_BLK_ID1] = XRFDC_BLK_ID1; + XRFdc_ClrSetReg(InstancePtr, XRFDC_BLOCK_BASE(XRFDC_DAC_TILE, Tile_Id, XRFDC_BLK_ID1), + XRFDC_DAC_MB_CFG_OFFSET, XRFDC_ALT_BOND_MASK, XRFDC_ENABLED << XRFDC_ALT_BOND_SHIFT); + XRFdc_ClrSetReg(InstancePtr, XRFDC_BLOCK_BASE(XRFDC_DAC_TILE, Tile_Id, XRFDC_BLK_ID2), + XRFDC_DAC_MB_CFG_OFFSET, XRFDC_ALT_BOND_MASK, XRFDC_ENABLED << XRFDC_ALT_BOND_SHIFT); + } + + if (BlockIndex[0] != DataPathIndex[0]) { + metal_log(METAL_LOG_ERROR, "\n Not a valid MB/SB " + "combination in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + /* UPdate MultibandConfig in driver instance */ + Status = XRFdc_UpdateMBConfig(InstancePtr, Type, Tile_Id, NoOfDataPaths, &Mode, + DataPathIndex); + if (Status != XRFDC_SUCCESS) { + goto RETURN_PATH; + } + + if ((MixerInOutDataType == XRFDC_MB_DATATYPE_C2C) && (Mode == XRFDC_SINGLEBAND_MODE)) { + /* Singleband C2C */ + XRFdc_SB_C2C(InstancePtr, Type, Tile_Id, MixerInOutDataType, Mode, + DataPathIndex, BlockIndex); + } else if (((MixerInOutDataType == XRFDC_MB_DATATYPE_R2C) || + (MixerInOutDataType == XRFDC_MB_DATATYPE_C2R)) && (Mode == XRFDC_SINGLEBAND_MODE)) { + /* Singleband R2C and C2R */ + XRFdc_SB_R2C_C2R(InstancePtr, Type, Tile_Id, MixerInOutDataType, Mode, + DataPathIndex, BlockIndex); + } + if ((MixerInOutDataType == XRFDC_MB_DATATYPE_C2C) && + ((Mode == XRFDC_MULTIBAND_MODE_2X) || (Mode == XRFDC_MULTIBAND_MODE_4X))) { + /* Multiband C2C */ + XRFdc_MB_C2C(InstancePtr, Type, Tile_Id, NoOfDataPaths, MixerInOutDataType, Mode, + DataPathIndex, BlockIndex); + } else if (((MixerInOutDataType == XRFDC_MB_DATATYPE_R2C) || (MixerInOutDataType == XRFDC_MB_DATATYPE_C2R)) && + ((Mode == XRFDC_MULTIBAND_MODE_2X) || (Mode == XRFDC_MULTIBAND_MODE_4X))) { + /* Multiband C2R and R2C */ + XRFdc_MB_R2C_C2R(InstancePtr, Type, Tile_Id, NoOfDataPaths, MixerInOutDataType, + Mode, DataPathIndex, BlockIndex); + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/*****************************************************************************/ +/** +* +* Sets up signal flow configuration. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Mode is connection mode SB/MB_2X/MB_4X. +* @param DigitalDataPathId for the requested I or Q data. +* @param MixerInOutDataType is mixer data type, valid values are XRFDC_MB_DATATYPE_* +* @param ConnectIData is analog blocks that are connected to +* DigitalDataPath I. +* @param ConnectQData is analog blocks that are connected to +* DigitalDataPath Q. +* +* @return None +* +* @note static API used internally. +* +******************************************************************************/ +static void XRFdc_SetSignalFlow(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Mode, u32 DigitalDataPathId, u32 MixerInOutDataType, + int ConnectIData, int ConnectQData) +{ + u16 ReadReg; + u32 BaseAddr; + + Xil_AssertVoid(InstancePtr != NULL); + Xil_AssertVoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + BaseAddr = XRFDC_BLOCK_BASE(Type, Tile_Id, DigitalDataPathId); + if (Type == XRFDC_ADC_TILE) { + /* ADC */ + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_SWITCH_MATRX_OFFSET); + ReadReg &= ~XRFDC_SWITCH_MTRX_MASK; + if (ConnectIData != -1) { + ReadReg |= ((u16)ConnectIData) << XRFDC_SEL_CB_TO_MIX0_SHIFT; + } + if (ConnectQData != -1) { + ReadReg |= (u16)ConnectQData; + } + if ((MixerInOutDataType == XRFDC_MB_DATATYPE_C2C) && + (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1)) { + ReadReg |= XRFDC_SEL_CB_TO_QMC_MASK; + } + if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) { + ReadReg |= XRFDC_SEL_CB_TO_DECI_MASK; + } + + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_SWITCH_MATRX_OFFSET, ReadReg); + } else { + /* DAC */ + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_DAC_MB_CFG_OFFSET); + ReadReg &= ~XRFDC_MB_CFG_MASK; + if (Mode == XRFDC_SINGLEBAND_MODE) { + if ((u32)ConnectIData == DigitalDataPathId) { + if (ConnectQData != -1) { + ReadReg |= XRFDC_SB_C2C_BLK0; + } else { + ReadReg |= XRFDC_SB_C2R; + } + } + if ((ConnectIData == -1) && (ConnectQData == -1)) { + ReadReg |= XRFDC_SB_C2C_BLK1; + } + } else { + if (Mode == XRFDC_MULTIBAND_MODE_4X) { + ReadReg |= XRFDC_MB_EN_4X_MASK; + } + if ((u32)ConnectIData == DigitalDataPathId) { + if (ConnectQData != -1) { + ReadReg |= XRFDC_MB_C2C_BLK0; + } else { + ReadReg |= XRFDC_MB_C2R_BLK0; + } + } else { + if (ConnectQData != -1) { + ReadReg |= XRFDC_MB_C2C_BLK1; + } else { + ReadReg |= XRFDC_MB_C2R_BLK1; + } + } + } + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_DAC_MB_CFG_OFFSET, ReadReg); + } +} + +/** @} */
\ No newline at end of file diff --git a/mpm/lib/rfdc/xrfdc_mixer.c b/mpm/lib/rfdc/xrfdc_mixer.c new file mode 100644 index 000000000..747e180a4 --- /dev/null +++ b/mpm/lib/rfdc/xrfdc_mixer.c @@ -0,0 +1,1091 @@ +/****************************************************************************** +* +* Copyright (C) 2018-2019 Xilinx, Inc. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* XILINX BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Except as contained in this notice, the name of the Xilinx shall not be used +* in advertising or otherwise to promote the sale, use or other dealings in +* this Software without prior written authorization from Xilinx. +* +******************************************************************************/ +/*****************************************************************************/ +/** +* +* @file xrfdc_mixer.c +* @addtogroup xrfdc_v6_0 +* @{ +* +* Contains the interface functions of the Mixer Settings in XRFdc driver. +* See xrfdc.h for a detailed description of the device and driver. +* +* <pre> +* MODIFICATION HISTORY: +* +* Ver Who Date Changes +* ----- --- -------- ----------------------------------------------- +* 5.0 sk 08/06/18 Initial release +* 5.1 cog 01/29/19 Replace structure reference ADC checks with +* function. +* cog 01/29/19 XRFdc_SetCoarseMixer and MixerRangeCheck now need +* Tile_id as a parameter. +* cog 01/29/19 Rename DataType to MixerInputDataType for +* readability. +* </pre> +* +******************************************************************************/ + +/***************************** Include Files *********************************/ +#include "mpm/rfdc/xrfdc.h" + +/************************** Constant Definitions *****************************/ + +/**************************** Type Definitions *******************************/ + +/***************** Macros (Inline Functions) Definitions *********************/ +static void XRFdc_SetFineMixer(XRFdc *InstancePtr, u32 BaseAddr, + XRFdc_Mixer_Settings *MixerSettingsPtr); +static void XRFdc_SetCoarseMixer(XRFdc *InstancePtr, u32 Type, u32 BaseAddr, + u32 Tile_Id, u32 Block_Id, u32 CoarseMixFreq, XRFdc_Mixer_Settings *MixerSettingsPtr); +static u32 XRFdc_MixerRangeCheck(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + XRFdc_Mixer_Settings *MixerSettingsPtr); + +/************************** Function Prototypes ******************************/ + +/*****************************************************************************/ +/** +* The API is used to update various mixer settings, fine, coarse, NCO etc. +* Mixer/NCO settings passed are used to update the corresponding +* block level registers. Driver structure is updated with the new values. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param MixerSettingsPtr Pointer to the XRFdc_Mixer_Settings structure +* in which the Mixer/NCO settings are passed. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note FineMixerScale in Mixer_Settings structure can have 3 values. +* XRFDC_MIXER_SCALE_* represents the valid values. +* XRFDC_MIXER_SCALE_AUTO - If mixer mode is R2C, Mixer Scale is +* set to 1 and for other modes mixer scale is set to 0.7 +* XRFDC_MIXER_SCALE_1P0 - To set fine mixer scale to 1. +* XRFDC_MIXER_SCALE_0P7 - To set fine mixer scale to 0.7. +* +******************************************************************************/ +u32 XRFdc_SetMixerSettings(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, XRFdc_Mixer_Settings *MixerSettingsPtr) +{ + u32 Status; + u16 ReadReg; + u32 BaseAddr; + double SamplingRate; + s64 Freq; + s32 PhaseOffset; + u32 NoOfBlocks; + u32 Index; + XRFdc_Mixer_Settings *MixerConfigPtr; + u8 CalibrationMode = 0U; + u32 CoarseMixFreq; + double NCOFreq; + u32 NyquistZone = 0U; + u32 Offset; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(MixerSettingsPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckDigitalPathEnabled(InstancePtr, Type, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + goto RETURN_PATH; + } + + Status = XRFdc_MixerRangeCheck(InstancePtr, Type, Tile_Id, MixerSettingsPtr); + if (Status != XRFDC_SUCCESS) { + goto RETURN_PATH; + } + + Index = Block_Id; + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE)) { + NoOfBlocks = XRFDC_NUM_OF_BLKS2; + if (Block_Id == XRFDC_BLK_ID1) { + Index = XRFDC_BLK_ID2; + NoOfBlocks = XRFDC_NUM_OF_BLKS4; + } + } else { + NoOfBlocks = Block_Id + 1U; + } + + for (; Index < NoOfBlocks; Index++) { + if (Type == XRFDC_ADC_TILE) { + /* ADC */ + MixerConfigPtr = &InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Index].Mixer_Settings; + SamplingRate = InstancePtr->ADC_Tile[Tile_Id]. + PLL_Settings.SampleRate; + } else { + /* DAC */ + MixerConfigPtr = &InstancePtr->DAC_Tile[Tile_Id]. + DACBlock_Digital_Datapath[Index].Mixer_Settings; + SamplingRate = InstancePtr->DAC_Tile[Tile_Id]. + PLL_Settings.SampleRate; + } + + BaseAddr = XRFDC_BLOCK_BASE(Type, Tile_Id, Index); + + if (SamplingRate <= 0) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, "\n Incorrect Sampling " + "rate in %s\r\n", __func__); + goto RETURN_PATH; + } else { + metal_log(METAL_LOG_DEBUG, "\n Sampling " + "rate is %2.4f in %s\r\n", SamplingRate, __func__); + } + + SamplingRate *= XRFDC_MILLI; + /* Set MixerInputDataType for ADC and DAC */ + if (Type == XRFDC_DAC_TILE) { + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_DAC_ITERP_DATA_OFFSET); + ReadReg &= ~XRFDC_DAC_INTERP_DATA_MASK; + InstancePtr->DAC_Tile[Tile_Id]. + DACBlock_Digital_Datapath[Index].MixerInputDataType = + XRFDC_DATA_TYPE_REAL; + if ((MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2C) || + (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2R)) { + ReadReg |= XRFDC_DAC_INTERP_DATA_MASK; + InstancePtr->DAC_Tile[Tile_Id]. + DACBlock_Digital_Datapath[Index]. + MixerInputDataType = XRFDC_DATA_TYPE_IQ; + } + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_DAC_ITERP_DATA_OFFSET, ReadReg); + } else { + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_DECI_CONFIG_OFFSET); + ReadReg &= ~XRFDC_DEC_CFG_MASK; + if (XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) { + ReadReg |= XRFDC_DEC_CFG_4GSPS_MASK; + } else if ((MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2C) || + (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_R2C)) { + ReadReg |= XRFDC_DEC_CFG_IQ_MASK; + } else { + ReadReg |= XRFDC_DEC_CFG_CHA_MASK; + } + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_DECI_CONFIG_OFFSET, ReadReg); + if (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2C) { + InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Index]. + MixerInputDataType = XRFDC_DATA_TYPE_IQ; + } + if ((MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_R2C) || + (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_R2R)) { + InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Index]. + MixerInputDataType = XRFDC_DATA_TYPE_REAL; + } + } + + /* Set NCO Phase Mode */ + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE)) { + if ((Index == XRFDC_BLK_ID0) || (Index == XRFDC_BLK_ID2)) { + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_NCO_PHASE_MOD_OFFSET, + XRFDC_NCO_PHASE_MOD_EVEN); + } else { + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_NCO_PHASE_MOD_OFFSET, + XRFDC_NCO_PHASE_MODE_ODD); + } + } + + /* Update NCO, CoarseMix freq based on calibration mode */ + CoarseMixFreq = MixerSettingsPtr->CoarseMixFreq; + NCOFreq = MixerSettingsPtr->Freq; + if (Type == XRFDC_ADC_TILE) { + Status = XRFdc_GetCalibrationMode(InstancePtr, + Tile_Id, Block_Id, &CalibrationMode); + if (Status != XRFDC_SUCCESS) { + return XRFDC_FAILURE; + } + if (CalibrationMode == XRFDC_CALIB_MODE1) { + switch (CoarseMixFreq) { + case XRFDC_COARSE_MIX_BYPASS: + CoarseMixFreq = + XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_TWO; + break; + case XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_FOUR: + CoarseMixFreq = + XRFDC_COARSE_MIX_MIN_SAMPLE_FREQ_BY_FOUR; + break; + case XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_TWO: + CoarseMixFreq = + XRFDC_COARSE_MIX_BYPASS; + break; + case XRFDC_COARSE_MIX_MIN_SAMPLE_FREQ_BY_FOUR: + CoarseMixFreq = + XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_FOUR; + break; + default: + CoarseMixFreq = + XRFDC_COARSE_MIX_OFF; + break; + } + NCOFreq -= SamplingRate / 2.0; + } + } + + if ((NCOFreq < -(SamplingRate / 2.0)) || + (NCOFreq > (SamplingRate / 2.0))) { + Status = XRFdc_GetNyquistZone(InstancePtr, Type, + Tile_Id, Block_Id, &NyquistZone); + if (Status != XRFDC_SUCCESS) { + return XRFDC_FAILURE; + } + do { + if (NCOFreq < -(SamplingRate / 2.0)) { + NCOFreq += SamplingRate; + } + if (NCOFreq > (SamplingRate / 2.0)) { + NCOFreq -= SamplingRate; + } + } while ((NCOFreq < -(SamplingRate / 2.0)) || + (NCOFreq > (SamplingRate / 2.0))); + + if ((NyquistZone == XRFDC_EVEN_NYQUIST_ZONE) && + (NCOFreq != 0)) { + NCOFreq *= -1; + } + } + + /* NCO Frequency */ + if (NCOFreq < 0) { + Freq = ((NCOFreq * XRFDC_NCO_FREQ_MIN_MULTIPLIER) / + SamplingRate); + } else { + Freq = ((NCOFreq * XRFDC_NCO_FREQ_MULTIPLIER) / + SamplingRate); + } + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_NCO_FQWD_LOW_OFFSET, (u16)Freq); + ReadReg = (Freq >> XRFDC_NCO_FQWD_MID_SHIFT) & XRFDC_NCO_FQWD_MID_MASK; + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_NCO_FQWD_MID_OFFSET, (u16)ReadReg); + ReadReg = (Freq >> XRFDC_NCO_FQWD_UPP_SHIFT) & XRFDC_NCO_FQWD_UPP_MASK; + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_NCO_FQWD_UPP_OFFSET, (u16)ReadReg); + + /* Phase Offset */ + PhaseOffset = ((MixerSettingsPtr->PhaseOffset * + XRFDC_NCO_PHASE_MULTIPLIER) / XRFDC_MIXER_PHASE_OFFSET_UP_LIMIT); + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_NCO_PHASE_LOW_OFFSET, + (u16)PhaseOffset); + + ReadReg = (PhaseOffset >> XRFDC_NCO_PHASE_UPP_SHIFT) & + XRFDC_NCO_PHASE_UPP_MASK; + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_NCO_PHASE_UPP_OFFSET, + ReadReg); + + if (MixerSettingsPtr->MixerType == XRFDC_MIXER_TYPE_COARSE) { + XRFdc_SetCoarseMixer(InstancePtr, Type, BaseAddr, Tile_Id, + Index, CoarseMixFreq, MixerSettingsPtr); + } else { + XRFdc_SetFineMixer(InstancePtr, BaseAddr, MixerSettingsPtr); + } + + /* Fine Mixer Scale */ + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_MXR_MODE_OFFSET); + if (MixerSettingsPtr->FineMixerScale == XRFDC_MIXER_SCALE_1P0) { + ReadReg |= XRFDC_FINE_MIX_SCALE_MASK; + InstancePtr->UpdateMixerScale = 0x1U; + } else if (MixerSettingsPtr->FineMixerScale == XRFDC_MIXER_SCALE_0P7) { + ReadReg &= ~XRFDC_FINE_MIX_SCALE_MASK; + InstancePtr->UpdateMixerScale = 0x1U; + } else { + InstancePtr->UpdateMixerScale = 0x0U; + } + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_MXR_MODE_OFFSET, + ReadReg); + + /* Event Source */ + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_NCO_UPDT_OFFSET, + XRFDC_NCO_UPDT_MODE_MASK, MixerSettingsPtr->EventSource); + if (MixerSettingsPtr->EventSource == XRFDC_EVNT_SRC_IMMEDIATE) { + if (Type == XRFDC_ADC_TILE) { + Offset = XRFDC_ADC_UPDATE_DYN_OFFSET; + } else { + Offset = XRFDC_DAC_UPDATE_DYN_OFFSET; + } + XRFdc_ClrSetReg(InstancePtr, BaseAddr, Offset, + XRFDC_UPDT_EVNT_MASK, XRFDC_UPDT_EVNT_NCO_MASK); + } + + /* Update the instance with new values */ + MixerConfigPtr->EventSource = MixerSettingsPtr->EventSource; + MixerConfigPtr->PhaseOffset = MixerSettingsPtr->PhaseOffset; + MixerConfigPtr->MixerMode = MixerSettingsPtr->MixerMode; + MixerConfigPtr->CoarseMixFreq = MixerSettingsPtr->CoarseMixFreq; + MixerConfigPtr->Freq = MixerSettingsPtr->Freq; + MixerConfigPtr->MixerType = MixerSettingsPtr->MixerType; + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; + +} + +/*****************************************************************************/ +/** +* Static API used to do the Mixer Settings range check. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param MixerSettingsPtr Pointer to the XRFdc_Mixer_Settings structure +* in which the Mixer/NCO settings are passed. +* +* @return +* - XRFDC_SUCCESS if mixer settings are within the range. +* - XRFDC_FAILURE if mixer settings are not in valid range +* +* @note None +* +******************************************************************************/ +static u32 XRFdc_MixerRangeCheck(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + XRFdc_Mixer_Settings *MixerSettingsPtr) +{ + u32 Status; + + if ((MixerSettingsPtr->PhaseOffset >= + XRFDC_MIXER_PHASE_OFFSET_UP_LIMIT) || + (MixerSettingsPtr->PhaseOffset <= + XRFDC_MIXER_PHASE_OFFSET_LOW_LIMIT)) { + metal_log(METAL_LOG_ERROR, "\n Invalid phase offset value " + "in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if ((MixerSettingsPtr->EventSource > XRFDC_EVNT_SRC_PL) || + ((MixerSettingsPtr->EventSource == XRFDC_EVNT_SRC_MARKER) && + (Type == XRFDC_ADC_TILE))) { + metal_log(METAL_LOG_ERROR, "\n Invalid event source " + "selection in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if (MixerSettingsPtr->MixerMode > XRFDC_MIXER_MODE_R2R) { + metal_log(METAL_LOG_ERROR, "\n Invalid fine mixer mode " + "in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if ((MixerSettingsPtr->CoarseMixFreq != XRFDC_COARSE_MIX_OFF) && + (MixerSettingsPtr->CoarseMixFreq != + XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_TWO) && + (MixerSettingsPtr->CoarseMixFreq != + XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_FOUR) && + (MixerSettingsPtr->CoarseMixFreq != + XRFDC_COARSE_MIX_MIN_SAMPLE_FREQ_BY_FOUR) && + (MixerSettingsPtr->CoarseMixFreq != + XRFDC_COARSE_MIX_BYPASS)) { + metal_log(METAL_LOG_ERROR, "\n Invalid coarse mix " + "frequency value in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + if (MixerSettingsPtr->FineMixerScale > XRFDC_MIXER_SCALE_0P7) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Mixer Scale in %s\r\n", __func__); + goto RETURN_PATH; + } + if (((MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_R2C) && + (Type == XRFDC_DAC_TILE)) || + ((MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2R) && + (Type == XRFDC_ADC_TILE))) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Mixer mode in %s\r\n", __func__); + goto RETURN_PATH; + } + if ((MixerSettingsPtr->MixerType != XRFDC_MIXER_TYPE_FINE) && + (MixerSettingsPtr->MixerType != XRFDC_MIXER_TYPE_COARSE)) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, + "\n Invalid Mixer Type in %s\r\n", __func__); + goto RETURN_PATH; + } + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE) && + ((MixerSettingsPtr->EventSource == XRFDC_EVNT_SRC_SLICE) || + (MixerSettingsPtr->EventSource == + XRFDC_EVNT_SRC_IMMEDIATE))) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, "\n Invalid Event Source, event " + "source is not supported in 4GSPS ADC %s\r\n", __func__); + goto RETURN_PATH; + } + if (((MixerSettingsPtr->MixerType == XRFDC_MIXER_TYPE_COARSE) && + (MixerSettingsPtr->CoarseMixFreq == XRFDC_COARSE_MIX_OFF)) || + ((MixerSettingsPtr->MixerType == XRFDC_MIXER_TYPE_FINE) && + (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_OFF))) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, "\n Invalid Combination of " + "Mixer settings in %s\r\n", __func__); + goto RETURN_PATH; + } + if ((MixerSettingsPtr->MixerType == XRFDC_MIXER_TYPE_COARSE) && + (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_OFF)) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, "\n Invalid Combination of " + "Mixer type and Mixer mode in %s\r\n", __func__); + goto RETURN_PATH; + } + if ((MixerSettingsPtr->MixerType == XRFDC_MIXER_TYPE_FINE) && + (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_R2R)) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, "\n Invalid Combination of " + "Mixer type and Mixer mode in %s\r\n", __func__); + goto RETURN_PATH; + } + if ((MixerSettingsPtr->MixerType == XRFDC_MIXER_TYPE_COARSE) && + (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_R2R) && + (MixerSettingsPtr->CoarseMixFreq != XRFDC_COARSE_MIX_BYPASS)) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, "\n Invalid Combination of " + "Mixer type and Mixer mode in %s\r\n", __func__); + goto RETURN_PATH; + } + + Status = XRFDC_SUCCESS; + +RETURN_PATH: + return Status; +} + +/*****************************************************************************/ +/** +* Static API used to set the Fine Mixer. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param BaseAddr is ADC or DAC base address. +* @param CoarseMixFreq is ADC or DAC Coarse mixer frequency. +* @param MixerSettingsPtr Pointer to the XRFdc_Mixer_Settings structure +* in which the Mixer/NCO settings are passed. +* +* @return +* - None +* +* @note Static API +* +******************************************************************************/ +static void XRFdc_SetFineMixer(XRFdc *InstancePtr, u32 BaseAddr, + XRFdc_Mixer_Settings *MixerSettingsPtr) +{ + + if (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2C) { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_MXR_MODE_OFFSET, + (XRFDC_SEL_I_IQ_MASK | XRFDC_SEL_Q_IQ_MASK | + XRFDC_EN_I_IQ_MASK | XRFDC_EN_Q_IQ_MASK), + (XRFDC_EN_I_IQ_MASK | XRFDC_EN_Q_IQ_MASK | + XRFDC_I_IQ_COS_MINSIN | XRFDC_Q_IQ_SIN_COS)); + } else if (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2R) { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_MXR_MODE_OFFSET, + (XRFDC_EN_I_IQ_MASK | XRFDC_SEL_I_IQ_MASK | XRFDC_EN_Q_IQ_MASK), + (XRFDC_EN_I_IQ_MASK | XRFDC_I_IQ_COS_MINSIN)); + } else if (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_R2C) { + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_MXR_MODE_OFFSET, + (XRFDC_EN_I_IQ_MASK | XRFDC_EN_Q_IQ_MASK | + XRFDC_SEL_I_IQ_MASK | XRFDC_SEL_Q_IQ_MASK | + XRFDC_FINE_MIX_SCALE_MASK), (XRFDC_EN_I_IQ | XRFDC_EN_Q_IQ | + XRFDC_I_IQ_COS_MINSIN | XRFDC_Q_IQ_SIN_COS | + XRFDC_FINE_MIX_SCALE_MASK)); + } else { + /* Fine mixer mode is OFF */ + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_MXR_MODE_OFFSET, + XRFDC_MIXER_MODE_OFF); + } + + /* Coarse Mixer is OFF */ + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_MXR_CFG0_OFFSET, + XRFDC_MIX_CFG0_MASK, XRFDC_CRSE_MIX_OFF); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_MXR_CFG1_OFFSET, + XRFDC_MIX_CFG1_MASK, XRFDC_CRSE_MIX_OFF); +} + +/*****************************************************************************/ +/** +* Static API used to set the Coarse Mixer. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param BaseAddr is ADC or DAC base address. +* @param Block_Id is ADC/DAC block number inside the tile. +* @param CoarseMixFreq is ADC or DAC Coarse mixer frequency. +* @param MixerSettingsPtr Pointer to the XRFdc_Mixer_Settings structure +* in which the Mixer/NCO settings are passed. +* +* @return +* - None +* +* @note Static API +* +******************************************************************************/ +static void XRFdc_SetCoarseMixer(XRFdc *InstancePtr, u32 Type, u32 BaseAddr, + u32 Tile_Id, u32 Block_Id, u32 CoarseMixFreq, XRFdc_Mixer_Settings *MixerSettingsPtr) +{ + u16 ReadReg; + + if (CoarseMixFreq == XRFDC_COARSE_MIX_BYPASS) { + /* Coarse Mix BYPASS */ + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_MXR_CFG0_OFFSET, + XRFDC_MIX_CFG0_MASK, XRFDC_CRSE_MIX_BYPASS); + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_MXR_CFG1_OFFSET); + ReadReg &= ~XRFDC_MIX_CFG1_MASK; + if ((MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2C) || + (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2R)) { + ReadReg |= XRFDC_CRSE_MIX_BYPASS; + } else { + ReadReg |= XRFDC_CRSE_MIX_OFF; + } + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_MXR_CFG1_OFFSET, (u16)ReadReg); + } else if (CoarseMixFreq == XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_TWO) { + /* Coarse Mix freq Fs/2 */ + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_MXR_CFG0_OFFSET); + ReadReg &= ~XRFDC_MIX_CFG0_MASK; + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 0) || + (Type == XRFDC_DAC_TILE)) { + ReadReg |= XRFDC_CRSE_MIX_I_Q_FSBYTWO; + } else { + if ((Block_Id % 2U) == 0U) { + ReadReg |= XRFDC_CRSE_MIX_BYPASS; + } else { + ReadReg |= XRFDC_CRSE_4GSPS_ODD_FSBYTWO; + } + } + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_MXR_CFG0_OFFSET, (u16)ReadReg); + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_MXR_CFG1_OFFSET); + ReadReg &= ~XRFDC_MIX_CFG1_MASK; + if ((MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2C) || + (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2R)) { + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 0) || + (Type == XRFDC_DAC_TILE)) { + ReadReg |= XRFDC_CRSE_MIX_I_Q_FSBYTWO; + } else { + ReadReg |= ((Block_Id % 2U) == 0U) ? + XRFDC_CRSE_MIX_BYPASS : + XRFDC_CRSE_4GSPS_ODD_FSBYTWO; + } + } else { + ReadReg |= XRFDC_CRSE_MIX_OFF; + } + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_MXR_CFG1_OFFSET, (u16)ReadReg); + } else if (CoarseMixFreq == XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_FOUR) { + /* Coarse Mix freq Fs/4 */ + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_MXR_CFG0_OFFSET); + ReadReg &= ~XRFDC_MIX_CFG0_MASK; + if ((MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2C) || + (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2R)) { + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 0) || + (Type == XRFDC_DAC_TILE)) { + ReadReg |= XRFDC_CRSE_MIX_I_FSBYFOUR; + } else { + ReadReg |= ((Block_Id % 2U) == 0U) ? + XRFDC_CRSE_MIX_I_Q_FSBYTWO : + XRFDC_CRSE_MIX_I_ODD_FSBYFOUR; + } + } else { + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 0) || + (Type == XRFDC_DAC_TILE)) { + ReadReg |= XRFDC_CRSE_MIX_R_I_FSBYFOUR; + } else { + ReadReg |= ((Block_Id % 2U) == 0U) ? + XRFDC_CRSE_MIX_I_Q_FSBYTWO : + XRFDC_CRSE_MIX_OFF; + } + } + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_MXR_CFG0_OFFSET, (u16)ReadReg); + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_MXR_CFG1_OFFSET); + ReadReg &= ~XRFDC_MIX_CFG1_MASK; + if ((MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2C) || + (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2R)) { + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 0) || + (Type == XRFDC_DAC_TILE)) { + ReadReg |= XRFDC_CRSE_MIX_Q_FSBYFOUR; + } else { + ReadReg |= ((Block_Id % 2U) == 0U) ? + XRFDC_CRSE_MIX_I_Q_FSBYTWO : + XRFDC_CRSE_MIX_Q_ODD_FSBYFOUR; + } + } else { + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 0) || + (Type == XRFDC_DAC_TILE)) { + ReadReg |= XRFDC_CRSE_MIX_R_Q_FSBYFOUR; + } else { + ReadReg |= ((Block_Id % 2U) == 0U) ? + XRFDC_CRSE_MIX_OFF : + XRFDC_CRSE_MIX_Q_ODD_FSBYFOUR; + } + } + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_MXR_CFG1_OFFSET, (u16)ReadReg); + } else if (CoarseMixFreq == XRFDC_COARSE_MIX_MIN_SAMPLE_FREQ_BY_FOUR) { + /* Coarse Mix freq -Fs/4 */ + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_MXR_CFG0_OFFSET); + ReadReg &= ~XRFDC_MIX_CFG0_MASK; + if ((MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2C) || + (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2R)) { + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 0) || + (Type == XRFDC_DAC_TILE)) { + ReadReg |= XRFDC_CRSE_MIX_I_MINFSBYFOUR; + } else { + ReadReg |= ((Block_Id % 2U) == 0U) ? + XRFDC_CRSE_MIX_I_Q_FSBYTWO : + XRFDC_CRSE_MIX_Q_ODD_FSBYFOUR; + } + } else { + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 0) || + (Type == XRFDC_DAC_TILE)) { + ReadReg |= XRFDC_CRSE_MIX_R_I_MINFSBYFOUR; + } else { + ReadReg |= ((Block_Id % 2U) == 0U) ? + XRFDC_CRSE_MIX_I_Q_FSBYTWO : + XRFDC_CRSE_MIX_OFF; + } + } + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_MXR_CFG0_OFFSET, (u16)ReadReg); + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_MXR_CFG1_OFFSET); + ReadReg &= ~XRFDC_MIX_CFG1_MASK; + if ((MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2C) || + (MixerSettingsPtr->MixerMode == XRFDC_MIXER_MODE_C2R)) { + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 0) || + (Type == XRFDC_DAC_TILE)) { + ReadReg |= XRFDC_CRSE_MIX_Q_MINFSBYFOUR; + } else { + ReadReg |= ((Block_Id % 2U) == 0U) ? + XRFDC_CRSE_MIX_I_Q_FSBYTWO : + XRFDC_CRSE_MIX_I_ODD_FSBYFOUR; + } + } else { + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 0) || + (Type == XRFDC_DAC_TILE)) { + ReadReg |= XRFDC_CRSE_MIX_R_Q_MINFSBYFOUR; + } else { + ReadReg |= ((Block_Id % 2U) == 0U) ? + XRFDC_CRSE_MIX_OFF : + XRFDC_CRSE_MIX_I_ODD_FSBYFOUR; + } + } + XRFdc_WriteReg16(InstancePtr, BaseAddr, + XRFDC_ADC_MXR_CFG1_OFFSET, (u16)ReadReg); + } else if (CoarseMixFreq == XRFDC_COARSE_MIX_OFF) { + /* Coarse Mix OFF */ + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_MXR_CFG0_OFFSET, + XRFDC_MIX_CFG0_MASK, XRFDC_CRSE_MIX_OFF); + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_ADC_MXR_CFG1_OFFSET, + XRFDC_MIX_CFG1_MASK, XRFDC_CRSE_MIX_OFF); + } else { + metal_log(METAL_LOG_ERROR, "\n Invalid Coarse " + "Mixer frequency in %s\r\n", __func__); + } + + /* Fine mixer mode is OFF */ + XRFdc_WriteReg16(InstancePtr, BaseAddr, XRFDC_MXR_MODE_OFFSET, + XRFDC_MIXER_MODE_OFF); +} + +/*****************************************************************************/ +/** +* +* The API returns back Mixer/NCO settings to the caller. +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Block_Id is ADC/DAC block number inside the tile. Valid values +* are 0-3. +* @param MixerSettingsPtr Pointer to the XRFdc_Mixer_Settings structure +* in which the Mixer/NCO settings are passed. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_FAILURE if Block not enabled. +* +* @note FineMixerScale in Mixer_Settings structure can have 3 values. +* XRFDC_MIXER_SCALE_* represents the valid values. +* XRFDC_MIXER_SCALE_AUTO - If mixer mode is R2C, Mixer Scale is +* set to 1 and for other modes mixer scale is set to 0.7 +* XRFDC_MIXER_SCALE_1P0 - To set fine mixer scale to 1. +* XRFDC_MIXER_SCALE_0P7 - To set fine mixer scale to 0.7. +* +******************************************************************************/ +u32 XRFdc_GetMixerSettings(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Block_Id, XRFdc_Mixer_Settings *MixerSettingsPtr) +{ + u32 Status; + u32 BaseAddr; + u64 ReadReg; + u64 ReadReg_Mix1; + double SamplingRate; + s64 Freq; + s32 PhaseOffset; + u32 Block; + u8 CalibrationMode = 0U; + XRFdc_Mixer_Settings *MixerConfigPtr; + u32 NyquistZone = 0U; + double NCOFreq; + u32 FineMixerMode; + u32 CoarseMixerMode = 0x0; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(MixerSettingsPtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckDigitalPathEnabled(InstancePtr, Type, Tile_Id, Block_Id); + if (Status != XRFDC_SUCCESS) { + goto RETURN_PATH; + } + + Block = Block_Id; + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 1) && + (Type == XRFDC_ADC_TILE)) { + if (Block_Id == XRFDC_BLK_ID1) { + Block_Id = XRFDC_BLK_ID3; + } + if (Block_Id == XRFDC_BLK_ID0) { + Block_Id = XRFDC_BLK_ID1; + } + } + + if (Type == XRFDC_ADC_TILE) { + /* ADC */ + SamplingRate = InstancePtr->ADC_Tile[Tile_Id].PLL_Settings. + SampleRate; + MixerConfigPtr = &InstancePtr->ADC_Tile[Tile_Id]. + ADCBlock_Digital_Datapath[Block_Id].Mixer_Settings; + } else { + /* DAC */ + SamplingRate = InstancePtr->DAC_Tile[Tile_Id].PLL_Settings. + SampleRate; + MixerConfigPtr = &InstancePtr->DAC_Tile[Tile_Id]. + DACBlock_Digital_Datapath[Block_Id].Mixer_Settings; + } + + if (SamplingRate <= 0) { + Status = XRFDC_FAILURE; + metal_log(METAL_LOG_ERROR, "\n Incorrect Sampling rate " + "in %s\r\n", __func__); + goto RETURN_PATH; + } + + BaseAddr = XRFDC_BLOCK_BASE(Type, Tile_Id, Block_Id); + SamplingRate *= XRFDC_MILLI; + ReadReg = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_MXR_CFG0_OFFSET, XRFDC_MIX_CFG0_MASK); + ReadReg_Mix1 = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_ADC_MXR_CFG1_OFFSET, XRFDC_MIX_CFG1_MASK); + MixerSettingsPtr->CoarseMixFreq = 0x20; + + /* Identify CoarseMixFreq and CoarseMixerMode */ + if (ReadReg == XRFDC_CRSE_MIX_BYPASS) { + if (ReadReg_Mix1 == XRFDC_CRSE_MIX_BYPASS) { + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_BYPASS; + CoarseMixerMode = XRFDC_MIXER_MODE_C2C; + } else if (ReadReg_Mix1 == XRFDC_CRSE_MIX_OFF) { + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_BYPASS; + CoarseMixerMode = XRFDC_MIXER_MODE_R2R; + if (MixerConfigPtr->MixerMode == XRFDC_MIXER_MODE_R2C) { + CoarseMixerMode = XRFDC_MIXER_MODE_R2C; + } + } + } + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 0) || + (Type == XRFDC_DAC_TILE)) { + if ((ReadReg_Mix1 == XRFDC_CRSE_MIX_I_Q_FSBYTWO) && + (ReadReg == XRFDC_CRSE_MIX_I_Q_FSBYTWO)) { + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_TWO; + CoarseMixerMode = XRFDC_MIXER_MODE_C2C; + } else if ((ReadReg_Mix1 == XRFDC_CRSE_MIX_OFF) && + (ReadReg == XRFDC_CRSE_MIX_I_Q_FSBYTWO)) { + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_TWO; + CoarseMixerMode = XRFDC_MIXER_MODE_R2C; + } + } else { + if (ReadReg == XRFDC_CRSE_4GSPS_ODD_FSBYTWO) { + if (ReadReg_Mix1 == XRFDC_CRSE_4GSPS_ODD_FSBYTWO) { + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_TWO; + CoarseMixerMode = XRFDC_MIXER_MODE_C2C; + } else if (ReadReg_Mix1 == XRFDC_CRSE_MIX_OFF) { + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_TWO; + CoarseMixerMode = XRFDC_MIXER_MODE_R2C; + } + } + } + + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 0) || + (Type == XRFDC_DAC_TILE)) { + if ((ReadReg_Mix1 == XRFDC_CRSE_MIX_Q_FSBYFOUR) && + (ReadReg == XRFDC_CRSE_MIX_I_FSBYFOUR)) { + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_FOUR; + CoarseMixerMode = XRFDC_MIXER_MODE_C2C; + } else if ((ReadReg_Mix1 == + XRFDC_CRSE_MIX_R_Q_FSBYFOUR) && + (ReadReg == XRFDC_CRSE_MIX_R_I_MINFSBYFOUR)) { + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_FOUR; + CoarseMixerMode = XRFDC_MIXER_MODE_R2C; + } + } else { + if ((ReadReg == XRFDC_CRSE_MIX_I_ODD_FSBYFOUR) && + (ReadReg_Mix1 == XRFDC_CRSE_MIX_Q_ODD_FSBYFOUR)) { + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_FOUR; + CoarseMixerMode = XRFDC_MIXER_MODE_C2C; + } else if ((ReadReg == XRFDC_CRSE_MIX_OFF) && + (ReadReg_Mix1 == XRFDC_CRSE_MIX_Q_ODD_FSBYFOUR)) { + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_FOUR; + CoarseMixerMode = XRFDC_MIXER_MODE_R2C; + } + + } + + if ((XRFdc_IsHighSpeedADC(InstancePtr, Tile_Id) == 0) || + (Type == XRFDC_DAC_TILE)) { + if ((ReadReg_Mix1 == XRFDC_CRSE_MIX_I_FSBYFOUR) && + (ReadReg == XRFDC_CRSE_MIX_Q_FSBYFOUR)) { + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_MIN_SAMPLE_FREQ_BY_FOUR; + CoarseMixerMode = XRFDC_MIXER_MODE_C2C; + } else if ((ReadReg_Mix1 == XRFDC_CRSE_MIX_R_Q_MINFSBYFOUR) && + (ReadReg == XRFDC_CRSE_MIX_R_I_MINFSBYFOUR)) { + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_MIN_SAMPLE_FREQ_BY_FOUR; + CoarseMixerMode = XRFDC_MIXER_MODE_R2C; + } + } else { + if ((ReadReg == XRFDC_CRSE_MIX_Q_ODD_FSBYFOUR) && + (ReadReg_Mix1 == XRFDC_CRSE_MIX_I_ODD_FSBYFOUR)) { + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_MIN_SAMPLE_FREQ_BY_FOUR; + CoarseMixerMode = XRFDC_MIXER_MODE_C2C; + } else if ((ReadReg == XRFDC_CRSE_MIX_OFF) && + (ReadReg_Mix1 == XRFDC_CRSE_MIX_I_ODD_FSBYFOUR)) { + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_MIN_SAMPLE_FREQ_BY_FOUR; + CoarseMixerMode = XRFDC_MIXER_MODE_R2C; + } + + } + + if ((ReadReg == XRFDC_CRSE_MIX_OFF) && (ReadReg_Mix1 == + XRFDC_CRSE_MIX_OFF)) { + MixerSettingsPtr->CoarseMixFreq = XRFDC_COARSE_MIX_OFF; + CoarseMixerMode = XRFDC_MIXER_MODE_C2C; + } + if (MixerSettingsPtr->CoarseMixFreq == 0x20U) { + metal_log(METAL_LOG_ERROR, + "\n Coarse mixer settings not match any of the modes %s\r\n", + __func__); + } + if ((MixerConfigPtr->MixerMode == XRFDC_MIXER_MODE_C2R) && + (CoarseMixerMode == XRFDC_MIXER_MODE_C2C)) { + CoarseMixerMode = XRFDC_MIXER_MODE_C2R; + } + + /* Identify FineMixerMode */ + ReadReg = XRFdc_RDReg(InstancePtr, BaseAddr, XRFDC_MXR_MODE_OFFSET, + (XRFDC_EN_I_IQ_MASK | XRFDC_EN_Q_IQ_MASK)); + if (ReadReg == 0xFU) { + FineMixerMode = XRFDC_MIXER_MODE_C2C; + } else if (ReadReg == 0x3U) { + FineMixerMode = XRFDC_MIXER_MODE_C2R; + } else if (ReadReg == 0x5U) { + FineMixerMode = XRFDC_MIXER_MODE_R2C; + } else { + FineMixerMode = XRFDC_MIXER_MODE_OFF; + } + + if (FineMixerMode == XRFDC_MIXER_MODE_OFF) { + MixerSettingsPtr->MixerType = XRFDC_MIXER_TYPE_COARSE; + MixerSettingsPtr->MixerMode = CoarseMixerMode; + } else { + MixerSettingsPtr->MixerType = XRFDC_MIXER_TYPE_FINE; + MixerSettingsPtr->MixerMode = FineMixerMode; + } + + /* Identify Fine Mixer Scale */ + ReadReg = XRFdc_RDReg(InstancePtr, BaseAddr, + XRFDC_MXR_MODE_OFFSET, XRFDC_FINE_MIX_SCALE_MASK); + if (InstancePtr->UpdateMixerScale == 0x0U) { + MixerSettingsPtr->FineMixerScale = + XRFDC_MIXER_SCALE_AUTO; + } else if ((ReadReg != 0U) && + (InstancePtr->UpdateMixerScale == 0x1U)) { + MixerSettingsPtr->FineMixerScale = + XRFDC_MIXER_SCALE_1P0; + } else if (InstancePtr->UpdateMixerScale == 0x1U) { + MixerSettingsPtr->FineMixerScale = + XRFDC_MIXER_SCALE_0P7; + } else { + metal_log(METAL_LOG_ERROR, + "\n Invalid Fine mixer scale in %s\r\n", __func__); + Status = XRFDC_FAILURE; + goto RETURN_PATH; + } + + /* Phase Offset */ + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_NCO_PHASE_UPP_OFFSET); + PhaseOffset = ReadReg << XRFDC_NCO_PHASE_UPP_SHIFT; + PhaseOffset |= XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_NCO_PHASE_LOW_OFFSET); + PhaseOffset &= XRFDC_NCO_PHASE_MASK; + PhaseOffset = ((PhaseOffset << 14) >> 14); + MixerSettingsPtr->PhaseOffset = ((PhaseOffset * 180.0) / + XRFDC_NCO_PHASE_MULTIPLIER); + + /* NCO Frequency */ + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_NCO_FQWD_UPP_OFFSET); + Freq = ReadReg << XRFDC_NCO_FQWD_UPP_SHIFT; + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_NCO_FQWD_MID_OFFSET); + Freq |= ReadReg << XRFDC_NCO_FQWD_MID_SHIFT; + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_ADC_NCO_FQWD_LOW_OFFSET); + Freq |= ReadReg; + Freq &= XRFDC_NCO_FQWD_MASK; + Freq = (Freq << 16) >> 16; + if (Freq < 0) { + MixerSettingsPtr->Freq = ((Freq * SamplingRate) / + XRFDC_NCO_FREQ_MIN_MULTIPLIER); + } else { + MixerSettingsPtr->Freq = ((Freq * SamplingRate) / + XRFDC_NCO_FREQ_MULTIPLIER); + } + + /* Event Source */ + ReadReg = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_NCO_UPDT_OFFSET); + MixerSettingsPtr->EventSource = ReadReg & XRFDC_NCO_UPDT_MODE_MASK; + + /* Update NCO, CoarseMix freq based on calibration mode */ + NCOFreq = MixerConfigPtr->Freq; + if (Type == XRFDC_ADC_TILE) { + Status = XRFdc_GetCalibrationMode(InstancePtr, Tile_Id, + Block, &CalibrationMode); + if (Status != XRFDC_SUCCESS) { + return XRFDC_FAILURE; + } + + if (CalibrationMode == XRFDC_CALIB_MODE1) { + switch (MixerSettingsPtr->CoarseMixFreq) { + case XRFDC_COARSE_MIX_BYPASS: + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_TWO; + break; + case XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_FOUR: + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_MIN_SAMPLE_FREQ_BY_FOUR; + break; + case XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_TWO: + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_BYPASS; + break; + case XRFDC_COARSE_MIX_MIN_SAMPLE_FREQ_BY_FOUR: + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_SAMPLE_FREQ_BY_FOUR; + break; + default: + MixerSettingsPtr->CoarseMixFreq = + XRFDC_COARSE_MIX_OFF; + break; + } + NCOFreq = (MixerConfigPtr->Freq - + (SamplingRate / 2.0)); + } + } + + if ((NCOFreq > (SamplingRate / 2.0)) || + (NCOFreq < -(SamplingRate / 2.0))) { + Status = XRFdc_GetNyquistZone(InstancePtr, + Type, Tile_Id, Block, &NyquistZone); + if (Status != XRFDC_SUCCESS) { + return XRFDC_FAILURE; + } + + if ((NyquistZone == XRFDC_EVEN_NYQUIST_ZONE) && + (MixerSettingsPtr->Freq != 0)) { + MixerSettingsPtr->Freq *= -1; + } + + do { + if (NCOFreq < -(SamplingRate / 2.0)) { + NCOFreq += SamplingRate; + MixerSettingsPtr->Freq -= SamplingRate; + } + if (NCOFreq > (SamplingRate / 2.0)) { + NCOFreq -= SamplingRate; + MixerSettingsPtr->Freq += SamplingRate; + } + } while ((NCOFreq > (SamplingRate / 2.0)) || + (NCOFreq < -(SamplingRate / 2.0))); + } + if ((Type == XRFDC_ADC_TILE) && + (CalibrationMode == XRFDC_CALIB_MODE1)) { + MixerSettingsPtr->Freq += (SamplingRate / 2.0); + } + + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; + +} + +/** @} */ diff --git a/mpm/lib/rfdc/xrfdc_mts.c b/mpm/lib/rfdc/xrfdc_mts.c new file mode 100644 index 000000000..00a1abc85 --- /dev/null +++ b/mpm/lib/rfdc/xrfdc_mts.c @@ -0,0 +1,1308 @@ +/****************************************************************************** +* +* Copyright (C) 2018-2019 Xilinx, Inc. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* XILINX BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Except as contained in this notice, the name of the Xilinx shall not be used +* in advertising or otherwise to promote the sale, use or other dealings in +* this Software without prior written authorization from Xilinx. +* +******************************************************************************/ +/*****************************************************************************/ +/** +* +* @file xrfdc_mts.c +* @addtogroup xrfdc_v6_0 +* @{ +* +* Contains the multi tile sync functions of the XRFdc driver. +* +* <pre> +* MODIFICATION HISTORY: +* +* Ver Who Date Changes +* ----- --- -------- ----------------------------------------------- +* 3.1 jm 01/24/18 Initial release +* 3.2 jm 03/12/18 Fixed DAC latency calculation. +* jm 03/12/18 Added support for reloading DTC scans. +* jm 03/12/18 Add option to configure sysref capture after MTS. +* 4.0 sk 04/09/18 Added API to enable/disable the sysref. +* rk 04/17/18 Adjust calculated latency by sysref period, where doing +* so results in closer alignment to the target latency. +* 5.0 sk 08/03/18 Fixed MISRAC warnings. +* sk 08/03/18 Check for Block0 enable for tiles participating in MTS. +* sk 08/24/18 Reorganize the code to improve readability and +* optimization. +* 5.1 cog 01/29/19 Replace structure reference ADC checks with +* function. +* 6.0 cog 02/17/19 Added XRFdc_GetMTSEnable API. +* +* </pre> +* +******************************************************************************/ + +/***************************** Include Files *********************************/ +#include "mpm/rfdc/xrfdc_mts.h" + +/************************** Constant Definitions *****************************/ + + +/**************************** Type Definitions *******************************/ + +/***************** Macros (Inline Functions) Definitions *********************/ + +/************************** Function Prototypes ******************************/ + +static void XRFdc_MTS_Sysref_TRx(XRFdc *InstancePtr, u32 Enable); +static void XRFdc_MTS_Sysref_Ctrl(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Is_PLL, u32 Enable_Cap, u32 Enable_Div_Reset); +static u32 XRFdc_MTS_Sysref_Dist(XRFdc *InstancePtr, int Num_DAC); +static u32 XRFdc_MTS_Sysref_Count(XRFdc *InstancePtr, u32 Type, u32 Count_Val); +static u32 XRFdc_MTS_Dtc_Scan(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + XRFdc_MTS_DTC_Settings *SettingsPtr); +static u32 XRFdc_MTS_Dtc_Code(XRFdc *InstancePtr, u32 Type, u32 BaseAddr, + u32 SRCtrlAddr, u32 DTCAddr, u16 SRctl, u16 SRclr_m, u32 Code); +static u32 XRFdc_MTS_Dtc_Calc(u32 Type, u32 Tile_Id, + XRFdc_MTS_DTC_Settings *SettingsPtr, u8 *FlagsPtr); +static void XRFdc_MTS_Dtc_Flag_Debug(u8 *FlagsPtr, u32 Type, u32 Tile_Id, + u32 Target, u32 Picked); +static void XRFdc_MTS_FIFOCtrl(XRFdc *InstancePtr, u32 Type, u32 FIFO_Mode, + u32 Tiles_To_Clear); +static u32 XRFdc_MTS_GetMarker(XRFdc *InstancePtr, u32 Type, u32 Tiles, + XRFdc_MTS_Marker *MarkersPtr, int Marker_Delay); +static void XRFdc_MTS_Marker_Read(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 FIFO_Id, u32 *CountPtr, u32 *LocPtr, u32 *DonePtr); +static u32 XRFdc_MTS_Latency(XRFdc *InstancePtr, u32 Type, + XRFdc_MultiConverter_Sync_Config *ConfigPtr, XRFdc_MTS_Marker *MarkersPtr); + +/*****************************************************************************/ +/** +* +* This API enables the master tile sysref Tx/Rx +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Enable the master tile sysref for Tx/Rx, valid values are 0 and 1. +* +* @return +* - None. +* +* @note None +* +******************************************************************************/ +static void XRFdc_MTS_Sysref_TRx(XRFdc *InstancePtr, u32 Enable) +{ + u32 BaseAddr; + u32 Data; + + BaseAddr = XRFDC_DRP_BASE(XRFDC_DAC_TILE, 0) + XRFDC_HSCOM_ADDR; + Data = (Enable != 0U) ? 0xFFFFU : 0U; + + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_MTS_SRCAP_T1, + XRFDC_MTS_SRCAP_EN_TRX_M, Data); +} + +/*****************************************************************************/ +/** +* +* This API Control SysRef Capture Settings +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Is_PLL Valid values are 0 and 1. +* @param Enable_Cap Valid values are 0 and 1. +* @param Enable_Div_Reset Valid values are 0 and 1. +* +* @return +* - None. +* +* @note None +* +******************************************************************************/ +static void XRFdc_MTS_Sysref_Ctrl(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 Is_PLL, u32 Enable_Cap, u32 Enable_Div_Reset) +{ + u32 BaseAddr; + u16 RegData; + + RegData = 0U; + BaseAddr = XRFDC_DRP_BASE(Type, Tile_Id) + XRFDC_HSCOM_ADDR; + + /* Write some bits to ensure sysref is in the right mode */ + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_MTS_SRCAP_T1, + XRFDC_MTS_SRCAP_INIT_M, 0U); + + if (Is_PLL != 0U) { + /* PLL Cap */ + RegData = (Enable_Cap != 0U) ? XRFDC_MTS_SRCAP_PLL_M : 0U; + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_MTS_SRCAP_PLL, + XRFDC_MTS_SRCAP_PLL_M, RegData); + } else { + /* Analog Cap disable */ + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_MTS_SRCAP_T1, + XRFDC_MTS_SRCAP_T1_EN, 0U); + + /* Analog Divider */ + RegData = (Enable_Div_Reset != 0U) ? 0U : XRFDC_MTS_SRCAP_T1_RST; + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_MTS_SRCAP_T1, + XRFDC_MTS_SRCAP_T1_RST, RegData); + + /* Digital Divider */ + RegData = (Enable_Div_Reset != 0U) ? 0U : XRFDC_MTS_SRCAP_DIG_M; + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_MTS_SRCAP_DIG, + XRFDC_MTS_SRCAP_DIG_M, RegData); + + /* Set SysRef Cap Clear */ + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_MTS_SRCAP_T1, + XRFDC_MTS_SRCLR_T1_M, XRFDC_MTS_SRCLR_T1_M); + + /* Analog Cap enable */ + RegData = (Enable_Cap != 0U) ? XRFDC_MTS_SRCAP_T1_EN : 0U; + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_MTS_SRCAP_T1, + XRFDC_MTS_SRCAP_T1_EN, RegData); + + /* Unset SysRef Cap Clear */ + XRFdc_ClrSetReg(InstancePtr, BaseAddr, XRFDC_MTS_SRCAP_T1, + XRFDC_MTS_SRCLR_T1_M, 0U); + } +} + +/*****************************************************************************/ +/** +* +* This API Update SysRef Distribution between tiles +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Num_DAC is number of DAC tiles +* +* @return +* - XRFDC_MTS_OK if successful. +* - XRFDC_MTS_NOT_SUPPORTED +* +* @note None +* +******************************************************************************/ +static u32 XRFdc_MTS_Sysref_Dist(XRFdc *InstancePtr, int Num_DAC) +{ + + if (Num_DAC < 0) { + /* Auto-detect. Only 2 types Supported - 2GSPS ADCs, 4GSPS ADCs */ + if (XRFdc_IsHighSpeedADC(InstancePtr,0) != 0U) { + Num_DAC = 2; + } else { + Num_DAC = 4; + } + } + + if (Num_DAC == XRFDC_NUM_OF_TILES2) { + /* 2 DACs, 4ADCs */ + XRFdc_WriteReg16(InstancePtr, XRFDC_DAC_TILE_DRP_ADDR(0U), + XRFDC_MTS_SRDIST, 0xC980U); + XRFdc_WriteReg16(InstancePtr, XRFDC_DAC_TILE_DRP_ADDR(1U), + XRFDC_MTS_SRDIST, 0x0100U); + XRFdc_WriteReg16(InstancePtr, XRFDC_ADC_TILE_DRP_ADDR(3U), + XRFDC_MTS_SRDIST, 0x1700U); + } else if (Num_DAC == XRFDC_NUM_OF_TILES4) { + /* 4 DACs, 4ADCs */ + XRFdc_WriteReg16(InstancePtr, XRFDC_DAC_TILE_DRP_ADDR(0U), + XRFDC_MTS_SRDIST, 0xCA80U); + XRFdc_WriteReg16(InstancePtr, XRFDC_DAC_TILE_DRP_ADDR(1U), + XRFDC_MTS_SRDIST, 0x2400U); + XRFdc_WriteReg16(InstancePtr, XRFDC_DAC_TILE_DRP_ADDR(2U), + XRFDC_MTS_SRDIST, 0x0980U); + XRFdc_WriteReg16(InstancePtr, XRFDC_DAC_TILE_DRP_ADDR(3U), + XRFDC_MTS_SRDIST, 0x0100U); + XRFdc_WriteReg16(InstancePtr, XRFDC_ADC_TILE_DRP_ADDR(3U), + XRFDC_MTS_SRDIST, 0x0700U); + } else { + return XRFDC_MTS_NOT_SUPPORTED; + } + + XRFdc_WriteReg16(InstancePtr, XRFDC_ADC_TILE_DRP_ADDR(0U), + XRFDC_MTS_SRDIST, 0x0280U); + XRFdc_WriteReg16(InstancePtr, XRFDC_ADC_TILE_DRP_ADDR(1U), + XRFDC_MTS_SRDIST, 0x0600U); + XRFdc_WriteReg16(InstancePtr, XRFDC_ADC_TILE_DRP_ADDR(2U), + XRFDC_MTS_SRDIST, 0x8880U); + + return XRFDC_MTS_OK; +} + +/*****************************************************************************/ +/** +* +* This API Wait for a number of sysref's to be captured +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Count_Val to wait for a number of sysref's to be captured. +* +* @return +* - XRFDC_MTS_OK if successful. +* - XRFDC_MTS_TIMEOUT if timeout occurs. +* +* @note None +* +******************************************************************************/ +static u32 XRFdc_MTS_Sysref_Count(XRFdc *InstancePtr, u32 Type, u32 Count_Val) +{ + u32 RegData; + u32 Timeout; + u32 Shift; + + RegData = (Type == XRFDC_DAC_TILE) ? 0x2U : 0x1U; + Shift = (Type == XRFDC_DAC_TILE) ? 8U : 0U; + + /* Start counter */ + XRFdc_WriteReg(InstancePtr, 0U, XRFDC_MTS_SRCOUNT_CTRL, RegData); + + /* Check counter with timeout in case sysref is not active */ + Timeout = 0U; + while (Timeout < XRFDC_MTS_SRCOUNT_TIMEOUT) { + RegData = XRFdc_ReadReg(InstancePtr, 0U, XRFDC_MTS_SRCOUNT_VAL); + RegData = ((RegData >> Shift) & XRFDC_MTS_SRCOUNT_M); + if (RegData >= Count_Val) { + break; + } + Timeout++; + } + + if (Timeout >= XRFDC_MTS_SRCOUNT_TIMEOUT) { + metal_log(METAL_LOG_ERROR, + "PL SysRef Timeout - PL SysRef not active: %d\n in %s\n", + Timeout, __func__); + return XRFDC_MTS_TIMEOUT; + } + + return XRFDC_MTS_OK; +} + +/*****************************************************************************/ +/** +* +* This API print the DTC scan results +* +* +* @param FlagsPtr is for internal usage. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param Target is for internal usage. +* @param Picked is for internal usage. +* +* @return None +* +* @note None +* +******************************************************************************/ +static void XRFdc_MTS_Dtc_Flag_Debug(u8 *FlagsPtr, u32 Type, u32 Tile_Id, + u32 Target, u32 Picked) +{ + u32 Index; + char buf[XRFDC_MTS_NUM_DTC+1]; + + for (Index = 0U; Index < XRFDC_MTS_NUM_DTC; Index++) { + if (Index == Picked) { + buf[Index] = '*'; + } else if (Index == Target) { + buf[Index] = '#'; + } else { + buf[Index] = '0' + FlagsPtr[Index]; + } + } + buf[XRFDC_MTS_NUM_DTC] = '\0'; + metal_log(METAL_LOG_INFO, "%s%d: %s\n", + (Type == XRFDC_DAC_TILE) ? "DAC" : "ADC", Tile_Id, buf); + + (void)buf; + (void)Type; + (void)Tile_Id; + +} + +/*****************************************************************************/ +/** +* +* This API Calculate the best DTC code to use +* +* +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param SettingsPtr dtc settings structure. +* @param FlagsPtr is for internal usage. +* +* @return +* - XRFDC_MTS_OK if successful. +* - XRFDC_MTS_NOT_SUPPORTED if MTS is not supported. +* +* @note None +* +******************************************************************************/ +static u32 XRFdc_MTS_Dtc_Calc(u32 Type, u32 Tile_Id, + XRFdc_MTS_DTC_Settings *SettingsPtr, u8 *FlagsPtr) +{ + u32 Index, Status, Num_Found; + int Last, Current_Gap, Max_Overlap, Overlap_Cnt; + int Min_Gap, Max_Gap, Diff, Min_Diff, Min_Range, Val, Target; + u8 Min_Gap_Allowed; + int Codes[XRFDC_MTS_MAX_CODE] = {0}; + + Min_Gap_Allowed = (SettingsPtr->IsPLL != 0U) ? XRFDC_MTS_MIN_GAP_PLL : + XRFDC_MTS_MIN_GAP_T1; + Status = XRFDC_MTS_OK; + + /* Scan the flags and find candidate DTC codes */ + Num_Found = 0U; + Max_Gap = 0; + Min_Gap = XRFDC_MTS_NUM_DTC; + Max_Overlap = 0; + Overlap_Cnt = 0; + Last = -1; + FlagsPtr[XRFDC_MTS_NUM_DTC] = 1; + for (Index = 0U; Index <= XRFDC_MTS_NUM_DTC; Index++) { + Current_Gap = Index-Last; + if (FlagsPtr[Index] != 0) { + if (Current_Gap > Min_Gap_Allowed) { + Codes[Num_Found] = Last + (Current_Gap / 2); + Num_Found++; + /* Record max/min gaps */ + Current_Gap--; + if (Current_Gap > Max_Gap) { + Max_Gap = Current_Gap; + } + if (Current_Gap < Min_Gap) { + Min_Gap = Current_Gap; + } + } + Last = Index; + } + /* check for the longest run of overlapping codes */ + if (FlagsPtr[Index] == 3U) { + Overlap_Cnt++; + if (Overlap_Cnt > Max_Overlap) { + Max_Overlap = Overlap_Cnt; + } + } else { + Overlap_Cnt = 0; + } + } + + /* Record some stats */ + SettingsPtr->Num_Windows[Tile_Id] = Num_Found; + SettingsPtr->Max_Gap[Tile_Id] = Max_Gap; + SettingsPtr->Min_Gap[Tile_Id] = Min_Gap; + SettingsPtr->Max_Overlap[Tile_Id] = Max_Overlap; + + /* Calculate the best code */ + if (SettingsPtr->Scan_Mode == XRFDC_MTS_SCAN_INIT) { + /* Initial scan */ + if (Tile_Id == SettingsPtr->RefTile) { + /* RefTile: Get the code closest to the target */ + Target = XRFDC_MTS_REF_TARGET; + SettingsPtr->Target[Tile_Id] = XRFDC_MTS_REF_TARGET; + Min_Diff = XRFDC_MTS_NUM_DTC; + /* scan all codes to find the closest */ + for (Index = 0U; Index < Num_Found; Index++) { + Diff = abs(Target - Codes[Index]); + if (Diff < Min_Diff) { + Min_Diff = Diff; + SettingsPtr->DTC_Code[Tile_Id] = Codes[Index]; + } + metal_log(METAL_LOG_DEBUG, + "Target %d, DTC Code %d, Diff %d, Min %d\n", Target, + Codes[Index], Diff, Min_Diff); + } + /* set the reference code as the target for the other tiles */ + for (Index = 0U; Index < 4U; Index++) { + if (Index != Tile_Id) { + SettingsPtr->Target[Index] = SettingsPtr->DTC_Code[Tile_Id]; + } + } + metal_log(METAL_LOG_DEBUG, + "RefTile (%d): DTC Code Target %d, Picked %d\n", Tile_Id, + Target, SettingsPtr->DTC_Code[Tile_Id]); + + } else { + /* + * Other Tiles: Get the code that minimises the total range of codes + * compute the range of the existing dtc codes + */ + Max_Gap = 0; + Min_Gap = XRFDC_MTS_NUM_DTC; + for (Index = XRFDC_TILE_ID0; Index < XRFDC_TILE_ID4; Index++) { + Val = SettingsPtr->DTC_Code[Index]; + if ((Val != -1) && (Val > Max_Gap)) { + Max_Gap = Val; + } + if ((Val != -1) && (Val < Min_Gap)) { + Min_Gap = Val; + } + } + metal_log(METAL_LOG_DEBUG, + "Tile (%d): Max/Min %d/%d, Range %d\n", Tile_Id, Max_Gap, + Min_Gap, Max_Gap-Min_Gap); + Min_Range = XRFDC_MTS_NUM_DTC; + for (Index = 0U; Index < Num_Found; Index++) { + Val = Codes[Index]; + Diff = Max_Gap - Min_Gap; + if (Val < Min_Gap) { + Diff = Max_Gap - Val; + } + if (Val > Max_Gap) { + Diff = Val - Min_Gap; + } + if (Diff <= Min_Range) { + Min_Range = Diff; + SettingsPtr->DTC_Code[Tile_Id] = Val; + } + metal_log(METAL_LOG_DEBUG, + "Tile (%d): Code %d, New-Range: %d, Min-Range: %d\n", + Tile_Id, Val, Diff, Min_Range); + } + metal_log(METAL_LOG_DEBUG, + "Tile (%d): Code %d, Range Prev %d, New %d\n", Tile_Id, + SettingsPtr->DTC_Code[Tile_Id], Max_Gap-Min_Gap, Min_Range); + } + } else { + /* Reload the results of an initial scan to seed a new scan */ + if (Tile_Id == SettingsPtr->RefTile) { + /* RefTile: Get code closest to the target */ + Target = SettingsPtr->Target[Tile_Id]; + } else { + Target = SettingsPtr->DTC_Code[SettingsPtr->RefTile] + + SettingsPtr->Target[Tile_Id] - SettingsPtr->Target[SettingsPtr->RefTile]; + } + Min_Diff = XRFDC_MTS_NUM_DTC; + /* scan all codes to find the closest */ + for (Index = 0U; Index < Num_Found; Index++) { + Diff = abs(Target - Codes[Index]); + if (Diff < Min_Diff) { + Min_Diff = Diff; + SettingsPtr->DTC_Code[Tile_Id] = Codes[Index]; + } + metal_log(METAL_LOG_DEBUG, + "Reload Target %d, DTC Code %d, Diff %d, Min %d\n", Target, + Codes[Index], Diff, Min_Diff); + } + } + + /* Print some debug info */ + XRFdc_MTS_Dtc_Flag_Debug(FlagsPtr, Type, Tile_Id, SettingsPtr->Target[Tile_Id], + SettingsPtr->DTC_Code[Tile_Id]); + + return Status; +} + +/*****************************************************************************/ +/** +* +* This API Set a DTC code and wait for it to be updated. Return early/late +* flags, if set +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param BaseAddr is for internal usage. +* @param SRCtrlAddr is for internal usage. +* @param DTCAddr is for internal usage. +* @param SRctl is for internal usage. +* @param SRclr_m is for internal usage. +* @param Code is for internal usage. +* +* @return +* - XRFDC_MTS_OK if successful. +* - XRFDC_MTS_TIMEOUT if timeout occurs. +* +* @note None +* +******************************************************************************/ +static u32 XRFdc_MTS_Dtc_Code(XRFdc *InstancePtr, u32 Type, u32 BaseAddr, + u32 SRCtrlAddr, u32 DTCAddr, u16 SRctl, u16 SRclr_m, u32 Code) +{ + u32 Status; + + /* set the DTC code */ + XRFdc_WriteReg16(InstancePtr, BaseAddr, DTCAddr, Code); + + /* set sysref cap clear */ + XRFdc_WriteReg16(InstancePtr, BaseAddr, SRCtrlAddr, SRctl | SRclr_m); + + /* unset sysref cap clear */ + XRFdc_WriteReg16(InstancePtr, BaseAddr, SRCtrlAddr, SRctl); + + Status = XRFdc_MTS_Sysref_Count(InstancePtr, Type, XRFDC_MTS_DTC_COUNT); + + return Status; +} + +/*****************************************************************************/ +/** +* +* This API Scan the DTC codes and determine the optimal capture code for +* both PLL and T1 cases +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param SettingsPtr dtc settings structure. +* +* @return +* - XRFDC_MTS_OK if successful. +* - XRFDC_MTS_TIMEOUT if timeout occurs. +* +* @note None +* +******************************************************************************/ +static u32 XRFdc_MTS_Dtc_Scan (XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + XRFdc_MTS_DTC_Settings *SettingsPtr) +{ + u32 Status; + u32 BaseAddr; + u32 SRCtrlAddr; + u32 DTCAddr; + u8 Flags[XRFDC_MTS_NUM_DTC+1]; + u16 SRctl; + u16 SRclr_m; + u16 Flag_s; + u32 Index; + + BaseAddr = XRFDC_DRP_BASE(Type, Tile_Id) + XRFDC_HSCOM_ADDR; + Status = XRFDC_MTS_OK; + + /* Enable SysRef Capture and Disable Divide Reset */ + XRFdc_MTS_Sysref_Ctrl(InstancePtr, Type, Tile_Id, SettingsPtr->IsPLL, 1, 0); + SRCtrlAddr = (SettingsPtr->IsPLL != 0U) ? XRFDC_MTS_SRCAP_PLL : XRFDC_MTS_SRCAP_T1; + DTCAddr = (SettingsPtr->IsPLL != 0U) ? XRFDC_MTS_SRDTC_PLL : XRFDC_MTS_SRDTC_T1; + SRclr_m = (SettingsPtr->IsPLL != 0U) ? XRFDC_MTS_SRCLR_PLL_M : XRFDC_MTS_SRCLR_T1_M; + Flag_s = (SettingsPtr->IsPLL != 0U) ? XRFDC_MTS_SRFLAG_PLL : XRFDC_MTS_SRFLAG_T1; + + SRctl = XRFdc_ReadReg16(InstancePtr, BaseAddr, SRCtrlAddr) & ~SRclr_m; + + for (Index = 0U; Index < XRFDC_MTS_NUM_DTC; Index++) { + Flags[Index] = 0U; + } + for (Index = 0U; (Index < XRFDC_MTS_NUM_DTC) && (Status == XRFDC_MTS_OK); Index++) { + Status |= XRFdc_MTS_Dtc_Code(InstancePtr, Type, BaseAddr, + SRCtrlAddr, DTCAddr, SRctl, SRclr_m, Index); + Flags[Index] = (XRFdc_ReadReg16(InstancePtr, BaseAddr, XRFDC_MTS_SRFLAG) >> + Flag_s) & 0x3U; + } + + /* Calculate the best DTC code */ + (void)XRFdc_MTS_Dtc_Calc(Type, Tile_Id, SettingsPtr, Flags); + + /* Program the calculated code */ + if (SettingsPtr->DTC_Code[Tile_Id] == -1) { + metal_log(METAL_LOG_ERROR, + "Unable to capture analog SysRef safely on %s tile %d\n" + , (Type == XRFDC_ADC_TILE) ? "ADC" : "DAC", Tile_Id); + Status |= XRFDC_MTS_DTC_INVALID; + } else { + (void)XRFdc_MTS_Dtc_Code(InstancePtr, Type, BaseAddr, SRCtrlAddr, DTCAddr, + SRctl, SRclr_m, SettingsPtr->DTC_Code[Tile_Id]); + } + + if (SettingsPtr->IsPLL != 0U) { + /* PLL - Disable SysRef Capture */ + XRFdc_MTS_Sysref_Ctrl(InstancePtr, Type, Tile_Id, 1, 0, 0); + } else { + /* T1 - Reset Dividers */ + XRFdc_MTS_Sysref_Ctrl(InstancePtr, Type, Tile_Id, 0, 1, 1); + Status |= XRFdc_MTS_Sysref_Count(InstancePtr, Type, + XRFDC_MTS_DTC_COUNT); + XRFdc_MTS_Sysref_Ctrl(InstancePtr, Type, Tile_Id, 0, 1, 0); + } + + return Status; +} + +/*****************************************************************************/ +/** +* +* This API Control the FIFO enable for the group. If Tiles_to_clear has bits +* set, the FIFOs of those tiles will have their FIFO flags cleared. +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param FIFO_Mode is fifo mode. +* @param Tiles_To_Clear bits set, FIFO flags will be cleared for those tiles. +* +* @return None +* +* @note None +* +******************************************************************************/ +static void XRFdc_MTS_FIFOCtrl (XRFdc *InstancePtr, u32 Type, u32 FIFO_Mode, + u32 Tiles_To_Clear) +{ + u32 RegAddr; + u32 BaseAddr; + u32 Tile_Id; + u32 Block_Id; + + /* Clear the FIFO Flags */ + RegAddr = (Type == XRFDC_ADC_TILE) ? XRFDC_ADC_FABRIC_ISR_OFFSET : + XRFDC_DAC_FABRIC_ISR_OFFSET; + for (Tile_Id = XRFDC_TILE_ID0; Tile_Id < XRFDC_TILE_ID4; Tile_Id++) { + if (((1U << Tile_Id) & Tiles_To_Clear) != 0U) { + for (Block_Id = XRFDC_BLK_ID0; Block_Id < XRFDC_BLK_ID4; Block_Id++) { + BaseAddr = XRFDC_DRP_BASE(Type, Tile_Id) + + XRFDC_BLOCK_ADDR_OFFSET(Block_Id); + XRFdc_WriteReg16(InstancePtr, BaseAddr, RegAddr, + XRFDC_IXR_FIFOUSRDAT_MASK); + } + } + } + + /* Enable the FIFOs */ + RegAddr = (Type == XRFDC_ADC_TILE) ? XRFDC_MTS_FIFO_CTRL_ADC : + XRFDC_MTS_FIFO_CTRL_DAC; + XRFdc_WriteReg(InstancePtr, 0, RegAddr, FIFO_Mode); +} + +/*****************************************************************************/ +/** +* +* This API Read-back the marker data for an ADC or DAC +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tile_Id Valid values are 0-3. +* @param FIFO_Id is FIFO number. +* @param Count is for internal usage. +* @param Loc is for internal usage. +* @param Done is for internal usage. +* +* @return +* - None. +* +* @note None +* +******************************************************************************/ +static void XRFdc_MTS_Marker_Read(XRFdc *InstancePtr, u32 Type, u32 Tile_Id, + u32 FIFO_Id, u32 *CountPtr, u32 *LocPtr, u32 *DonePtr) +{ + u32 BaseAddr; + u32 RegData = 0x0; + + if (Type == XRFDC_ADC_TILE) { + BaseAddr = XRFDC_DRP_BASE(Type, Tile_Id) - 0x2000; + RegData = XRFdc_ReadReg(InstancePtr, BaseAddr, + XRFDC_MTS_ADC_MARKER_CNT+(FIFO_Id << 2)); + *CountPtr = XRFDC_MTS_FIELD(RegData, XRFDC_MTS_AMARK_CNT_M, 0); + *LocPtr = XRFDC_MTS_FIELD(RegData, XRFDC_MTS_AMARK_LOC_M, + XRFDC_MTS_AMARK_LOC_S); + *DonePtr = XRFDC_MTS_FIELD(RegData, XRFDC_MTS_AMARK_DONE_M, + XRFDC_MTS_AMARK_DONE_S); + } else { + BaseAddr = XRFDC_DRP_BASE(Type, Tile_Id) + + XRFDC_BLOCK_ADDR_OFFSET(FIFO_Id); + *CountPtr = XRFdc_ReadReg(InstancePtr, BaseAddr, + XRFDC_MTS_DAC_MARKER_CNT); + *LocPtr = XRFdc_ReadReg(InstancePtr, BaseAddr, + XRFDC_MTS_DAC_MARKER_LOC); + *DonePtr = 1; + } + metal_log(METAL_LOG_DEBUG, + "Marker Read Tile %d, FIFO %d - %08X = %04X: count=%d, loc=%d," + "done=%d\n", Tile_Id, FIFO_Id, BaseAddr, RegData, *CountPtr, + *LocPtr, *DonePtr); +} + +/*****************************************************************************/ +/** +* +* This API Run the marker counter and read the results +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param Tiles is tiles to get marker +* @param MarkersPtr mts marker structure. +* @param Marker_Delay is marker delay. +* +* @return +* - XRFDC_MTS_OK if successful. +* - XRFDC_MTS_TIMEOUT if timeout occurs. +* - XRFDC_MTS_MARKER_RUN +* - XRFDC_MTS_MARKER_MISM +* - +* +* @note None +* +******************************************************************************/ +static u32 XRFdc_MTS_GetMarker(XRFdc *InstancePtr, u32 Type, u32 Tiles, + XRFdc_MTS_Marker *MarkersPtr, int Marker_Delay) +{ + u32 Done; + u32 Count; + u32 Loc; + u32 Tile_Id; + u32 Block_Id; + u32 Status; + + Status = XRFDC_MTS_OK; + if (Type == XRFDC_ADC_TILE) { + /* Reset marker counter */ + XRFdc_WriteReg(InstancePtr, 0, XRFDC_MTS_ADC_MARKER, 1); + XRFdc_WriteReg(InstancePtr, 0, XRFDC_MTS_ADC_MARKER, 0); + } else { + /* + * SysRef Capture should be still active from the DTC Scan + * but set it anyway to be sure + */ + for (Tile_Id = XRFDC_TILE_ID0; Tile_Id < XRFDC_TILE_ID4; Tile_Id++) { + if (((1U << Tile_Id) & Tiles) != 0U) { + XRFdc_MTS_Sysref_Ctrl(InstancePtr, XRFDC_DAC_TILE, + Tile_Id, 0, 1, 0); + } + } + + /* Set marker delay */ + XRFdc_WriteReg(InstancePtr, 0, XRFDC_MTS_DAC_MARKER_CTRL, + Marker_Delay); + } + + /* Allow the marker counter to run */ + Status |= XRFdc_MTS_Sysref_Count(InstancePtr, Type, + XRFDC_MTS_MARKER_COUNT); + + /* Read master FIFO (FIFO0 in each Tile) */ + for (Tile_Id = XRFDC_TILE_ID0; Tile_Id < XRFDC_TILE_ID4; Tile_Id++) { + if (((1U << Tile_Id) & Tiles) != 0U) { + if (Type == XRFDC_DAC_TILE) { + /* Disable SysRef Capture before reading it */ + XRFdc_MTS_Sysref_Ctrl(InstancePtr, XRFDC_DAC_TILE, + Tile_Id, 0, 0, 0); + Status |= XRFdc_MTS_Sysref_Count(InstancePtr, Type, + XRFDC_MTS_MARKER_COUNT); + } + + XRFdc_MTS_Marker_Read(InstancePtr, Type, Tile_Id, 0, &Count, + &Loc, &Done); + MarkersPtr->Count[Tile_Id] = Count; + MarkersPtr->Loc[Tile_Id] = Loc; + metal_log(METAL_LOG_INFO, + "%s%d: Marker: - %d, %d\n", (Type == XRFDC_DAC_TILE) ? + "DAC":"ADC", Tile_Id, MarkersPtr->Count[Tile_Id], MarkersPtr->Loc[Tile_Id]); + + if ((!Done) != 0U) { + metal_log(METAL_LOG_ERROR, "Analog SysRef timeout," + "SysRef not detected on %s tile %d\n", + (Type == XRFDC_ADC_TILE) ? "ADC" : "DAC", Tile_Id); + Status |= XRFDC_MTS_MARKER_RUN; + } + + /* + * Check all enabled FIFOs agree with the master FIFO. + * This is optional. + */ + for (Block_Id = XRFDC_BLK_ID0; Block_Id < XRFDC_BLK_ID4; Block_Id++) { + if (XRFdc_IsFifoEnabled(InstancePtr, Type, Tile_Id, Block_Id) != 0U) { + XRFdc_MTS_Marker_Read(InstancePtr, Type, Tile_Id, Block_Id, + &Count, &Loc, &Done); + if ((MarkersPtr->Count[Tile_Id] != Count) || + (MarkersPtr->Loc[Tile_Id] != Loc)) { + metal_log(METAL_LOG_DEBUG, + "Tile %d, FIFO %d Marker != Expected: %d, %d vs" + "%d, %d\n", Tile_Id, Block_Id, MarkersPtr->Count[Tile_Id], + MarkersPtr->Loc[Tile_Id], Count, Loc); + metal_log(METAL_LOG_ERROR, + "SysRef capture mismatch on %s tile %d," + " PL SysRef may not have been" + " captured synchronously\n", + (Type == XRFDC_ADC_TILE) ? "ADC" : "DAC", Block_Id); + Status |= XRFDC_MTS_MARKER_MISM; + + } + } + } + } + } + + return Status; +} + +/*****************************************************************************/ +/** +* +* This API Calculate the absoulte/relative latencies +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param ConfigPtr is mts config structure. +* @param MarkersPtr is mts marker structure. +* +* @return +* - XRFDC_MTS_OK if successful. +* - XRFDC_MTS_DELAY_OVER +* - XRFDC_MTS_TARGET_LOW +* - +* +* @note Latency calculation will use Sysref frequency counters +* logic which will work with IP version 2.0.1 and above. +* +******************************************************************************/ +static u32 XRFdc_MTS_Latency(XRFdc *InstancePtr, u32 Type, + XRFdc_MultiConverter_Sync_Config *ConfigPtr, XRFdc_MTS_Marker *MarkersPtr) +{ + u32 Status, Fifo, Index, BaseAddr, RegAddr; + int Count_W, Loc_W, Latency, Offset, Max_Latency, Target, Delta; + int I_Part, F_Part, SysRefT1Period, LatencyDiff, LatencyOffset; + u32 RegData, SysRefFreqCntrDone; + int Target_Latency = -1; + int LatencyOffsetDiff; + u32 Factor = 1U; + u32 Write_Words = 0U; + u32 Read_Words = 1U; + + Status = XRFDC_MTS_OK; + if (Type == XRFDC_ADC_TILE) { + (void)XRFdc_GetDecimationFactor(InstancePtr, ConfigPtr->RefTile, 0, &Factor); + } else { + (void)XRFdc_GetInterpolationFactor(InstancePtr, ConfigPtr->RefTile, 0, &Factor); + (void)XRFdc_GetFabWrVldWords(InstancePtr, Type, ConfigPtr->RefTile, 0, &Write_Words); + } + (void)XRFdc_GetFabRdVldWords(InstancePtr, Type, ConfigPtr->RefTile, 0, &Read_Words); + Count_W = Read_Words * Factor; + Loc_W = Factor; + + metal_log(METAL_LOG_DEBUG, + "Count_W %d, loc_W %d\n", Count_W, Loc_W); + + /* Find the individual latencies */ + Max_Latency = 0; + + /* Determine relative SysRef frequency */ + RegData = XRFdc_ReadReg(InstancePtr, 0, XRFDC_MTS_SRFREQ_VAL); + if (Type == XRFDC_ADC_TILE) { + /* ADC SysRef frequency information contained in lower 16 bits */ + RegData = RegData & 0XFFFFU; + } else { + /* DAC SysRef frequency information contained in upper 16 bits */ + RegData = (RegData >> 16U) & 0XFFFFU; + } + + /* + * Ensure SysRef frequency counter has completed. + * Sysref frequency counters logic will work with IP version + * 2.0.1 and above. + */ + SysRefFreqCntrDone = RegData & 0x1U; + if (SysRefFreqCntrDone == 0U) { + metal_log(METAL_LOG_ERROR, "Error : %s SysRef frequency counter not yet done\n", + (Type == XRFDC_ADC_TILE) ? "ADC" : "DAC"); + Status |= XRFDC_MTS_SYSREF_FREQ_NDONE; + /* Set SysRef period in terms of T1's will not be used */ + SysRefT1Period = 0; + } else { + SysRefT1Period = (RegData >> 1) * Count_W; + if (Type == XRFDC_DAC_TILE) { + /* + * DAC marker counter is on the tile clock domain so need + * to update SysRef period accordingly + */ + SysRefT1Period = (SysRefT1Period * Write_Words) / Read_Words; + } + metal_log(METAL_LOG_INFO, "SysRef period in terms of %s T1s = %d\n", + (Type == XRFDC_ADC_TILE) ? "ADC" : "DAC", SysRefT1Period); + } + + /* Work out the latencies */ + for (Index = XRFDC_TILE_ID0; Index < XRFDC_TILE_ID4; Index++) { + if (((1U << Index) & ConfigPtr->Tiles) != 0U) { + Latency = (MarkersPtr->Count[Index] * Count_W) + (MarkersPtr->Loc[Index] * Loc_W); + /* Set marker counter target on first tile */ + if (Target_Latency < 0) { + Target_Latency = ConfigPtr->Target_Latency; + if (Target_Latency < 0) { + Target_Latency = Latency; + } + metal_log(METAL_LOG_INFO, "%s target latency = %d\n", + (Type == XRFDC_ADC_TILE) ? "ADC" : "DAC", Target_Latency); + } + + /* + * Adjust reported counter values if offsetting by a SysRef + * period reduces distance between current and target latencies + */ + LatencyDiff = Target_Latency - Latency; + LatencyOffset = (LatencyDiff > 0) ? (Latency + SysRefT1Period) : + (Latency - SysRefT1Period); + LatencyOffsetDiff = Target_Latency - LatencyOffset; + if (abs(LatencyDiff) > abs(LatencyOffsetDiff)) { + Latency = LatencyOffset; + metal_log(METAL_LOG_INFO, "%s%d latency offset by a SysRef period to %d\n", + (Type == XRFDC_ADC_TILE) ? "ADC" : "DAC", Index, Latency); + } + ConfigPtr->Latency[Index] = Latency; + if (Latency > Max_Latency) { + Max_Latency = Latency; + } + metal_log(METAL_LOG_DEBUG, "Tile %d, latency %d, max %d\n", + Index, Latency, Max_Latency); + } + } + + /* + * Adjust the latencies to meet the target. Choose max, if it + * is not supplied by the user. + */ + Target = (ConfigPtr->Target_Latency < 0) ? Max_Latency : + ConfigPtr->Target_Latency; + + if (Target < Max_Latency) { + /* Cannot correct for -ve latencies, so default to aligning */ + Target = Max_Latency; + metal_log(METAL_LOG_ERROR, "Error : %s alignment target latency of %d < minimum possible %d\n", + (Type == XRFDC_ADC_TILE) ? "ADC" : "DAC", Target, Max_Latency); + Status |= XRFDC_MTS_TARGET_LOW; + } + + for (Index = XRFDC_TILE_ID0; Index < XRFDC_TILE_ID4; Index++) { + if (((1U << Index) & ConfigPtr->Tiles) != 0U) { + Delta = Target - ConfigPtr->Latency[Index]; + if (Delta < 0) { + Delta = 0; + } + I_Part = Delta / Factor; + F_Part = Delta % Factor; + Offset = I_Part; + if (F_Part > (int)(Factor / 2U)) { + Offset++; + } + metal_log(METAL_LOG_DEBUG, + "Target %d, Tile %d, delta %d, i/f_part %d/%d, offset %d\n", + Target, Index, Delta, I_Part, F_Part, Offset * Factor); + + /* check for excessive delay correction values */ + if (Offset > (int)XRFDC_MTS_DELAY_MAX) { + Offset = (int)XRFDC_MTS_DELAY_MAX; + metal_log(METAL_LOG_ERROR, + "Alignment correction delay %d" + " required exceeds maximum for %s Tile %d\n", + Offset, (Type == XRFDC_ADC_TILE) ? "ADC" : "DAC", + XRFDC_MTS_DELAY_MAX, Index); + Status |= XRFDC_MTS_DELAY_OVER; + } + + /* Adjust the latency, write the same value to each FIFO */ + BaseAddr = XRFDC_DRP_BASE(Type, Index) - 0x2000; + for (Fifo = XRFDC_BLK_ID0; Fifo < XRFDC_BLK_ID4; Fifo++) { + RegAddr = XRFDC_MTS_DELAY_CTRL + (Fifo << 2); + RegData = XRFdc_ReadReg(InstancePtr, BaseAddr, RegAddr); + RegData = XRFDC_MTS_RMW(RegData, XRFDC_MTS_DELAY_VAL_M, + Offset); + XRFdc_WriteReg(InstancePtr, BaseAddr, RegAddr, RegData); + } + + /* Report the total latency for this tile */ + ConfigPtr->Latency[Index] = ConfigPtr->Latency[Index] + (Offset * Factor); + ConfigPtr->Offset[Index] = Offset; + + /* Set the Final SysRef Capture Enable state */ + XRFdc_MTS_Sysref_Ctrl(InstancePtr, Type, Index, 0, ConfigPtr->SysRef_Enable, 0); + } + } + + return Status; +} + +/*****************************************************************************/ +/** +* +* This API is used to enable/disable the sysref. +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param DACSyncConfigPtr is pointer to DAC Multi-Tile Sync config structure. +* @param ADCSyncConfigPtr is pointer to ADC Multi-Tile Sync config structure. +* @param SysRefEnable valid values are 0(disable) and 1(enable). +* +* @return +* - XRFDC_MTS_OK if successful. +* +* @note None +* +******************************************************************************/ +u32 XRFdc_MTS_Sysref_Config(XRFdc *InstancePtr, + XRFdc_MultiConverter_Sync_Config *DACSyncConfigPtr, + XRFdc_MultiConverter_Sync_Config *ADCSyncConfigPtr, u32 SysRefEnable) +{ + u32 Tile; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(DACSyncConfigPtr != NULL); + Xil_AssertNonvoid(ADCSyncConfigPtr != NULL); + + /* Enable/disable SysRef Capture on all DACs participating in MTS */ + for (Tile = XRFDC_TILE_ID0; Tile < XRFDC_TILE_ID4; Tile++) { + if (((1U << Tile) & DACSyncConfigPtr->Tiles) != 0U) { + XRFdc_MTS_Sysref_Ctrl(InstancePtr, + XRFDC_DAC_TILE, Tile, 0, SysRefEnable, 0); + } + } + + /* Enable/Disable SysRef Capture on all ADCs participating in MTS */ + for (Tile = XRFDC_TILE_ID0; Tile < XRFDC_TILE_ID4; Tile++) { + if (((1U << Tile) & ADCSyncConfigPtr->Tiles) != 0U) { + XRFdc_MTS_Sysref_Ctrl(InstancePtr, + XRFDC_ADC_TILE, Tile, 0, SysRefEnable, 0); + } + } + + /* Enable/Disable SysRef TRX */ + XRFdc_MTS_Sysref_TRx(InstancePtr, SysRefEnable); + + return XRFDC_MTS_OK; +} + +/*****************************************************************************/ +/** +* +* This API Initializes the multi-tile sync config structures. +* Optionally allows target codes to be provided for the Pll/T1 +* analog sysref capture +* +* @param ConfigPtr pointer to Multi-tile sync config structure. +* @param PLL_CodesPtr pointer to PLL analog sysref capture. +* @param T1_CodesPtr pointer to T1 analog sysref capture. +* +* @return None +* +* @note None +* +******************************************************************************/ +void XRFdc_MultiConverter_Init(XRFdc_MultiConverter_Sync_Config *ConfigPtr, + int *PLL_CodesPtr, int *T1_CodesPtr) +{ + u32 Index; + + Xil_AssertVoid(ConfigPtr != NULL); + + ConfigPtr->RefTile = 0U; + ConfigPtr->DTC_Set_PLL.Scan_Mode = (PLL_CodesPtr == NULL) ? + XRFDC_MTS_SCAN_INIT : XRFDC_MTS_SCAN_RELOAD; + ConfigPtr->DTC_Set_T1.Scan_Mode = (T1_CodesPtr == NULL) ? + XRFDC_MTS_SCAN_INIT : XRFDC_MTS_SCAN_RELOAD; + ConfigPtr->DTC_Set_PLL.IsPLL = 1U; + ConfigPtr->DTC_Set_T1.IsPLL = 0U; + ConfigPtr->Target_Latency = -1; + ConfigPtr->Marker_Delay = 15; + ConfigPtr->SysRef_Enable = 1; /* By default enable Sysref capture after MTS */ + + /* Initialize variables per tile */ + for (Index = XRFDC_TILE_ID0; Index < XRFDC_TILE_ID4; Index++) { + if (PLL_CodesPtr != NULL) { + ConfigPtr->DTC_Set_PLL.Target[Index] = PLL_CodesPtr[Index]; + } else { + ConfigPtr->DTC_Set_PLL.Target[Index] = 0; + } + if (T1_CodesPtr != NULL) { + ConfigPtr->DTC_Set_T1.Target[Index] = T1_CodesPtr[Index]; + } else { + ConfigPtr->DTC_Set_T1.Target[Index] = 0; + } + + ConfigPtr->DTC_Set_PLL.DTC_Code[Index] = -1; + ConfigPtr->DTC_Set_T1.DTC_Code[Index] = -1; + } + +} + +/*****************************************************************************/ +/** +* +* This is the top level API which will be used for Multi-tile +* Synchronization. +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC +* @param ConfigPtr Multi-tile sync config structure. +* +* @return +* - XRFDC_MTS_OK if successful. +* - XRFDC_MTS_TIMEOUT if timeout occurs. +* - XRFDC_MTS_MARKER_RUN +* - XRFDC_MTS_MARKER_MISM +* - XRFDC_MTS_NOT_SUPPORTED if MTS is not supported. +* +* @note None +* +******************************************************************************/ +u32 XRFdc_MultiConverter_Sync(XRFdc *InstancePtr, u32 Type, + XRFdc_MultiConverter_Sync_Config *ConfigPtr) +{ + u32 Status; + u32 Index; + u32 RegData; + XRFdc_IPStatus IPStatus = {0}; + XRFdc_MTS_Marker Markers = {0U}; + u32 BaseAddr; + u32 TileState; + u32 BlockStatus; + + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(ConfigPtr != NULL); + + Status = XRFDC_MTS_OK; + + (void)XRFdc_GetIPStatus(InstancePtr, &IPStatus); + for (Index = XRFDC_TILE_ID0; Index < XRFDC_TILE_ID4; Index++) { + if ((ConfigPtr->Tiles & (1U << Index)) != 0U) { + TileState = (Type == XRFDC_DAC_TILE) ? + IPStatus.DACTileStatus[Index].TileState : + IPStatus.ADCTileStatus[Index].TileState ; + if (TileState != 0xFU) { + metal_log(METAL_LOG_ERROR, + "%s tile %d in Multi-Tile group not started\n", + (Type == XRFDC_ADC_TILE) ? "ADC" : "DAC", Index); + + Status |= XRFDC_MTS_IP_NOT_READY; + } + BaseAddr = XRFDC_DRP_BASE(Type, Index) - XRFDC_TILE_DRP_OFFSET; + RegData = XRFdc_ReadReg(InstancePtr, BaseAddr, XRFDC_MTS_DLY_ALIGNER); + if (RegData == 0U) { + metal_log(METAL_LOG_ERROR, "%s tile %d is not enabled for MTS, check IP configuration\n", + (Type == XRFDC_ADC_TILE) ? "ADC" : "DAC", Index); + Status |= XRFDC_MTS_NOT_ENABLED; + } + + BlockStatus = XRFdc_CheckBlockEnabled(InstancePtr, Type, Index, 0x0U); + if (BlockStatus != 0U) { + metal_log(METAL_LOG_ERROR, "%s%d block0 is not enabled, check IP configuration\n", + (Type == XRFDC_ADC_TILE) ? "ADC" : "DAC", Index); + Status |= XRFDC_MTS_NOT_SUPPORTED; + } + } + } + + if (Status != XRFDC_MTS_OK) { + return Status; + } + + /* Disable the FIFOs */ + XRFdc_MTS_FIFOCtrl(InstancePtr, Type, XRFDC_MTS_FIFO_DISABLE, 0); + + /* Enable SysRef Rx */ + XRFdc_MTS_Sysref_TRx(InstancePtr, 1); + + /* Update distribution */ + Status |= XRFdc_MTS_Sysref_Dist(InstancePtr, -1); + + /* Scan DTCs for each tile */ + for (Index = XRFDC_TILE_ID0; Index < XRFDC_TILE_ID4; Index++) { + if ((ConfigPtr->Tiles & (1U << Index)) != 0U) { + /* Run DTC Scan for T1/PLL */ + BaseAddr = XRFDC_DRP_BASE(Type, Index) + XRFDC_HSCOM_ADDR; + RegData = XRFdc_ReadReg16(InstancePtr, BaseAddr, + XRFDC_MTS_CLKSTAT); + if ((RegData & XRFDC_MTS_PLLEN_M) != 0U) { + /* DTC Scan PLL */ + if (Index == 0U) { + metal_log(METAL_LOG_INFO, "\nDTC Scan PLL\n", 0); + } + ConfigPtr->DTC_Set_PLL.RefTile = ConfigPtr->RefTile; + Status |= XRFdc_MTS_Dtc_Scan(InstancePtr, Type, Index, + &ConfigPtr->DTC_Set_PLL); + } + } + } + + /* Scan DTCs for each tile T1 */ + metal_log(METAL_LOG_INFO, "\nDTC Scan T1\n", 0); + for (Index = XRFDC_TILE_ID0; Index < XRFDC_TILE_ID4; Index++) { + if ((ConfigPtr->Tiles & (1U << Index)) != 0U) { + ConfigPtr->DTC_Set_T1 .RefTile = ConfigPtr->RefTile; + Status |= XRFdc_MTS_Dtc_Scan(InstancePtr, Type, Index, + &ConfigPtr->DTC_Set_T1); + } + } + + /* Enable FIFOs */ + XRFdc_MTS_FIFOCtrl(InstancePtr, Type, XRFDC_MTS_FIFO_ENABLE, + ConfigPtr->Tiles); + + /* Measure latency */ + Status |= XRFdc_MTS_GetMarker(InstancePtr, Type, ConfigPtr->Tiles, + &Markers, ConfigPtr->Marker_Delay); + + /* Calculate latency difference and adjust for it */ + Status |= XRFdc_MTS_Latency(InstancePtr, Type, ConfigPtr, &Markers); + + return Status; +} +/*****************************************************************************/ +/** +* +* This is the top level API which will be used to check if Multi-tile +* is enabled. +* +* +* @param InstancePtr is a pointer to the XRfdc instance. +* @param Type is ADC or DAC. 0 for ADC and 1 for DAC. +* @param Tile_Id indicates Tile number (0-3). +* @param EnablePtr to be filled with the enable state. +* +* @return +* - XRFDC_SUCCESS if successful. +* - XRFDC_SUCCESS if error occurs. +* +* @note None +* +******************************************************************************/ +u32 XRFdc_GetMTSEnable(XRFdc *InstancePtr, u32 Type,u32 Tile_Id, u32 *EnablePtr) +{ + u32 RegData; + u32 BaseAddr; + u32 Status; + Xil_AssertNonvoid(InstancePtr != NULL); + Xil_AssertNonvoid(EnablePtr != NULL); + Xil_AssertNonvoid(InstancePtr->IsReady == XRFDC_COMPONENT_IS_READY); + + Status = XRFdc_CheckTileEnabled(InstancePtr, Type, Tile_Id); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, + "\n Requested Tile not " + "available in %s\r\n", + __func__); + goto RETURN_PATH; + } + + BaseAddr = XRFDC_DRP_BASE(Type, Tile_Id) - XRFDC_TILE_DRP_OFFSET; + RegData = XRFdc_ReadReg(InstancePtr, BaseAddr, XRFDC_MTS_DLY_ALIGNER); + if (RegData == 0) { + *EnablePtr = 0; + } else { + *EnablePtr = 1; + } + Status = XRFDC_SUCCESS; +RETURN_PATH: + return Status; +} +/** @} */ diff --git a/mpm/lib/rfdc/xrfdc_sinit.c b/mpm/lib/rfdc/xrfdc_sinit.c new file mode 100644 index 000000000..0fb907859 --- /dev/null +++ b/mpm/lib/rfdc/xrfdc_sinit.c @@ -0,0 +1,272 @@ +/****************************************************************************** +* +* Copyright (C) 2017-2019 Xilinx, Inc. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* XILINX BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +* +* Except as contained in this notice, the name of the Xilinx shall not be used +* in advertising or otherwise to promote the sale, use or other dealings in +* this Software without prior written authorization from Xilinx. +* +******************************************************************************/ +/*****************************************************************************/ +/** +* +* @file xrfdc_sinit.c +* @addtogroup rfdc_v6_0 +* @{ +* +* The implementation of the XRFdc component's static initialization +* functionality. +* +* <pre> +* MODIFICATION HISTORY: +* +* Ver Who Date Changes +* ----- --- -------- ----------------------------------------------- +* 1.0 sk 05/16/17 Initial release +* 5.0 mus 08/17/18 Updated XRFdc_LookupConfig to make use of device +* tree instead of xrfdc_g.c, to obtain +* XRFdc_Config for provided device id.It is being +* achieved through "param-list" property in RFDC +* device node, it will be having 1:1 mapping with +* the XRFdc_Config structure. Said changes +* have been done, to remove the xparameters.h +* dependency from RFDC Linux user space driver. +* +* </pre> +* +******************************************************************************/ + +/***************************** Include Files *********************************/ +#define METAL_INTERNAL +#include "mpm/rfdc/xrfdc.h" +#ifdef __BAREMETAL__ +#include "xparameters.h" +#else +#include <dirent.h> +#include <arpa/inet.h> +#endif +/************************** Constant Definitions *****************************/ + +/**************************** Type Definitions *******************************/ + +/***************** Macros (Inline Functions) Definitions *********************/ + +/************************** Function Prototypes ******************************/ + +/************************** Variable Definitions *****************************/ +#ifdef __BAREMETAL__ +extern XRFdc_Config XRFdc_ConfigTable[]; +#else +static XRFdc_Config *XRFdc_ConfigTablePtr=NULL; +#endif + +#ifndef __BAREMETAL__ +/*****************************************************************************/ +/** +* +* Compare two strings in the reversed order.This function compares only +* the last "Count" number of characters of Str1Ptr and Str2Ptr. +* +* @param Str1Ptr is base address of first string +* @param Str2Ptr is base address of second string +* @param Count is number of last characters to be compared between +* Str1Ptr and Str2Ptr +* +* @return +* 0 if last "Count" number of bytes matches between Str1Ptr and +* Str2Ptr, else differnce in unmatched character. +* +*@note None. +* +******************************************************************************/ +static s32 XRFdc_Strrncmp(const char *Str1Ptr, const char *Str2Ptr, size_t Count) +{ + u16 Len1 = strlen(Str1Ptr); + u16 Len2 = strlen(Str2Ptr); + u8 Diff; + + for (; Len1 && Len2; Len1--, Len2--) { + if ((Diff = Str1Ptr[Len1 - 1] - Str2Ptr[Len2 - 1]) != 0) { + return Diff; + } + if (--Count == 0) { + return 0; + } + } + + return (Len1 - Len2); +} + +/*****************************************************************************/ +/** +* +* Traverse "/sys/bus/platform/device" directory, to find RFDC device entry, +* corresponding to provided device id. If device entry corresponding to said +* device id is found, store it in output buffer DevNamePtr. +* +* @param DevNamePtr is base address of char array, where device name +* will be stored +* @param DevId contains the ID of the device to look up the +* RFDC device name entry in "/sys/bus/platform/device" +* +* @return + * - XRFDC_SUCCESS if successful. + * - XRFDC_FAILURE if device entry not found for given device id. + * + *@note None. +* +******************************************************************************/ +s32 XRFdc_GetDeviceNameByDeviceId(char *DevNamePtr, u16 DevId) +{ + s32 Status = XRFDC_FAILURE; + u32 Data = 0; + char CompatibleString[NAME_MAX]; + struct metal_device *DevicePtr; + DIR *DirPtr; + struct dirent *DirentPtr; + char Len = strlen(XRFDC_COMPATIBLE_STRING); + char SignLen = strlen(XRFDC_SIGNATURE); + + DirPtr = opendir(XRFDC_PLATFORM_DEVICE_DIR); + if (DirPtr) { + while ((DirentPtr = readdir(DirPtr)) != NULL) { + if (XRFdc_Strrncmp(DirentPtr->d_name, + XRFDC_SIGNATURE, SignLen) == 0) { + Status = metal_device_open("platform",DirentPtr->d_name, + &DevicePtr); + if (Status) { + metal_log(METAL_LOG_ERROR, + "\n Failed to open device %s", DirentPtr->d_name); + continue; + } + Status = metal_linux_get_device_property(DevicePtr, + XRFDC_COMPATIBLE_PROPERTY, CompatibleString , + Len); + if (Status < 0) { + metal_log(METAL_LOG_ERROR, + "\n Failed to read device tree property"); + } else if (strncmp(CompatibleString, \ + XRFDC_COMPATIBLE_STRING, Len) == 0) { + Status = metal_linux_get_device_property(DevicePtr, + XRFDC_CONFIG_DATA_PROPERTY, + &Data, XRFDC_DEVICE_ID_SIZE); + if (Status < 0) { + metal_log(METAL_LOG_ERROR, + "\n Failed to read device tree property"); + } else if ( Data == DevId ) { + strcpy(DevNamePtr, DirentPtr->d_name); + Status = XRFDC_SUCCESS; + metal_device_close(DevicePtr); + break; + } + } + metal_device_close(DevicePtr); + } + } + closedir(DirPtr); + } + return Status; +} +#endif +/*****************************************************************************/ +/** +* +* Looks up the device configuration based on the unique device ID. A table +* contains the configuration info for each device in the system. +* +* @param DeviceId contains the ID of the device to look up the +* configuration for. +* +* @return +* +* A pointer to the configuration found or NULL if the specified device ID was +* not found. See xrfdc.h for the definition of XRFdc_Config. +* +* @note None. +* +******************************************************************************/ +XRFdc_Config *XRFdc_LookupConfig(u16 DeviceId) +{ + XRFdc_Config *CfgPtr = NULL; +#ifndef __BAREMETAL__ + s32 Status=0; + u32 NumInstances; + struct metal_device *Deviceptr; + char DeviceName[NAME_MAX]; + + Status = XRFdc_GetDeviceNameByDeviceId(DeviceName, DeviceId); + if (Status != XRFDC_SUCCESS) { + metal_log(METAL_LOG_ERROR, "\n Invalid device id %d", DeviceId); + goto RETURN_PATH2; + } + + Status = metal_device_open(XRFDC_BUS_NAME, DeviceName, &Deviceptr); + if (Status) { + metal_log(METAL_LOG_ERROR, "\n Failed to open device %s", DeviceName); + goto RETURN_PATH2; + } + + if (XRFdc_ConfigTablePtr == NULL) { + Status = metal_linux_get_device_property(Deviceptr, + XRFDC_NUM_INSTANCES_PROPERTY, + &NumInstances, XRFDC_NUM_INST_SIZE); + if (Status < 0) { + metal_log(METAL_LOG_ERROR, + "\n Failed to read device tree property %s", + XRFDC_NUM_INSTANCES_PROPERTY); + goto RETURN_PATH1; + } + XRFdc_ConfigTablePtr = (XRFdc_Config*) malloc(ntohl(NumInstances) * \ + XRFDC_CONFIG_DATA_SIZE); + if (XRFdc_ConfigTablePtr == NULL) { + metal_log(METAL_LOG_ERROR, + "\n Failed to allocate memory for XRFdc_ConfigTablePtr"); + goto RETURN_PATH1; + } + } + Status = metal_linux_get_device_property(Deviceptr, + XRFDC_CONFIG_DATA_PROPERTY, + &XRFdc_ConfigTablePtr[DeviceId], + XRFDC_CONFIG_DATA_SIZE); + if (Status == XRFDC_SUCCESS) { + CfgPtr = &XRFdc_ConfigTablePtr[DeviceId]; + } else { + metal_log(METAL_LOG_ERROR, + "\n Failed to read device tree property %s", + XRFDC_CONFIG_DATA_PROPERTY); + + } +RETURN_PATH1: + metal_device_close(Deviceptr); +RETURN_PATH2: +#else + u32 Index; + + for (Index = 0U; Index < (u32)XPAR_XRFDC_NUM_INSTANCES; Index++) { + if (XRFdc_ConfigTable[Index].DeviceId == DeviceId) { + CfgPtr = &XRFdc_ConfigTable[Index]; + break; + } + } +#endif + return (XRFdc_Config *)CfgPtr; +} +/** @} */ diff --git a/mpm/python/CMakeLists.txt b/mpm/python/CMakeLists.txt index 1900c4004..ba4bf075b 100644 --- a/mpm/python/CMakeLists.txt +++ b/mpm/python/CMakeLists.txt @@ -55,6 +55,8 @@ elseif(MPM_DEVICE STREQUAL "e320") add_library(pyusrp_periphs SHARED pyusrp_periphs/e320/pyusrp_periphs.cpp) elseif(MPM_DEVICE STREQUAL "e31x") add_library(pyusrp_periphs SHARED pyusrp_periphs/e31x/pyusrp_periphs.cpp) +elseif(MPM_DEVICE STREQUAL "x4xx") + add_library(pyusrp_periphs SHARED pyusrp_periphs/x4xx/pyusrp_periphs.cpp) endif(MPM_DEVICE STREQUAL "n3xx") if(NOT ENABLE_SIM) @@ -114,6 +116,11 @@ elseif (ENABLE_E320) e320_bist DESTINATION ${RUNTIME_DIR} ) +elseif (ENABLE_X400) + install(PROGRAMS + x4xx_bist + DESTINATION ${RUNTIME_DIR} + ) endif (ENABLE_MYKONOS) if (HAVE_MPM_TEST_PREREQS) diff --git a/mpm/python/pyusrp_periphs/x4xx/pyusrp_periphs.cpp b/mpm/python/pyusrp_periphs/x4xx/pyusrp_periphs.cpp new file mode 100644 index 000000000..31b1ed6c4 --- /dev/null +++ b/mpm/python/pyusrp_periphs/x4xx/pyusrp_periphs.cpp @@ -0,0 +1,27 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <pybind11/pybind11.h> +namespace py = pybind11; +#define LIBMPM_PYTHON + +// Allow boost::shared_ptr<T> to be a holder class of an object (PyBind11 +// supports std::shared_ptr and std::unique_ptr out of the box) +#include <boost/shared_ptr.hpp> +PYBIND11_DECLARE_HOLDER_TYPE(T, boost::shared_ptr<T>); + +#include <mpm/i2c/i2c_python.hpp> +#include <mpm/rfdc/rfdc_ctrl.hpp> +#include <mpm/spi/spi_python.hpp> +#include <mpm/types/types_python.hpp> + +PYBIND11_MODULE(libpyusrp_periphs, m) +{ + export_types(m); + export_spi(m); + export_i2c(m); + export_rfdc(m); +} diff --git a/mpm/python/tests/components_tests.py b/mpm/python/tests/components_tests.py new file mode 100644 index 000000000..120b89121 --- /dev/null +++ b/mpm/python/tests/components_tests.py @@ -0,0 +1,114 @@ +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Tests the components classes (currently ZynqComponents) +""" + +from usrp_mpm.components import ZynqComponents +from base_tests import TestBase + +import copy +import os.path +import tempfile +import unittest + +class TestZynqComponents(TestBase): + """ + Test functions of the ZynqComponents class + """ + + _testcase_input = '// mpm_version foo_current_version 1.10\n' \ + '// mpm_version foo_oldest_compatible_version 1.5\n' \ + '// mpm_version bar_current_version 1.2\n' \ + '// mpm_version bar_oldest_compatible_version 1.0\n' \ + '// mpm_version baz_current_version 1.2.3\n' \ + '// mpm_version baz_oldest_compatible_version 1.0.0\n' \ + '// mpm_version zack 2.0\n' \ + '// mpm_version zack_oldest_compatible_version 1.0\n' \ + '// mpm_other_tag noname_current_version 1.2.3\n' \ + '// other comment\n' + + _testcase_result = { + 'bar': {'current': (1, 2), 'oldest': (1,0)}, + 'baz': {'current': (1, 2, 3), 'oldest': (1,0,0)}, + 'foo': {'current': (1, 10), 'oldest': (1, 5)}, + 'zack': {'current': (2, 0), 'oldest': (1,0)}, + } + + def _write_dts_file_from_test_cases(self, content): + """ Write content to a temporary .dts file """ + f = tempfile.NamedTemporaryFile(mode="w+", suffix=".dts") + expected = {} + f.write(content) + f.flush() + return f + + def test_parse_dts_version_info_from_file(self): + """ Test function ZynqComponents._parse_dts_version_info_from_file """ + f = self._write_dts_file_from_test_cases(self._testcase_input) + expected = self._testcase_result + result = ZynqComponents._parse_dts_version_info_from_file(f.name) + self.assertEqual(result, expected) + + def test_verify_compatibility(self): + """ Test function ZynqComponents._verify_compatibility """ + class _log_dummy(): + def _dummy(self, *args): + pass + trace = _dummy + info = _dummy + warning = _dummy + error = _dummy + + f = self._write_dts_file_from_test_cases(self._testcase_input) + compatibility = self._testcase_result + for version_type in ['current', 'oldest']: + for case in ['normal', 'smaller_mpm_minor', 'bigger_mpm_minor', + 'smaller_mpm_major', 'bigger_mpm_major', 'component_missing', + 'additional_component']: + compatibility_testcase = copy.deepcopy(compatibility) + foo_major, foo_minor = compatibility['foo'][version_type] + if case == 'normal': + compatibility_testcase['foo'][version_type] = (foo_major, foo_minor) + error_expected = None + elif case == 'smaller_mpm_minor': + compatibility_testcase['foo'][version_type] = (foo_major, foo_minor-1) + error_expected = None + elif case == 'bigger_mpm_minor': + compatibility_testcase['foo'][version_type] = (foo_major, foo_minor+1) + error_expected = None + elif case == 'smaller_mpm_major': + compatibility_testcase['foo'][version_type] = (foo_major-1, foo_minor) + if version_type == 'oldest': + error_expected = None + else: + error_expected = RuntimeError() + elif case == 'bigger_mpm_major': + compatibility_testcase['foo'][version_type] = (foo_major+1, foo_minor) + if version_type == 'oldest': + error_expected = RuntimeError() + else: + error_expected = None + elif case == 'component_missing': + del compatibility_testcase['foo'] + error_expected = None + elif case == 'additional_component': + compatibility_testcase['newcomp'] = {version_type: (2, 10)} + error_expected = None + update_dict = { + 'compatibility': compatibility_testcase, + 'check_dts_for_compatibility': True, + } + filebasename, _ = os.path.splitext(f.name) + try: + self._zynqcomponents = ZynqComponents() + self._zynqcomponents.log = _log_dummy() + self._zynqcomponents._verify_compatibility(filebasename, update_dict) + error = None + except RuntimeError as r: + error = r + self.assertEqual(error.__class__, error_expected.__class__, + f"Unexpected result for test case {case} (version type: {version_type})") diff --git a/mpm/python/tests/run_unit_tests.py b/mpm/python/tests/run_unit_tests.py index c563804ae..a26fa1d22 100755 --- a/mpm/python/tests/run_unit_tests.py +++ b/mpm/python/tests/run_unit_tests.py @@ -13,6 +13,7 @@ import argparse from sys_utils_tests import TestNet from mpm_utils_tests import TestMpmUtils from eeprom_tests import TestEeprom +from usrp_mpm import __simulated__ import importlib.util if importlib.util.find_spec("xmlrunner"): @@ -25,8 +26,15 @@ TESTS = { TestEeprom, }, 'n3xx': set(), + 'x4xx': set() } +if not __simulated__: + from components_tests import TestZynqComponents + TESTS['x4xx'].update({ + TestZynqComponents + }) + def parse_args(): """Parse arguments when running this as a script""" parser_help = 'Run MPM Python unittests' diff --git a/mpm/python/tests/sys_utils_tests.py b/mpm/python/tests/sys_utils_tests.py index 50a10e1a1..c189257c2 100755 --- a/mpm/python/tests/sys_utils_tests.py +++ b/mpm/python/tests/sys_utils_tests.py @@ -62,6 +62,11 @@ class TestNet(TestBase): """ if self.device_name == 'n3xx': possible_ifaces = ['eth0', 'sfp0', 'sfp1'] + elif self.device_name == 'x4xx': + # x4xx devices have an internal network interface + # TODO: change this when sfp0 is enabled + # possible_ifaces = ['eth0', 'sfp0', 'int0'] + possible_ifaces = ['eth0', 'int0'] else: possible_ifaces = ['eth0', 'sfp0'] diff --git a/mpm/python/tests/test_utilities.py b/mpm/python/tests/test_utilities.py index 198eda5e8..942ad956a 100755 --- a/mpm/python/tests/test_utilities.py +++ b/mpm/python/tests/test_utilities.py @@ -90,6 +90,7 @@ class MockRegsIface(object): self.map = register_map self.recent_vals = {} self.next_vals = {} + self.recent_addrs = [] def peek32(self, addr): """ @@ -110,12 +111,32 @@ class MockRegsIface(object): """ self.map.set_reg(addr, value) + self.recent_addrs.append(addr) + # Store written value in a list if addr in self.recent_vals: self.recent_vals[addr].append(value) else: self.recent_vals[addr] = [value] + def peek16(self, addr): + """ + Pass the request to the 32 bit version + """ + return self.peek32(addr) & 0xFFFF + + def poke16(self, addr, value): + """ + Pass the request to the 32 bit version + """ + self.poke32(addr, value) + + def get_recent_addrs(self): + return self.recent_addrs + + def clear_recent_addrs(self): + self.recent_addrs = [] + def get_recent_vals(self, addr): """ Returns the past values written to a given address. @@ -123,6 +144,13 @@ class MockRegsIface(object): """ return self.recent_vals.get(addr, []) + def clear_recent_vals(self, addr): + """ + Clears the past values written to a given address. + Useful for validating HW interaction + """ + self.recent_vals[addr] = [] + def set_next_vals(self, addr, vals): """ Sets a list of the next values to be read from the diff --git a/mpm/python/usrp_mpm/bist.py b/mpm/python/usrp_mpm/bist.py index bd0dbb8d3..6678b132d 100644 --- a/mpm/python/usrp_mpm/bist.py +++ b/mpm/python/usrp_mpm/bist.py @@ -18,6 +18,7 @@ from datetime import datetime import argparse import subprocess from six import iteritems +from usrp_mpm.sys_utils import ectool ############################################################################## # Aurora/SFP BIST code @@ -220,6 +221,7 @@ def get_product_id_from_eeprom(valid_ids, cmd='eeprom-id'): """Return the mboard product ID Returns something like n300, n310, e320... + the eeprom parameter is needed if there are several eeprom within the system """ output = subprocess.check_output( [cmd], @@ -268,13 +270,24 @@ def gpio_set_all(gpio_bank, value, gpio_size, ddr_mask): ############################################################################## # Common tests ############################################################################## -def test_ddr3_with_usrp_probe(): +def test_ddr3_with_usrp_probe(extra_args=None): """ Run uhd_usrp_probe and scrape the output to see if the DRAM FIFO block is reporting a good throughput. This is a bit of a roundabout way of testing the DDR3, but it uses existing software and also tests the RFNoC pathways. """ - ddr3_bist_executor = 'uhd_usrp_probe --args addr=127.0.0.1' + dflt_args = {'addr':'127.0.0.1', 'rfnoc_num_blocks':1} + extra_args = extra_args or {} + # merge args dicts, extra_args overrides dflt_args if keys exists in both dicts + args = {**dflt_args, **extra_args} + args_str = ",".join( + ['{k}={v}'.format(k=k, v=v) for k, v in iteritems(args)]) + cmd = [ + 'uhd_usrp_probe', + '--args', + '{e}'.format(e=args_str) + ] + ddr3_bist_executor = ' '.join(cmd) try: output = subprocess.check_output( ddr3_bist_executor, @@ -335,15 +348,19 @@ def get_ref_clock_prop(clock_source, time_source, extra_args=None): - <sensor-name>: - locked: Boolean lock status """ + dflt_args = {'addr':'127.0.0.1'} extra_args = extra_args or {} + # merge args dicts, extra_args overrides dflt_args if keys exists in both dicts + args = {**dflt_args, **extra_args} result = {} - extra_args_str = ",".join( - ['{k}={v}'.format(k=k, v=v) for k, v in iteritems(extra_args)]) + + args_str = ",".join( + ['{k}={v}'.format(k=k, v=v) for k, v in iteritems(args)]) cmd = [ 'uhd_usrp_probe', '--args', - 'addr=127.0.0.1,clock_source={c},time_source={t},{e}'.format( - c=clock_source, t=time_source, e=extra_args_str), + 'clock_source={c},time_source={t},{e}'.format( + c=clock_source, t=time_source, e=args_str), '--sensor' ] sensor_path = '/mboards/0/sensors/ref_locked' @@ -382,6 +399,33 @@ def get_temp_sensor_value(temp_sensor_map): and device.attributes.get('temp') is not None } + +def get_iio_temp_sensor_values(): + """ + Read all devices in the IIO subsystem that can report a temperature and + returns dictionary containing {name: temperature}, where name is the + temperature device name and the temperature is a value in mC. + """ + import pyudev + context = pyudev.Context() + iio_devs = context.list_devices(subsystem='iio') + + def is_temp_dev(dev): + return 'in_temp_raw' in dev.attributes.available_attributes + + def get_temp(dev): + raw = float(dev.attributes.get('in_temp_raw').decode('ascii')) + offset = float(dev.attributes.get('in_temp_offset').decode('ascii')) + scale = float(dev.attributes.get('in_temp_scale').decode('ascii')) + return int(scale * (raw + offset)) + + def get_name(dev): + return dev.attributes.get('name').decode('ascii') + + temp_devs = [dev for dev in iio_devs if is_temp_dev(dev)] + return {get_name(dev): get_temp(dev) for dev in temp_devs} + + def get_fan_values(): """ Return a dict of fan name -> fan speed key/values. @@ -395,6 +439,14 @@ def get_fan_values(): and device.attributes.get('cur_state') is not None } +def get_ectool_fan_values(): + try: + return ectool.get_fan_rpm() + except RuntimeError as ex: + return { + 'error_msg': "{}".format(str(ex)) + } + def get_link_up(if_name): """ Return a dictionary {if_name: IFLA_OPERSTATE} diff --git a/mpm/python/usrp_mpm/chips/CMakeLists.txt b/mpm/python/usrp_mpm/chips/CMakeLists.txt index 5c5670b14..692e4f11d 100644 --- a/mpm/python/usrp_mpm/chips/CMakeLists.txt +++ b/mpm/python/usrp_mpm/chips/CMakeLists.txt @@ -7,12 +7,14 @@ set(USRP_MPM_FILES ${USRP_MPM_FILES}) set(USRP_MPM_CHIP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py + ${CMAKE_CURRENT_SOURCE_DIR}/adf400x.py + ${CMAKE_CURRENT_SOURCE_DIR}/ds125df410.py ${CMAKE_CURRENT_SOURCE_DIR}/lmk04828.py ${CMAKE_CURRENT_SOURCE_DIR}/lmk04832.py ${CMAKE_CURRENT_SOURCE_DIR}/lmk03328.py - ${CMAKE_CURRENT_SOURCE_DIR}/adf400x.py - ${CMAKE_CURRENT_SOURCE_DIR}/ds125df410.py ${CMAKE_CURRENT_SOURCE_DIR}/lmk05318.py + ${CMAKE_CURRENT_SOURCE_DIR}/lmx2572.py + ${CMAKE_CURRENT_SOURCE_DIR}/max10_cpld_flash_ctrl.py ) list(APPEND USRP_MPM_FILES ${USRP_MPM_CHIP_FILES}) add_subdirectory(ic_reg_maps) diff --git a/mpm/python/usrp_mpm/chips/ic_reg_maps/CMakeLists.txt b/mpm/python/usrp_mpm/chips/ic_reg_maps/CMakeLists.txt index 631f30264..1a49daef6 100755 --- a/mpm/python/usrp_mpm/chips/ic_reg_maps/CMakeLists.txt +++ b/mpm/python/usrp_mpm/chips/ic_reg_maps/CMakeLists.txt @@ -34,6 +34,14 @@ if(ENABLE_REGMAPS) ${UHD_HOST_ROOT}/lib/ic_reg_maps/gen_lmk04816_regs.py ${CMAKE_CURRENT_BINARY_DIR}/lmk04816_regs.py ) + REG_MAPS_GEN_SOURCE( + ${UHD_HOST_ROOT}/lib/ic_reg_maps/gen_lmx2572_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/lmx2572_regs.py + ) + REG_MAPS_GEN_SOURCE( + ${UHD_HOST_ROOT}/lib/ic_reg_maps/gen_zbx_cpld_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/zbx_cpld_regs.py + ) # add an ic_reg_maps target which can be referenced outside of this subdirectory add_custom_target(ic_reg_maps DEPENDS ${IC_REG_MAPS}) diff --git a/mpm/python/usrp_mpm/chips/lmx2572.py b/mpm/python/usrp_mpm/chips/lmx2572.py new file mode 100644 index 000000000..e480dd53b --- /dev/null +++ b/mpm/python/usrp_mpm/chips/lmx2572.py @@ -0,0 +1,338 @@ +# +# Copyright 2019-2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +LMX2572 parent driver class +""" + +import math +from builtins import object +from usrp_mpm.mpmlog import get_logger +from usrp_mpm.chips.ic_reg_maps import lmx2572_regs_t + +NUMBER_OF_LMX2572_REGISTERS = 126 + +class LMX2572(object): + """ + Generic driver class for LMX2572 access. + """ + + READ_ONLY_REGISTERS = [107, 108, 109, 110, 111, 112, 113] + + def __init__(self, regs_iface, parent_log = None): + self.log = parent_log + + self.regs_iface = regs_iface + assert hasattr(self.regs_iface, 'peek16') + assert hasattr(self.regs_iface, 'poke16') + self._poke16 = regs_iface.poke16 + self._peek16 = regs_iface.peek16 + + self._lmx2572_regs = lmx2572_regs_t() + + self._need_recalculation = True + self._enabled = False + + @property + def enabled(self): + return self._enabled + + @enabled.setter + def enabled(self, enable): + self._set_chip_enable(bool(enable)) + self._enabled = bool(enable) + + def reset(self): + """ + Performs a reset of the LMX2572 by using the software reset register. + """ + self._lmx2572_regs = lmx2572_regs_t() + self._lmx2572_regs.reset = lmx2572_regs_t.reset_t.RESET_RESET + self._poke16(0, self._lmx2572_regs.get_reg(0)) + self._lmx2572_regs.reset = lmx2572_regs_t.reset_t.RESET_NORMAL_OPERATION + self._set_default_values() + self._power_up_sequence() + + def commit(self): + """ + Calculates the settings when needed and writes the settings to the device + """ + if self._need_recalculation: + self._calculate_settings() + self._need_recalculation = False + self._write_registers_reference_chain() + self._write_registers_frequency_tuning() + + def check_pll_locked(self): + """ + Returns True if the PLL is locked, False otherwise. + """ + # SPI MISO is multiplexed to lock detect and register readback. Reading any + # register when the mux is set to the lock detect will return just the lock detect signal + self._lmx2572_regs.muxout_ld_sel = lmx2572_regs_t.muxout_ld_sel_t.MUXOUT_LD_SEL_LOCK_DETECT + self._poke16(0, self._lmx2572_regs.get_reg(0)) + + # If the PLL is locked we expect to read 0xFFFF from any read, else 0x0000 + value_read = self._peek16(0) + + return value_read == 0xFFFF + + def set_synchronization(self, enable_synchronization): + """ + Enables and disables the phase synchronization + """ + vco_phase_sync = lmx2572_regs_t.vco_phase_sync_en_t.VCO_PHASE_SYNC_EN_PHASE_SYNC_MODE if \ + enable_synchronization else \ + lmx2572_regs_t.vco_phase_sync_en_t.VCO_PHASE_SYNC_EN_NORMAL_OPERATION + self._lmx2572_regs.vco_phase_sync_en = vco_phase_sync + self._need_recalculation = True + + def get_synchronization(self): + """ + Returns the enabled/disabled state of the phase synchronization + """ + return self._lmx2572_regs.vco_phase_sync_en == \ + lmx2572_regs_t.vco_phase_sync_en_t.VCO_PHASE_SYNC_EN_PHASE_SYNC_MODE + + def set_output_enable_all(self, enable_output): + """ + Enables or disables the output on both ports + """ + self._set_output_a_enable(enable_output) + self._set_output_b_enable(enable_output) + + def _set_chip_enable(self, chip_enable): + """ + Enables or disables the LMX2572 using the powerdown register + All other registers are maintained during powerdown + """ + powerdown = lmx2572_regs_t.powerdown_t.POWERDOWN_NORMAL_OPERATION if chip_enable else \ + lmx2572_regs_t.powerdown_t.POWERDOWN_POWER_DOWN + self._lmx2572_regs.powerdown = powerdown + self._poke16(0, self._lmx2572_regs.get_reg(0)) + + def peek16(self, address): + """ + Wraps _peek16 to account for mux_ld_sel + """ + # SPI MISO is multiplexed to lock detect and register readback. Set the mux to register + # readback before trying to read the register. + self._lmx2572_regs.muxout_ld_sel = \ + lmx2572_regs_t.muxout_ld_sel_t.MUXOUT_LD_SEL_REGISTER_READBACK + self._poke16(0, self._lmx2572_regs.get_reg(0)) + + value_read = self._peek16(address) + + self._lmx2572_regs.muxout_ld_sel = lmx2572_regs_t.muxout_ld_sel_t.MUXOUT_LD_SEL_LOCK_DETECT + self._poke16(0, self._lmx2572_regs.get_reg(0)) + + return value_read + + + def _calculate_settings(self): + """ + This function is intended to be called for calculating the register settings, + however it is implementation, not chip specific, so it is defined but not implemented + """ + raise NotImplementedError("This function is meant to be overriden by a child class.") + + def _set_default_values(self): + """ + The register map has all base class defaults. + Subclasses can override this function to have the values populated on reset. + """ + pass + + def _pokes16(self, addr_vals): + """ + Apply a series of pokes. + pokes16((0,1),(0,2)) is the same as calling poke16(0,1), poke16(0,2). + """ + for addr, val in addr_vals: + self._poke16(addr, val) + + def _set_output_a_enable(self, enable_output): + """ + Sets output A (OUTA_PD) + """ + new_value = lmx2572_regs_t.outa_pd_t.OUTA_PD_NORMAL_OPERATION if enable_output \ + else lmx2572_regs_t.outa_pd_t.OUTA_PD_POWER_DOWN + self._lmx2572_regs.outa_pd = new_value + + def _set_output_b_enable(self, enable_output): + """ + Sets output B (OUTB_PD) + """ + new_value = lmx2572_regs_t.outb_pd_t.OUTB_PD_NORMAL_OPERATION if enable_output \ + else lmx2572_regs_t.outb_pd_t.OUTB_PD_POWER_DOWN + self._lmx2572_regs.outb_pd = new_value + + def _set_output_a_power(self, power): + """ + Sets the output A power + """ + self._lmx2572_regs.outa_pwr = power & self._lmx2572_regs.outa_pwr_mask + + def _set_output_b_power(self, power): + """ + Sets the output B power + """ + self._lmx2572_regs.outb_pwr = power & self._lmx2572_regs.outb_pwr_mask + + def _set_fcal_hpfd_adj(self, phase_detector_frequency): + """ + Sets the FCAL_HPFD_ADJ value based on the Fpfd + """ + # These magic number frequency constants are from the data sheet + if phase_detector_frequency <= 37.5e6: + self._lmx2572_regs.fcal_hpfd_adj = 0x0 + elif 37.5e6 < phase_detector_frequency <= 75e6: + self._lmx2572_regs.fcal_hpfd_adj = 0x1 + elif 75e6 < phase_detector_frequency <= 100e6: + self._lmx2572_regs.fcal_hpfd_adj = 0x2 + else: # 100MHz < phase_detector_frequency + self._lmx2572_regs.fcal_hpfd_adj = 0x3 + + def _set_fcal_lpfd_adj(self, phase_detector_frequency): + """ + Sets the FCAL_LPFD_ADJ value based on the Fpfd + """ + # These magic number frequency constants are from the data sheet + if phase_detector_frequency >= 10e6: + self._lmx2572_regs.fcal_lpfd_adj = 0x0 + elif 10e6 > phase_detector_frequency >= 5e6: + self._lmx2572_regs.fcal_lpfd_adj = 0x1 + elif 5e6 > phase_detector_frequency >= 2.5e6: + self._lmx2572_regs.fcal_lpfd_adj = 0x2 + else: # phase_detector_frequency < 2.5MHz + self._lmx2572_regs.fcal_lpfd_adj = 0x3 + + def _set_pll_n(self, n): + """ + Sets the pll_n registers + """ + self._lmx2572_regs.pll_n_upper_3_bits = (n >> 16) & \ + self._lmx2572_regs.pll_n_upper_3_bits_mask + self._lmx2572_regs.pll_n_lower_16_bits = n & self._lmx2572_regs.pll_n_lower_16_bits_mask + + def _set_pll_den(self, den): + """ + Sets the pll_den registers + """ + self._lmx2572_regs.pll_den_upper = (den >> 16) & self._lmx2572_regs.pll_den_upper_mask + self._lmx2572_regs.pll_den_lower = den & self._lmx2572_regs.pll_den_lower_mask + + def _set_mash_seed(self, mash_seed): + """ + Sets the mash seed register + """ + self._lmx2572_regs.mash_seed_upper = (mash_seed >> 16) & \ + self._lmx2572_regs.mash_seed_upper_mask + self._lmx2572_regs.mash_seed_lower = mash_seed & self._lmx2572_regs.mash_seed_lower_mask + + def _set_pll_num(self, num): + """ + Sets the pll_num registers + """ + self._lmx2572_regs.pll_num_upper = (num >> 16) & self._lmx2572_regs.pll_num_upper_mask + self._lmx2572_regs.pll_num_lower = num & self._lmx2572_regs.pll_num_lower_mask + + def _set_mash_rst_count(self, mash_rst_count): + """ + Sets the mash_rst_count registers + """ + self._lmx2572_regs.mash_rst_count_upper = (mash_rst_count >> 16) & \ + self._lmx2572_regs.mash_rst_count_upper_mask + self._lmx2572_regs.mash_rst_count_lower = mash_rst_count & \ + self._lmx2572_regs.mash_rst_count_lower_mask + + def _compute_and_set_mult_hi(self, reference_frequency): + multiplier_output_frequency = (reference_frequency*(int(self._lmx2572_regs.osc_2x.value)\ + +1)*self._lmx2572_regs.mult) / self._lmx2572_regs.pll_r_pre + new_mult_hi = lmx2572_regs_t.mult_hi_t.MULT_HI_GREATER_THAN_100M \ + if self._lmx2572_regs.mult > 1 and multiplier_output_frequency > 100e6 else \ + lmx2572_regs_t.mult_hi_t.MULT_HI_LESS_THAN_EQUAL_TO_100M + self._lmx2572_regs.mult_hi = new_mult_hi + + def _power_up_sequence(self): + """ + Performs the intial register writes for the LMX2572 + """ + for register in reversed(range(NUMBER_OF_LMX2572_REGISTERS)): + if register in LMX2572.READ_ONLY_REGISTERS: + continue + self._poke16(register, self._lmx2572_regs.get_reg(register)) + + def _write_registers_frequency_tuning(self): + """ + This function writes just the registers for frequency tuning + """ + # Write PLL_N to registers R34 and R36 + self._poke16(34, self._lmx2572_regs.get_reg(34)) + self._poke16(36, self._lmx2572_regs.get_reg(36)) + # Write PLL_DEN to registers R38 and R39 + self._poke16(38, self._lmx2572_regs.get_reg(38)) + self._poke16(39, self._lmx2572_regs.get_reg(39)) + # Write PLL_NUM to registers R42 and R43 + self._poke16(42, self._lmx2572_regs.get_reg(42)) + self._poke16(43, self._lmx2572_regs.get_reg(43)) + + # MASH_SEED to registers R40 and R41 + self._poke16(40, self._lmx2572_regs.get_reg(40)) + self._poke16(41, self._lmx2572_regs.get_reg(41)) + + # Write OUTA_PWR to register R44 or OUTB_PWR to register R45 + # Write OUTA_MUX to register R45 and/or OUTB_MUX to register R46 + self._poke16(44, self._lmx2572_regs.get_reg(44)) + self._poke16(45, self._lmx2572_regs.get_reg(45)) + self._poke16(46, self._lmx2572_regs.get_reg(46)) + + # Write CHDIV to register R75 + self._poke16(75, self._lmx2572_regs.get_reg(75)) + + # Write CPG to register R14 + self._poke16(14, self._lmx2572_regs.get_reg(14)) + + # Write PFD_DLY_SEL to register R37 + self._poke16(37, self._lmx2572_regs.get_reg(37)) + + # Write VCO_SEL to register R20 + self._poke16(20, self._lmx2572_regs.get_reg(20)) + + # Write VCO_DACISET_STRT to register R17 + self._poke16(17, self._lmx2572_regs.get_reg(17)) + + # Write VCO_CALCTRL_STRT to register R78 + self._poke16(78, self._lmx2572_regs.get_reg(78)) + + # Write R0 to latch double buffered registers + self._poke16(0, self._lmx2572_regs.get_reg(0)) + self._poke16(0, self._lmx2572_regs.get_reg(0)) + + def _write_registers_reference_chain(self): + """ + This function writes the registers that are used for setting the reference chain + """ + # Write FCAL_HPFD_ADJ to register R0 + # Write FCAL_LPFD_ADJ to register R0 + self._poke16(0, self._lmx2572_regs.get_reg(0)) + + # Write MULT_HI to register R9 + # Write OSC_2X to register R9 + self._poke16(9, self._lmx2572_regs.get_reg(9)) + + # Write MULT to register R10 + self._poke16(10, self._lmx2572_regs.get_reg(10)) + + # Write PLL_R to register R11 + self._poke16(11, self._lmx2572_regs.get_reg(11)) + # Write PLL_R_PRE to register R12 + self._poke16(12, self._lmx2572_regs.get_reg(12)) + + # if Phase SYNC being used: + # Write MASH_RST_COUNT to registers R69 and 70 + if self.get_synchronization(): + self._poke16(70, self._lmx2572_regs.get_reg(70)) + self._poke16(69, self._lmx2572_regs.get_reg(69)) diff --git a/mpm/python/usrp_mpm/chips/max10_cpld_flash_ctrl.py b/mpm/python/usrp_mpm/chips/max10_cpld_flash_ctrl.py new file mode 100644 index 000000000..c1e756124 --- /dev/null +++ b/mpm/python/usrp_mpm/chips/max10_cpld_flash_ctrl.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Update the CPLD image using the on-chip flash on Intel MAX10 devices +""" +import os +import time + +class Max10CpldFlashCtrl(): + """ + Context manager class used to handle CPLD Flash reconfiguration. + Calling "with" using an instance of this class will disable write + protection for the duration of that context. + """ + REVISION_REG = 0x0004 + # Addresses relative to reconfiguration register offset + FLASH_STATUS_REG = 0x0000 + FLASH_CONTROL_REG = 0x0004 + FLASH_ADDR_REG = 0x0008 + FLASH_WRITE_DATA_REG = 0x000C + FLASH_READ_DATA_REG = 0x0010 + FLASH_CFM0_START_ADDR_REG = 0x0014 + FLASH_CFM0_END_ADDR_REG = 0x0018 + + # Masks for FLASH_STATUS_REG + FLASH_MEM_INIT_ENABLED_MASK = 0x10000 + + def __init__(self, logger, regs, reconfig_regs_offset, cpld_min_revision): + if logger == None: + logger = get_logger('update_cpld') + self.log = logger + self.regs = regs + self.reconfig_regs_offset = reconfig_regs_offset + self.cpld_min_revision = cpld_min_revision + + def peek32(self, addr): + return self.regs.peek32(addr + self.reconfig_regs_offset) + + def poke32(self, addr, val): + self.regs.poke32(addr + self.reconfig_regs_offset, val) + + def __enter__(self): + self.enabled_write_protection(enable=False) + + def __exit__(self, exc_type, exc_value, traceback): + self.enabled_write_protection(enable=True) + + def enabled_write_protection(self, enable=True): + if enable: + self.poke32(self.FLASH_CONTROL_REG, 1 << 0) # FLASH_ENABLE_WP_STB + else: + self.poke32(self.FLASH_CONTROL_REG, 1 << 1) # FLASH_DISABLE_WP_STB + + def check_revision(self): + self.log.debug('Checking for compatible CPLD revision') + cpld_revision = self.regs.peek32(self.REVISION_REG) + if cpld_revision < self.cpld_min_revision: + self.log.error("Unexpected CPLD revision 0x{:X}".format(cpld_revision)) + return False + return True + + def get_start_addr(self): + return self.peek32(self.FLASH_CFM0_START_ADDR_REG) + + def get_end_addr(self): + return self.peek32(self.FLASH_CFM0_END_ADDR_REG) + + def is_memory_initialization_enabled(self): + return self.peek32(self.FLASH_STATUS_REG) & self.FLASH_MEM_INIT_ENABLED_MASK + + # expected value of 0x1110 indicates idle state of read, write, and erase + # routines (see function wait_for_idle for details), 0x0001 indicates the + # write protection is enabled + def check_reconfig_engine_status(self, expected_value=0x1111): + status = self.peek32(self.FLASH_STATUS_REG) + status = status & ~self.FLASH_MEM_INIT_ENABLED_MASK + if (status != expected_value): + self.log.error("Unexpected reconfig engine status 0x%08X" % status) + return False + return True + + def wait_for_idle(self, operation, timeout=350): + """ + Wait for the idle bit to assert for the given operation. + If the idle bit is not True before the timeout (given in ms), + return False. + """ + if operation == 'write': + status_bit = 1 << 12 # FLASH_WRITE_IDLE + elif operation == 'erase': + status_bit = 1 << 8 # FLASH_ERASE_IDLE + elif operation == 'read': + status_bit = 1 << 4 # FLASH_READ_IDLE + else: + self.log.error('Cannot wait for unknown operation {}'.format(operation)) + raise RuntimeError('Cannot wait for unknown operation {}'.format(operation)) + for _ in range(0, timeout): + status = self.peek32(self.FLASH_STATUS_REG) + if (status & status_bit): + return True + time.sleep(0.001) # 1 ms + return False + + def erase_flash_memory(self): + with self: + # check for sectors to be erased: + if self.is_memory_initialization_enabled(): + sectors = [2, 3, 4] + else: + sectors = [4] + # erase each sector individually + for sector in sectors: + # start erase + self.poke32(self.FLASH_CONTROL_REG, (1 << 4) | ((sector & 0x7) << 5)) + # wait for erase to finish + if not self.wait_for_idle('erase', timeout=350): + self.log.error('There was a timeout waiting for ' + 'Flash erase to complete!') + return False + return True + + def program_flash_memory(self, raw_data): + with self: + # write words one at a time + for i, data in enumerate(raw_data): + # status display + if (i%1000 == 0): + self.log.debug('%d%% written', i*4/self.file_size*100) + # write address and data + self.poke32(self.FLASH_ADDR_REG, self.cpld_start_address+i) + self.poke32(self.FLASH_WRITE_DATA_REG, data) + # start write operation + self.poke32(self.FLASH_CONTROL_REG, 1 << 3) + # wait for write to finish + if not self.wait_for_idle('write', timeout=2): + self.log.error('There was a timeout waiting for ' + 'Flash write to complete!') + return False + if not self.check_reconfig_engine_status(expected_value=0x1110): + return False + return True + + def verify_flash_memory(self, raw_data): + # read words one at a time + for i, data in enumerate(raw_data): + # status display + if (i%1000 == 0): + self.log.debug('%d%% done', i*4/self.file_size*100) + # write address + self.poke32(self.FLASH_ADDR_REG, self.cpld_start_address+i) + # start read operation + self.poke32(self.FLASH_CONTROL_REG, 1 << 2) + # wait for read to finish + if not self.wait_for_idle('read', timeout=1): + self.log.error('There was a timeout waiting for ' + 'Flash read to complete!') + return False + # read data from device + device_data = self.peek32(self.FLASH_READ_DATA_REG) + if (data != device_data): + self.log.error("Data mismatch! address %d, expected value 0x%08X," + " read value 0x%08X" % + (i+self.cpld_start_address, data, device_data)) + return False + return True + + def reverse_bits_in_byte(self, n): + result = 0 + for _ in range(8): + result <<= 1 + result |= n & 1 + n >>= 1 + return result + + def update(self, filename): + if not self.check_revision(): + return False + + self.log.debug('Checking CPLD image file') + self.file_size = os.path.getsize(filename) + self.cpld_start_address = self.get_start_addr() + cpld_end_address = self.get_end_addr() + expected_size = (cpld_end_address+1-self.cpld_start_address)*4 + if (self.file_size != expected_size): + self.log.error("Unexpected file size! Required size: %d bytes" % expected_size) + return False + + # Convert data from bytes to 32-bit words and reverse bit order + # to be compatible with Altera's on-chip flash IP + raw_data = [] + with open(filename, 'rb') as binary_file: + for _ in range(self.file_size//4): + number = 0 + for _ in range(4): + number <<= 8 + number |= self.reverse_bits_in_byte(int.from_bytes(binary_file.read(1), 'big')) + raw_data.append(number) + + if not self.check_reconfig_engine_status(): + return False + + self.log.debug('Erasing CPLD flash memory...') + if not (self.erase_flash_memory() + and self.check_reconfig_engine_status()): + self.log.error('There was an error while reprogramming the CPLD image. ' + 'Please program the CPLD again with a valid image before power ' + 'cycling the board to ensure it is in a valid state.') + return False + self.log.debug('CPLD flash memory erased.') + + self.log.debug('Programming flash memory...') + if not (self.program_flash_memory(raw_data) + and self.check_reconfig_engine_status()): + self.log.error('There was an error while reprogramming the CPLD image. ' + 'Please program the CPLD again with a valid image before power ' + 'cycling the board to ensure it is in a valid state.') + return False + self.log.debug('Flash memory programming complete.') + + self.log.debug('Verifying image in flash...') + if not (self.verify_flash_memory(raw_data) + and self.check_reconfig_engine_status()): + self.log.error('There was an error while reprogramming the CPLD image. ' + 'Please program the CPLD again with a valid image before power ' + 'cycling the board to ensure it is in a valid state.') + return False + self.log.debug('Flash image verification complete.') + + self.log.info('CPLD reprogrammed! Please power-cycle the device.') + + return True diff --git a/mpm/python/usrp_mpm/components.py b/mpm/python/usrp_mpm/components.py index 5d1a31325..a87dbdce7 100644 --- a/mpm/python/usrp_mpm/components.py +++ b/mpm/python/usrp_mpm/components.py @@ -7,6 +7,7 @@ MPM Component management """ import os +import re import shutil import subprocess from usrp_mpm.rpc_server import no_rpc @@ -30,6 +31,124 @@ class ZynqComponents(object): ########################################################################### # Component updating ########################################################################### + def _log_and_raise(self, logstr): + self.log.error(logstr) + raise RuntimeError(logstr) + + @classmethod + def _parse_dts_mpm_version_tag(cls, text): + """ parse a version line from the dts file. E.g. + "// mpm_version component1 3.4.5" will return + {"component1": (3, 4, 5)} """ + dts_version_re = re.compile(r'^// mpm_version\s+(?P<comp>\S+)\s+(?P<ver>\S+)$') + match = dts_version_re.match(text) + if match is None: + return (None, None) + + component = match[1] + version_list = [int(x, base=0) for x in match[2].split('.')] + return (component, tuple(version_list)) + + @classmethod + def _parse_dts_version_info_from_file(cls, filepath): + """ + parse all version informations from dts file and store in dict + a c-style comment in the dts file like this + // mpm_version component1 3.4.5 + // mpm_version other_component 3.5.0 + will return a dict: + {"component1": (3, 4, 5), "other_component": (3, 5, 0)"} + """ + suffix_current = "_current_version" + suffix_oldest = "_oldest_compatible_version" + + version_info = {} + with open(filepath) as f: + text = f.read() + for line in text.splitlines(): + component, version = cls._parse_dts_mpm_version_tag(line) + if not component: + continue + if component.endswith(suffix_oldest): + component = component[:-len(suffix_oldest)] + version_type = 'oldest' + elif component.endswith(suffix_current): + component = component[:-len(suffix_current)] + version_type = 'current' + else: + version_type = 'current' + if component not in version_info: + version_info[component] = {} + version_info[component][version_type] = version + return version_info + + def _verify_compatibility(self, filebasename, update_dict): + """ + check whether the given MPM compatibility matches the + version information stored in the FPGA DTS file + """ + def _get_version_string(versions_enum): + version_strings = [] + if 'current' in versions_enum: + version_strings.append("current: {}".format( + ".".join([str(x) for x in versions_enum['current']]))) + if 'oldest' in versions_enum: + version_strings.append("oldest compatible: {}".format( + ".".join([str(x) for x in versions_enum['oldest']]))) + return ', '.join(version_strings) + + + if update_dict.get('check_dts_for_compatibility'): + self.log.trace("Compatibility check MPM <-> FPGA via DTS enabled") + dtsfilepath = filebasename + '.dts' + if not os.path.exists(dtsfilepath): + self._log_and_raise("DTS file not found: {}".format(dtsfilepath)) + self.log.trace("Parse DTS file {} for version information"\ + .format(dtsfilepath)) + fpga_versions = self._parse_dts_version_info_from_file(dtsfilepath) + if not fpga_versions: + self._log_and_raise("no component version information in DTS file") + if 'compatibility' not in update_dict: + self._log_and_raise("MPM FPGA version infos not found") + mpm_versions = update_dict['compatibility'] + self.log.trace("DTS version infos: {}".format(fpga_versions)) + self.log.trace("MPM version infos: {}".format(mpm_versions)) + + try: + for component in mpm_versions.keys(): + # check for components that aren't available in the DTS file + if component in fpga_versions.keys(): + self.log.trace(f"check compatibility for: FPGA-{component}") + mpm_version = mpm_versions[component] + fpga_version = fpga_versions[component] + self.log.trace("mpm_version: current: {}, compatible: {}".format( + mpm_version['current'], mpm_version['oldest'])) + self.log.trace("fpga_version: current: {}, compatible: {}".format( + fpga_version['current'], fpga_version['oldest'])) + if mpm_version['oldest'][0] > fpga_version['current'][0]: + error = "Component {} is too old ({}, MPM version: {})".format( + component, + _get_version_string(fpga_version), + _get_version_string(mpm_version)) + self._log_and_raise(error) + elif mpm_version['current'][0] < fpga_version['oldest'][0]: + error = "Component {} is too new ({}, MPM version: {})".format( + component, + _get_version_string(fpga_version), + _get_version_string(mpm_version)) + self._log_and_raise(error) + self.log.trace(f"Component {component} is good!") + else: + self.log.warning(f"component {component} defined in "\ + f"MPM but not found in FPGA info, skipping.") + except RuntimeError as ex: + self._log_and_raise("MPM compatibility infos suggest that the "\ + "new bitfile is not compatible, skipping installation. {}"\ + .format(ex)) + else: + self.log.trace("Compatibility check MPM <-> FPGA is disabled") + return + @no_rpc def update_fpga(self, filepath, metadata): """ @@ -37,13 +156,19 @@ class ZynqComponents(object): :param filepath: path to new FPGA image :param metadata: Dictionary of strings containing metadata """ - self.log.trace("Updating FPGA with image at {} (metadata: `{}')" - .format(filepath, str(metadata))) - _, file_extension = os.path.splitext(filepath) + self.log.trace(f"Updating FPGA with image at {filepath}"\ + " (metadata: `{str(metadata)}')") + file_name, file_extension = os.path.splitext(filepath) + self.log.trace("file_name: {}".format(file_name)) # Cut off the period from the file extension file_extension = file_extension[1:].lower() - binfile_path = self.updateable_components['fpga']['path'].format( - self.device_info.get('product')) + if file_extension not in ['bit', 'bin']: + self._log_and_raise(f"Invalid FPGA bitfile: {filepath}") + binfile_path = self.updateable_components['fpga']['path']\ + .format(self.device_info.get('product')) + + self._verify_compatibility(file_name, self.updateable_components['fpga']) + if file_extension == "bit": self.log.trace("Converting bit to bin file and writing to {}" .format(binfile_path)) @@ -52,9 +177,7 @@ class ZynqComponents(object): elif file_extension == "bin": self.log.trace("Copying bin file to %s", binfile_path) shutil.copy(filepath, binfile_path) - else: - self.log.error("Invalid FPGA bitfile: %s", filepath) - raise RuntimeError("Invalid N3xx FPGA bitfile") + # RPC server will reload the periph manager after this. return True diff --git a/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt b/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt index dfac467d0..b01d220df 100644 --- a/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt +++ b/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt @@ -28,9 +28,14 @@ set(USRP_MPM_DBMGR_FILES ${CMAKE_CURRENT_SOURCE_DIR}/magnesium_update_cpld.py ${CMAKE_CURRENT_SOURCE_DIR}/mg_init.py ${CMAKE_CURRENT_SOURCE_DIR}/mg_periphs.py + ${CMAKE_CURRENT_SOURCE_DIR}/zbx.py ${CMAKE_CURRENT_SOURCE_DIR}/test.py ${CMAKE_CURRENT_SOURCE_DIR}/unknown.py ${CMAKE_CURRENT_SOURCE_DIR}/dboard_iface.py + ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_db_iface.py + ${CMAKE_CURRENT_SOURCE_DIR}/zbx_update_cpld.py + ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_debug_db.py + ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_if_test_cca.py ) list(APPEND USRP_MPM_FILES ${USRP_MPM_DBMGR_FILES}) set(USRP_MPM_FILES ${USRP_MPM_FILES} PARENT_SCOPE) diff --git a/mpm/python/usrp_mpm/dboard_manager/__init__.py b/mpm/python/usrp_mpm/dboard_manager/__init__.py index 58262025e..28a8ef80b 100644 --- a/mpm/python/usrp_mpm/dboard_manager/__init__.py +++ b/mpm/python/usrp_mpm/dboard_manager/__init__.py @@ -14,6 +14,11 @@ if not __simulated__: from .neon import Neon from .e31x_db import E31x_db from .eiscat import EISCAT + from .empty_slot import EmptySlot + from .zbx import ZBX from .test import test from .unknown import unknown from .dboard_iface import DboardIface + from .x4xx_db_iface import X4xxDboardIface + from .x4xx_debug_db import X4xxDebugDboard + from .x4xx_if_test_cca import X4xxIfTestCCA diff --git a/mpm/python/usrp_mpm/dboard_manager/base.py b/mpm/python/usrp_mpm/dboard_manager/base.py index be37a6264..978f1c5ae 100644 --- a/mpm/python/usrp_mpm/dboard_manager/base.py +++ b/mpm/python/usrp_mpm/dboard_manager/base.py @@ -24,6 +24,9 @@ class DboardManagerBase(object): # Very important: A list of PIDs that apply to the current device. Must be # list, even if there's only one entry. pids = [] + # tuple of id and name of the first revision, + # id and name of revisions are consecutive (2, B), (3, C), ... + first_revision = (1, 'A') # See PeriphManager.mboard_sensor_callback_map for a description. rx_sensor_callback_map = {} # See PeriphManager.mboard_sensor_callback_map for a description. @@ -91,6 +94,13 @@ class DboardManagerBase(object): """ self.log.debug("deinit() called, but not implemented.") + def tear_down(self): + """ + Tear down all members that need to be specially handled before + deconstruction. + """ + pass + def get_serial(self): """ Return this daughterboard's serial number as a string. Will return an @@ -98,6 +108,33 @@ class DboardManagerBase(object): """ return self.device_info.get("serial", "") + def get_revision(self): + """ + Return this daughterboard's revision number as integer. Will return + -1 if no revision can be found or revision is not an integer + """ + try: + return int(self.device_info.get('rev', '-1')) + except ValueError: + return -1 + + def get_revision_string(self): + """ + Converts revision number to string. + """ + return chr(ord(self.first_revision[1]) + + self.get_revision() + - self.first_revision[0]) + + ########################################################################## + # Clocking + ########################################################################## + def reset_clock(self, value): + """ + Called when the motherboard is reconfiguring its clocks. + """ + pass + def update_ref_clock_freq(self, freq, **kwargs): """ Call this function if the frequency of the reference clock changes. diff --git a/mpm/python/usrp_mpm/dboard_manager/dboard_iface.py b/mpm/python/usrp_mpm/dboard_manager/dboard_iface.py index 87bff846b..e100b02a2 100755..100644 --- a/mpm/python/usrp_mpm/dboard_manager/dboard_iface.py +++ b/mpm/python/usrp_mpm/dboard_manager/dboard_iface.py @@ -28,6 +28,32 @@ class DboardIface(object): if hasattr(self.mboard, 'log'): self.log = self.mboard.log.getChild("DboardIface") + def tear_down(self): + """ + Tear down all members that need to be specially handled before + deconstruction. + """ + # The mboard object is the periph_manager that has the dboard + # that in turn has this DboardIface. Breaking the reference + # cycle will make garbage collection easier. + self.mboard = None + + #################################################################### + # Power + # Enable and disable the DB's power rails + #################################################################### + def enable_daughterboard(self, enable = True): + """ + Enable or disable the daughterboard. + """ + raise NotImplementedError('DboardIface::enable_daughterboard() not supported!') + + def check_enable_daughterboard(self): + """ + Return the enable state of the daughterboard. + """ + raise NotImplementedError('DboardIface::check_enable_daughterboard() not supported!') + #################################################################### # CTRL SPI # CTRL SPI lines are connected to the CPLD of the DB if it exists @@ -42,18 +68,6 @@ class DboardIface(object): raise NotImplementedError('DboardIface::ctrl_spi_reset() not supported!') #################################################################### - # GPIO - # GPIO lines are used for high speed control of the DB - #################################################################### - def get_high_speed_gpio_ctrl_core(self): - """ - Return a GpioAtrCore4000 instance that controls the GPIO lines - interfacing the MB and DB - """ - raise NotImplementedError('DboardIface::get_high_speed_gpio_ctrl_core()' - ' not supported!') - - #################################################################### # Management Bus #################################################################### @@ -82,20 +96,28 @@ class DboardIface(object): """ raise NotImplementedError('DboardIface::set_if_freq() not supported!') + def get_if_freq(self, direction, channel): + """ + Gets the IF frequency of the ADC/DAC corresponding + to the specified channel of the DB. + """ + raise NotImplementedError('DboardIface::get_if_freq() not supported!') + + def enable_iq_swap(self, enable, direction, channel): + """ + Enable or disable swap of I and Q samples from the RFDCs. + """ + raise NotImplementedError('DboardIface::enable_iq_swap() not supported!') + + def get_sample_rate(self): + """ + Gets the sample rate of the RFDCs. + """ + raise NotImplementedError('DboardIface::get_sample_rate() not supported!') + def get_prc_rate(self): """ Returns the rate of the PLL Reference Clock (PRC) which is routed to the daughterboard. """ raise NotImplementedError('DboardIface::get_pll_ref_clock() not supported!') - - #################################################################### - # SPCC MPCC Control - #################################################################### - def get_protocol_cores(self): - """ - Returns all discovered protocols in SPCC and MPCC blocks on the - Daughterboard's CPLD in the form of SpiCore4000, I2cCore4000, - UartCore4000, and GpioAtrCore4000 - """ - raise NotImplementedError('DboardIface::get_protocol_cores() not supported!') diff --git a/mpm/python/usrp_mpm/dboard_manager/empty_slot.py b/mpm/python/usrp_mpm/dboard_manager/empty_slot.py new file mode 100644 index 000000000..997d3ac6a --- /dev/null +++ b/mpm/python/usrp_mpm/dboard_manager/empty_slot.py @@ -0,0 +1,37 @@ +# +# Copyright 2021 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Dummy daughterboard class for empty slots +""" +from usrp_mpm.dboard_manager import DboardManagerBase +from usrp_mpm.mpmlog import get_logger + +class EmptySlot(DboardManagerBase): + """ + DboardManager class for when a slot is empty + """ + ######################################################################### + # Overridables + # + # See DboardManagerBase for documentation on these fields + ######################################################################### + pids = [0x0] + ### End of overridables ################################################# + + def __init__(self, slot_idx, **kwargs): + DboardManagerBase.__init__(self, slot_idx, **kwargs) + self.log = get_logger("EmptyDB-{}".format(slot_idx)) + self.log.trace("Initializing Empty dboard, slot index %d", + self.slot_idx) + + 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]) + )) + return True diff --git a/mpm/python/usrp_mpm/dboard_manager/x4xx_db_iface.py b/mpm/python/usrp_mpm/dboard_manager/x4xx_db_iface.py new file mode 100644 index 000000000..d89236859 --- /dev/null +++ b/mpm/python/usrp_mpm/dboard_manager/x4xx_db_iface.py @@ -0,0 +1,144 @@ +# +# Copyright 2019 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +from usrp_mpm.sys_utils.db_flash import DBFlash +from usrp_mpm.sys_utils.gpio import Gpio +from usrp_mpm.dboard_manager import DboardIface +from usrp_mpm import lib # Pulls in everything from C++-land + +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() diff --git a/mpm/python/usrp_mpm/dboard_manager/x4xx_debug_db.py b/mpm/python/usrp_mpm/dboard_manager/x4xx_debug_db.py new file mode 100644 index 000000000..f5e023229 --- /dev/null +++ b/mpm/python/usrp_mpm/dboard_manager/x4xx_debug_db.py @@ -0,0 +1,152 @@ +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Debug dboard implementation module +""" + +from usrp_mpm.dboard_manager import DboardManagerBase +from usrp_mpm.mpmlog import get_logger +from usrp_mpm.sys_utils.gpio import Gpio + +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) + + +class X4xxDebugDboard(DboardManagerBase): + """ + Holds all dboard specific information and methods of the X4xx debug dboard + """ + ######################################################################### + # Overridables + # + # See DboardManagerBase for documentation on these fields + ######################################################################### + pids = [0x4001] + ### End of overridables ################################################# + + def __init__(self, slot_idx, **kwargs): + DboardManagerBase.__init__(self, slot_idx, **kwargs) + self.log = get_logger("X4xxDebugDboard-{}".format(slot_idx)) + self.log.trace("Initializing X4xxDebug daughterboard, 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('Debug dboard {} power up failed'.format(self.slot_idx)) + raise RuntimeError('Debug dboard {} power up failed'.format(self.slot_idx)) + + self._paths = { + "a": DebugDboardSignalPath( + slot_idx, + "A", + { + "adc0": 1, + "adc2": 0, + }, + { + "dac0": 1, + "dac2": 0, + }, + 1 # TRXA_CTRL=1 enables loopback + ), + "b": DebugDboardSignalPath( + slot_idx, + "B", + { + "adc3": 1, + "adc1": 0, + }, + { + "dac3": 1, + "dac1": 0, + }, + 0 # TRXB_CTRL=0 enables loopback + ), + } + + + # TODO: Configure the correct RFDC settings for this board + #if not self.db_iface.disable_mixer(): + # raise RuntimeError("Received an error disabling the mixer for slot_idx={}".format(slot_idx)) + + 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_path("a", "adc0", "dac0", 0) + self.config_path("b", "adc1", "dac1", 0) + return True + + def deinit(self): + pass + + def tear_down(self): + self.db_iface.tear_down() + + def config_path(self, path, adc, dac, loopback): + """ + Configure the signal paths on the daughterboard. + path - Select between front panel connectors A or B. The two paths are unconnected. + adc - Select which ADC to connect to the path (adc0 or adc2 on A, adc1 or adc3 on B) + dac - Select which DAC to connect to the path (dac0 or dac2 on A, dac1 or dac3 on B) + loopback - Whether to enable loopback (1) or route the ADC/DACs to the front panel (0) + + Example MPM shell usage: + > db_0_config_path a adc0 dac2 1 + """ + if path.lower() not in self._paths: + self.log.error("Tried to configure path {} which does not exist!".format(path)) + raise RuntimeError("Tried to configure path {} which does not exist!".format(path)) + + path = self._paths[path.lower()] + path.configure(adc, dac, int(loopback)) diff --git a/mpm/python/usrp_mpm/dboard_manager/x4xx_if_test_cca.py b/mpm/python/usrp_mpm/dboard_manager/x4xx_if_test_cca.py new file mode 100644 index 000000000..c435ddb1c --- /dev/null +++ b/mpm/python/usrp_mpm/dboard_manager/x4xx_if_test_cca.py @@ -0,0 +1,167 @@ +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +IF Test CCA implementation module +""" +from usrp_mpm.dboard_manager import DboardManagerBase +from usrp_mpm.mpmlog import get_logger +from usrp_mpm.sys_utils.gpio import Gpio + +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 diff --git a/mpm/python/usrp_mpm/dboard_manager/zbx.py b/mpm/python/usrp_mpm/dboard_manager/zbx.py new file mode 100644 index 000000000..8343119c8 --- /dev/null +++ b/mpm/python/usrp_mpm/dboard_manager/zbx.py @@ -0,0 +1,461 @@ +# +# Copyright 2019-2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +ZBX dboard implementation module +""" + +import time +from usrp_mpm import tlv_eeprom +from usrp_mpm.dboard_manager import DboardManagerBase +from usrp_mpm.mpmlog import get_logger +from usrp_mpm.chips.ic_reg_maps import zbx_cpld_regs_t +from usrp_mpm.periph_manager.x4xx_periphs import get_temp_sensor +from usrp_mpm.sys_utils.udev import get_eeprom_paths_by_symbol + +############################################################################### +# Helpers +############################################################################### +def parse_encoded_git_hash(encoded): + """ + Helper function: Unpacks the git hash encoded in the ZBX CPLD image into + the git hash and a dirty flag. + """ + git_hash = encoded & 0x0FFFFFFF + tree_dirty = ((encoded & 0xF0000000) > 0) + dirtiness_qualifier = 'dirty' if tree_dirty else 'clean' + return (git_hash, dirtiness_qualifier) + + +# pylint: disable=too-few-public-methods +class EepromTagMap: + """ + Defines the tagmap for EEPROMs matching this magic. + The tagmap is a dictionary mapping an 8-bit tag to a NamedStruct instance. + The canonical list of tags and the binary layout of the associated structs + is defined in mpm/tools/tlv_eeprom/usrp_eeprom.h. Only the subset relevant + to MPM are included below. + """ + magic = 0x55535250 + tagmap = { + # 0x10: usrp_eeprom_board_info + 0x10: tlv_eeprom.NamedStruct('< H H H 7s 1x', + ['pid', 'rev', 'rev_compat', 'serial']), + } + + +############################################################################### +# Main dboard control class +############################################################################### +class ZBX(DboardManagerBase): + """ + Holds all dboard specific information and methods of the ZBX dboard + """ + ######################################################################### + # Overridables + # + # See DboardManagerBase for documentation on these fields + ######################################################################### + pids = [0x4002] + rx_sensor_callback_map = { + 'temperature': 'get_rf_temp_sensor', + } + tx_sensor_callback_map = { + 'temperature': 'get_rf_temp_sensor', + } + ### End of overridables ################################################# + + # Daughterboard required rev_compat value, this is compared against + # rev_compat in the eeprom + # Change only on breaking changes + DBOARD_REQUIRED_COMPAT_REV = 0x1 + + # CPLD compatibility revision + # Change this revision only on breaking changes. + REQ_OLDEST_COMPAT_REV = 0x20110611 + REQ_COMPAT_REV = 0x20110611 + + ######################################################################### + # MPM Initialization + ######################################################################### + def __init__(self, slot_idx, **kwargs): + DboardManagerBase.__init__(self, slot_idx, **kwargs) + self.log = get_logger("ZBX-{}".format(slot_idx)) + self.log.trace("Initializing ZBX daughterboard, slot index %d", + self.slot_idx) + + # local variable to track if PLL ref clock is enabled for the CPLD logic + self._clock_enabled = False + + # 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'] + + self.eeprom_symbol = f"db{slot_idx}_eeprom" + eeprom = self._get_eeprom() + if eeprom["rev_compat"] != self.DBOARD_REQUIRED_COMPAT_REV: + err = f"Found ZBX rev_compat 0x{eeprom['rev_compat']:02x}," \ + f" required is 0x{self.DBOARD_REQUIRED_COMPAT_REV:02x}" + self.log.error(err) + raise RuntimeError(err) + + # Initialize daughterboard CPLD control + self.poke_cpld = self.db_iface.poke_db_cpld + self.peek_cpld = self.db_iface.peek_db_cpld + self.regs = zbx_cpld_regs_t() + self._spi_addr = self.regs.SPI_READY_addr + self._enable_base_power() + # Check register map compatibility + self._check_compat_version() + self.log.debug("ZBX CPLD build git hash: %s", self._get_cpld_git_hash()) + # Power up the DB + self._enable_power() + # enable PLL reference clock + self.reset_clock(False) + self._cpld_set_safe_defaults() + + def _get_eeprom(self): + """ + Return the eeprom data. + """ + path = get_eeprom_paths_by_symbol(self.eeprom_symbol)[self.eeprom_symbol] + eeprom, _ = tlv_eeprom.read_eeprom(path, EepromTagMap.tagmap, EepromTagMap.magic, None) + return eeprom + + def _enable_base_power(self, enable=True): + """ + Enables or disables power to the DB which enables communication to DB CPLD + """ + if enable: + 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('ZBX {} power up failed'.format(self.slot_idx)) + raise RuntimeError('ZBX {} power up failed'.format(self.slot_idx)) + else: # disable + # Removing power from the CPLD will set all the the output pins to open and the + # supplies default to disabled on power up. + self.db_iface.enable_daughterboard(enable=False) + if self.db_iface.check_enable_daughterboard(): + self.log.error('ZBX {} power down failed'.format(self.slot_idx)) + + def _enable_power(self, enable=True): + """ Enables or disables power switches internal to the DB CPLD """ + self.regs.ENABLE_TX_POS_7V0 = self.regs.ENABLE_TX_POS_7V0_t(int(enable)) + self.regs.ENABLE_RX_POS_7V0 = self.regs.ENABLE_RX_POS_7V0_t(int(enable)) + self.regs.ENABLE_POS_3V3 = self.regs.ENABLE_POS_3V3_t(int(enable)) + self.poke_cpld( + self.regs.ENABLE_POS_3V3_addr, + self.regs.get_reg(self.regs.ENABLE_POS_3V3_addr)) + + def _check_compat_version(self): + """ Check compatibility of DB CPLD image and SW regmap """ + compat_revision_addr = self.regs.OLDEST_COMPAT_REVISION_addr + cpld_oldest_compat_revision = self.peek_cpld(compat_revision_addr) + if cpld_oldest_compat_revision < self.REQ_OLDEST_COMPAT_REV: + err_msg = ( + f'DB CPLD oldest compatible revision 0x{cpld_oldest_compat_revision:x}' + f' is out of date, the required revision is 0x{self.REQ_OLDEST_COMPAT_REV:x}. ' + f'Update your CPLD image.') + self.log.error(err_msg) + raise RuntimeError(err_msg) + if cpld_oldest_compat_revision > self.REQ_OLDEST_COMPAT_REV: + err_msg = ( + f'DB CPLD oldest compatible revision 0x{cpld_oldest_compat_revision:x}' + f' is newer than the expected revision 0x{self.REQ_OLDEST_COMPAT_REV:x}.' + ' Downgrade your CPLD image or update MPM.') + self.log.error(err_msg) + raise RuntimeError(err_msg) + + if not self.has_compat_version(self.REQ_COMPAT_REV): + err_msg = ( + "ZBX DB CPLD revision is too old. Update your" + f" CPLD image to at least 0x{self.REQ_COMPAT_REV:08x}.") + self.log.error(err_msg) + raise RuntimeError(err_msg) + + def has_compat_version(self, min_required_version): + """ + Check for a minimum required version. + """ + cpld_image_compat_revision = self.peek_cpld(self.regs.REVISION_addr) + return cpld_image_compat_revision >= min_required_version + + # pylint: disable=too-many-statements + def _cpld_set_safe_defaults(self): + """ + Set the CPLD into a safe state. + """ + cpld_regs = zbx_cpld_regs_t() + # We un-configure some registers to force a change later. None of these + # values get written to the CPLD! + cpld_regs.RF0_OPTION = cpld_regs.RF0_OPTION.RF0_OPTION_FPGA_STATE + cpld_regs.RF1_OPTION = cpld_regs.RF1_OPTION.RF1_OPTION_FPGA_STATE + cpld_regs.SW_RF0_CONFIG = 255 + cpld_regs.SW_RF1_CONFIG = 255 + cpld_regs.TX0_DSA1[0] = 0 + cpld_regs.TX0_DSA2[0] = 0 + cpld_regs.RX0_DSA1[0] = 0 + cpld_regs.RX0_DSA2[0] = 0 + cpld_regs.RX0_DSA3_A[0] = 0 + cpld_regs.RX0_DSA3_B[0] = 0 + cpld_regs.save_state() + # Now all the registers we touch will be enumerated by get_changed_addrs() + # Everything below *will* get written to the CPLD: + # ATR control + cpld_regs.RF0_OPTION = cpld_regs.RF0_OPTION.RF0_OPTION_SW_DEFINED + cpld_regs.RF1_OPTION = cpld_regs.RF1_OPTION.RF1_OPTION_SW_DEFINED + # Back to state 0 and sw-defined. That means nothing will get configured + # until UHD boots again. + cpld_regs.SW_RF0_CONFIG = 0 + cpld_regs.SW_RF1_CONFIG = 0 + # TX0 path control + cpld_regs.TX0_IF2_1_2[0] = cpld_regs.TX0_IF2_1_2[0].TX0_IF2_1_2_FILTER_2 + cpld_regs.TX0_IF1_3[0] = cpld_regs.TX0_IF1_3[0].TX0_IF1_3_FILTER_0_3 + cpld_regs.TX0_IF1_4[0] = cpld_regs.TX0_IF1_4[0].TX0_IF1_4_TERMINATION + cpld_regs.TX0_IF1_5[0] = cpld_regs.TX0_IF1_5[0].TX0_IF1_5_TERMINATION + cpld_regs.TX0_IF1_6[0] = cpld_regs.TX0_IF1_6[0].TX0_IF1_6_FILTER_0_3 + cpld_regs.TX0_7[0] = cpld_regs.TX0_7[0].TX0_7_TERMINATION + cpld_regs.TX0_RF_8[0] = cpld_regs.TX0_RF_8[0].TX0_RF_8_RF_1 + cpld_regs.TX0_RF_9[0] = cpld_regs.TX0_RF_9[0].TX0_RF_9_RF_1 + cpld_regs.TX0_ANT_10[0] = cpld_regs.TX0_ANT_10[0].TX0_ANT_10_BYPASS_AMP + cpld_regs.TX0_ANT_11[0] = cpld_regs.TX0_ANT_11[0].TX0_ANT_11_BYPASS_AMP + cpld_regs.TX0_LO_13[0] = cpld_regs.TX0_LO_13[0].TX0_LO_13_INTERNAL + cpld_regs.TX0_LO_14[0] = cpld_regs.TX0_LO_14[0].TX0_LO_14_INTERNAL + # TX1 path control + cpld_regs.TX1_IF2_1_2[0] = cpld_regs.TX1_IF2_1_2[0].TX1_IF2_1_2_FILTER_2 + cpld_regs.TX1_IF1_3[0] = cpld_regs.TX1_IF1_3[0].TX1_IF1_3_FILTER_0_3 + cpld_regs.TX1_IF1_4[0] = cpld_regs.TX1_IF1_4[0].TX1_IF1_4_TERMINATION + cpld_regs.TX1_IF1_5[0] = cpld_regs.TX1_IF1_5[0].TX1_IF1_5_TERMINATION + cpld_regs.TX1_IF1_6[0] = cpld_regs.TX1_IF1_6[0].TX1_IF1_6_FILTER_0_3 + cpld_regs.TX1_7[0] = cpld_regs.TX1_7[0].TX1_7_TERMINATION + cpld_regs.TX1_RF_8[0] = cpld_regs.TX1_RF_8[0].TX1_RF_8_RF_1 + cpld_regs.TX1_RF_9[0] = cpld_regs.TX1_RF_9[0].TX1_RF_9_RF_1 + cpld_regs.TX1_ANT_10[0] = cpld_regs.TX1_ANT_10[0].TX1_ANT_10_BYPASS_AMP + cpld_regs.TX1_ANT_11[0] = cpld_regs.TX1_ANT_11[0].TX1_ANT_11_BYPASS_AMP + cpld_regs.TX1_LO_13[0] = cpld_regs.TX1_LO_13[0].TX1_LO_13_INTERNAL + cpld_regs.TX1_LO_14[0] = cpld_regs.TX1_LO_14[0].TX1_LO_14_INTERNAL + # RX0 path control + cpld_regs.RX0_ANT_1[0] = cpld_regs.RX0_ANT_1[0].RX0_ANT_1_TERMINATION + cpld_regs.RX0_2[0] = cpld_regs.RX0_2[0].RX0_2_LOWBAND + cpld_regs.RX0_RF_3[0] = cpld_regs.RX0_RF_3[0].RX0_RF_3_RF_1 + cpld_regs.RX0_4[0] = cpld_regs.RX0_4[0].RX0_4_LOWBAND + cpld_regs.RX0_IF1_5[0] = cpld_regs.RX0_IF1_5[0].RX0_IF1_5_FILTER_1 + cpld_regs.RX0_IF1_6[0] = cpld_regs.RX0_IF1_6[0].RX0_IF1_6_FILTER_1 + cpld_regs.RX0_LO_9[0] = cpld_regs.RX0_LO_9[0].RX0_LO_9_INTERNAL + cpld_regs.RX0_LO_10[0] = cpld_regs.RX0_LO_10[0].RX0_LO_10_INTERNAL + cpld_regs.RX0_RF_11[0] = cpld_regs.RX0_RF_11[0].RX0_RF_11_RF_3 + # RX1 path control + cpld_regs.RX1_ANT_1[0] = cpld_regs.RX1_ANT_1[0].RX1_ANT_1_TERMINATION + cpld_regs.RX1_2[0] = cpld_regs.RX1_2[0].RX1_2_LOWBAND + cpld_regs.RX1_RF_3[0] = cpld_regs.RX1_RF_3[0].RX1_RF_3_RF_1 + cpld_regs.RX1_4[0] = cpld_regs.RX1_4[0].RX1_4_LOWBAND + cpld_regs.RX1_IF1_5[0] = cpld_regs.RX1_IF1_5[0].RX1_IF1_5_FILTER_1 + cpld_regs.RX1_IF1_6[0] = cpld_regs.RX1_IF1_6[0].RX1_IF1_6_FILTER_1 + cpld_regs.RX1_LO_9[0] = cpld_regs.RX1_LO_9[0].RX1_LO_9_INTERNAL + cpld_regs.RX1_LO_10[0] = cpld_regs.RX1_LO_10[0].RX1_LO_10_INTERNAL + cpld_regs.RX1_RF_11[0] = cpld_regs.RX1_RF_11[0].RX1_RF_11_RF_3 + # TX DSA + cpld_regs.TX0_DSA1[0] = 31 + cpld_regs.TX0_DSA2[0] = 31 + # RX DSA + cpld_regs.RX0_DSA1[0] = 15 + cpld_regs.RX0_DSA2[0] = 15 + cpld_regs.RX0_DSA3_A[0] = 15 + cpld_regs.RX0_DSA3_B[0] = 15 + for addr in cpld_regs.get_changed_addrs(): + self.poke_cpld(addr, cpld_regs.get_reg(addr)) + # pylint: enable=too-many-statements + + ######################################################################### + # UHD (De-)Initialization + ######################################################################### + def init(self, args): + """ + Execute necessary init dance to bring up dboard. This happens when a UHD + session starts. + """ + self.log.debug("init() called with args `{}'".format( + ",".join(['{}={}'.format(x, args[x]) for x in args]) + )) + return True + + def deinit(self): + """ + De-initialize after UHD session completes + """ + self.log.debug("Setting CPLD back to safe defaults after UHD session.") + self._cpld_set_safe_defaults() + + def tear_down(self): + self.db_iface.tear_down() + + ######################################################################### + # API calls needed by the zbx_dboard driver + ######################################################################### + def enable_iq_swap(self, enable, trx, channel): + """ + Turn on IQ swapping in the RFDC + """ + self.db_iface.enable_iq_swap(enable, trx, channel) + + def get_dboard_sample_rate(self): + """ + Return the RFDC rate. This is usually a big number in the 3 GHz range. + """ + return self.db_iface.get_sample_rate() + + def get_dboard_prc_rate(self): + """ + Return the PRC rate. The CPLD and LOs are clocked with this. + """ + return self.db_iface.get_prc_rate() + + def _has_compat_version(self, min_required_version): + """ + Check for a minimum required version. + """ + cpld_image_compat_revision = self.peek_cpld(self.regs.REVISION_addr) + return cpld_image_compat_revision >= min_required_version + + def _get_cpld_git_hash(self): + """ + Trace build of MB CPLD + """ + git_hash_rb = self.peek_cpld(self.regs.GIT_HASH_addr) + (git_hash, dirtiness_qualifier) = parse_encoded_git_hash(git_hash_rb) + return "{:07x} ({})".format(git_hash, dirtiness_qualifier) + + def reset_clock(self, value): + """ + Disable PLL reference clock to enable SPLL reconfiguration + + Puts the clock into reset if value is True, takes it out of reset + otherwise. + """ + if self._clock_enabled != bool(value): + return + addr = self.regs.get_addr("PLL_REF_CLOCK_ENABLE") + enum = self.regs.PLL_REF_CLOCK_ENABLE_t + if value: + reg_value = enum.PLL_REF_CLOCK_ENABLE_DISABLE.value + else: + reg_value = enum.PLL_REF_CLOCK_ENABLE_ENABLE.value + self.poke_cpld(addr, reg_value) + self._clock_enabled = not bool(value) + + ######################################################################### + # LO SPI API + # + # We keep a LO peek/poke interface for debugging purposes. + ######################################################################### + def _wait_for_spi_ready(self, timeout): + """ Returns False if a timeout occurred. timeout is in ms """ + for _ in range(timeout): + if (self.peek_cpld(self._spi_addr) >> self.regs.SPI_READY_shift) \ + & self.regs.SPI_READY_mask: + return True + time.sleep(0.001) + return False + + def _lo_spi_send_tx(self, lo_name, write, addr, data=None): + """ Wait for SPI Ready and setup the TX data for a LO SPI transaction """ + if not self._wait_for_spi_ready(timeout=100): + self.log.error('Timeout before LO SPI transaction waiting for SPI Ready') + raise RuntimeError('Timeout before LO SPI transaction waiting for SPI Ready') + lo_enum_name = 'LO_SELECT_' + lo_name.upper() + assert hasattr(self.regs.LO_SELECT_t, lo_enum_name), \ + "Invalid LO name: {}".format(lo_name) + self.regs.LO_SELECT = getattr(self.regs.LO_SELECT_t, lo_enum_name) + if write: + self.regs.READ_FLAG = self.regs.READ_FLAG_t.READ_FLAG_WRITE + else: + self.regs.READ_FLAG = self.regs.READ_FLAG_t.READ_FLAG_READ + if data is not None: + self.regs.DATA = data + else: + self.regs.DATA = 0 + self.regs.ADDRESS = addr + self.regs.START_TRANSACTION = \ + self.regs.START_TRANSACTION_t.START_TRANSACTION_ENABLE + self.poke_cpld(self._spi_addr, self.regs.get_reg(self._spi_addr)) + + def _lo_spi_check_status(self, lo_name, addr, write=False): + """ Wait for SPI Ready and check the success of the LO SPI transaction """ + # SPI Ready indicates that the previous transaction has completed + # and the RX data is ready to be consumed + if not write and not self._wait_for_spi_ready(timeout=100): + self.log.error('Timeout after LO SPI transaction waiting for SPI Ready') + raise RuntimeError('Timeout after LO SPI transaction waiting for SPI Ready') + # If the address or CS are not the same as what we set, there + # was interference during the SPI transaction + lo_select = self.regs.LO_SELECT.name[len('LO_SELECT_'):] + if self.regs.ADDRESS != addr or lo_select != lo_name.upper(): + self.log.error('SPI transaction to LO failed!') + raise RuntimeError('SPI transaction to LO failed!') + + def _lo_spi_get_rx(self): + """ Return RX data read from the LO SPI transaction """ + spi_reg = self.peek_cpld(self._spi_addr) + return (spi_reg >> self.regs.DATA_shift) & self.regs.DATA_mask + + def peek_lo_spi(self, lo_name, addr): + """ Perform a register read access to an LO via SPI """ + self._lo_spi_send_tx(lo_name=lo_name, write=False, addr=addr) + self._lo_spi_check_status(lo_name, addr) + return self._lo_spi_get_rx() + + def poke_lo_spi(self, lo_name, addr, val): + """ Perform a register write access to an LO via SPI """ + self._lo_spi_send_tx(lo_name=lo_name, write=True, addr=addr, data=val) + self._lo_spi_check_status(lo_name, addr, write=True) + + ########################################################################### + # LEDs + ########################################################################### + def set_leds(self, channel, rx, trx_rx, trx_tx): + """ Set the frontpanel LEDs """ + assert channel in (0, 1) + + self.regs.save_state() + if channel == 0: + # ensure to be in SW controlled mode + self.regs.RF0_OPTION = self.regs.RF0_OPTION.RF0_OPTION_SW_DEFINED + self.regs.SW_RF0_CONFIG = 0 + self.regs.RX0_RX_LED[0] = self.regs.RX0_RX_LED[0].RX0_RX_LED_ENABLE \ + if bool(rx) else self.regs.RX0_RX_LED[0].RX0_RX_LED_DISABLE + self.regs.RX0_TRX_LED[0] = self.regs.RX0_TRX_LED[0].RX0_TRX_LED_ENABLE \ + if bool(trx_rx) else self.regs.RX0_TRX_LED[0].RX0_TRX_LED_DISABLE + self.regs.TX0_TRX_LED[0] = self.regs.TX0_TRX_LED[0].TX0_TRX_LED_ENABLE \ + if bool(trx_tx) else self.regs.TX0_TRX_LED[0].TX0_TRX_LED_DISABLE + else: + # ensure to be in SW controlled mode + self.regs.RF1_OPTION = self.regs.RF1_OPTION.RF1_OPTION_SW_DEFINED + self.regs.SW_RF1_CONFIG = 0 + self.regs.RX1_RX_LED[0] = self.regs.RX1_RX_LED[0].RX1_RX_LED_ENABLE \ + if bool(rx) else self.regs.RX1_RX_LED[0].RX1_RX_LED_DISABLE + self.regs.RX1_TRX_LED[0] = self.regs.RX1_TRX_LED[0].RX1_TRX_LED_ENABLE \ + if bool(trx_rx) else self.regs.RX1_TRX_LED[0].RX1_TRX_LED_DISABLE + self.regs.TX1_TRX_LED[0] = self.regs.TX1_TRX_LED[0].TX1_TRX_LED_ENABLE \ + if bool(trx_tx) else self.regs.TX1_TRX_LED[0].TX1_TRX_LED_DISABLE + + for addr in self.regs.get_changed_addrs(): + self.poke_cpld(addr, self.regs.get_reg(addr)) + + ########################################################################### + # Sensors + ########################################################################### + def get_rf_temp_sensor(self, _): + """ + Return the RF temperature sensor value + """ + self.log.trace("Reading RF daughterboard temperature.") + sensor_names = [ + f"TMP112 DB{self.slot_idx} Top", + f"TMP112 DB{self.slot_idx} Bottom", + ] + return get_temp_sensor(sensor_names, log=self.log) diff --git a/mpm/python/usrp_mpm/dboard_manager/zbx_update_cpld.py b/mpm/python/usrp_mpm/dboard_manager/zbx_update_cpld.py new file mode 100644 index 000000000..851cfe997 --- /dev/null +++ b/mpm/python/usrp_mpm/dboard_manager/zbx_update_cpld.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +# +# Copyright 2019 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Update the CPLD image for a ZBX daughterboard +""" + +import sys +import os +import argparse +import subprocess +import pyudev +from usrp_mpm.mpmlog import get_logger +from usrp_mpm.mpmutils import check_fpga_state +from usrp_mpm.sys_utils.sysfs_gpio import GPIOBank +from usrp_mpm.periph_manager.x4xx_periphs import CtrlportRegs +from usrp_mpm.periph_manager.x4xx_mb_cpld import MboardCPLD +from usrp_mpm.chips.max10_cpld_flash_ctrl import Max10CpldFlashCtrl +from usrp_mpm.sys_utils.udev import dt_symbol_get_spidev + +OPENOCD_DIR = "/usr/share/openocd/scripts" +CONFIGS = { + 'axi_bitq' : { + 'files' : ["fpga/altera-10m50.cfg"], + 'cmd' : ["interface axi_bitq; axi_bitq_config %u %u %u; adapter_khz %u", + "init; svf -tap 10m50.tap %s -progress -quiet;exit"] + } +} + +AXI_BITQ_ADAPTER_SPEED = 5000 +AXI_BITQ_BUS_CLK = 50000000 + +#The offsets are for JTAG_DB0 and JTAG_DB1 on the motherboard CPLD +DAUGHTERBOARD0_OFFSET = CtrlportRegs.MB_PL_CPLD + 0x60 +DAUGHTERBOARD1_OFFSET = CtrlportRegs.MB_PL_CPLD + 0x80 + +# ZBX flash reconfiguration engine specific offsets +RECONFIG_ENGINE_OFFSET = 0x20 +CPLD_MIN_REVISION = 0x20052016 + +def check_openocd_files(files, logger=None): + """ + Check if all file required by OpenOCD exist + :param logger: logger object + """ + for ocd_file in files: + if not os.path.exists(os.path.join(OPENOCD_DIR, ocd_file)): + if logger is not None: + logger.error("Missing file %s" % os.path.join(OPENOCD_DIR, ocd_file)) + return False + return True + +def find_offset(dboard): + """ + Find the AXI Bitq UIO device + :param dboard: the dboard, can be either 0 or 1 + """ + assert dboard in (0, 1) + return DAUGHTERBOARD0_OFFSET if dboard == 0 else DAUGHTERBOARD1_OFFSET + +def find_axi_bitq_uio(): + """ + Find the AXI Bitq UIO device + """ + label = 'ctrlport-mboard-regs' + + logger = get_logger('update_cpld') + + try: + context = pyudev.Context() + for uio in context.list_devices(subsystem="uio"): + uio_label = uio.attributes.asstring('maps/map0/name') + logger.trace("UIO label: {}, match: {} number: {}".format( + uio_label, uio_label == label, uio.sys_number)) + if uio_label == label: + return int(uio.sys_number) + return None + except OSError as ex: + logger.error("Error while looking for axi_bitq uio nodes: {}".format(ex)) + return None + +def do_update_cpld(filename, daughterboards, updater_mode): + """ + Carry out update process for the CPLD + :param filename: path (on device) to the new CPLD image + :param daughterboards: iterable containing dboard numbers to update + :param updater_mode: the updater method to use- Either flash or legacy (JTAG) + :return: True on success, False otherwise + """ + assert updater_mode in ('flash', 'legacy'), \ + f"Invalid updater method {updater_mode} given" + logger = get_logger('update_cpld') + logger.info("Programming CPLD of dboards {} with image {} using {} mode" + .format(daughterboards, filename, updater_mode)) + + if not daughterboards: + logger.error("Invalid daughterboard selection.") + return False + + if not os.path.exists(filename): + logger.error("CPLD image file {} not found".format(filename)) + return False + + if not check_fpga_state(logger=logger): + logger.error("CPLD lines are routed through fabric, FPGA is not programmed, giving up") + return False + + if updater_mode == 'legacy': + return jtag_cpld_update(filename, daughterboards, logger) + # updater_mode == flash: + for dboard in daughterboards: + dboard = int(dboard, 10) + logger.info("Updating daughterboard slot {}...".format(dboard)) + # enable required daughterboard clock + cpld_spi_node = dt_symbol_get_spidev('mb_cpld') + cpld_control = MboardCPLD(cpld_spi_node, logger) + cpld_control.enable_daughterboard_support_clock(dboard, enable=True) + # setup flash configuration engine and required register access + label = "ctrlport-mboard-regs" + ctrlport_regs = CtrlportRegs(label, logger) + regs = ctrlport_regs.get_db_cpld_iface(dboard) + flash_control = Max10CpldFlashCtrl( + logger, regs, RECONFIG_ENGINE_OFFSET, CPLD_MIN_REVISION) + success = flash_control.update(filename) + # disable clock + cpld_control.enable_daughterboard_support_clock(dboard, enable=False) + if not success: + return success + return True + +def jtag_cpld_update(filename, daughterboards, logger=None): + """ + Carry out update process for the CPLD + :param filename: path (on device) to the new CPLD image + :param daughterboards: iterable containing dboard numbers to update + :return: True on success, False otherwise + """ + mode = 'axi_bitq' + config = CONFIGS[mode] + + if check_openocd_files(config['files'], logger=logger): + logger.trace("Found required OpenOCD files.") + else: + # check_openocd_files logs errors + return False + + for dboard in daughterboards: + logger.info("Updating daughterboard slot {}...".format(dboard)) + + uio_id = find_axi_bitq_uio() + offset = find_offset(int(dboard, 10)) + if uio_id is None or uio_id < 0: + logger.error('Failed to find axi_bitq uio devices. '\ + 'Make sure overlays are up to date') + return False + + cmd = [ + "openocd", + "-c", config['cmd'][0] % (uio_id, AXI_BITQ_BUS_CLK, offset, AXI_BITQ_ADAPTER_SPEED), + "-f", (config['files'][0]).strip(), + "-c", config['cmd'][1] % filename] + + logger.trace("Update CPLD CMD: {}".format(" ".join(cmd))) + subprocess.call(cmd) + + logger.trace("Done programming CPLD...") + return True + +def main(): + """ + Go, go, go! + """ + # Do some setup + def parse_args(): + """Parse the command-line arguments""" + parser = argparse.ArgumentParser(description='Update the CPLD image on ZBX daughterboard') + parser.add_argument("--file", help="Filename of CPLD image", + default="/lib/firmware/ni/cpld-zbx.rpd") + parser.add_argument("--dboards", help="Slot name to program", default="0,1") + parser.add_argument("--updater", + help="The image updater method to use, either " + " 'legacy' (uses openocd) or 'flash'", + default="flash") + parser.add_argument( + '-v', + '--verbose', + help="Increase verbosity level", + action="count", + default=1 + ) + parser.add_argument( + '-q', + '--quiet', + help="Decrease verbosity level", + action="count", + default=0 + ) + return parser.parse_args() + + args = parse_args() + + # We need to make a logger if we're running stand-alone + from usrp_mpm.mpmlog import get_main_logger + log = get_main_logger(log_default_delta=args.verbose-args.quiet) + + dboards = args.dboards.split(",") + if any([x not in ('0', '1') for x in dboards]): + log.error("Unsupported dboards requested: %s", dboards) + return False + + return do_update_cpld(args.file, dboards, args.updater) + + +if __name__ == "__main__": + sys.exit(not main()) diff --git a/mpm/python/usrp_mpm/mpmutils.py b/mpm/python/usrp_mpm/mpmutils.py index 5da81ecfe..a569c85ad 100644 --- a/mpm/python/usrp_mpm/mpmutils.py +++ b/mpm/python/usrp_mpm/mpmutils.py @@ -8,6 +8,7 @@ Miscellaneous utilities for MPM """ import time +import pyudev from contextlib import contextmanager def poll_with_timeout(state_check, timeout_ms, interval_ms): @@ -90,18 +91,18 @@ def assert_compat_number( log=None, ): """ - Check if a compat number pair is acceptable. A compat number is a pair of - integers (MAJOR, MINOR). A compat number is not acceptable if the major + Check if a compat number tuple is acceptable. A compat number is a tuple of + integers (MAJOR, MINOR, BUILD). A compat number is not acceptable if the major part differs from the expected value (regardless of how it's different) or if the minor part is behind the expected value and fail_on_old_minor was - given. + given. Build number is not checked here. On failure, will throw a RuntimeError. Arguments: - expected_compat -- A tuple (major, minor) which represents the compat - number we are expecting. - actual_compat -- A tuple (major, minor) which represents the compat number - that is actually available. + expected_compat -- A tuple (major, minor) or (major, minor, build) which + represents the compat number we are expecting. + actual_compat -- A tuple (major, minor) or (major, minor, build) which + represents the compat number that is actually available. component -- A name of the component for which we are checking the compat number, e.g. "FPGA". fail_on_old_minor -- Will also fail if the actual minor compat number is @@ -110,15 +111,20 @@ def assert_compat_number( log -- Logger object. If given, will use this to report on intermediate steps and non-fatal minor compat mismatches. """ - assert len(expected_compat) == 2 - assert len(actual_compat) == 2 + valid_tuple_lengths = (2, 3) + assert len(expected_compat) in valid_tuple_lengths, ( + f"Version {expected_compat} has invalid format. Valid formats are" + "(major, minor) or (major, minor, build)") + assert len(actual_compat) in valid_tuple_lengths, ( + f"Version {expected_compat} has invalid format. Valid formats are" + "(major, minor) or (major, minor, build)") log_err = lambda msg: log.error(msg) if log is not None else None log_warn = lambda msg: log.warning(msg) if log is not None else None expected_actual_str = "Expected: {:d}.{:d} Actual: {:d}.{:d}".format( expected_compat[0], expected_compat[1], actual_compat[0], actual_compat[1], ) - component_str = "" if component is None else " for component `{}'".format( + component_str = "" if component is None else " for component '{}'".format( component ) if actual_compat[0] != expected_compat[0]: @@ -128,7 +134,7 @@ def assert_compat_number( log_err(err_msg) raise RuntimeError(err_msg) if actual_compat[1] > expected_compat[1]: - log_warn("Actual minor compat ahead of expected compat{}. {}".format( + log_warn("Minor compat ahead of expected compat{}. {}".format( component_str, expected_actual_str )) if actual_compat[1] < expected_compat[1]: @@ -139,7 +145,6 @@ def assert_compat_number( log_err(err_msg) raise RuntimeError(err_msg) log_warn(err_msg) - return def str2bool(value): """Return a Boolean value from a string, even if the string is not simply @@ -188,3 +193,21 @@ def lock_guard(lockable): finally: lockable.unlock() +def check_fpga_state(which=0, logger=None): + """ + Check if the FPGA is operational + :param which: the FPGA to check + """ + try: + context = pyudev.Context() + fpga_mgrs = list(context.list_devices(subsystem="fpga_manager")) + if fpga_mgrs: + state = fpga_mgrs[which].attributes.asstring('state') + if logger is not None: + logger.trace("FPGA State: {}".format(state)) + return state == "operating" + return False + except OSError as ex: + if logger is not None: + logger.error("Error while checking FPGA status: {}".format(ex)) + return False diff --git a/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt b/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt index 747b8967a..1ca96f53e 100644 --- a/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt +++ b/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2017-2018 Ettus Research, a National Instruments Company +# Copyright 2017-2019 Ettus Research, a National Instruments Company # # SPDX-License-Identifier: GPL-3.0-or-later # @@ -18,6 +18,17 @@ set(USRP_MPM_PERIPHMGR_FILES ${CMAKE_CURRENT_SOURCE_DIR}/e320_periphs.py ${CMAKE_CURRENT_SOURCE_DIR}/e31x.py ${CMAKE_CURRENT_SOURCE_DIR}/e31x_periphs.py + ${CMAKE_CURRENT_SOURCE_DIR}/x4xx.py + ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_periphs.py + ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_clk_aux.py + ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_clk_mgr.py + ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_sample_pll.py + ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_reference_pll.py + ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_update_cpld.py + ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_gps_mgr.py + ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_mb_cpld.py + ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_rfdc_regs.py + ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_rfdc_ctrl.py ${CMAKE_CURRENT_SOURCE_DIR}/sim.py ) list(APPEND USRP_MPM_FILES ${USRP_MPM_PERIPHMGR_FILES}) diff --git a/mpm/python/usrp_mpm/periph_manager/base.py b/mpm/python/usrp_mpm/periph_manager/base.py index 53426a615..35986a83c 100644 --- a/mpm/python/usrp_mpm/periph_manager/base.py +++ b/mpm/python/usrp_mpm/periph_manager/base.py @@ -170,6 +170,8 @@ class PeriphManagerBase(object): dboard_eeprom_symbols = "db[0,1]_eeprom" # symbol glob fox auxiliary boards auxboard_eeprom_symbols = "*aux_eeprom" + # List of discoverable features supported by a device. + discoverable_features = [] # Disable checks for unused args in the overridables, because the default @@ -504,7 +506,18 @@ class PeriphManagerBase(object): for name, path in eeprom_paths.items(): self.log.debug("Reading EEPROM info for %s...", name) if not path: - self.log.debug("Not present. Skipping board") + if "db" in name: + # In order to support having a single dboard in slot 1 + # with slot 0 empty on a x4xx, we pretend that there is + # a dummy "EmptyDaughterboard" here. + self.log.debug("Not present. Inserting dummy DB info") + result[name] = { + 'eeprom_md': {'serial': 'deadbee', 'pid': 0x0}, + 'eeprom_raw': [], + 'pid': 0x0 + } + else: + self.log.debug("Not present. Skipping board") continue try: eeprom_md, eeprom_rawdata = self._read_dboard_eeprom_data(path) @@ -743,7 +756,8 @@ class PeriphManagerBase(object): "Cannot run deinit(), device was never fully initialized!") return self.log.trace("Mboard deinit() called.") - for dboard in self.dboards: + for slot, dboard in enumerate(self.dboards): + self.log.trace("call deinit() on dBoard in slot {}".format(slot)) dboard.deinit() def tear_down(self): @@ -752,6 +766,8 @@ class PeriphManagerBase(object): deconstruction. """ self.log.trace("Teardown called for Peripheral Manager base.") + for each in self.dboards: + each.tear_down() ########################################################################### # RFNoC & Device Info @@ -895,6 +911,7 @@ class PeriphManagerBase(object): assert (len(metadata_l) == len(data_l)),\ "update_component arguments must be the same length" # Iterate through the components, updating each in turn + basepath = os.path.join(os.sep, "tmp", "uploads") for metadata, data in zip(metadata_l, data_l): id_str = metadata['id'] filename = os.path.basename(metadata['filename']) @@ -903,7 +920,7 @@ class PeriphManagerBase(object): id_str, self.updateable_components.keys() )) raise KeyError("Update component not implemented for {}".format(id_str)) - self.log.trace("Updating component: {}".format(id_str)) + self.log.trace("Downloading component: {}".format(id_str)) if 'md5' in metadata: given_hash = metadata['md5'] comp_hash = md5() @@ -920,10 +937,9 @@ class PeriphManagerBase(object): comp_hash, given_hash)) raise RuntimeError("Component file hash mismatch") else: - self.log.trace("Loading unverified {} image.".format( + self.log.trace("Downloading unhashed {} image.".format( id_str )) - basepath = os.path.join(os.sep, "tmp", "uploads") filepath = os.path.join(basepath, filename) if not os.path.isdir(basepath): self.log.trace("Creating directory {}".format(basepath)) @@ -931,9 +947,15 @@ class PeriphManagerBase(object): self.log.trace("Writing data to {}".format(filepath)) with open(filepath, 'wb') as comp_file: comp_file.write(data) + + # do the actual installation on the device + for metadata in metadata_l: + id_str = metadata['id'] + filename = os.path.basename(metadata['filename']) + filepath = os.path.join(basepath, filename) update_func = \ getattr(self, self.updateable_components[id_str]['callback']) - self.log.info("Updating component `%s'", id_str) + self.log.info("Installing component `%s'", id_str) update_func(filepath, metadata) return True @@ -950,7 +972,7 @@ class PeriphManagerBase(object): self.log.trace("Component info: {}".format(metadata)) # Convert all values to str return dict([a, str(x)] for a, x in metadata.items()) - # else: + self.log.trace("Component not found in updateable components: {}" .format(component_name)) return {} @@ -1202,3 +1224,21 @@ class PeriphManagerBase(object): "time_source": self.get_time_source(), "clock_source": self.get_clock_source(), } + + ########################################################################### + # Clock/Time API + ########################################################################### + def set_clock_source_out(self, enable=True): + """ + Allows routing the clock configured as source to the RefOut terminal. + """ + raise NotImplementedError("set_clock_source_out() not implemented.") + + ####################################################################### + # Discoverable Features + ####################################################################### + def supports_feature(self, query): + """ + Returns true if the queried feature is supported by a device. + """ + return query in self.discoverable_features diff --git a/mpm/python/usrp_mpm/periph_manager/common.py b/mpm/python/usrp_mpm/periph_manager/common.py index e09be835e..6ed7f6c53 100644 --- a/mpm/python/usrp_mpm/periph_manager/common.py +++ b/mpm/python/usrp_mpm/periph_manager/common.py @@ -10,7 +10,7 @@ Common code for all MPM devices import datetime from usrp_mpm.sys_utils.uio import UIO -class MboardRegsCommon(object): +class MboardRegsCommon: """ Parent class for mboard regs that are common between *all* MPM devices """ @@ -34,6 +34,10 @@ class MboardRegsCommon(object): MB_TIME_BASE_PERIOD_LO = 0x101C MB_TIME_BASE_PERIOD_HI = 0x1020 MB_TIMEKEEPER_OFFSET = 12 + # Timekeeper control words + MB_TIME_SET_NOW = 0x0001 + MB_TIME_SET_NEXT_PPS = 0x0002 + MB_TIME_SET_NEXT_SYNC = 0x0004 # Bitfield locations for the MB_RFNOC_INFO register. MB_RFNOC_INFO_PROTO_VER = 0 MB_RFNOC_INFO_CHDR_WIDTH = 16 @@ -179,12 +183,13 @@ class MboardRegsCommon(object): """ addr_lo = \ self.MB_TIME_EVENT_LO + tk_idx * self.MB_TIMEKEEPER_OFFSET - addr_hi = addr_lo + 4 + addr_hi = \ + self.MB_TIME_EVENT_HI + tk_idx * self.MB_TIMEKEEPER_OFFSET addr_ctrl = \ self.MB_TIME_CTRL + tk_idx * self.MB_TIMEKEEPER_OFFSET time_lo = ticks & 0xFFFFFFFF time_hi = (ticks >> 32) & 0xFFFFFFFF - time_ctrl = 0x2 if next_pps else 0x1 + time_ctrl = self.MB_TIME_SET_NEXT_PPS if next_pps else self.MB_TIME_SET_NOW self.log.trace("Setting time on timekeeper %d to %d %s", tk_idx, ticks, ("on next pps" if next_pps else "now")) with self.regs: diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx.py b/mpm/python/usrp_mpm/periph_manager/x4xx.py new file mode 100644 index 000000000..b32cbeb08 --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/x4xx.py @@ -0,0 +1,1280 @@ +# +# Copyright 2019 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +X400 implementation module +""" + +import threading +import copy +from time import sleep +from os import path +from collections import namedtuple +from pyudev import DeviceNotFoundByNameError +from usrp_mpm import lib # Pulls in everything from C++-land +from usrp_mpm import tlv_eeprom +from usrp_mpm.cores import WhiteRabbitRegsControl +from usrp_mpm.components import ZynqComponents +from usrp_mpm.sys_utils import dtoverlay +from usrp_mpm.sys_utils import ectool +from usrp_mpm.sys_utils import i2c_dev +from usrp_mpm.sys_utils.gpio import Gpio +from usrp_mpm.sys_utils.udev import dt_symbol_get_spidev +from usrp_mpm.rpc_server import no_claim, no_rpc +from usrp_mpm.mpmutils import assert_compat_number, poll_with_timeout +from usrp_mpm.periph_manager import PeriphManagerBase +from usrp_mpm.xports import XportMgrUDP +from usrp_mpm.periph_manager.x4xx_periphs import MboardRegsControl +from usrp_mpm.periph_manager.x4xx_periphs import CtrlportRegs +from usrp_mpm.periph_manager.x4xx_periphs import DioControl +from usrp_mpm.periph_manager.x4xx_periphs import QSFPModule +from usrp_mpm.periph_manager.x4xx_periphs import get_temp_sensor +from usrp_mpm.periph_manager.x4xx_mb_cpld import MboardCPLD +from usrp_mpm.periph_manager.x4xx_clk_aux import ClockingAuxBrdControl +from usrp_mpm.periph_manager.x4xx_clk_mgr import X4xxClockMgr +from usrp_mpm.periph_manager.x4xx_gps_mgr import X4xxGPSMgr +from usrp_mpm.periph_manager.x4xx_rfdc_ctrl import X4xxRfdcCtrl +from usrp_mpm.dboard_manager.x4xx_db_iface import X4xxDboardIface +from usrp_mpm.dboard_manager.zbx import ZBX + + +X400_DEFAULT_EXT_CLOCK_FREQ = 10e6 +X400_DEFAULT_MASTER_CLOCK_RATE = 122.88e6 +X400_DEFAULT_TIME_SOURCE = X4xxClockMgr.TIME_SOURCE_INTERNAL +X400_DEFAULT_CLOCK_SOURCE = X4xxClockMgr.CLOCK_SOURCE_INTERNAL +X400_DEFAULT_ENABLE_PPS_EXPORT = True +X400_FPGA_COMPAT = (7, 2) +X400_DEFAULT_TRIG_DIRECTION = ClockingAuxBrdControl.DIRECTION_OUTPUT +X400_MONITOR_THREAD_INTERVAL = 1.0 # seconds +QSFPModuleConfig = namedtuple("QSFPModuleConfig", "modprs modsel devsymbol") +X400_QSFP_I2C_CONFIGS = [ + QSFPModuleConfig(modprs='QSFP0_MODPRS', modsel='QSFP0_MODSEL_n', devsymbol='qsfp0_i2c'), + QSFPModuleConfig(modprs='QSFP1_MODPRS', modsel='QSFP1_MODSEL_n', devsymbol='qsfp1_i2c')] +RPU_SUCCESS_REPORT = 'Success' +RPU_FAILURE_REPORT = 'Failure' +RPU_REMOTEPROC_FIRMWARE_PATH = '/lib/firmware' +RPU_REMOTEPROC_PREFIX_PATH = '/sys/class/remoteproc/remoteproc' +RPU_REMOTEPROC_PROPERTY_FIRMWARE = 'firmware' +RPU_REMOTEPROC_PROPERTY_STATE = 'state' +RPU_STATE_COMMAND_START = 'start' +RPU_STATE_COMMAND_STOP = 'stop' +RPU_STATE_OFFLINE = 'offline' +RPU_STATE_RUNNING = 'running' +RPU_MAX_FIRMWARE_SIZE = 0x100000 +RPU_MAX_STATE_CHANGE_TIME_IN_MS = 10000 +RPU_STATE_CHANGE_POLLING_INTERVAL_IN_MS = 100 + +DIOAUX_EEPROM = "dioaux_eeprom" +DIOAUX_PID = 0x4003 + +# pylint: disable=too-few-public-methods +class EepromTagMap: + """ + Defines the tagmap for EEPROMs matching this magic. + The tagmap is a dictionary mapping an 8-bit tag to a NamedStruct instance. + The canonical list of tags and the binary layout of the associated structs + is defined in mpm/tools/tlv_eeprom/usrp_eeprom.h. Only the subset relevant + to MPM are included below. + """ + magic = 0x55535250 + tagmap = { + # 0x10: usrp_eeprom_board_info + 0x10: tlv_eeprom.NamedStruct('< H H H 7s 1x', + ['pid', 'rev', 'rev_compat', 'serial']), + # 0x11: usrp_eeprom_module_info + 0x11: tlv_eeprom.NamedStruct('< H H 7s 1x', + ['module_pid', 'module_rev', 'module_serial']), + } + + +############################################################################### +# Transport managers +############################################################################### +class X400XportMgrUDP(XportMgrUDP): + "X400-specific UDP configuration" + iface_config = { + 'sfp0': { + 'label': 'misc-enet-regs0', + 'type': 'sfp', + }, + 'sfp0_1': { + 'label': 'misc-enet-regs0-1', + 'type': 'sfp', + }, + 'sfp0_2': { + 'label': 'misc-enet-regs0-2', + 'type': 'sfp', + }, + 'sfp0_3': { + 'label': 'misc-enet-regs0-3', + 'type': 'sfp', + }, + 'sfp1': { + 'label': 'misc-enet-regs1', + 'type': 'sfp', + }, + 'sfp1_1': { + 'label': 'misc-enet-regs1-1', + 'type': 'sfp', + }, + 'sfp1_2': { + 'label': 'misc-enet-regs1-2', + 'type': 'sfp', + }, + 'sfp1_3': { + 'label': 'misc-enet-regs1-3', + 'type': 'sfp', + }, + 'int0': { + 'label': 'misc-enet-int-regs', + 'type': 'internal', + }, + 'eth0': { + 'label': '', + 'type': 'forward', + } + } +# pylint: enable=too-few-public-methods + + +############################################################################### +# Main Class +############################################################################### +class x4xx(ZynqComponents, PeriphManagerBase): + """ + Holds X400 specific attributes and methods + """ + ######################################################################### + # Overridables + # + # See PeriphManagerBase for documentation on these fields. We try and keep + # them in the same order as they are in PeriphManagerBase for easier lookup. + ######################################################################### + pids = {0x0410: 'x410'} + description = "X400-Series Device" + eeprom_search = PeriphManagerBase._EepromSearch.SYMBOL + # This is not in the overridables section from PeriphManagerBase, but we use + # it below + eeprom_magic = EepromTagMap.magic + mboard_eeprom_offset = 0 + mboard_eeprom_max_len = 256 + mboard_eeprom_magic = eeprom_magic + mboard_info = {"type": "x4xx"} + mboard_max_rev = 5 # RevE + max_num_dboards = 2 + mboard_sensor_callback_map = { + # List of motherboard sensors that are always available. There are also + # GPS sensors, but they get added during __init__() only when there is + # a GPS available. + 'ref_locked': 'get_ref_lock_sensor', + 'fan0': 'get_fan0_sensor', + 'fan1': 'get_fan1_sensor', + 'temp_fpga' : 'get_fpga_temp_sensor', + 'temp_internal' : 'get_internal_temp_sensor', + 'temp_main_power' : 'get_main_power_temp_sensor', + 'temp_scu_internal' : 'get_scu_internal_temp_sensor', + } + db_iface = X4xxDboardIface + dboard_eeprom_magic = eeprom_magic + updateable_components = { + 'fpga': { + 'callback': "update_fpga", + 'path': '/lib/firmware/{}.bin', + 'reset': True, + 'check_dts_for_compatibility': True, + 'compatibility': { + 'fpga': { + 'current': X400_FPGA_COMPAT, + 'oldest': (7, 0), + }, + 'cpld_ifc' : { + 'current': (2, 0), + 'oldest': (2, 0), + }, + 'db_gpio_ifc': { + 'current': (1, 0), + 'oldest': (1, 0), + }, + 'rf_core_100m': { + 'current': (1, 0), + 'oldest': (1, 0), + }, + 'rf_core_400m': { + 'current': (1, 0), + 'oldest': (1, 0), + }, + } + }, + 'dts': { + 'callback': "update_dts", + 'path': '/lib/firmware/{}.dts', + 'output': '/lib/firmware/{}.dtbo', + 'reset': False, + }, + } + discoverable_features = ["ref_clk_calibration", "time_export"] + # + # End of overridables from PeriphManagerBase + ########################################################################### + + + # X400-specific settings + # Label for the mboard UIO + mboard_regs_label = "mboard-regs" + ctrlport_regs_label = "ctrlport-mboard-regs" + # Label for the white rabbit UIO + wr_regs_label = "wr-regs" + # Override the list of updateable components + # X4xx specific discoverable features + + @classmethod + def generate_device_info(cls, eeprom_md, mboard_info, dboard_infos): + """ + Hard-code our product map + """ + # Add the default PeriphManagerBase information first + device_info = super().generate_device_info( + eeprom_md, mboard_info, dboard_infos) + # Then add X4xx-specific information + mb_pid = eeprom_md.get('pid') + device_info['product'] = cls.pids.get(mb_pid, 'unknown') + module_serial = eeprom_md.get('module_serial') + if module_serial is not None: + device_info['serial'] = module_serial + return device_info + + @staticmethod + def list_required_dt_overlays(device_info): + """ + Lists device tree overlays that need to be applied before this class can + be used. List of strings. + Are applied in order. + + eeprom_md -- Dictionary of info read out from the mboard EEPROM + device_args -- Arbitrary dictionary of info, typically user-defined + """ + return [device_info['product']] + + def _init_mboard_overlays(self): + """ + Load all required overlays for this motherboard + Overriden from the base implementation to force apply even if + the overlay was already loaded. + """ + requested_overlays = self.list_required_dt_overlays( + self.device_info, + ) + self.log.debug("Motherboard requests device tree overlays: {}".format( + requested_overlays + )) + # Remove all overlays before applying new ones + for overlay in requested_overlays: + dtoverlay.rm_overlay_safe(overlay) + for overlay in requested_overlays: + dtoverlay.apply_overlay_safe(overlay) + # Need to wait here a second to make sure the ethernet interfaces are up + # TODO: Fine-tune this number, or wait for some smarter signal. + sleep(1) + + ########################################################################### + # Ctor and device initialization tasks + ########################################################################### + def __init__(self, args): + super(x4xx, self).__init__() + + self._tear_down = False + self._rpu_initialized = False + self._status_monitor_thread = None + self._master_clock_rate = None + self._gps_mgr = None + self._clk_mgr = None + self._safe_sync_source = { + 'clock_source': X400_DEFAULT_CLOCK_SOURCE, + 'time_source': X400_DEFAULT_TIME_SOURCE, + } + self.rfdc = None + self.mboard_regs_control = None + self.ctrlport_regs = None + self.cpld_control = None + self.dio_control = None + try: + self._init_peripherals(args) + self.init_dboards(args) + self._clk_mgr.set_dboard_reset_cb( + lambda enable: [db.reset_clock(enable) for db in self.dboards]) + except Exception as ex: + self.log.error("Failed to initialize motherboard: %s", str(ex), exc_info=ex) + self._initialization_status = str(ex) + self._device_initialized = False + if not self._device_initialized: + # Don't try and figure out what's going on. Just give up. + return + try: + if not args.get('skip_boot_init', False): + self.init(args) + except Exception as ex: + self.log.warning("Failed to initialize device on boot: %s", str(ex)) + + # The parent class versions of these functions require access to self, but + # these versions don't. + # pylint: disable=no-self-use + def _read_mboard_eeprom_data(self, eeprom_path): + """ Returns a tuple (eeprom_dict, eeprom_rawdata) for the motherboard + EEPROM. + """ + return tlv_eeprom.read_eeprom(eeprom_path, EepromTagMap.tagmap, + EepromTagMap.magic, None) + + def _read_dboard_eeprom_data(self, eeprom_path): + """ Returns a tuple (eeprom_dict, eeprom_rawdata) for a daughterboard + EEPROM. + """ + return tlv_eeprom.read_eeprom(eeprom_path, EepromTagMap.tagmap, + EepromTagMap.magic, None) + # pylint: enable=no-self-use + + def _check_fpga_compat(self): + " Throw an exception if the compat numbers don't match up " + actual_compat = self.mboard_regs_control.get_compat_number() + self.log.debug("Actual FPGA compat number: {:d}.{:d}".format( + actual_compat[0], actual_compat[1] + )) + assert_compat_number( + X400_FPGA_COMPAT, + actual_compat, + component="FPGA", + fail_on_old_minor=False, + log=self.log + ) + + def _init_gps_mgr(self): + """ + Initialize the GPS manager and the sensors. + Note that mpmd_impl queries all available sensors at initialization + time, in order to populate the property tree. That means we can't + dynamically load/unload sensors. Instead, we have to make sure that + the sensors can handle the GPS sensors, even when it's disabled. That + is pushed into the GPS manager class. + """ + self.log.debug("Found GPS, adding sensors.") + gps_mgr = X4xxGPSMgr(self._clocking_auxbrd, self.log) + # We can't use _add_public_methods(), because we only want a subset of + # the public methods. Also, we want to know which sensors were added so + # we can also add them to mboard_sensor_callback_map. + new_methods = gps_mgr.extend(self) + self.mboard_sensor_callback_map.update(new_methods) + return gps_mgr + + def _monitor_status(self): + """ + Status monitoring thread: This should be executed in a thread. It will + continuously monitor status of the following peripherals: + + - REF lock (update back-panel REF LED) + """ + self.log.trace("Launching monitor loop...") + cond = threading.Condition() + cond.acquire() + while not self._tear_down: + ref_locked = self.get_ref_lock_sensor()['value'] == 'true' + if self._clocking_auxbrd is not None: + self._clocking_auxbrd.set_ref_lock_led(ref_locked) + # Now wait + if cond.wait_for( + lambda: self._tear_down, + X400_MONITOR_THREAD_INTERVAL): + break + cond.release() + self.log.trace("Terminating monitor loop.") + + def _assert_rfdc_powered(self): + """ + Assert that RFdc power is enabled, throw RuntimeError otherwise. + """ + if not self._rfdc_powered.get(): + err_msg = "RFDC is not powered on" + self.log.error(err_msg) + raise RuntimeError(err_msg) + + def _get_serial_number(self): + """ + Read the serial number from eeprom, falling back to the board S/N + if the module S/N is not populated. + """ + serial_number = self._eeprom_head.get("module_serial") + if serial_number is None: + self.log.warning( + 'Module serial number not programmed, falling back to motherboard serial') + serial_number = self._eeprom_head["serial"] + return serial_number.rstrip(b'\x00') + + def _init_peripherals(self, args): + """ + Turn on all peripherals. This may throw an error on failure, so make + sure to catch it. + """ + # Sanity checks + assert self.mboard_info.get('product') in self.pids.values(), \ + "Device product could not be determined!" + # Init peripherals + self._rfdc_powered = Gpio('RFDC_POWERED', Gpio.INPUT) + # Init RPU Manager + self.log.trace("Initializing RPU manager peripheral...") + self.init_rpu() + # Init clocking aux board + self.log.trace("Initializing Clocking Aux Board controls...") + has_gps = False + try: + self._clocking_auxbrd = ClockingAuxBrdControl() + self.log.trace("Initialized Clocking Aux Board controls") + has_gps = self._clocking_auxbrd.is_gps_supported() + except RuntimeError: + self.log.warning( + "GPIO I2C bus could not be found for the Clocking Aux Board, " + "disabling Clocking Aux Board functionality.") + self._clocking_auxbrd = None + self._safe_sync_source = { + 'clock_source': X4xxClockMgr.CLOCK_SOURCE_MBOARD, + 'time_source': X4xxClockMgr.TIME_SOURCE_INTERNAL, + } + + initial_clock_source = args.get('clock_source', X400_DEFAULT_CLOCK_SOURCE) + if self._clocking_auxbrd: + self._add_public_methods(self._clocking_auxbrd, "clkaux") + else: + initial_clock_source = X4xxClockMgr.CLOCK_SOURCE_MBOARD + + # Init CPLD before talking to clocking ICs + cpld_spi_node = dt_symbol_get_spidev('mb_cpld') + self.cpld_control = MboardCPLD(cpld_spi_node, self.log) + self.cpld_control.check_signature() + self.cpld_control.check_compat_version() + self.cpld_control.trace_git_hash() + + self._assert_rfdc_powered() + # Init clocking after CPLD as the SPLL communication is relying on it. + # We try and guess the correct master clock rate here based on defaults + # and args. Since we are still in __init__(), the args that come from mpm.conf + # are empty. We can't detect the real default MCR, because we need + # the RFDC controls for that -- but they won't work without clocks. So + # let's pick a sensible default MCR value, init the clocks, and fix the + # MCR value further down. + self._master_clock_rate = float( + args.get('master_clock_rate', X400_DEFAULT_MASTER_CLOCK_RATE)) + sample_clock_freq, _, is_legacy_mode, _ = \ + X4xxRfdcCtrl.master_to_sample_clk[self._master_clock_rate] + self._clk_mgr = X4xxClockMgr( + initial_clock_source, + time_source=args.get('time_source', X400_DEFAULT_TIME_SOURCE), + ref_clock_freq=float(args.get( + 'ext_clock_freq', X400_DEFAULT_EXT_CLOCK_FREQ)), + sample_clock_freq=sample_clock_freq, + is_legacy_mode=is_legacy_mode, + clk_aux_board=self._clocking_auxbrd, + cpld_control=self.cpld_control, + log=self.log) + self._add_public_methods( + self._clk_mgr, + prefix="", + filter_cb=lambda name, method: not hasattr(method, '_norpc') + ) + + # Overlay must be applied after clocks have been configured + self.overlay_apply() + + # Init Mboard Regs + self.log.trace("Initializing MBoard reg controls...") + serial_number = self._get_serial_number() + self.mboard_regs_control = MboardRegsControl( + self.mboard_regs_label, self.log) + self._check_fpga_compat() + self.mboard_regs_control.set_serial_number(serial_number) + self.mboard_regs_control.get_git_hash() + self.mboard_regs_control.get_build_timestamp() + self._clk_mgr.mboard_regs_control = self.mboard_regs_control + + # Create control for RFDC + self.rfdc = X4xxRfdcCtrl(self._clk_mgr.get_spll_freq, self.log) + self._add_public_methods( + self.rfdc, prefix="", + filter_cb=lambda name, method: not hasattr(method, '_norpc') + ) + + self._update_fpga_type() + + # Force reset the RFDC to ensure it is in a good state + self.rfdc.set_reset(reset=True) + self.rfdc.set_reset(reset=False) + + # Synchronize SYSREF and clock distributed to all converters + self.rfdc.sync() + self._clk_mgr.set_rfdc_reset_cb(self.rfdc.set_reset) + + # The initial default mcr only works if we have an FPGA with + # a decimation of 2. But we need the overlay applied before we + # can detect decimation, and that requires clocks to be initialized. + self.set_master_clock_rate(self.rfdc.get_default_mcr()) + + # Init ctrlport endpoint + self.ctrlport_regs = CtrlportRegs(self.ctrlport_regs_label, self.log) + + # Init IPass cable status forwarding and CMI + self.cpld_control.set_serial_number(serial_number) + self.cpld_control.set_cmi_device_ready( + self.mboard_regs_control.is_pcie_present()) + # The CMI transmission can be disabled by setting the cable status + # to be not connected. All images except for the LV PCIe variant + # provide a fixed "cables are unconnected" status. The LV PCIe image + # reports the correct status. As the FPGA holds this information it + # is possible to always enable the iPass cable present forwarding. + self.ctrlport_regs.enable_cable_present_forwarding(True) + + # Init DIO + if self._check_compat_aux_board(DIOAUX_EEPROM, DIOAUX_PID): + self.dio_control = DioControl(self.mboard_regs_control, + self.cpld_control, self.log) + # add dio_control public methods to MPM API + self._add_public_methods(self.dio_control, "dio") + + # Init QSFP modules + for idx, config in enumerate(X400_QSFP_I2C_CONFIGS): + attr = QSFPModule( + config.modprs, config.modsel, config.devsymbol, self.log) + setattr(self, "_qsfp_module{}".format(idx), attr) + self._add_public_methods(attr, "qsfp{}".format(idx)) + + # Init GPS + if has_gps: + self._gps_mgr = self._init_gps_mgr() + # Init CHDR transports + self._xport_mgrs = { + 'udp': X400XportMgrUDP(self.log, args), + } + # Spawn status monitoring thread + self.log.trace("Spawning status monitor thread...") + self._status_monitor_thread = threading.Thread( + target=self._monitor_status, + name="X4xxStatusMonitorThread", + daemon=True, + ) + self._status_monitor_thread.start() + # Init complete. + self.log.debug("Device info: {}".format(self.device_info)) + + def _check_compat_aux_board(self, name, pid): + """ + Check whether auxiliary board given by name and pid can be found + :param name: symbol name of the auxiliary board which is used as + lookup for the dictionary of available boards. + :param pid: PID the board must have to be considered compatible + :return True if board is available with matching PID, + False otherwise + """ + assert(isinstance(self._aux_board_infos, dict)), "No EEPROM data" + board_info = self._aux_board_infos.get(name, None) + if board_info is None: + self.log.warning("Board for %s not present" % name) + return False + if board_info.get("pid", 0) != pid: + self.log.error("Expected PID for board %s to be 0x%04x but found " + "0x%04x" % (name, pid, board_info["pid"])) + return False + self.log.debug("Found compatible board for %s " + "(PID: 0x%04x)" % (name, board_info["pid"])) + return True + + def init_rpu(self): + """ + Initializes the RPU image manager + """ + if self._rpu_initialized: + return + + # Check presence/state of RPU cores + try: + for core_number in [0, 1]: + self.log.trace( + "RPU Core %d state: %s", + core_number, + self.get_rpu_state(core_number)) + # TODO [psisterh, 5 Dec 2019] + # Should we force core to + # stop if running or in error state? + self.log.trace("Initialized RPU cores successfully.") + self._rpu_initialized = True + except FileNotFoundError: + self.log.warning( + "Failed to initialize RPU: remoteproc sysfs not present.") + + ########################################################################### + # Session init and deinit + ########################################################################### + def init(self, args): + """ + Calls init() on the parent class, and then programs the Ethernet + dispatchers accordingly. + """ + if not self._device_initialized: + self.log.warning( + "Cannot run init(), device was never fully initialized!") + return False + + # We need to disable the PPS out during clock and dboard initialization in order + # to avoid glitches. + if self._clocking_auxbrd is not None: + self._clocking_auxbrd.set_trig(False) + + # If the caller has not specified clock_source or time_source, set them + # to the values currently configured. + args['clock_source'] = args.get('clock_source', self._clk_mgr.get_clock_source()) + args['time_source'] = args.get('time_source', self._clk_mgr.get_time_source()) + self.set_sync_source(args) + + # If a Master Clock Rate was specified, + # re-configure the Sample PLL and all downstream clocks + if 'master_clock_rate' in args: + self.set_master_clock_rate(float(args['master_clock_rate'])) + + # Initialize CtrlportRegs (manually opens the UIO resource for faster access) + self.ctrlport_regs.init() + + # Note: The parent class takes care of calling init() on all the + # daughterboards + result = super(x4xx, self).init(args) + + # Now the clocks are all enabled, we can also enable PPS export: + if self._clocking_auxbrd is not None: + self._clocking_auxbrd.set_trig( + args.get('pps_export', X400_DEFAULT_ENABLE_PPS_EXPORT), + args.get('trig_direction', X400_DEFAULT_TRIG_DIRECTION) + ) + + for xport_mgr in self._xport_mgrs.values(): + xport_mgr.init(args) + return result + + def deinit(self): + """ + Clean up after a UHD session terminates. + """ + if not self._device_initialized: + self.log.warning( + "Cannot run deinit(), device was never fully initialized!") + return + + if self.get_ref_lock_sensor()['unit'] != 'locked': + self.log.error("ref clocks aren't locked, falling back to default") + source = {"clock_source": X400_DEFAULT_CLOCK_SOURCE, + "time_source": X400_DEFAULT_TIME_SOURCE + } + self.set_sync_source(source) + super(x4xx, self).deinit() + self.ctrlport_regs.deinit() + for xport_mgr in self._xport_mgrs.values(): + xport_mgr.deinit() + + def tear_down(self): + """ + Tear down all members that need to be specially handled before + deconstruction. + For X400, this means the overlay. + """ + self.log.trace("Tearing down X4xx device...") + self._tear_down = True + if self._device_initialized: + self._status_monitor_thread.join(3 * X400_MONITOR_THREAD_INTERVAL) + if self._status_monitor_thread.is_alive(): + self.log.error("Could not terminate monitor thread! " + "This could result in resource leaks.") + # call tear_down on daughterboards first + super(x4xx, self).tear_down() + if self.dio_control is not None: + self.dio_control.tear_down() + self.rfdc.unset_cbs() + self._clk_mgr.unset_cbs() + # remove x4xx overlay + active_overlays = self.list_active_overlays() + self.log.trace("X4xx has active device tree overlays: {}".format( + active_overlays + )) + for overlay in active_overlays: + dtoverlay.rm_overlay(overlay) + + ########################################################################### + # Transport API + ########################################################################### + # pylint: disable=no-self-use + def get_chdr_link_types(self): + """ + This will only ever return a single item (udp). + """ + return ["udp"] + # pylint: enable=no-self-use + + def get_chdr_link_options(self, xport_type): + """ + Returns a list of dictionaries. Every dictionary contains information + about one way to connect to this device in order to initiate CHDR + traffic. + + The interpretation of the return value is very highly dependant on the + transport type (xport_type). + For UDP, the every entry of the list has the following keys: + - ipv4 (IP Address) + - port (UDP port) + - link_rate (bps of the link, e.g. 10e9 for 10GigE) + """ + if xport_type not in self._xport_mgrs: + self.log.warning("Can't get link options for unknown link type: `{}'.") + return [] + if xport_type == "udp": + return self._xport_mgrs[xport_type].get_chdr_link_options( + self.mboard_info['rpc_connection']) + # else: + return self._xport_mgrs[xport_type].get_chdr_link_options() + + ########################################################################### + # Device info + ########################################################################### + def get_device_info_dyn(self): + """ + Append the device info with current IP addresses. + """ + if not self._device_initialized: + return {} + device_info = self._xport_mgrs['udp'].get_xport_info() + device_info.update({ + 'fpga_version': "{}.{}".format( + *self.mboard_regs_control.get_compat_number()), + 'fpga_version_hash': "{:x}.{}".format( + *self.mboard_regs_control.get_git_hash()), + 'fpga': self.updateable_components.get('fpga', {}).get('type', ""), + }) + return device_info + + def is_db_gpio_ifc_present(self, slot_id): + """ + Return if daughterboard GPIO interface at 'slot_id' is present in the FPGA + """ + db_gpio_version = self.mboard_regs_control.get_db_gpio_ifc_version(slot_id) + return db_gpio_version[0] > 0 + + ########################################################################### + # Clock/Time API + ########################################################################### + def get_clock_sources(self): + """ + Lists all available clock sources. + """ + return self._clk_mgr.get_clock_sources() + + def set_clock_source(self, *args): + """ + Ensures the new reference clock source and current time source pairing + is valid and sets both by calling set_sync_source(). + """ + clock_source = args[0] + time_source = self._clk_mgr.get_time_source() + assert clock_source is not None + assert time_source is not None + if (clock_source, time_source) not in self._clk_mgr.valid_sync_sources: + old_time_source = time_source + if clock_source in ( + X4xxClockMgr.CLOCK_SOURCE_MBOARD, + X4xxClockMgr.CLOCK_SOURCE_INTERNAL): + time_source = X4xxClockMgr.TIME_SOURCE_INTERNAL + elif clock_source == X4xxClockMgr.CLOCK_SOURCE_EXTERNAL: + time_source = X4xxClockMgr.TIME_SOURCE_EXTERNAL + elif clock_source == X4xxClockMgr.CLOCK_SOURCE_GPSDO: + time_source = X4xxClockMgr.TIME_SOURCE_GPSDO + self.log.warning( + f"Time source '{old_time_source}' is an invalid selection with " + f"clock source '{clock_source}'. " + f"Coercing time source to '{time_source}'") + self.set_sync_source({ + "clock_source": clock_source, "time_source": time_source}) + + def set_clock_source_out(self, enable): + """ + Allows routing the clock configured as source on the clk aux board to + the RefOut terminal. This only applies to internal, gpsdo and nsync. + """ + self._clk_mgr.set_clock_source_out(enable) + + def get_time_sources(self): + " Returns list of valid time sources " + return self._clk_mgr.get_time_sources() + + def set_time_source(self, time_source): + """ + Set a time source + + This will call set_sync_source() internally, and use the current clock + source as a clock source. If the current clock source plus the requested + time source is not a valid combination, it will coerce the clock source + to a valid choice and print a warning. + """ + clock_source = self._clk_mgr.get_clock_source() + assert clock_source is not None + assert time_source is not None + if (clock_source, time_source) not in self._clk_mgr.valid_sync_sources: + old_clock_source = clock_source + if time_source == X4xxClockMgr.TIME_SOURCE_QSFP0: + clock_source = X4xxClockMgr.CLOCK_SOURCE_MBOARD + elif time_source == X4xxClockMgr.TIME_SOURCE_INTERNAL: + clock_source = X4xxClockMgr.CLOCK_SOURCE_MBOARD + elif time_source == X4xxClockMgr.TIME_SOURCE_EXTERNAL: + clock_source = X4xxClockMgr.CLOCK_SOURCE_EXTERNAL + elif time_source == X4xxClockMgr.TIME_SOURCE_GPSDO: + clock_source = X4xxClockMgr.CLOCK_SOURCE_GPSDO + self.log.warning( + 'Clock source {} is an invalid selection with time source {}. ' + 'Coercing clock source to {}' + .format(old_clock_source, time_source, clock_source)) + self.set_sync_source( + {"time_source": time_source, "clock_source": clock_source}) + + def set_sync_source(self, args): + """ + 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). + """ + # Check the clock source, time source, and combined pair are valid: + clock_source = args.get('clock_source', self._clk_mgr.get_clock_source()) + if clock_source not in self.get_clock_sources(): + raise ValueError(f'Clock source {clock_source} is not a valid selection') + time_source = args.get('time_source', self._clk_mgr.get_time_source()) + if time_source not in self.get_time_sources(): + raise ValueError(f'Time source {time_source} is not a valid selection') + if (clock_source, time_source) not in self._clk_mgr.valid_sync_sources: + raise ValueError( + f'Clock and time source pair ({clock_source}, {time_source}) is ' + 'not a valid selection') + # Sanity checks complete. Now check if we need to disable the RefOut. + # Reminder: RefOut and PPSIn share an SMA. Besides, you can't export an + # external clock. We are thus not checking for time_source == 'external' + # because that's a subset of clock_source == 'external'. + # We also disable clock exports for 'mboard', because the mboard clock + # does not get routed back to the clocking aux board and thus can't be + # exported either. + if clock_source in (X4xxClockMgr.CLOCK_SOURCE_EXTERNAL, + X4xxClockMgr.CLOCK_SOURCE_MBOARD) and \ + self._clocking_auxbrd: + self._clocking_auxbrd.export_clock(enable=False) + # Now the clock manager can do its thing. + ret_val = self._clk_mgr.set_sync_source(clock_source, time_source) + if ret_val == self._clk_mgr.SetSyncRetVal.NOP: + return + try: + # Re-set master clock rate. If this doesn't work, it will time out + # and throw an exception. We need to put the device back into a safe + # state in that case. + self.set_master_clock_rate(self._master_clock_rate) + except RuntimeError as ex: + err = f"Setting clock_source={clock_source},time_source={time_source} " \ + f"failed, falling back to {self._safe_sync_source}. Error: " \ + f"{ex}" + self.log.error(err) + if args.get('__noretry__', False): + self.log.error("Giving up.") + else: + self.set_sync_source({**self._safe_sync_source, '__noretry__': True}) + raise + + def set_master_clock_rate(self, master_clock_rate): + """ + Sets the master clock rate by configuring the RFDC decimation and SPLL, + and then resetting downstream clocks. + """ + if master_clock_rate not in self.rfdc.master_to_sample_clk: + self.log.error('Unsupported master clock rate selection {}' + .format(master_clock_rate)) + raise RuntimeError('Unsupported master clock rate selection') + sample_clock_freq, decimation, is_legacy_mode, halfband = \ + self.rfdc.master_to_sample_clk[master_clock_rate] + for db_idx, _ in enumerate(self.dboards): + db_rfdc_resamp, db_halfband = self.rfdc.get_rfdc_resampling_factor(db_idx) + if db_rfdc_resamp != decimation or db_halfband != halfband: + msg = (f'master_clock_rate {master_clock_rate} is not compatible ' + f'with FPGA which expected decimation {db_rfdc_resamp}') + self.log.error(msg) + raise RuntimeError(msg) + self.log.trace(f"Set master clock rate (SPLL) to: {master_clock_rate}") + self._clk_mgr.set_spll_rate(sample_clock_freq, is_legacy_mode) + self._master_clock_rate = master_clock_rate + self.rfdc.sync() + self._clk_mgr.config_pps_to_timekeeper(master_clock_rate) + + def set_trigger_io(self, direction): + """ + Switch direction of clocking board Trigger I/O SMA socket. + IMPORTANT! Ensure downstream devices depending on TRIG I/O's output ignore + this signal when calling this method or re-run their synchronization routine + after calling this method. The output-enable control is async. to the output. + :param self: + :param direction: "off" trigger io socket unused + "pps_output" device will output PPS signal + "input" PPS is fed into the device from external + :return: success status as boolean + """ + OFF = "off" + INPUT = "input" + PPS_OUTPUT = "pps_output" + directions = [OFF, INPUT, PPS_OUTPUT] + + if not self._clocking_auxbrd: + raise RuntimeError("No clocking aux board available") + if not direction in directions: + raise RuntimeError("Invalid trigger io direction (%s). Use one of %s" + % (direction, directions)) + + # Switching order of trigger I/O lines depends on requested direction. + # Always turn on new driver last so both drivers cannot be active + # simultaneously. + if direction == INPUT: + self.mboard_regs_control.set_trig_io_output(False) + self._clocking_auxbrd.set_trig(1, ClockingAuxBrdControl.DIRECTION_INPUT) + elif direction == PPS_OUTPUT: + self._clocking_auxbrd.set_trig(1, ClockingAuxBrdControl.DIRECTION_OUTPUT) + self.mboard_regs_control.set_trig_io_output(True) + else: # direction == OFF: + self.mboard_regs_control.set_trig_io_output(False) + self._clocking_auxbrd.set_trig(0) + + return True + + ########################################################################### + # EEPROMs + ########################################################################### + def get_db_eeprom(self, dboard_idx): + """ + See PeriphManagerBase.get_db_eeprom() for docs. + """ + try: + dboard = self.dboards[dboard_idx] + except IndexError: + error_msg = "Attempted to access invalid dboard index `{}' " \ + "in get_db_eeprom()!".format(dboard_idx) + self.log.error(error_msg) + raise RuntimeError(error_msg) + db_eeprom_data = copy.copy(dboard.device_info) + return db_eeprom_data + + ########################################################################### + # Component updating + ########################################################################### + # Note: Component updating functions defined by ZynqComponents + @no_rpc + def _update_fpga_type(self): + """Update the fpga type stored in the updateable components""" + fpga_string = "{}_{}".format( + self.mboard_regs_control.get_fpga_type(), + self.rfdc.get_dsp_bw()) + self.log.debug("Updating mboard FPGA type info to {}".format(fpga_string)) + self.updateable_components['fpga']['type'] = fpga_string + + ####################################################################### + # Timekeeper API + ####################################################################### + def get_master_clock_rate(self): + """ Return the master clock rate set during init """ + return self._master_clock_rate + + def get_clocks(self): + """ + Gets the RFNoC-related clocks present in the FPGA design + """ + # TODO: The 200 and 40 MHz clocks should not be hard coded, and ideally + # be linked to the FPGA image somehow + return [ + { + 'name': 'radio_clk', + 'freq': str(self.get_master_clock_rate()), + 'mutable': 'true' + }, + { + 'name': 'bus_clk', + 'freq': str(200e6), + }, + { + 'name': 'ctrl_clk', + 'freq': str(40e6), + } + ] + + + ########################################################################### + # Utility for validating RPU core number + ########################################################################### + @no_rpc + def _validate_rpu_core_number(self, core_number): + if ((core_number < 0) or (core_number > 1)): + raise RuntimeError("RPU core number must be 0 or 1.") + + + ########################################################################### + # Utility for validating RPU state change command + ########################################################################### + @no_rpc + def _validate_rpu_state(self, new_state_command, previous_state): + if ((new_state_command != RPU_STATE_COMMAND_START) + and (new_state_command != RPU_STATE_COMMAND_STOP)): + raise RuntimeError("RPU state command must be start or stop.") + if ((new_state_command == RPU_STATE_COMMAND_START) + and (previous_state == RPU_STATE_RUNNING)): + raise RuntimeError("RPU already running.") + if ((new_state_command == RPU_STATE_COMMAND_STOP) + and (previous_state == RPU_STATE_OFFLINE)): + raise RuntimeError("RPU already offline.") + + ########################################################################### + # Utility for validating RPU firmware + ########################################################################### + @no_rpc + def _validate_rpu_firmware(self, firmware): + file_path = path.join(RPU_REMOTEPROC_FIRMWARE_PATH, firmware) + if not path.isfile(file_path): + raise RuntimeError("Specified firmware does not exist.") + + ########################################################################### + # Utility for reading contents of a file + ########################################################################### + @no_rpc + def _read_file(self, file_path): + self.log.trace("_read_file: file_path= %s", file_path) + with open(file_path, 'r') as f: + return f.read().strip() + + + ########################################################################### + # Utility for writing contents of a file + ########################################################################### + @no_rpc + def _write_file(self, file_path, data): + self.log.trace("_write_file: file_path= %s, data= %s", file_path, data) + with open(file_path, 'w') as f: + f.write(data) + + + ########################################################################### + # RPU Image Deployment API + ########################################################################### + def get_rpu_state(self, core_number, validate=True): + """ Report the state for the specified RPU core """ + if validate: + self._validate_rpu_core_number(core_number) + return self._read_file( + path.join( + RPU_REMOTEPROC_PREFIX_PATH + str(core_number), + RPU_REMOTEPROC_PROPERTY_STATE)) + + + def set_rpu_state(self, core_number, new_state_command, validate=True): + """ Set the specified state for the specified RPU core """ + if not self._rpu_initialized: + self.log.warning( + "Failed to set RPU state: RPU peripheral not "\ + "initialized.") + return RPU_FAILURE_REPORT + # OK, RPU is initialized, now go set its state: + if validate: + self._validate_rpu_core_number(core_number) + previous_state = self.get_rpu_state(core_number, False) + if validate: + self._validate_rpu_state(new_state_command, previous_state) + self._write_file( + path.join( + RPU_REMOTEPROC_PREFIX_PATH + str(core_number), + RPU_REMOTEPROC_PROPERTY_STATE), + new_state_command) + + # Give RPU core time to change state (might load new fw) + poll_with_timeout( + lambda: previous_state != self.get_rpu_state(core_number, False), + RPU_MAX_STATE_CHANGE_TIME_IN_MS, + RPU_STATE_CHANGE_POLLING_INTERVAL_IN_MS) + + # Quick validation of new state + resulting_state = self.get_rpu_state(core_number, False) + if ((new_state_command == RPU_STATE_COMMAND_START) + and (resulting_state != RPU_STATE_RUNNING)): + raise RuntimeError('Unable to start specified RPU core.') + if ((new_state_command == RPU_STATE_COMMAND_STOP) + and (resulting_state != RPU_STATE_OFFLINE)): + raise RuntimeError('Unable to stop specified RPU core.') + return RPU_SUCCESS_REPORT + + def get_rpu_firmware(self, core_number): + """ Report the firmware for the specified RPU core """ + self._validate_rpu_core_number(core_number) + return self._read_file( + path.join( + RPU_REMOTEPROC_PREFIX_PATH + str(core_number), + RPU_REMOTEPROC_PROPERTY_FIRMWARE)) + + + def set_rpu_firmware(self, core_number, firmware, start=0): + """ Deploy the image at the specified path to the RPU """ + self.log.trace("set_rpu_firmware") + self.log.trace( + "image path: %s, core_number: %d, start?: %d", + firmware, + core_number, + start) + + if not self._rpu_initialized: + self.log.warning( + "Failed to deploy RPU image: "\ + "RPU peripheral not initialized.") + return RPU_FAILURE_REPORT + # RPU is initialized, now go set firmware: + self._validate_rpu_core_number(core_number) + self._validate_rpu_firmware(firmware) + + # Stop the core if necessary + if self.get_rpu_state(core_number, False) == RPU_STATE_RUNNING: + self.set_rpu_state(core_number, RPU_STATE_COMMAND_STOP, False) + + # Set the new firmware path + self._write_file( + path.join( + RPU_REMOTEPROC_PREFIX_PATH + str(core_number), + RPU_REMOTEPROC_PROPERTY_FIRMWARE), + firmware) + + # Start the core if requested + if start != 0: + self.set_rpu_state(core_number, RPU_STATE_COMMAND_START, False) + return RPU_SUCCESS_REPORT + + ####################################################################### + # Debugging + # Provides temporary methods for arbitrary hardware access while + # development for these components is ongoing. + ####################################################################### + def peek_ctrlport(self, addr): + """ Peek the MPM Endpoint to ctrlport registers on the FPGA """ + return '0x{:X}'.format(self.ctrlport_regs.peek32(addr)) + + def poke_ctrlport(self, addr, val): + """ Poke the MPM Endpoint to ctrlport registers on the FPGA """ + self.ctrlport_regs.poke32(addr, val) + + def peek_cpld(self, addr): + """ Peek the PS portion of the MB CPLD """ + return '0x{:X}'.format(self.cpld_control.peek32(addr)) + + def poke_cpld(self, addr, val): + """ Poke the PS portion of the MB CPLD """ + self.cpld_control.poke32(addr, val) + + def peek_db(self, db_id, addr): + """ Peek the DB CPLD, even if the DB is not discovered by MPM """ + assert db_id in (0, 1) + self.cpld_control.enable_daughterboard(db_id) + return '0x{:X}'.format( + self.ctrlport_regs.get_db_cpld_iface(db_id).peek32(addr)) + + def poke_db(self, db_id, addr, val): + """ Poke the DB CPLD, even if the DB is not discovered by MPM """ + assert db_id in (0, 1) + self.cpld_control.enable_daughterboard(db_id) + self.ctrlport_regs.get_db_cpld_iface(db_id).poke32(addr, val) + + def peek_clkaux(self, addr): + """Peek the ClkAux DB over SPI""" + return '0x{:X}'.format(self._clocking_auxbrd.peek8(addr)) + + def poke_clkaux(self, addr, val): + """Poke the ClkAux DB over SPI""" + self._clocking_auxbrd.poke8(addr, val) + + ########################################################################### + # Sensors + ########################################################################### + def get_ref_lock_sensor(self): + """ + Return main refclock lock status. This is the lock status of the + reference and sample PLLs. + """ + lock_status = self._clk_mgr.get_ref_locked() + return { + 'name': 'ref_locked', + 'type': 'BOOLEAN', + 'unit': 'locked' if lock_status else 'unlocked', + 'value': str(lock_status).lower(), + } + + def get_fpga_temp_sensor(self): + """ Get temperature sensor reading of the X4xx FPGA. """ + self.log.trace("Reading FPGA temperature.") + return get_temp_sensor(["RFSoC"], log=self.log) + + def get_main_power_temp_sensor(self): + """ + Get temperature sensor reading of PM-BUS devices which supply + 0.85V power supply to RFSoC. + """ + self.log.trace("Reading PMBus Power Supply Chip(s) temperature.") + return get_temp_sensor(["PMBUS-0", "PMBUS-1"], log=self.log) + + def get_scu_internal_temp_sensor(self): + """ Get temperature sensor reading of STM32 SCU's internal sensor. """ + self.log.trace("Reading SCU internal temperature.") + return get_temp_sensor(["EC Internal"], log=self.log) + + def get_internal_temp_sensor(self): + """ TODO: Determine how to interpret this function """ + self.log.warning("Reading internal temperature is not yet implemented.") + return { + 'name': 'temperature', + 'type': 'REALNUM', + 'unit': 'C', + 'value': '-1' + } + + def _get_fan_sensor(self, fan='fan0'): + """ Get fan speed. """ + self.log.trace("Reading {} speed sensor.".format(fan)) + fan_rpm = -1 + try: + fan_rpm_all = ectool.get_fan_rpm() + fan_rpm = fan_rpm_all[fan] + except Exception as ex: + self.log.warning( + "Error occurred when getting {} speed value: {} " + .format(fan, str(ex))) + return { + 'name': fan, + 'type': 'INTEGER', + 'unit': 'rpm', + 'value': str(fan_rpm) + } + + def get_fan0_sensor(self): + """ Get fan0 speed. """ + return self._get_fan_sensor('fan0') + + def get_fan1_sensor(self): + """ Get fan1 speed.""" + return self._get_fan_sensor('fan1') + + def get_gps_sensor_status(self): + """ + Get all status of GPS as sensor dict + """ + assert self._gps_mgr + self.log.trace("Reading all GPS status pins") + return f""" + {self.get_gps_lock_sensor()} + {self.get_gps_alarm_sensor()} + {self.get_gps_warmup_sensor()} + {self.get_gps_survey_sensor()} + {self.get_gps_phase_lock_sensor()} + """ diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_clk_aux.py b/mpm/python/usrp_mpm/periph_manager/x4xx_clk_aux.py new file mode 100644 index 000000000..fcd655d9d --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/x4xx_clk_aux.py @@ -0,0 +1,682 @@ +# +# Copyright 2021 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Drivers for the X410 clocking aux board. +""" + +import subprocess +from usrp_mpm import lib # Pulls in everything from C++-land +from usrp_mpm import tlv_eeprom +from usrp_mpm.mpmlog import get_logger +from usrp_mpm.sys_utils.gpio import Gpio +from usrp_mpm.sys_utils.udev import dt_symbol_get_spidev +from usrp_mpm.sys_utils.udev import get_eeprom_paths_by_symbol +from usrp_mpm.sys_utils import i2c_dev +from usrp_mpm.chips import LMK05318 + +X400_CLKAUX_DEFAULT_TUNING_WORD = 0x200 # 1.65V which would be a DAC value of 512 +X400_CLKAUX_DEFAULT_REVISION = 0x1 +X400_CLKAUX_I2C_LABEL = 'clkaux_i2c' +X400_CLKAUX_SPI_LABEL = 'clkaux_spi' +X400_CLKAUX_GPSDO_PID = 0x4004 +X400_CLKAUX_NOGPSDO_PID = 0x4005 + + +def _check_i2c_bus(): + """ + Assert that the I2C connection to the clocking board is available in the + device tree. + """ + i2c_bus = i2c_dev.dt_symbol_get_i2c_bus(X400_CLKAUX_I2C_LABEL) + if i2c_bus is None: + raise RuntimeError("ClockingAuxBrdControl I2C bus not found") + +def _check_spi_bus(): + """ + Assert that the SPI connection to the clocking board is available in the + device tree. + """ + spi_node = dt_symbol_get_spidev(X400_CLKAUX_SPI_LABEL) + if spi_node is None: + raise RuntimeError("ClockingAuxBrdControl SPI bus not found") + + +# pylint: disable=too-few-public-methods +class ClkAuxTagMap: + """ + x4xx main tagmap is in the main class. Only the subset relevant to the + clocking aux board is included below. + """ + magic = 0x55535250 + tagmap = { + # 0x23: usrp_eeprom_clkaux_tuning_word + 0x23: tlv_eeprom.NamedStruct('< H', + ['tuning_word']), + # 0x10: usrp_eeprom_board_info + 0x10: tlv_eeprom.NamedStruct('< H H H 7s 1x', + ['pid', 'rev', 'rev_compat', 'serial']), + } + +# We are encapsulating a complicated piece of hardware here, so let's live with +# the fact that there will be many things hanging off of it. +# pylint: disable=too-many-public-methods +# pylint: disable=too-many-instance-attributes +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)) diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_clk_mgr.py b/mpm/python/usrp_mpm/periph_manager/x4xx_clk_mgr.py new file mode 100644 index 000000000..bf845dd65 --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/x4xx_clk_mgr.py @@ -0,0 +1,780 @@ +# +# Copyright 2021 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +X400 Clocking Management + +This module handles the analog clocks on the X4x0 motherboard. The clocking +architecture of the motherboard is spread out between a clocking auxiliary board, +which contains a GPS-displicined OCXO, but also connects an external reference +to the motherboard. It also houses a PLL for deriving a clock from the network +(eCPRI). The clocking aux board has its own control class (ClockingAuxBrdControl) +which also contains controls for the eCPRI PLL. + +The motherboard itself has two main PLLs for clocking purposes: The Sample PLL +(also SPLL) is used to create all clocks used for RF-related purposes. It creates +the sample clock (a very fast clock, ~3 GHz) and the PLL reference clock (PRC) +which is used as a timebase for the daughterboard CPLD and a reference for the +LO synthesizers (50-64 MHz). + +Its input is the base reference clock (BRC). This clock comes either from the +clocking aux board, which in turn can provide a reference from the OCXO (with or +without GPS-disciplining) or from the external reference input SMA port. +The BRC is typically 10-25 MHz. + +The BRC can also come from the reference PLL (RPLL), when the clock source is +set to 'mboard'. The RPLL produces clocks that are consumed by the GTY banks +(for Ethernet and Aurora), but it can also generate a reference clock for +the SPLL. By default, its reference is a fixed 100 MHz clock, but it can also be +driven by the eCPRI PLL, which itself can be driven by a clock from the GTY banks, +which is the case if the clock source is set to 'nsync'. + +The master clock rate (MCR) is not actually controlled in this module, but it +depends on the sample clock rate. It also depends on the RFDC settings, so it is +controlled in x4xx.py, which has access to both RFDC and X4xxClockMgr. + +Block diagram (for more details, see the schematic):: + + ┌────────────────────────────────────────────────────────┐ + │ Clocking Aux Board │ + │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ + │ │ GPSDO ├─> OCXO │ │External│ │eCPRI/ │ │ + │ │ │ │ │ │ │ │nsync │ │ + │ └────────┘ └────┬───┘ └───┬────┘ └────────┘ │ + │ │ │ │ + │ │ │ │ + │ ┌───────────v─────────v───┐ ┌───────────┐ │ + │ │ │ │eCPRI PLL │ │ + │ └┐ MUX ┌┘ │LMK05318 │ │ + │ └─┐ ┌─┘ │ │ │ + │ └─┬─────────────────┘ └──┬────────┘ │ + │ │ │ │ + └───────────┼───────────────────────────┼────────────────┘ + │ │ + │ ┌─────────────┐ │ + ┌──v──v┐ │ │ + │ MUX │ │ │ ┌───── 100 MHz + └──┬───┘ │ │ │ + │Base Ref. Clock │ │ │ + ┌───────v───────┐ │ ┌───────v──v──┐ + │ Sample PLL │ └──┤Reference PLL│ + │ LMK04832 │ │LMK03328 │ + │ │ │ │ + │ │ │ │ + └──┬─────────┬──┘ └────┬────────┘ + │ │ │ + │ │ │ + v v v + Sample PLL Reference GTY Banks + Clock Clock + + +The code in this module controls the RPLL and SPLL as well as some of the muxing. +The eCPRI PLL is controlled from the ClockingAuxBrdControl class. +Most importantly, this class controls the sequencing of configuring clocks. This +means it won't only switch muxes and configure PLLs, but will also do things in +the right order, and put components in reset where necessary. +For this reason, it requires callbacks to reset RFDC and daughterboard clocks. +""" + +from enum import Enum +from usrp_mpm import lib # Pulls in everything from C++-land +from usrp_mpm.sys_utils import i2c_dev +from usrp_mpm.sys_utils.gpio import Gpio +from usrp_mpm.sys_utils.udev import dt_symbol_get_spidev +from usrp_mpm.periph_manager.x4xx_periphs import MboardRegsControl +from usrp_mpm.periph_manager.x4xx_sample_pll import LMK04832X4xx +from usrp_mpm.periph_manager.x4xx_reference_pll import LMK03328X4xx +from usrp_mpm.periph_manager.x4xx_clk_aux import ClockingAuxBrdControl +from usrp_mpm.mpmutils import poll_with_timeout +from usrp_mpm.rpc_server import no_rpc + +# this is not the frequency out of the GPSDO(GPS Lite, 20MHz) itself but +# the GPSDO on the CLKAUX board is used to fine tune the OCXO via EFC +# which is running at 10MHz +X400_GPSDO_OCXO_CLOCK_FREQ = 10e6 +X400_RPLL_I2C_LABEL = 'rpll_i2c' +X400_DEFAULT_RPLL_REF_SOURCE = '100M_reliable_clk' +X400_DEFAULT_MGT_CLOCK_RATE = 156.25e6 +X400_DEFAULT_INT_CLOCK_FREQ = 25e6 + +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) diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_gps_mgr.py b/mpm/python/usrp_mpm/periph_manager/x4xx_gps_mgr.py new file mode 100644 index 000000000..25a91c19b --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/x4xx_gps_mgr.py @@ -0,0 +1,157 @@ +# +# Copyright 2021 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +X4XX GPS Manager + +Handles GPS-related tasks +""" + +import re +from usrp_mpm.gpsd_iface import GPSDIfaceExtension + +class X4xxGPSMgr: + """ + Manager class for GPS-related actions for the X4XX. + + This also "disables" the sensors when the GPS is not enabled. + """ + def __init__(self, clk_aux_board, log): + assert clk_aux_board and clk_aux_board.is_gps_supported() + self._clocking_auxbrd = clk_aux_board + self.log = log.getChild('GPS') + self.log.trace("Initializing GPSd interface") + self._gpsd = GPSDIfaceExtension() + # To disable sensors, we simply return an empty value if GPS is disabled. + # For TPV, SKY, and GPGGA, we can do this in the same fashion (they are + # very similar). gps_time is different (it returns an int) so for sake + # of simplicity it's defined separately below. + for sensor_name in ('gps_tpv', 'gps_sky', 'gps_gpgga'): + sensor_api = f'get_{sensor_name}_sensor' + setattr( + self, sensor_api, + lambda sensor_name=sensor_name: { + 'name': sensor_name, 'type': 'STRING', + 'unit': '', 'value': 'n/a'} \ + if not self.is_gps_enabled() \ + else getattr(self._gpsd, f'get_{sensor_name}_sensor')() + ) + + def extend(self, context): + """ + Extend 'context' with the sensor methods of this class (get_gps_*_sensor). + If 'context' already has such a method, it is skipped. + + Returns a dictionary compatible to mboard_sensor_callback_map. + """ + new_methods = { + re.search(r"get_(.*)_sensor", method_name).group(1): method_name + for method_name in dir(self) + if not method_name.startswith('_') \ + and callable(getattr(self, method_name)) \ + and method_name.endswith("sensor")} + for method_name in new_methods.values(): + if hasattr(context, method_name): + continue + new_method = getattr(self, method_name) + self.log.trace("%s: Adding %s method", context, method_name) + setattr(context, method_name, new_method) + return new_methods + + def is_gps_enabled(self): + """ + Return True if the GPS is enabled/active. + """ + return self._clocking_auxbrd.is_gps_enabled() + + def get_gps_enabled_sensor(self): + """ + Get enabled status of GPS as a sensor dict + """ + gps_enabled = self.is_gps_enabled() + return { + 'name': 'gps_enabled', + 'type': 'BOOLEAN', + 'unit': 'enabled' if gps_enabled else 'disabled', + 'value': str(gps_enabled).lower(), + } + + def get_gps_locked_sensor(self): + """ + Get lock status of GPS as a sensor dict + """ + gps_locked = self.is_gps_enabled() and \ + bool(self._clocking_auxbrd.get_gps_lock()) + return { + 'name': 'gps_lock', + 'type': 'BOOLEAN', + 'unit': 'locked' if gps_locked else 'unlocked', + 'value': str(gps_locked).lower(), + } + + def get_gps_alarm_sensor(self): + """ + Get alarm status of GPS as a sensor dict + """ + gps_alarm = self.is_gps_enabled() and \ + bool(self._clocking_auxbrd.get_gps_alarm()) + return { + 'name': 'gps_alarm', + 'type': 'BOOLEAN', + 'unit': 'active' if gps_alarm else 'not active', + 'value': str(gps_alarm).lower(), + } + + def get_gps_warmup_sensor(self): + """ + Get warmup status of GPS as a sensor dict + """ + gps_warmup = self.is_gps_enabled() and \ + bool(self._clocking_auxbrd.get_gps_warmup()) + return { + 'name': 'gps_warmup', + 'type': 'BOOLEAN', + 'unit': 'warming up' if gps_warmup else 'warmup done', + 'value': str(gps_warmup).lower(), + } + + def get_gps_survey_sensor(self): + """ + Get survey status of GPS as a sensor dict + """ + gps_survey = self.is_gps_enabled() and \ + bool(self._clocking_auxbrd.get_gps_survey()) + return { + 'name': 'gps_survey', + 'type': 'BOOLEAN', + 'unit': 'survey active' if gps_survey else 'survey not active', + 'value': str(gps_survey).lower(), + } + + def get_gps_phase_lock_sensor(self): + """ + Get phase_lock status of GPS as a sensor dict + """ + gps_phase_lock = self.is_gps_enabled() and \ + bool(self._clocking_auxbrd.get_gps_phase_lock()) + return { + 'name': 'gps_phase_lock', + 'type': 'BOOLEAN', + 'unit': 'phase locked' if gps_phase_lock else 'no phase lock', + 'value': str(gps_phase_lock).lower(), + } + + def get_gps_time_sensor(self): + """ + + """ + if not self.is_gps_enabled(): + return { + 'name': 'gps_time', + 'type': 'INTEGER', + 'unit': 'seconds', + 'value': str(-1), + } + return self._gpsd.get_gps_time_sensor() diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_mb_cpld.py b/mpm/python/usrp_mpm/periph_manager/x4xx_mb_cpld.py new file mode 100644 index 000000000..927db3360 --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/x4xx_mb_cpld.py @@ -0,0 +1,204 @@ +# +# Copyright 2021 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +X4xx motherboard CPLD control +""" + +from usrp_mpm import lib # Pulls in everything from C++-land + +def parse_encoded_git_hash(encoded): + git_hash = encoded & 0x0FFFFFFF + tree_dirty = ((encoded & 0xF0000000) > 0) + dirtiness_qualifier = 'dirty' if tree_dirty else 'clean' + return (git_hash, dirtiness_qualifier) + +class MboardCPLD: + """ + Control for the CPLD. + """ + # pylint: disable=bad-whitespace + SIGNATURE_OFFSET = 0x0000 + COMPAT_REV_OFFSET = 0x0004 + OLDEST_COMPAT_REV_OFFSET = 0x0008 + GIT_HASH_OFFSET = 0x0010 + DB_ENABLE_OFFSET = 0x0020 + SERIAL_NO_LO_OFFSET = 0x0034 + SERIAL_NO_HI_OFFSET = 0x0038 + CMI_OFFSET = 0x003C + + # change these revisions only on breaking changes + OLDEST_REQ_COMPAT_REV = 0x20122114 + REQ_COMPAT_REV = 0x20122114 + SIGNATURE = 0x0A522D27 + PLL_REF_CLOCK_ENABLED = 1 << 2 + ENABLE_CLK_DB0 = 1 << 8 + ENABLE_CLK_DB1 = 1 << 9 + ENABLE_PRC = 1 << 10 + DISABLE_CLK_DB0 = 1 << 12 + DISABLE_CLK_DB1 = 1 << 13 + DISABLE_PRC = 1 << 14 + RELEASE_RST_DB0 = 1 << 16 + RELEASE_RST_DB1 = 1 << 17 + ASSERT_RST_DB0 = 1 << 20 + ASSERT_RST_DB1 = 1 << 21 + # pylint: enable=bad-whitespace + + def __init__(self, spi_dev_node, log): + self.log = log.getChild("CPLD") + self.regs = lib.spi.make_spidev_regs_iface( + spi_dev_node, + 1000000, # Speed (Hz) + 0, # SPI mode + 32, # Addr shift + 0, # Data shift + 0, # Read flag + 1<<47 # Write flag + ) + self.poke32 = self.regs.poke32 + self.peek32 = self.regs.peek32 + + def enable_pll_ref_clk(self, enable=True): + """ + Enables or disables the PLL reference clock. + + This makes no assumptions on the prior state of the clock. It does check + if the clock-enable was successful by polling the CPLD, and throws if + not. + """ + def check_pll_enabled(): + return self.peek32(self.DB_ENABLE_OFFSET) & self.PLL_REF_CLOCK_ENABLED + if enable: + self.poke32(self.DB_ENABLE_OFFSET, self.ENABLE_PRC) + if not check_pll_enabled(): + self.log.error("PRC enable failed!") + raise RuntimeError('PRC enable failed!') + return + # Disable PRC: + self.poke32(self.DB_ENABLE_OFFSET, self.DISABLE_PRC) + if check_pll_enabled(): + self.log.error('PRC reset failed!') + raise RuntimeError('PRC reset failed!') + + def enable_daughterboard(self, db_id, enable=True): + """ Enable or disable clock forwarding to a given DB """ + if db_id == 0: + release_reset = self.RELEASE_RST_DB0 + assert_reset = self.ASSERT_RST_DB0 + else: + release_reset = self.RELEASE_RST_DB1 + assert_reset = self.ASSERT_RST_DB1 + value = self.peek32(self.DB_ENABLE_OFFSET) + if enable: + # De-assert reset + value = (value | release_reset) & (~assert_reset) + else: #disable + # Assert reset + value = (value | assert_reset) & (~release_reset) + self.poke32(self.DB_ENABLE_OFFSET, value) + + def enable_daughterboard_support_clock(self, db_id, enable=True): + """ Enable or disable clock forwarding to a given DB """ + if db_id == 0: + clk_enable = self.ENABLE_CLK_DB0 + clk_disable = self.DISABLE_CLK_DB0 + else: + clk_enable = self.ENABLE_CLK_DB1 + clk_disable = self.DISABLE_CLK_DB1 + value = self.peek32(self.DB_ENABLE_OFFSET) + if enable: + # Enable clock + value = (value | clk_enable) & (~clk_disable) + else: #disable + # Disable clock + value = (value | clk_disable) & (~clk_enable) + self.poke32(self.DB_ENABLE_OFFSET, value) + + def check_signature(self): + """ + Assert that the CPLD signature is correct. If the CPLD 'signature' + register returns something unexpectected, throws a RuntimeError. + """ + read_signature = self.peek32(self.SIGNATURE_OFFSET) + if self.SIGNATURE != read_signature: + self.log.error('MB PS CPLD signature {:X} does not match ' + 'expected value {:X}'.format(read_signature, self.SIGNATURE)) + raise RuntimeError('MB PS CPLD signature {:X} does not match ' + 'expected value {:X}'.format(read_signature, self.SIGNATURE)) + + def check_compat_version(self): + """ + Check oldest compatible revision offset of HW against required revision. + The value has to match as the register offsets depends on them. + Furthermore there needs to be a minimum revision to check for existence + of functionality. + """ + cpld_image_compat_revision = self.peek32(self.OLDEST_COMPAT_REV_OFFSET) + if cpld_image_compat_revision < self.OLDEST_REQ_COMPAT_REV: + error_message = ( + 'MB CPLD oldest compatible revision' + f' 0x{cpld_image_compat_revision:08x} is out of date. Update' + f' your CPLD image to 0x{self.OLDEST_REQ_COMPAT_REV:08x}.') + self.log.error(error_message) + raise RuntimeError(error_message) + if cpld_image_compat_revision > self.OLDEST_REQ_COMPAT_REV: + error_message = ( + 'MB CPLD oldest compatible revision' + f' 0x{cpld_image_compat_revision:08x} is unknown. Downgrade' + f' your CPLD image to 0x{self.OLDEST_REQ_COMPAT_REV:08x}.') + self.log.error(error_message) + raise RuntimeError(error_message) + + if not self.has_compat_version(self.REQ_COMPAT_REV): + error_message = ( + "MB CPLD compatible revision is too old. Update your CPLD" + f" image to at least 0x{self.REQ_COMPAT_REV:08x}.") + self.log.error(error_message) + raise RuntimeError(error_message) + + def has_compat_version(self, min_required_version): + """ + Check for a minimum required version. + """ + if min_required_version < self.REQ_COMPAT_REV: + self.log.warning( + "Somebody called MB CPLD has_compat_version with revision" + f" 0x{min_required_version:x} which is older than the mandated" + f" version 0x{self.REQ_COMPAT_REV:x}.") + cpld_image_compat_revision = self.peek32(self.COMPAT_REV_OFFSET) + return cpld_image_compat_revision >= min_required_version + + def trace_git_hash(self): + """ + Trace build of MB CPLD + """ + git_hash_rb = self.peek32(self.GIT_HASH_OFFSET) + (git_hash, dirtiness_qualifier) = parse_encoded_git_hash(git_hash_rb) + self.log.trace("MB CPLD build GIT Hash: {:07x} ({})".format( + git_hash, dirtiness_qualifier)) + + def set_serial_number(self, serial_number): + """ + Set serial number register + """ + assert len(serial_number) > 0 + assert len(serial_number) <= 8 + serial_number_string = str(serial_number, 'ascii') + serial_number_int = int(serial_number_string, 16) + self.poke32(self.SERIAL_NO_LO_OFFSET, serial_number_int & 0xFFFFFFFF) + self.poke32(self.SERIAL_NO_HI_OFFSET, serial_number_int >> 32) + + def set_cmi_device_ready(self, ready=True): + """ + Inform CMI partner that this device is ready for PCI-Express communication. + """ + value = 1 if ready else 0 + self.poke32(self.CMI_OFFSET, value) + + def get_cmi_status(self): + """ + Return true if upstream CMI device was found. + """ + return bool(self.peek32(self.CMI_OFFSET)) diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_periphs.py b/mpm/python/usrp_mpm/periph_manager/x4xx_periphs.py new file mode 100644 index 000000000..be0487872 --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/x4xx_periphs.py @@ -0,0 +1,1445 @@ +# +# Copyright 2019 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +X4xx peripherals +""" + +import re +import time +import signal +import struct +from multiprocessing import Process, Event +from statistics import mean +from usrp_mpm import lib # Pulls in everything from C++-land +from usrp_mpm.sys_utils import i2c_dev +from usrp_mpm.sys_utils.gpio import Gpio +from usrp_mpm.sys_utils.uio import UIO +from usrp_mpm.mpmutils import poll_with_timeout +from usrp_mpm.sys_utils.sysfs_thermal import read_thermal_sensor_value +from usrp_mpm.periph_manager.common import MboardRegsCommon + +class DioControl: + """ + DioControl acts as front end for DIO AUX BOARD + + The DioControl class uses three hardware resources to control the behavior + of the board, which are + * I2C extender + * MB registers + * MB cpld registers + + DioControl supports arbitrary methods of addressing the pins on the + frontend. The current implementation supports two ways of pin addressing: + HDMI and DIO. Use set_port_mapping to switch between both of them. + + When using HDMI as pin addressing scheme you have to give the real pin + number of the HDMI adapter like this:: + ┌───────────────────────────────┐ + └┐19 17 15 13 11 09 07 05 03 01┌┘ + └┐ 18 16 14 12 10 08 06 04 02┌┘ + └───────────────────────────┘ + Be aware that not all pins are accessible. The DioControl class will warn + about the usage of unassigned pins. + + The second option is the DIO addressing scheme. Here all user accessible + pins are numbered along the HDMI pin numbers which gives a pin table like + this:: + ┌───────────────────────────────┐ + └┐11 -- 09 08 -- 05 04 -- 01 00┌┘ + └┐ -- 10 -- 07 06 -- 03 02 --┌┘ + └───────────────────────────┘ + + Within the MPM shell one can query the state of the DIO board using + dio_status. This gives an output like this:: + HDMI mapping | PORT A | PORT B + --------------+-------------------------+------------------------- + voltage | OFF - PG:NO - EXT:OFF | 1V8 - PG:YES - EXT:OFF + --------------+-------------------------+------------------------- + master | 0.. 00.0 0.00 .00. 00.1 | 0.. 00.0 0.00 .00. 00.1 + direction | 0.. 00.0 0.00 .00. 00.1 | 0.. 00.0 0.00 .00. 00.0 + --------------+-------------------------+------------------------- + output | 0.. 00.0 0.00 .00. 00.1 | 0.. 00.0 0.00 .00. 00.0 + input | 0.. 00.0 0.00 .00. 00.1 | 0.. 00.0 0.00 .00. 00.0 + The table displays the current state of HDMI port A and B provided by the + DIO board as well as the state of the corresponding register maps and GPIO + pins in a user readable form. + + The header shows the active mapping and the port names. Change the + mapping with set_port_mapping. + + The first row shows the voltage state of each port. Voltage can be one of + the states in (OFF, 1V8, 2V5, 3V3). Change the power state by using + set_voltage_level. When the voltage level is set to OFF, the corresponding + GPIO pin EN_PORT is set low, high otherwise. + When voltage is set to one of the other states EN_PORT<x> is set to high and + EN_PORT<x>_<?V?> is set accordingly where 1V8 corresponds to 2V5 and 3V3 + being both low. PG shows whether the PG (power good) pin corresponding to + the port (PORT<x>_PG) is high. This is NO if power is OFF and YES otherwise. + EXT shows whether EN_EXT_PWR_<x> is enabled for the port. Change the + external power using set_external_power. + Note: A port must have a reasonable voltage level assigned to it before + changes to the output register takes effect on the HDMI port pins or + pin states of the HDMI port can be read in input register. + + The master row shows the pin assignments for the master register which + decides whether PS (1) or FPGA (0) drives output register pins. Change + values using set_master_pin(s). + + The direction row shows the pin assignments for the direction register which + decides whether the pin is written (1) or read (0) by the FPGA. Change + values using set_direction_pin(s). + + The output and input rows shows the pin assignments for the FPGAs input and + output registers. Only the output register pins can be changed. Change + values using set_output_pin(s). + + """ + + # Available DIO ports + DIO_PORTS = ("PORTA", "PORTB") + # Available voltage levels + DIO_VOLTAGE_LEVELS = ("OFF", "1V8", "2V5", "3V3") + + # For each mapping supported by DioControl the class needs the following + # information + # * map_name: name of the mapping (in uppercase) + # * pin_names: names of the pins starting with smallest. Unassignable PINS + # must be named as well + # * port_map: mapping table from FPGA register indices to pin indices. A + # mapping of (4, 2) means pin 4 is mapped to register bit 0 and + # pin2 is mapped to register bit 1. Only assignable pins + # may appear in the mapping. + # * first_pin: index of the first pin for the mapping + + # HDMI mapping constants + HDMI_MAP_NAME = "HDMI" + HDMI_PIN_NAMES = ("Data2+", "Data2_SHD", "Data2-", "Data1+", "Data1_SHD", + "Data1-", "Data0+", "Data0_SHD", "Data0-", "CLK+", + "CLK_SHD", "CLK-", "RESERVED", "HEC_Data-", "SCL", + "SDA", "HEC_GND", "V+", "HEC_Data+") + HDMI_PORT_MAP = {DIO_PORTS[0]: (3, 1, 4, 6, 9, 7, 10, 12, 15, 13, 16, 19), + DIO_PORTS[1]: (16, 19, 15, 13, 10, 12, 9, 7, 4, 6, 3, 1)} + HDMI_FIRST_PIN = 1 + + # DIO mapping constants + DIO_MAP_NAME = "DIO" + DIO_PIN_NAMES = ("DIO0", "DIO1", "DIO2", "DIO3", "DIO4", "DIO5", + "DIO6", "DIO7", "DIO8", "DIO9", "DIO10", "DIO11") + DIO_PORT_MAP = {DIO_PORTS[0]: (1, 0, 2, 3, 5, 4, 6, 7, 9, 8, 10, 11), + DIO_PORTS[1]: (10, 11, 9, 8, 6, 7, 5, 4, 2, 3, 1, 0)} + DIO_FIRST_PIN = 0 + + # Register layout/size constants + PORT_BIT_SIZE = 16 # number of bits used in register per port + PORT_USED_BITS_MASK = 0xFFF # masks out lower 12 of 16 bits used per port + + # DIO registers addresses in FPGA + FPGA_DIO_REGISTER_BASE = 0x2000 + FPGA_DIO_MASTER_REGISTER = FPGA_DIO_REGISTER_BASE + FPGA_DIO_DIRECTION_REGISTER = FPGA_DIO_REGISTER_BASE + 0x4 + FPGA_DIO_INPUT_REGISTER = FPGA_DIO_REGISTER_BASE + 0x8 + FPGA_DIO_OUTPUT_REGISTER = FPGA_DIO_REGISTER_BASE + 0xC + # DIO registers addresses in CPLD + CPLD_DIO_DIRECTION_REGISTER = 0x30 + + class _PortMapDescriptor: + """ + Helper class to hold port mapping relevant information + """ + def __init__(self, name, pin_names, map, first_pin): + self.name = name + self.pin_names = pin_names + self.map = map + self.first_pin = first_pin + + + class _PortControl: + """ + Helper class for controlling ports on the I2C expander + """ + 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) + + + def __init__(self, mboard_regs, mboard_cpld, log): + """ + Initializes access to hardware components as well as creating known + port mappings + :param log: logger to be used for output + """ + self.log = log.getChild(self.__class__.__name__) + self.port_control = {port: self._PortControl(port) for port in self.DIO_PORTS} + self.mboard_regs = mboard_regs + self.mboard_cpld = mboard_cpld + + # initialize port mapping for HDMI and DIO + self.port_mappings = {} + self.mapping = None + self.port_mappings[self.HDMI_MAP_NAME] = self._PortMapDescriptor( + self.HDMI_MAP_NAME, self.HDMI_PIN_NAMES, + self.HDMI_PORT_MAP, self.HDMI_FIRST_PIN) + self.port_mappings[self.DIO_MAP_NAME] = self._PortMapDescriptor( + self.DIO_MAP_NAME, self.DIO_PIN_NAMES, + self.DIO_PORT_MAP, self.DIO_FIRST_PIN) + self.set_port_mapping(self.HDMI_MAP_NAME) + self.log.trace("Spawning DIO fault monitors...") + self._tear_down_monitor = Event() + self._dio0_fault_monitor = Process( + target=self._monitor_dio_fault, + args=('A', "DIO_INT0", self._tear_down_monitor) + ) + self._dio1_fault_monitor = Process( + target=self._monitor_dio_fault, + args=('B', "DIO_INT1", self._tear_down_monitor) + ) + signal.signal(signal.SIGINT, self._monitor_int_handler) + self._dio0_fault_monitor.start() + self._dio1_fault_monitor.start() + + def _monitor_dio_fault(self, dio_port, fault, tear_down): + """ + 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("DIO fault occurred on port {} - turning off external power" + .format(dio_port)) + self.set_external_power(dio_port, 0) + # 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 + + def _monitor_int_handler(self, signum, frame): + """ + If we see an expected interrupt signal, mark the DIO fault monitors + for tear down. + """ + self._tear_down_monitor.set() + + # -------------------------------------------------------------------------- + # Helper methods + # -------------------------------------------------------------------------- + def _map_to_register_bit(self, port, pin): + """ + Maps a pin denoted in current mapping scheme to a corresponding bit in + the register map. + :param port: port to do the mapping on + :param pin: pin (in current mapping scheme) + :return: bit position in register map + :raises RuntimeError: pin is not in range of current mapping scheme + or not user assignable. + """ + assert isinstance(pin, int) + port = self._normalize_port_name(port) + first_pin = self.mapping.first_pin + last_pin = first_pin + len(self.mapping.pin_names) - 1 + port_map = self.mapping.map[port] + + if not (first_pin <= pin <= last_pin): + raise RuntimeError("Pin must be in range [%d,%d]. Given pin: %d" % + (first_pin, last_pin, pin)) + if pin not in port_map: + raise RuntimeError("Pin %d (%s) is not a user assignable pin." % + (pin, + self.mapping.pin_names[pin - first_pin])) + + # map pin back to register bit + bit = port_map.index(pin) + # lift register bit up by PORT_BIT_SIZE for port b + bit = bit if port == self.DIO_PORTS[0] else bit + self.PORT_BIT_SIZE + return bit + + def _calc_register_value(self, register, port, pin, value): + """ + Recalculates register value. + + Current register state is read and the bit that corresponds to the + values given by port and pin is determined. The register content is + changed at position of bit to what is given by value. + + Note: This routine only reads the current and calculates the new + register value. It is up to the callee to set the register value. + :param register: Address of the register value to recalculate + :param port: port associated with pin + :param pin: pin to change (will be mapped to bit according to + current mapping scheme an given port) + :param value: new bit value to set + :return: new register value. + """ + assert value in [0, 1] + + content = self.mboard_regs.peek32(register) + bit = self._map_to_register_bit(port, pin) + content = (content | 1 << bit) if value == 1 else (content & ~(1 << bit)) + return content + + def _set_pin_values(self, port, values, set_method): + """ + Helper method to assign multiple pins in one call. + :param port: Port to set pins on + :param values: New pin assignment represented by an integer. Each bit of + values corresponds to a pin on board according to current + mapping scheme. Bits that do not correspond to a pin in + the current mapping scheme are skipped. + :param set_method: method to be used to set/unset a pin. Signature of + set_method is (port, pin). + """ + first_pin = self.mapping.first_pin + port = self._normalize_port_name(port) + for i, pin_name in enumerate(self.mapping.pin_names): + if i + first_pin in self.mapping.map[port]: + set_method(port, i + first_pin, int(values & 1 << i != 0)) + + # -------------------------------------------------------------------------- + # Helper to convert abbreviations to constants defined in DioControl + # -------------------------------------------------------------------------- + + def _normalize_mapping(self, mapping): + """ + Map name to one of the key in self.port_mappings. + :param mapping: mapping name or any abbreviation by removing letters + from the end of the name + :return: Key found for mapping name + :raises RuntimeError: no matching mapping could be found + """ + assert isinstance(mapping, str) + mapping = mapping.upper() + mapping_names = self.port_mappings.keys() + try: + # search for abbr of mapping in mapping names + index = [re.match("^%s" % mapping, name) is not None for name in mapping_names].index(True) + return list(self.port_mappings.keys())[index] + except ValueError: + raise RuntimeError("Mapping %s not found in %s" % (mapping, mapping_names)) + + def _normalize_port_name(self, name): + """ + Map port name to the normalized form of self.DIO_PORTS + :param name: port name or abbreviation with A or B, case insensitive + :return: normalized port name + :raises RuntimeError: name could not be normalized + """ + assert isinstance(name, str) + if not name.upper() in self.DIO_PORTS + ("A", "B"): + raise RuntimeError("Could not map %s to port name" % name) + return self.DIO_PORTS[0] if name.upper() in (self.DIO_PORTS[0], "A") \ + else self.DIO_PORTS[1] + + # -------------------------------------------------------------------------- + # Helper to format status output + # -------------------------------------------------------------------------- + + def _get_port_voltage(self, port): + """ + Format voltage table cell value. + """ + port_control = self.port_control[port] + result = "" + if port_control.enable.get() == 0: + result += self.DIO_VOLTAGE_LEVELS[0] + elif port_control.en_2v5.get() == 1: + result += self.DIO_VOLTAGE_LEVELS[2] + elif port_control.en_3v3.get() == 1: + result += self.DIO_VOLTAGE_LEVELS[3] + else: + result += self.DIO_VOLTAGE_LEVELS[1] + result += " - PG:" + result += "YES" if port_control.power_good.get() else "NO" + result += " - EXT:" + result += "ON" if port_control.ext_pwr.get() else "OFF" + return result + + def _get_voltage(self): + """ + Format voltage table cells + """ + return [self._get_port_voltage(port) for port in self.DIO_PORTS] + + def _format_register(self, port, content): + """ + Format a port value according to current mapping scheme. Pins are + grouped by 4. Pins which are not user assignable are marked with a dot. + :param content: register content + :return: register content as pin assignment according to current + mapping scheme + """ + result = "" + first_pin = self.mapping.first_pin + pin_names = self.mapping.pin_names + mapping = self.mapping.map[port] + for i, _ in enumerate(pin_names): + if i % 4 == 0 and i > 0: + result = " " + result + if i + first_pin in mapping: + result = str(int(content & (1 << mapping.index(i + first_pin)) != 0)) + result + else: + result = "." + result + return result + + def _format_registers(self, content): + """ + Formats register content for port A and B + :param content: + :return: + """ + port_a = content & self.PORT_USED_BITS_MASK + port_b = (content >> self.PORT_BIT_SIZE) & self.PORT_USED_BITS_MASK + return [self._format_register(self.DIO_PORTS[0], port_a), + self._format_register(self.DIO_PORTS[1], port_b)] + + def _format_row(self, values, fill=" ", delim="|"): + """ + Format a table row with fix colums widths. Generates row spaces using + value list with empty strings and "-" as fill and "+" as delim. + :param values: cell values (list of three elements) + :param fill: fill character to use (space by default) + :param delim: delimiter character between columns + :return: formated row + """ + col_widths = [14, 25, 25] + return delim.join([ + fill + values[i].ljust(width - len(fill), fill) + for i, width in enumerate(col_widths) + ]) + "\n" + + # -------------------------------------------------------------------------- + # Public API + # -------------------------------------------------------------------------- + + def tear_down(self): + """ + Mark the DIO monitoring processes for tear down and terminate the processes + """ + self._tear_down_monitor.set() + self._dio0_fault_monitor.terminate() + self._dio1_fault_monitor.terminate() + self._dio0_fault_monitor.join(3) + self._dio1_fault_monitor.join(3) + if self._dio0_fault_monitor.is_alive() or \ + self._dio1_fault_monitor.is_alive(): + self.log.warning("DIO monitor didn't exit properly") + + def set_port_mapping(self, mapping): + """ + Change the port mapping to mapping. Mapping must denote a mapping found + in this.port_mappings.keys() or any abbreviation allowed by + _normalize_port_mapping. The mapping does not change the status of the + FPGA registers. It only changes the status display and the way calls + to set_pin_<register_name>(s) are interpreted. + :param mapping: new mapping to be used + :raises RuntimeError: mapping could not be found + """ + assert isinstance(mapping, str) + map_name = self._normalize_mapping(mapping) + if not map_name in self.port_mappings.keys(): + raise RuntimeError("Could not map %s to port mapping" % mapping) + self.mapping = self.port_mappings[map_name] + + def set_pin_master(self, port, pin, value=1): + """ + Set master pin of a port. The master pin decides whether the DIO board + pin is driven by the PS (1) or FPGA (0) register interface. To change + the pin value the current register content is read first and modified + before it is written back, so the register must be readable. + :param port: port to change master assignment on + :param pin: pin to change + :param value: desired pin value + """ + content = self._calc_register_value(self.FPGA_DIO_MASTER_REGISTER, + port, pin, value) + self.mboard_regs.poke32(self.FPGA_DIO_MASTER_REGISTER, content) + + def set_pin_masters(self, port, values): + """ + Set all master pins of a port at once using a bit mask. + :param port: port to change master pin assignment + :param values: New pin assignment represented by an integer. Each bit of + values corresponds to a pin on board according to current + mapping scheme. Bits that do not correspond to a pin in + the current mapping scheme are skipped. + """ + self._set_pin_values(port, values, self.set_pin_master) + + def set_pin_direction(self, port, pin, value=1): + """ + Set direction pin of a port. The direction pin decides whether the DIO + external pin is used as an output (write - value is 1) or input (read - + value is 0). To change the pin value the current register content is + read first and modified before it is written back, so the register must + be readable. + Besides the FPGA register map, the CPLD register map is also written. To + prevent the internal line to be driven by FGPA and DIO board at the same + time the CPLD register is written first if the direction will become an + output. If direction will become an input the FPGA register is written + first. + :param port: port to change direction assignment on + :param pin: pin to change + :param value: desired pin value + """ + content = self._calc_register_value(self.FPGA_DIO_DIRECTION_REGISTER, + port, pin, value) + # When setting direction pin, order matters. Always switch the component + # first that will get the driver disabled. + # This ensures that there wont be two drivers active at a time. + if value == 1: # FPGA is driver => write DIO register first + self.mboard_cpld.poke32(self.CPLD_DIO_DIRECTION_REGISTER, content) + self.mboard_regs.poke32(self.FPGA_DIO_DIRECTION_REGISTER, content) + else: # DIO is driver => write FPGA register first + self.mboard_regs.poke32(self.FPGA_DIO_DIRECTION_REGISTER, content) + self.mboard_cpld.poke32(self.CPLD_DIO_DIRECTION_REGISTER, content) + # Read back values to ensure registers are in sync + cpld_content = self.mboard_cpld.peek32(self.CPLD_DIO_DIRECTION_REGISTER) + mbrd_content = self.mboard_regs.peek32(self.FPGA_DIO_DIRECTION_REGISTER) + if not ((cpld_content == content) and (mbrd_content == content)): + raise RuntimeError("Direction register content mismatch. Expected:" + "0x%0.8X, CPLD: 0x%0.8X, FPGA: 0x%0.8X." % + (content, cpld_content, mbrd_content)) + + def set_pin_directions(self, port, values): + """ + Set all direction pins of a port at once using a bit mask. + :param port: port to change direction pin assignment + :param values: New pin assignment represented by an integer. Each bit of + values corresponds to a pin on board according to current + mapping scheme. Bits that do not correspond to a pin in + the current mapping scheme are skipped. + """ + self._set_pin_values(port, values, self.set_pin_direction) + + def set_pin_output(self, port, pin, value=1): + """ + Set output value of a pin on a port. Setting this value only takes + effect if the direction of the corresponding pin of this port is set + accordingly. To change the pin value the current register content is + read first and modified before it is written back, so the register must + be readable. + :param port: port to change output assignment on + :param pin: pin to change + :param value: desired pin value + """ + content = self._calc_register_value(self.FPGA_DIO_OUTPUT_REGISTER, + port, pin, value) + self.mboard_regs.poke32(self.FPGA_DIO_OUTPUT_REGISTER, content) + + def set_pin_outputs(self, port, values): + """ + Set all output pins of a port at once using a bit mask. + :param port: port to change direction pin assignment + :param values: New pin assignment represented by an integer. Each bit of + values corresponds to a pin on board according to current + mapping scheme. Bits that do not correspond to a pin in + the current mapping scheme are skipped. + """ + self._set_pin_values(port, values, self.set_pin_output) + + def get_pin_input(self, port, pin): + """ + Returns the input pin value of a port. + If the pin is not assignable in the current mapping None is returned. + + :param port: port to read pin value from + :param pin: pin value to read + :returns: actual pin value or None if pin is not assignable + """ + port = self._normalize_port_name(port) + + register = self.mboard_regs.peek32(self.FPGA_DIO_INPUT_REGISTER) + if port == self.DIO_PORTS[1]: + register = register >> self.PORT_BIT_SIZE + register &= self.PORT_USED_BITS_MASK + + mapping = self.mapping.map[port] + if not pin in mapping: + raise RuntimeError("Pin %d (%s) is not a user readable pin." % + (pin, + self.mapping.pin_names[pin - self.mapping.first_pin])) + return 0 if (register & (1 << mapping.index(pin)) == 0) else 1 + + def get_pin_inputs(self, port): + """ + Returns a bit mask of all pins for the given port. + + :param port: port to read input pins from + :returns: Bit map of input pins, each bit of pins corresponds to a pin + on board according to current mapping scheme. Unused pins + stay zero + """ + result = 0 + first_pin = self.mapping.first_pin + pin_names = self.mapping.pin_names + port = self._normalize_port_name(port) + mapping = self.mapping.map[port] + for i, name in enumerate(pin_names): + if i + first_pin in mapping: + if self.get_pin_input(port, i + first_pin): + result |= 1 << i + return result + + def set_voltage_level(self, port, level): + """ + Change voltage level of a port. This is how EN_<port>, EN_<port>_2V5 and + EN_<port>_3V3 are set according to level:: + level EN_<port> EN_<port>_2V5 EN_<port>_3V3 + off 0 0 0 + 1V8 1 0 0 + 2V5 1 1 0 + 3V3 1 0 1 + If level is set to anything other than off this method waits for + <port>_PG to go high. Waiting stops as soon as <port>_PG goes high or + a timeout of 1s occurs. + Note: All pins are set to zero first before the new level is applied. + :param port: port to change power level for + :param level: new power level + :raises RuntimeError: power good pin did not go high + """ + port = self._normalize_port_name(port) + level = level.upper() + assert port in self.DIO_PORTS + assert level in self.DIO_VOLTAGE_LEVELS + port_control = self.port_control[port] + + port_control.enable.set(0) + port_control.en_2v5.set(0) + port_control.en_3v3.set(0) + if level == self.DIO_VOLTAGE_LEVELS[2]: + port_control.en_2v5.set(1) + elif level == self.DIO_VOLTAGE_LEVELS[3]: + port_control.en_3v3.set(1) + + # wait for <port>_PG to go high + if not level == self.DIO_VOLTAGE_LEVELS[0]: # off + port_control.enable.set(1) + if not poll_with_timeout( + lambda: port_control.power_good.get() == 1, 1000, 10): + raise RuntimeError( + "Power good pin did not go high after power up") + + def set_external_power(self, port, value): + """ + Change EN_EXT_PWR_<port> to value. + :param port: port to change external power level for + :param value: 1 to enable external power, 0 to disable + :raise RuntimeError: port or pin value could not be mapped + """ + port = self._normalize_port_name(port) + value = int(value) + assert value in (0, 1) + assert port in self.DIO_PORTS + self.port_control[port].ext_pwr.set(value) + + def status(self): + """ + Build a full status string for the DIO AUX board, including + I2C pin states and register content in a human readable form. + :return: board status + """ + result = "\n" \ + + self._format_row(["%s mapping" % self.mapping.name, self.DIO_PORTS[0], self.DIO_PORTS[1]]) \ + + self._format_row(["", "", ""], "-", "+") \ + + self._format_row(["voltage"] + self._get_voltage()) \ + + self._format_row(["", "", ""], "-", "+") + + register = self.mboard_regs.peek32(self.FPGA_DIO_MASTER_REGISTER) + result += self._format_row(["master"] + self._format_registers(register)) + + register = self.mboard_regs.peek32(self.FPGA_DIO_DIRECTION_REGISTER) + result += self._format_row(["direction"] + self._format_registers(register)) + + result += self._format_row(["", "", ""], "-", "+") + + register = self.mboard_regs.peek32(self.FPGA_DIO_OUTPUT_REGISTER) + result += self._format_row(["output"] + self._format_registers(register)) + + register = self.mboard_regs.peek32(self.FPGA_DIO_INPUT_REGISTER) + result += self._format_row(["input"] + self._format_registers(register)) + return result + + def debug(self): + """ + Create a debug string containing the FPGA register maps. The CPLD + direction register is not part of the string as the DioControl maintains + it in sync with the FPGA direction register. + :return: register states for debug purpose in human readable form. + """ + master = format(self.mboard_regs.peek32(self.FPGA_DIO_MASTER_REGISTER), "032b") + direction = format(self.mboard_regs.peek32(self.FPGA_DIO_DIRECTION_REGISTER), "032b") + output = format(self.mboard_regs.peek32(self.FPGA_DIO_OUTPUT_REGISTER), "032b") + input = format(self.mboard_regs.peek32(self.FPGA_DIO_INPUT_REGISTER), "032b") + return "\nmaster: " + " ".join(re.findall('....', master)) + "\n" + \ + "direction: " + " ".join(re.findall('....', direction)) + "\n" + \ + "output: " + " ".join(re.findall('....', output)) + "\n" + \ + "input: " + " ".join(re.findall('....', input)) + + +class MboardRegsControl(MboardRegsCommon): + """ + Control the FPGA Motherboard registers + + A note on using this object: All HDL-specifics should be encoded in this + class. That means that calling peek32 or poke32 on this class should only be + used in rare exceptions. The call site shouldn't have to know about + implementation details behind those registers. + + To do multiple peeks/pokes without opening and closing UIO objects, it is + possible to do that from outside the object: + >>> with mb_regs_control.regs: + ... mb_regs_control.poke32(addr0, data0) + ... mb_regs_control.poke32(addr1, data1) + """ + # Motherboard registers + # pylint: disable=bad-whitespace + # 0 through 0x14 are common regs (see MboardRegsCommon) + MB_CLOCK_CTRL = 0x0018 + MB_PPS_CTRL = 0x001C + MB_BUS_CLK_RATE = 0x0020 + MB_BUS_COUNTER = 0x0024 + MB_GPIO_CTRL = 0x002C + MB_GPIO_MASTER = 0x0030 + MB_GPIO_RADIO_SRC = 0x0034 + # MB_NUM_TIMEKEEPERS = 0x0048 Shared with MboardRegsCommon + MB_SERIAL_NO_LO = 0x004C + MB_SERIAL_NO_HI = 0x0050 + MB_MFG_TEST_CTRL = 0x0054 + MB_MFG_TEST_STATUS = 0x0058 + # QSFP port info consists of 2 ports of 4 lanes each, + # both separated by their corresponding stride value + MB_QSFP_PORT_INFO = 0x0060 + MB_QSFP_LANE_STRIDE = 0x4 + MB_QSFP_PORT_STRIDE = 0x10 + # Versioning registers + MB_VER_FPGA = 0x0C00 + MB_VER_CPLD_IFC = 0x0C10 + MB_VER_RF_CORE_DB0 = 0x0C20 + MB_VER_RF_CORE_DB1 = 0x0C30 + MB_VER_GPIO_IFC_DB0 = 0x0C40 + MB_VER_GPIO_IFC_DB1 = 0x0C50 + CURRENT_VERSION_OFFSET = 0x0 + OLDEST_COMPATIBLE_VERSION_OFFSET = 0x4 + VERSION_LAST_MODIFIED_OFFSET = 0x8 + # Timekeeper registers start at 0x1000 (see MboardRegsCommon) + + # Clock control register bit masks + CLOCK_CTRL_PLL_SYNC_DELAY = 0x00FF0000 + CLOCK_CTRL_PLL_SYNC_DONE = 0x00000200 + CLOCK_CTRL_PLL_SYNC_TRIGGER = 0x00000100 + CLOCK_CTRL_TRIGGER_IO_SEL = 0x00000030 + CLOCK_CTRL_TRIGGER_PPS_SEL = 0x00000003 + + MFG_TEST_CTRL_GTY_RCV_CLK_EN = 0x00000001 + MFG_TEST_CTRL_FABRIC_CLK_EN = 0x00000002 + MFG_TEST_AUX_REF_FREQ = 0x03FFFFFF + # Clock control register values + CLOCK_CTRL_TRIG_IO_INPUT = 0 + CLOCK_CTRL_PPS_INT_25MHz = 0 + CLOCK_CTRL_TRIG_IO_PPS_OUTPUT = 0x10 + CLOCK_CTRL_PPS_INT_10MHz = 0x1 + CLOCK_CTRL_PPS_EXT = 0x2 + # pylint: enable=bad-whitespace + + def __init__(self, label, log): + MboardRegsCommon.__init__(self, label, log) + def peek32(address): + """ + Safe peek (opens and closes UIO). + """ + with self.regs: + return self.regs.peek32(address) + def poke32(address, value): + """ + Safe poke (opens and closes UIO). + """ + with self.regs: + self.regs.poke32(address, value) + # MboardRegsCommon.poke32() and ...peek32() don't open the UIO, so we + # overwrite them with "safe" versions that do open the UIO. + self.peek32 = peek32 + self.poke32 = poke32 + + def set_serial_number(self, serial_number): + """ + Set serial number register + """ + assert len(serial_number) > 0 + assert len(serial_number) <= 8 + serial_number = serial_number + b'\x00' * (8 - len(serial_number)) + (sn_lo, sn_hi) = struct.unpack("II", serial_number) + with self.regs: + self.poke32(self.MB_SERIAL_NO_LO, sn_lo) + self.poke32(self.MB_SERIAL_NO_HI, sn_hi) + + def get_compat_number(self): + """get FPGA compat number + + This function reads back FPGA compat number. + The return is a tuple of + 2 numbers: (major compat number, minor compat number) + X4xx stores this tuple in MB_VER_FPGA instead of MB_COMPAT_NUM + """ + version = self.peek32(self.MB_VER_FPGA + self.CURRENT_VERSION_OFFSET) + major = (version >> 23) & 0x1FF + minor = (version >> 12) & 0x7FF + return (major, minor) + + def get_db_gpio_ifc_version(self, slot_id): + """ + Get the version of the DB GPIO interface for the corresponding slot + """ + if slot_id == 0: + current_version = self.peek32(self.MB_VER_GPIO_IFC_DB0) + elif slot_id == 1: + current_version = self.peek32(self.MB_VER_GPIO_IFC_DB1) + else: + raise RuntimeError("Invalid daughterboard slot id: {}".format(slot_id)) + + major = (current_version>>23) & 0x1ff + minor = (current_version>>12) & 0x7ff + build = current_version & 0xfff + return (major, minor, build) + + def _get_qsfp_lane_value(self, port, lane): + addr = self.MB_QSFP_PORT_INFO + (port * self.MB_QSFP_PORT_STRIDE) \ + + (lane * self.MB_QSFP_LANE_STRIDE) + return (self.peek32(addr) >> 8) & 0xFF + + def _get_qsfp_type(self, port=0): + """ + Read the type of qsfp port is in the specified port + """ + x4xx_qsfp_types = { + 0: "", # Port not connected + 1: "1G", + 2: "10G", + 3: "A", # Aurora + 4: "W", # White Rabbit + 5: "100G" + } + + lane_0_val = self._get_qsfp_lane_value(port, 0) + num_lanes = 1 + + # Because we have qsfp, we could have up to 4x connection at the port + if lane_0_val > 0: + for lane in range(1, 4): + lane_val = self._get_qsfp_lane_value(port, lane) + if lane_val == lane_0_val: + num_lanes += 1 + + if num_lanes > 1: + return str(num_lanes) + "x" + x4xx_qsfp_types.get(lane_0_val, "") + + return x4xx_qsfp_types.get(lane_0_val, "") + + def get_fpga_type(self): + """ + Reads the type of the FPGA image currently loaded + Returns a string with the type (ie CG, XG, C2, etc.) + """ + x4xx_fpga_types_by_qsfp = { + ("", ""): "", + ("10G", "10G"): "XG", + ("10G", ""): "X1", + ("2x10G", ""): "X2", + ("4x10G", ""): "X4", + ("4x10G", "100G"): "X4C", + ("100G", "100G"): "CG", + ("100G", ""): "C1" + } + + qsfp0_type = self._get_qsfp_type(0) + qsfp1_type = self._get_qsfp_type(1) + self.log.trace("QSFP types: ({}, {})".format(qsfp0_type, qsfp1_type)) + try: + fpga_type = x4xx_fpga_types_by_qsfp[(qsfp0_type, qsfp1_type)] + except KeyError: + self.log.warning("Unrecognized QSFP type combination: ({}, {})" + .format(qsfp0_type, qsfp1_type)) + fpga_type = "" + + if not fpga_type and self.is_pcie_present(): + fpga_type = "LV" + + return fpga_type + + def is_pcie_present(self): + """ + Return True in case the PCI_EXPRESS_BIT is set in the FPGA image, which + means there is a PCI-Express core. False otherwise. + """ + regs_val = self.peek32(self.MB_DEVICE_ID) + return (regs_val & 0x80000000) != 0 + + def is_pll_sync_done(self): + """ + Return state of PLL sync bit from motherboard clock control register. + """ + return bool(self.peek32(MboardRegsControl.MB_CLOCK_CTRL) & \ + self.CLOCK_CTRL_PLL_SYNC_DONE) + + def pll_sync_trigger(self, clock_ctrl_pps_src): + """ + Callback for LMK04832 driver to actually trigger the sync. Set PPS + source accordingly. + """ + with self.regs: + # Update clock control config register to use the currently relevant + # PPS source + config = self.peek32(self.MB_CLOCK_CTRL) + trigger_config = \ + (config & ~MboardRegsControl.CLOCK_CTRL_TRIGGER_PPS_SEL) \ + | clock_ctrl_pps_src + # trigger sync with appropriate configuration + self.poke32( + self.MB_CLOCK_CTRL, + trigger_config | self.CLOCK_CTRL_PLL_SYNC_TRIGGER) + # wait for sync done indication from FPGA + # The following value is in ms, it was experimentally picked. + pll_sync_timeout = 1500 # ms + result = poll_with_timeout(self.is_pll_sync_done, pll_sync_timeout, 10) + # de-assert sync trigger signal + self.poke32(self.MB_CLOCK_CTRL, trigger_config) + if not result: + self.log.error("PLL_SYNC_DONE not received within timeout") + return result + + def set_trig_io_output(self, enable_output): + """ + Enable TRIG I/O output. If enable_output is "True", then we set TRIG I/O + to be an output. If enable_output is "False", then we make it an input. + + Note that the clocking board also is involved in configuring the TRIG I/O. + This is only the part that configures the FPGA registers. + """ + with self.regs: + # prepare clock control FPGA register content + clock_ctrl_reg = self.peek32(MboardRegsControl.MB_CLOCK_CTRL) + clock_ctrl_reg &= ~self.CLOCK_CTRL_TRIGGER_IO_SEL + if enable_output: + clock_ctrl_reg |= self.CLOCK_CTRL_TRIG_IO_PPS_OUTPUT + else: + # for both input and off ensure FPGA does not drive trigger IO line + clock_ctrl_reg |= self.CLOCK_CTRL_TRIG_IO_INPUT + self.poke32(MboardRegsControl.MB_CLOCK_CTRL, clock_ctrl_reg) + + def configure_pps_forwarding(self, enable, master_clock_rate, prc_rate, delay): + """ + 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 master_clock_rate: Master clock rate in MHz + + :param prc_rate: PRC rate in MHz + + :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 + """ + # delay range check 0 < x <= 1 + if (delay <= 0 or delay > 1): + raise RuntimeError("The delay has to be in range 0 < x <= 1") + + with self.regs: + # configure delay in BRC clock domain + value = self.peek32(self.MB_CLOCK_CTRL) + pll_sync_delay = (value >> 16) & 0xFF + # pps_brc_delay constants required by HDL implementation + pps_brc_delay = pll_sync_delay + 2 - 1 + value = (value & 0x00FFFFFF) | (pps_brc_delay << 24) + self.poke32(self.MB_CLOCK_CTRL, value) + + # configure delay in PRC clock domain + # reduction by 4 required by HDL implementation + pps_prc_delay = (int(delay * prc_rate) - 4) & 0x3FFFFFF + if pps_prc_delay == 0: + # limitation from HDL implementation + raise RuntimeError("The calculated delay has to be greater than 0") + value = pps_prc_delay + + # configure clock divider + # reduction by 2 required by HDL implementation + prc_rc_divider = (int(master_clock_rate/prc_rate) - 2) & 0x3 + value = value | (prc_rc_divider << 28) + + # write configuration to PPS control register (with PPS disabled) + self.poke32(self.MB_PPS_CTRL, value) + + # enables PPS depending on parameter + if enable: + # wait for 1 second to let configuration settle for any old PPS pulse + time.sleep(1) + # update value with enabled PPS + value = value | (1 << 31) + # write final configuration to PPS control register + self.poke32(self.MB_PPS_CTRL, value) + return True + + def enable_ecpri_clocks(self, enable, clock): + """ + Enable or disable the export of FABRIC and GTY_RCV eCPRI + clocks. Main use case until we support eCPRI is manufacturing + testing. + """ + valid_clocks_list = ['gty_rcv', 'fabric', 'both'] + assert clock in valid_clocks_list + clock_enable_sig = 0 + if clock == 'gty_rcv': + clock_enable_sig = self.MFG_TEST_CTRL_GTY_RCV_CLK_EN + elif clock == 'fabric': + clock_enable_sig = self.MFG_TEST_CTRL_FABRIC_CLK_EN + else:# 'both' case + clock_enable_sig = (self.MFG_TEST_CTRL_GTY_RCV_CLK_EN | + self.MFG_TEST_CTRL_FABRIC_CLK_EN) + with self.regs: + clock_ctrl_reg = self.peek32(MboardRegsControl.MB_MFG_TEST_CTRL) + if enable: + clock_ctrl_reg |= clock_enable_sig + else: + clock_ctrl_reg &= ~clock_enable_sig + self.poke32(MboardRegsControl.MB_MFG_TEST_CTRL, clock_ctrl_reg) + + 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. + """ + status_reg = self.peek32(self.MB_MFG_TEST_STATUS) + return status_reg & self.MFG_TEST_AUX_REF_FREQ + + +class CtrlportRegs: + """ + Control the FPGA Ctrlport registers + """ + # pylint: disable=bad-whitespace + IPASS_OFFSET = 0x000010 + MB_PL_SPI_CONFIG = 0x000020 + DB_SPI_CONFIG = 0x000024 + MB_PL_CPLD = 0x008000 + DB_0_CPLD = 0x010000 + DB_1_CPLD = 0x018000 + # pylint: enable=bad-whitespace + + min_mb_cpld_spi_divider = 2 + min_db_cpld_spi_divider = 5 + class MbPlCpldIface: + """ Exposes access to register mapped MB PL CPLD register space """ + SIGNATURE_OFFSET = 0x0000 + REVISION_OFFSET = 0x0004 + + SIGNATURE = 0x3FDC5C47 + MIN_REQ_REVISION = 0x20082009 + + def __init__(self, regs_iface, offset, log): + self.log = log + self.offset = offset + self.regs = regs_iface + + def peek32(self, addr): + return self.regs.peek32(addr + self.offset) + + def poke32(self, addr, val): + self.regs.poke32(addr + self.offset, val) + + def check_signature(self): + read_signature = self.peek32(self.SIGNATURE_OFFSET) + if self.SIGNATURE != read_signature: + self.log.error('MB PL CPLD signature {:X} does not match ' + 'expected value {:X}'.format(read_signature, self.SIGNATURE)) + raise RuntimeError('MB PL CPLD signature {:X} does not match ' + 'expected value {:X}'.format(read_signature, self.SIGNATURE)) + + def check_revision(self): + read_revision = self.peek32(self.REVISION_OFFSET) + if read_revision < self.MIN_REQ_REVISION: + error_message = ('MB PL CPLD revision {:X} is out of date. ' + 'Expected value {:X}. Update your CPLD image.' + .format(read_revision, self.MIN_REQ_REVISION)) + self.log.error(error_message) + raise RuntimeError(error_message) + + class DbCpldIface: + """ Exposes access to register mapped DB CPLD register spaces """ + def __init__(self, regs_iface, offset): + self.offset = offset + self.regs = regs_iface + + def peek32(self, addr): + return self.regs.peek32(addr + self.offset) + + def poke32(self, addr, val): + self.regs.poke32(addr + self.offset, val) + + def __init__(self, label, log): + self.log = log.getChild("CtrlportRegs") + self._regs_uio_opened = False + try: + self.regs = UIO( + label=label, + read_only=False + ) + except RuntimeError: + self.log.warning('Ctrlport regs could not be found. ' \ + 'MPM Endpoint to the FPGA is not part of this image.') + self.regs = None + # Initialize SPI interface to MB PL CPLD and DB CPLDs + self.set_mb_pl_cpld_divider(self.min_mb_cpld_spi_divider) + self.set_db_divider_value(self.min_db_cpld_spi_divider) + self.mb_pl_cpld_regs = self.MbPlCpldIface(self, self.MB_PL_CPLD, self.log) + self.mb_pl_cpld_regs.check_signature() + self.mb_pl_cpld_regs.check_revision() + self.db_0_regs = self.DbCpldIface(self, self.DB_0_CPLD) + self.db_1_regs = self.DbCpldIface(self, self.DB_1_CPLD) + + def init(self): + if not self._regs_uio_opened: + self.regs._open() + self._regs_uio_opened = True + + def deinit(self): + if self._regs_uio_opened: + self.regs._close() + self._regs_uio_opened = False + + def peek32(self, addr): + if self.regs is None: + raise RuntimeError('The ctrlport registers were never configured!') + if self._regs_uio_opened: + return self.regs.peek32(addr) + else: + with self.regs: + return self.regs.peek32(addr) + + def poke32(self, addr, val): + if self.regs is None: + raise RuntimeError('The ctrlport registers were never configured!') + if self._regs_uio_opened: + return self.regs.poke32(addr, val) + else: + with self.regs: + return self.regs.poke32(addr, val) + + def set_mb_pl_cpld_divider(self, divider_value): + if not self.min_mb_cpld_spi_divider <= divider_value <= 0xFFFF: + self.log.error('Cannot set MB CPLD SPI divider to invalid value {}' + .format(divider_value)) + raise RuntimeError('Cannot set MB CPLD SPI divider to invalid value {}' + .format(divider_value)) + self.poke32(self.MB_PL_SPI_CONFIG, divider_value) + + def set_db_divider_value(self, divider_value): + if not self.min_db_cpld_spi_divider <= divider_value <= 0xFFFF: + self.log.error('Cannot set DB SPI divider to invalid value {}' + .format(divider_value)) + raise RuntimeError('Cannot set DB SPI divider to invalid value {}' + .format(divider_value)) + self.poke32(self.DB_SPI_CONFIG, divider_value) + + def get_db_cpld_iface(self, db_id): + return self.db_0_regs if db_id == 0 else self.db_1_regs + + def get_mb_pl_cpld_iface(self): + return self.mb_pl_cpld_regs + + def enable_cable_present_forwarding(self, enable=True): + value = 1 if enable else 0 + self.poke32(self.IPASS_OFFSET, value) + + +# QSFP Adapter IDs according to SFF-8436 rev 4.9 table 30 +QSFP_IDENTIFIERS = { + 0x00: "Unknown or unspecified", + 0x01: "GBIC", + 0x02: "Module/connector soldered to motherboard (using SFF-8472)", + 0x03: "SFP/SFP+/SFP28", + 0x04: "300 pin XBI", + 0x05: "XENPAK", + 0x06: "XFP", + 0x07: "XFF", + 0x08: "XFP-E", + 0x09: "XPAK", + 0x0A: "X2", + 0x0B: "DWDM-SFP/SFP+ (not using SFF-8472)", + 0x0C: "QSFP (INF-8438)", + 0x0D: "QSFP+ or later (SFF-8436, SFF-8635, SFF-8665, SFF-8685 et al)", + 0x0E: "CXP or later", + 0x0F: "Shielded Mini Multilane HD0x4X", + 0x10: "Shielded Mini Multilane HD0x8X", + 0x11: "QSFP28 or later (SFF-8665 et al)", + 0x12: "CXP2 (aka CXP28) or later", + 0x13: "CDFP (Style0x1/Style2)", + 0x14: "Shielded Mini Multilane HD0x4X Fanout Cable", + 0x15: "Shielded Mini Multilane HD0x8X Fanout Cable", + 0x16: "CDFP (Style0x3)" +} + +# QSFP revison compliance according to SFF-8636 rev 2.9 table 6-3 +QSFP_REVISION_COMPLIANCE = { + 0x00: "Not specified.", + 0x01: "SFF-8436 Rev 4.8 or earlier", + 0x02: "SFF-8436 Rev 4.8 or earlier (except 0x186-0x189)", + 0x03: "SFF-8636 Rev 1.3 or earlier", + 0x04: "SFF-8636 Rev 1.4", + 0x05: "SFF-8636 Rev 1.5", + 0x06: "SFF-8636 Rev 2.0", + 0x07: "SFF-8636 Rev 2.5, 2.6 and 2.7", + 0x08: "SFF-8636 Rev 2.8 or later" +} + +# QSFP connector types according to SFF-8029 rev 3.2 table 4-3 +QSFP_CONNECTOR_TYPE = { + 0x00: "Unknown or unspecified", + 0x01: "SC (Subscriber Connector)", + 0x02: "Fibre Channel Style 1 copper connector", + 0x03: "Fibre Channel Style 2 copper connector", + 0x04: "BNC/TNC (Bayonet/Threaded Neill-Concelman)", + 0x05: "Fibre Channel coax headers", + 0x06: "Fiber Jack", + 0x07: "LC (Lucent Connector)", + 0x08: "MT-RJ (Mechanical Transfer - Registered Jack)", + 0x09: "MU (Multiple Optical)", + 0x0A: "SG", + 0x0B: "Optical Pigtail", + 0x0C: "MPO 1x12 (Multifiber Parallel Optic)", + 0x0D: "MPO 2x16", + 0x20: "HSSDC II (High S peed Serial Data Connector)", + 0x21: "Copper pigtail", + 0x22: "RJ45 (Registered Jack)", + 0x23: "No separable connector", + 0x24: "MXC 2x16" +} + +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" + +def get_temp_sensor(sensor_names, reduce_fn=mean, log=None): + """ Get temperature sensor reading from X4xx. """ + temps = [] + try: + for sensor_name in sensor_names: + temp_raw = read_thermal_sensor_value( + sensor_name, 'in_temp_raw', 'iio', 'name') + temp_offset = read_thermal_sensor_value( + sensor_name, 'in_temp_offset', 'iio', 'name') + temp_scale = read_thermal_sensor_value( + sensor_name, 'in_temp_scale', 'iio', 'name') + # sysfs-bus-iio linux kernel API reports temp in milli deg C + # https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-iio + temp_in_deg_c = (temp_raw + temp_offset) * temp_scale / 1000 + temps.append(temp_in_deg_c) + except ValueError: + if log: + log.warning("Error when converting temperature value.") + temps = [-1] + except KeyError: + if log: + log.warning("Can't read %s temp sensor fron iio sub-system.", + str(sensor_name)) + temps = [-1] + return { + 'name': 'temperature', + 'type': 'REALNUM', + 'unit': 'C', + 'value': str(reduce_fn(temps)) + } diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_reference_pll.py b/mpm/python/usrp_mpm/periph_manager/x4xx_reference_pll.py new file mode 100644 index 000000000..4e3a26de2 --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/x4xx_reference_pll.py @@ -0,0 +1,339 @@ +# +# Copyright 2019 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +LMK03328 driver for use with X4xx +""" + +from time import sleep +from usrp_mpm.chips import LMK03328 +from usrp_mpm.sys_utils.gpio import Gpio + +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!") diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_ctrl.py b/mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_ctrl.py new file mode 100644 index 000000000..44c4a32e2 --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_ctrl.py @@ -0,0 +1,480 @@ +# +# Copyright 2019 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +X400 RFDC Control Module +""" + +import ast +from collections import OrderedDict +from usrp_mpm import lib # Pulls in everything from C++-land +from usrp_mpm.periph_manager.x4xx_rfdc_regs import RfdcRegsControl +from usrp_mpm.rpc_server import no_rpc + +# Map the interpolation/decimation factor to fabric words. +# Keys: is_dac (False -> ADC, True -> DAC) and factor +FABRIC_WORDS_ARRAY = { # [is_dac][factor] + False: {0: 16, 1: 16, 2: 8, 4: 4, 8: 2}, # ADC + True: {0: -1, 1: -1, 2: 16, 4: 8, 8: 4} # DAC +} + +RFDC_DEVICE_ID = 0 + +class X4xxRfdcCtrl: + """ + Control class for the X4xx's RFDC + + """ + # Label for RFDC UIO + rfdc_regs_label = "rfdc-regs" + # Describes the mapping of ADC/DAC Tiles and Blocks to DB Slot IDs + # Follows the below structure: + # <slot_idx> + # 'adc': [ (<tile_number>, <block_number>), ... ] + # 'dac': [ (<tile_number>, <block_number>), ... ] + RFDC_DB_MAP = [ + { + 'adc': [(0, 1), (0, 0)], + 'dac': [(0, 0), (0, 1)], + }, + { + 'adc': [(2, 1), (2, 0)], + 'dac': [(1, 0), (1, 1)], + }, + ] + + # Maps all possible master_clock_rate (data clk rate * data SPC) values to the + # corresponding sample rate, expected FPGA decimation, whether to configure + # the SPLL in legacy mode (which uses a different divider), and whether half-band + # resampling is used. + # Using an OrderedDict to use the first rates as a preference for the default + # rate for its corresponding decimation. + master_to_sample_clk = OrderedDict({ + # MCR: (SPLL, decimation, legacy mode, half-band resampling) + 122.88e6*4: (2.94912e9, 2, False, False), # RF (1M-8G) + 122.88e6*2: (2.94912e9, 2, False, True), # RF (1M-8G) + 122.88e6*1: (2.94912e9, 8, False, False), # RF (1M-8G) + 125e6*4: (3.00000e9, 2, False, False), # RF (1M-8G) + 200e6: (3.00000e9, 4, True, False), # RF (Legacy Mode) + }) + + + def __init__(self, get_spll_freq, log): + self.log = log.getChild('RFDC') + self._get_spll_freq = get_spll_freq + self._rfdc_regs = RfdcRegsControl(self.rfdc_regs_label, self.log) + self._rfdc_ctrl = lib.rfdc.rfdc_ctrl() + self._rfdc_ctrl.init(RFDC_DEVICE_ID) + + self.set_cal_frozen(1, 0, "both") + self.set_cal_frozen(1, 1, "both") + + @no_rpc + def unset_cbs(self): + """ + Removes any stored references to our owning X4xx class instance + """ + self._get_spll_freq = None + + ########################################################################### + # Public APIs (not available as MPM RPC calls) + ########################################################################### + @no_rpc + def set_reset(self, reset=True): + """ + Resets the RFDC FPGA components or takes them out of reset. + """ + if reset: + # Assert RFDC AXI-S, filters and associated gearbox reset. + self._rfdc_regs.set_reset_adc_dac_chains(reset=True) + self._rfdc_regs.log_status() + # Assert Radio clock PLL reset + self._rfdc_regs.set_reset_mmcm(reset=True) + # Resetting the MMCM will automatically disable clock buffers + return + + # Take upstream MMCM out of reset + self._rfdc_regs.set_reset_mmcm(reset=False) + + # Once the MMCM has locked, enable driving the clocks + # to the rest of the design. Poll lock status for up + # to 1 ms + self._rfdc_regs.wait_for_mmcm_locked(timeout=0.001) + self._rfdc_regs.set_gated_clock_enables(value=True) + + # De-assert RF signal chain reset + self._rfdc_regs.set_reset_adc_dac_chains(reset=False) + + # Restart tiles in XRFdc + # All ADC Tiles + if not self._rfdc_ctrl.reset_tile(-1, False): + self.log.warning('Error starting up ADC tiles') + # All DAC Tiles + if not self._rfdc_ctrl.reset_tile(-1, True): + self.log.warning('Error starting up DAC tiles') + + # Set sample rate for all active tiles + active_converters = set() + for db_idx, db_info in enumerate(self.RFDC_DB_MAP): + db_rfdc_resamp, _ = self._rfdc_regs.get_rfdc_resampling_factor(db_idx) + for converter_type, tile_block_set in db_info.items(): + for tile, block in tile_block_set: + is_dac = converter_type != 'adc' + active_converter_tuple = (tile, block, db_rfdc_resamp, is_dac) + active_converters.add(active_converter_tuple) + for tile, block, resampling_factor, is_dac in active_converters: + self._rfdc_ctrl.reset_mixer_settings(tile, block, is_dac) + self._rfdc_ctrl.set_sample_rate(tile, is_dac, self._get_spll_freq()) + self._set_interpolation_decimation(tile, block, is_dac, resampling_factor) + + self._rfdc_regs.log_status() + + # Set RFDC NCO reset event source to analog SYSREF + for tile, block, _, is_dac in active_converters: + self._rfdc_ctrl.set_nco_event_src(tile, block, is_dac) + + + @no_rpc + def sync(self): + """ + Multi-tile Synchronization on both ADC and DAC + """ + # These numbers are determined from the procedure mentioned in + # PG269 section "Advanced Multi-Tile Synchronization API use". + adc_latency = 1228 # ADC delay in sample clocks + dac_latency = 800 # DAC delay in sample clocks + + # Ideally, this would be a set to avoiding duplicate indices, + # but we need to use a list for compatibility with the rfdc_ctrl + # C++ interface (std::vector) + adc_tiles_to_sync = [] + dac_tiles_to_sync = [] + + rfdc_map = self.RFDC_DB_MAP + for db_id in rfdc_map: + for converter_type, tile_block_set in db_id.items(): + for tile, _ in tile_block_set: + if converter_type == 'adc': + if tile not in adc_tiles_to_sync: + adc_tiles_to_sync.append(tile) + else: # dac + if tile not in dac_tiles_to_sync: + dac_tiles_to_sync.append(tile) + + self._rfdc_ctrl.sync_tiles(adc_tiles_to_sync, False, adc_latency) + self._rfdc_ctrl.sync_tiles(dac_tiles_to_sync, True, dac_latency) + + # We expect all sync'd tiles to have equal latencies + # Sets don't add duplicates, so we can use that to look + # for erroneous tiles + adc_tile_latency_set = set() + for tile in adc_tiles_to_sync: + adc_tile_latency_set.add( + self._rfdc_ctrl.get_tile_latency(tile, False)) + if len(adc_tile_latency_set) != 1: + raise RuntimeError("ADC tiles failed to sync properly") + + dac_tile_latency_set = set() + for tile in dac_tiles_to_sync: + dac_tile_latency_set.add( + self._rfdc_ctrl.get_tile_latency(tile, True)) + if len(dac_tile_latency_set) != 1: + raise RuntimeError("DAC tiles failed to sync properly") + + @no_rpc + def get_default_mcr(self): + """ + Gets the default master clock rate based on FPGA decimation + """ + fpga_decimation, fpga_halfband = self._rfdc_regs.get_rfdc_resampling_factor(0) + for master_clock_rate in self.master_to_sample_clk: + _, decimation, _, halfband = self.master_to_sample_clk[master_clock_rate] + if decimation == fpga_decimation and fpga_halfband == halfband: + return master_clock_rate + raise RuntimeError('No master clock rate acceptable for current fpga ' + 'with decimation of {}'.format(fpga_decimation)) + + @no_rpc + def get_dsp_bw(self): + """ + Return the bandwidth encoded in the RFdc registers. + + Note: This is X4xx-specific, not RFdc-specific. But this class owns the + access to RfdcRegsControl, and the bandwidth is strongly related to the + RFdc settings. + """ + return self._rfdc_regs.get_fabric_dsp_info(0)[0] + + @no_rpc + def get_rfdc_resampling_factor(self, db_idx): + """ + Returns a tuple resampling_factor, halfbands. + + See RfdcRegsControl.get_rfdc_resampling_factor(). + """ + return self._rfdc_regs.get_rfdc_resampling_factor(db_idx) + + + ########################################################################### + # Public APIs that get exposed as MPM RPC calls + ########################################################################### + def rfdc_set_nco_freq(self, direction, slot_id, channel, freq): + """ + Sets the RFDC NCO Frequency for the specified channel + """ + converters = self._find_converters(slot_id, direction, channel) + assert len(converters) == 1 + (tile_id, block_id, is_dac) = converters[0] + + if not self._rfdc_ctrl.set_if(tile_id, block_id, is_dac, freq): + raise RuntimeError("Error setting RFDC IF Frequency") + return self._rfdc_ctrl.get_nco_freq(tile_id, block_id, is_dac) + + def rfdc_get_nco_freq(self, direction, slot_id, channel): + """ + Gets the RFDC NCO Frequency for the specified channel + """ + converters = self._find_converters(slot_id, direction, channel) + assert len(converters) == 1 + (tile_id, block_id, is_dac) = converters[0] + + return self._rfdc_ctrl.get_nco_freq(tile_id, block_id, is_dac) + + ### ADC cal ############################################################### + def set_cal_frozen(self, frozen, slot_id, channel): + """ + Set the freeze state for the ADC cal blocks + + Usage: + > set_cal_frozen <frozen> <slot_id> <channel> + + <frozen> should be 0 to unfreeze the calibration blocks or 1 to freeze them. + """ + for tile_id, block_id, _ in self._find_converters(slot_id, "rx", channel): + self._rfdc_ctrl.set_cal_frozen(tile_id, block_id, frozen) + + def get_cal_frozen(self, slot_id, channel): + """ + Get the freeze states for each ADC cal block in the channel + + Usage: + > get_cal_frozen <slot_id> <channel> + """ + return [ + 1 if self._rfdc_ctrl.get_cal_frozen(tile_id, block_id) else 0 + for tile_id, block_id, is_dac in self._find_converters(slot_id, "rx", channel) + ] + + def set_cal_coefs(self, channel, slot_id, cal_block, coefs): + """ + Manually override calibration block coefficients. You probably don't need to use this. + """ + self.log.trace( + "Setting ADC cal coefficients for channel={} slot_id={} cal_block={}".format( + channel, slot_id, cal_block)) + for tile_id, block_id, _ in self._find_converters(slot_id, "rx", channel): + self._rfdc_ctrl.set_adc_cal_coefficients( + tile_id, block_id, cal_block, ast.literal_eval(coefs)) + + def get_cal_coefs(self, channel, slot_id, cal_block): + """ + Manually retrieve raw coefficients for the ADC calibration blocks. + + Usage: + > get_cal_coefs <channel, 0-1> <slot_id, 0-1> <cal_block, 0-3> + e.g. + > get_cal_coefs 0 1 3 + Retrieves the coefficients for the TSCB block on channel 0 of DB 1. + + Valid values for cal_block are: + 0 - OCB1 (Unaffected by cal freeze) + 1 - OCB2 (Unaffected by cal freeze) + 2 - GCB + 3 - TSCB + """ + self.log.trace( + "Getting ADC cal coefficients for channel={} slot_id={} cal_block={}".format( + channel, slot_id, cal_block)) + result = [] + for tile_id, block_id, _ in self._find_converters(slot_id, "rx", channel): + result.append(self._rfdc_ctrl.get_adc_cal_coefficients(tile_id, block_id, cal_block)) + return result + + ### DAC mux + def set_dac_mux_data(self, i_val, q_val): + """ + Sets the data which is muxed into the DACs when the DAC mux is enabled + + Usage: + > set_dac_mux_data <I> <Q> + e.g. + > set_dac_mux_data 123 456 + """ + self._rfdc_regs.set_cal_data(i_val, q_val) + + def set_dac_mux_enable(self, channel, enable): + """ + Sets whether the DAC mux is enabled for a given channel + + Usage: + > set_dac_mux_enable <channel, 0-3> <enable, 1=enabled> + e.g. + > set_dac_mux_enable 1 0 + """ + self._rfdc_regs.set_cal_enable(channel, bool(enable)) + + ### ADC thresholds + def setup_threshold(self, slot_id, channel, threshold_idx, mode, delay, under, over): + """ + Configure the given ADC threshold block. + + Usage: + > setup_threshold <slot_id> <channel> <threshold_idx> <mode> <delay> <under> <over> + + slot_id: Slot ID to configure, 0 or 1 + channel: Channel on the slot to configure, 0 or 1 + threshold_idx: Threshold block index, 0 or 1 + mode: Mode to configure, one of ["sticky_over", "sticky_under", "hysteresis"] + delay: In hysteresis mode, number of samples before clearing flag. + under: 0-16384, ADC codes to set the "under" threshold to + over: 0-16384, ADC codes to set the "over" threshold to + """ + for tile_id, block_id, _ in self._find_converters(slot_id, "rx", channel): + THRESHOLDS = { + 0: lib.rfdc.threshold_id_options.THRESHOLD_0, + 1: lib.rfdc.threshold_id_options.THRESHOLD_1, + } + MODES = { + "sticky_over": lib.rfdc.threshold_mode_options.TRSHD_STICKY_OVER, + "sticky_under": lib.rfdc.threshold_mode_options.TRSHD_STICKY_UNDER, + "hysteresis": lib.rfdc.threshold_mode_options.TRSHD_HYSTERESIS, + } + if mode not in MODES: + raise RuntimeError( + f"Mode {mode} is not one of the allowable modes {list(MODES.keys())}") + if threshold_idx not in THRESHOLDS: + raise RuntimeError("threshold_idx must be 0 or 1") + delay = int(delay) + under = int(under) + over = int(over) + assert 0 <= under <= 16383 + assert 0 <= over <= 16383 + self._rfdc_ctrl.set_threshold_settings( + tile_id, block_id, + lib.rfdc.threshold_id_options.THRESHOLD_0, + MODES[mode], + delay, + under, + over) + + def get_threshold_status(self, slot_id, channel, threshold_idx): + """ + Read the threshold status bit for the given threshold block from the device. + + Usage: + > get_threshold_status <slot_id> <channel> <threshold_idx> + e.g. + > get_threshold_status 0 1 0 + """ + return self._rfdc_regs.get_threshold_status(slot_id, channel, threshold_idx) != 0 + + + ########################################################################### + # Private helpers (note: x4xx_db_iface calls into those) + ########################################################################### + def _set_interpolation_decimation(self, tile, block, is_dac, factor): + """ + Set the provided interpolation/decimation factor to the + specified ADC/DAC tile, block + + Only gets called from set_reset_rfdc(). + """ + # Map the interpolation/decimation factor to fabric words. + # Keys: is_dac (False -> ADC, True -> DAC) and factor + # Disable FIFO + self._rfdc_ctrl.set_data_fifo_state(tile, is_dac, False) + # Define fabric rate based on given factor. + fab_words = FABRIC_WORDS_ARRAY[is_dac].get(int(factor)) + if fab_words == -1: + raise RuntimeError('Unsupported dec/int factor in RFDC') + # Define dec/int constant based on integer factor + if factor == 0: + int_dec = lib.rfdc.interp_decim_options.INTERP_DECIM_OFF + elif factor == 1: + int_dec = lib.rfdc.interp_decim_options.INTERP_DECIM_1X + elif factor == 2: + int_dec = lib.rfdc.interp_decim_options.INTERP_DECIM_2X + elif factor == 4: + int_dec = lib.rfdc.interp_decim_options.INTERP_DECIM_4X + elif factor == 8: + int_dec = lib.rfdc.interp_decim_options.INTERP_DECIM_8X + else: + raise RuntimeError('Unsupported dec/int factor in RFDC') + # Update tile, block settings... + self.log.debug( + "Setting %s for %s tile %d, block %d to %dx", + ('interpolation' if is_dac else 'decimation'), + 'DAC' if is_dac else 'ADC', tile, block, factor) + if is_dac: + # Set interpolation + self._rfdc_ctrl.set_interpolation_factor(tile, block, int_dec) + self.log.trace( + " interpolation: %s", + self._rfdc_ctrl.get_interpolation_factor(tile, block).name) + # Set fabric write rate + self._rfdc_ctrl.set_data_write_rate(tile, block, fab_words) + self.log.trace( + " Read words: %d", + self._rfdc_ctrl.get_data_write_rate(tile, block, True)) + else: # ADC + # Set decimation + self._rfdc_ctrl.set_decimation_factor(tile, block, int_dec) + self.log.trace( + " Decimation: %s", + self._rfdc_ctrl.get_decimation_factor(tile, block).name) + # Set fabric read rate + self._rfdc_ctrl.set_data_read_rate(tile, block, fab_words) + self.log.trace( + " Read words: %d", + self._rfdc_ctrl.get_data_read_rate(tile, block, False)) + # Clear interrupts + self._rfdc_ctrl.clear_data_fifo_interrupts(tile, block, is_dac) + # Enable FIFO + self._rfdc_ctrl.set_data_fifo_state(tile, is_dac, True) + + + def _find_converters(self, slot, direction, channel): + """ + Returns a list of (tile_id, block_id, is_dac) tuples describing + the data converters associated with a given channel and direction. + """ + if direction not in ('rx', 'tx', 'both'): + self.log.error('Invalid direction "{}". Cannot find ' + 'associated data converters'.format(direction)) + raise RuntimeError('Invalid direction "{}". Cannot find ' + 'associated data converters'.format(direction)) + if str(channel) not in ('0', '1', 'both'): + self.log.error('Invalid channel "{}". Cannot find ' + 'associated data converters'.format(channel)) + raise RuntimeError('Invalid channel "{}". Cannot find ' + 'associated data converters'.format(channel)) + data_converters = [] + rfdc_map = self.RFDC_DB_MAP[slot] + + if direction in ('rx', 'both'): + if str(channel) == '0' or str(channel) == 'both': + (tile_id, block_id) = rfdc_map['adc'][0] + data_converters.append((tile_id, block_id, False)) + if str(channel) == '1' or str(channel) == 'both': + (tile_id, block_id) = rfdc_map['adc'][1] + data_converters.append((tile_id, block_id, False)) + if direction in ('tx', 'both'): + if str(channel) == '0' or str(channel) == 'both': + (tile_id, block_id) = rfdc_map['dac'][0] + data_converters.append((tile_id, block_id, True)) + if str(channel) == '1' or str(channel) == 'both': + (tile_id, block_id) = rfdc_map['dac'][1] + data_converters.append((tile_id, block_id, True)) + return data_converters diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_regs.py b/mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_regs.py new file mode 100644 index 000000000..95f3cc007 --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_regs.py @@ -0,0 +1,263 @@ +# +# Copyright 2021 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +X4xx RFDC register control +""" + +import time +from usrp_mpm.sys_utils.uio import UIO + +class RfdcRegsControl: + """ + Control the FPGA RFDC registers external to the XRFdc API + """ + # pylint: disable=bad-whitespace + IQ_SWAP_OFFSET = 0x10000 + MMCM_RESET_BASE_OFFSET = 0x11000 + RF_RESET_CONTROL_OFFSET = 0x12000 + RF_RESET_STATUS_OFFSET = 0x12008 + RF_STATUS_OFFSET = 0x13000 + FABRIC_DSP_INFO_OFFSET = 0x13008 + CAL_DATA_OFFSET = 0x14000 + CAL_ENABLE_OFFSET = 0x14008 + THRESHOLD_STATUS_OFFSET = 0x15000 + RF_PLL_CONTROL_OFFSET = 0x16000 + RF_PLL_STATUS_OFFSET = 0x16008 + # pylint: enable=bad-whitespace + + def __init__(self, label, log): + self.log = log.getChild("RfdcRegs") + self.regs = UIO( + label=label, + read_only=False + ) + self.poke32 = self.regs.poke32 + self.peek32 = self.regs.peek32 + + # Index corresponds to dboard number. + self._converter_chains_in_reset = True + + def get_threshold_status(self, slot_id, channel, threshold_idx): + """ + Retrieves the status bit for the given threshold block + """ + BITMASKS = { + (0, 0, 0): 0x04, + (0, 0, 1): 0x08, + (0, 1, 0): 0x01, + (0, 1, 1): 0x02, + (1, 0, 0): 0x400, + (1, 0, 1): 0x800, + (1, 1, 0): 0x100, + (1, 1, 1): 0x200, + } + assert (slot_id, channel, threshold_idx) in BITMASKS + status = self.peek(self.THRESHOLD_STATUS_OFFSET) + status_bool = (status & BITMASKS[(slot_id, channel, threshold_idx)]) != 0 + return 1 if status_bool else 0 + + def set_cal_data(self, i, q): + assert 0 <= i < 2**16 + assert 0 <= q < 2**16 + self.poke(self.CAL_DATA_OFFSET, (q << 16) | i) + + def set_cal_enable(self, channel, enable): + assert 0 <= channel <= 3 + assert enable in [False, True] + en = self.peek(self.CAL_ENABLE_OFFSET) + bit_offsets = { + 0: 0, + 1: 1, + 2: 4, + 3: 5, + } + en_mask = 1 << bit_offsets[channel] + en = en & ~en_mask + self.poke(self.CAL_ENABLE_OFFSET, en | (en_mask if enable else 0)) + + def enable_iq_swap(self, enable, db_id, block_id, is_dac): + iq_swap_bit = (int(is_dac) * 8) + (db_id * 4) + block_id + + # Write IQ swap bit with a mask + reg_val = self.peek(self.IQ_SWAP_OFFSET) + reg_val = (reg_val & ~(1 << iq_swap_bit)) \ + | (enable << iq_swap_bit) + self.poke(self.IQ_SWAP_OFFSET, reg_val) + + def set_reset_mmcm(self, reset=True): + if reset: + # Put the MMCM in reset (active low) + self.poke(self.MMCM_RESET_BASE_OFFSET, 0) + else: + # Take the MMCM out of reset + self.poke(self.MMCM_RESET_BASE_OFFSET, 1) + + def wait_for_mmcm_locked(self, timeout=0.001): + """ + Wait for MMCM to come to a stable locked state. + The datasheet specifies a 100us max lock time + """ + DATA_CLK_PLL_LOCKED = 1 << 20 + + POLL_SLEEP = 0.0002 + for _ in range(int(timeout / POLL_SLEEP)): + time.sleep(POLL_SLEEP) + status = self.peek(self.RF_PLL_STATUS_OFFSET) + if status & DATA_CLK_PLL_LOCKED: + self.log.trace("RF MMCM lock detected.") + return + self.log.error("MMCM failed to lock in the expected time.") + raise RuntimeError("MMCM failed to lock within the expected time.") + + def set_gated_clock_enables(self, value=True): + """ + Controls the clock enable for data_clk and + data_clk_2x + """ + ENABLE_DATA_CLK = 1 + ENABLE_DATA_CLK_2X = 1 << 4 + ENABLE_RF_CLK = 1 << 8 + ENABLE_RF_CLK_2X = 1 << 12 + if value: + # Enable buffers gating the clocks + self.poke(self.RF_PLL_CONTROL_OFFSET, + ENABLE_DATA_CLK | + ENABLE_DATA_CLK_2X | + ENABLE_RF_CLK | + ENABLE_RF_CLK_2X + ) + else: + # Disable clock buffers to have clocks gated. + self.poke(self.RF_PLL_CONTROL_OFFSET, 0) + + def get_fabric_dsp_info(self, dboard): + """ + Read the DSP information register and returns the + DSP bandwidth, rx channel count and tx channel count + """ + # Offsets + DSP_BW = 0 + 16*dboard + DSP_RX_CNT = 12 + 16*dboard + DSP_TX_CNT = 14 + 16*dboard + # Masks + DSP_BW_MSK = 0xFFF + DSP_RX_CNT_MSK = 0x3 + DSP_TX_CNT_MSK = 0x3 + + dsp_info = self.peek(self.FABRIC_DSP_INFO_OFFSET) + self.log.trace("Fabric DSP for dboard %d...", dboard) + dsp_bw = (dsp_info >> DSP_BW) & DSP_BW_MSK + self.log.trace(" Bandwidth (MHz): %d", dsp_bw) + dsp_rx_cnt = (dsp_info >> DSP_RX_CNT) & DSP_RX_CNT_MSK + self.log.trace(" Rx channel count: %d", dsp_rx_cnt) + dsp_tx_cnt = (dsp_info >> DSP_TX_CNT) & DSP_TX_CNT_MSK + self.log.trace(" Tx channel count: %d", dsp_tx_cnt) + + return [dsp_bw, dsp_rx_cnt, dsp_tx_cnt] + + def get_rfdc_resampling_factor(self, dboard): + """ + Returns the appropriate decimation/interpolation factor to set in the RFDC. + """ + # DSP vs. RFDC decimation/interpolation dictionary + # Key: bandwidth in MHz + # Value: (RFDC resampling factor, is Half-band resampling used?) + RFDC_RESAMPLING_FACTOR = { + 100: (8, False), # 100 MHz BW requires 8x RFDC resampling + 200: (2, True), # 200 MHz BW requires 2x RFDC resampling + # (400 MHz RFDC DSP used w/ half-band resampling) + 400: (2, False) # 400 MHz BW requires 2x RFDC resampling + } + dsp_bw, _, _ = self.get_fabric_dsp_info(dboard) + # When no RF fabric DSP is present (dsp_bw = 0), MPM should + # simply use the default RFDC resampling factor (400 MHz). + if dsp_bw in RFDC_RESAMPLING_FACTOR: + rfdc_resampling_factor, halfband = RFDC_RESAMPLING_FACTOR[dsp_bw] + else: + rfdc_resampling_factor, halfband = RFDC_RESAMPLING_FACTOR[400] + self.log.trace(" Using default resampling!") + self.log.trace(" RFDC resampling: %d", rfdc_resampling_factor) + return (rfdc_resampling_factor, halfband) + + def set_reset_adc_dac_chains(self, reset=True): + """ Resets or enables the ADC and DAC chain for the given dboard """ + + def _wait_for_done(done_bit, timeout=5): + """ + Wait for the specified sequence done bit when resetting or + enabling an ADC or DAC chain. Throws an error on timeout. + """ + status = self.peek(self.RF_RESET_STATUS_OFFSET) + if (status & done_bit): + return + for _ in range(0, timeout): + time.sleep(0.001) # 1 ms + status = self.peek(self.RF_RESET_STATUS_OFFSET) + if (status & done_bit): + return + self.log.error("Timeout while resetting or enabling ADC/DAC chains.") + raise RuntimeError("Timeout while resetting or enabling ADC/DAC chains.") + + # CONTROL OFFSET + ADC_RESET = 1 << 4 + DAC_RESET = 1 << 8 + # STATUS OFFSET + ADC_SEQ_DONE = 1 << 7 + DAC_SEQ_DONE = 1 << 11 + + if reset: + if self._converter_chains_in_reset: + self.log.debug('Converters are already in reset. ' + 'The reset bit will NOT be toggled.') + return + # Reset the ADC and DAC chains + self.log.trace('Resetting ADC chain') + self.poke(self.RF_RESET_CONTROL_OFFSET, ADC_RESET) + _wait_for_done(ADC_SEQ_DONE) + self.poke(self.RF_RESET_CONTROL_OFFSET, 0x0) + + self.log.trace('Resetting DAC chain') + self.poke(self.RF_RESET_CONTROL_OFFSET, DAC_RESET) + _wait_for_done(DAC_SEQ_DONE) + self.poke(self.RF_RESET_CONTROL_OFFSET, 0x0) + + self._converter_chains_in_reset = True + else: # enable + self._converter_chains_in_reset = False + + def log_status(self): + status = self.peek(self.RF_STATUS_OFFSET) + self.log.debug("Daughterboard 0") + self.log.debug(" @RFDC") + self.log.debug(" DAC(1:0) TREADY : {:02b}".format((status >> 0) & 0x3)) + self.log.debug(" DAC(1:0) TVALID : {:02b}".format((status >> 2) & 0x3)) + self.log.debug(" ADC(1:0) I TREADY : {:02b}".format((status >> 6) & 0x3)) + self.log.debug(" ADC(1:0) I TVALID : {:02b}".format((status >> 10) & 0x3)) + self.log.debug(" ADC(1:0) Q TREADY : {:02b}".format((status >> 4) & 0x3)) + self.log.debug(" ADC(1:0) Q TVALID : {:02b}".format((status >> 8) & 0x3)) + self.log.debug(" @USER") + self.log.debug(" ADC(1:0) OUT TVALID: {:02b}".format((status >> 12) & 0x3)) + self.log.debug(" ADC(1:0) OUT TREADY: {:02b}".format((status >> 14) & 0x3)) + self.log.debug("Daughterboard 1") + self.log.debug(" @RFDC") + self.log.debug(" DAC(1:0) TREADY : {:02b}".format((status >> 16) & 0x3)) + self.log.debug(" DAC(1:0) TVALID : {:02b}".format((status >> 18) & 0x3)) + self.log.debug(" ADC(1:0) I TREADY : {:02b}".format((status >> 22) & 0x3)) + self.log.debug(" ADC(1:0) I TVALID : {:02b}".format((status >> 26) & 0x3)) + self.log.debug(" ADC(1:0) Q TREADY : {:02b}".format((status >> 20) & 0x3)) + self.log.debug(" ADC(1:0) Q TVALID : {:02b}".format((status >> 24) & 0x3)) + self.log.debug(" @USER") + self.log.debug(" ADC(1:0) OUT TVALID: {:02b}".format((status >> 28) & 0x3)) + self.log.debug(" ADC(1:0) OUT TREADY: {:02b}".format((status >> 30) & 0x3)) + + def poke(self, addr, val): + with self.regs: + self.regs.poke32(addr, val) + + def peek(self, addr): + with self.regs: + result = self.regs.peek32(addr) + return result diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_sample_pll.py b/mpm/python/usrp_mpm/periph_manager/x4xx_sample_pll.py new file mode 100644 index 000000000..3c3040d55 --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/x4xx_sample_pll.py @@ -0,0 +1,315 @@ +# +# Copyright 2019 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +LMK04832 driver for use with X4xx +""" + +from usrp_mpm.chips import LMK04832 +from usrp_mpm.sys_utils.gpio import Gpio + +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.') diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_update_cpld.py b/mpm/python/usrp_mpm/periph_manager/x4xx_update_cpld.py new file mode 100644 index 000000000..8920b6941 --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/x4xx_update_cpld.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +# +# Copyright 2019 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Update the CPLD image for the X4xx +""" + +import sys +import os +import argparse +import subprocess +import pyudev +from usrp_mpm.mpmlog import get_logger +from usrp_mpm.sys_utils.sysfs_gpio import GPIOBank +from usrp_mpm.sys_utils.udev import dt_symbol_get_spidev +from usrp_mpm.periph_manager.x4xx_mb_cpld import MboardCPLD +from usrp_mpm.periph_manager.x4xx import x4xx +from usrp_mpm.chips.max10_cpld_flash_ctrl import Max10CpldFlashCtrl + +OPENOCD_DIR = "/usr/share/openocd/scripts" +CONFIGS = { + 'axi_bitq' : { + 'files' : ["fpga/altera-10m50.cfg"], + 'cmd' : ["interface axi_bitq; axi_bitq_config %u %u; adapter_khz %u", + "init; svf -tap 10m50.tap %s -progress -quiet;exit"] + } +} + +AXI_BITQ_ADAPTER_SPEED = 5000 +AXI_BITQ_BUS_CLK = 40000000 + +def check_openocd_files(files, logger=None): + """ + Check if all file required by OpenOCD exist + :param logger: logger object + """ + for ocd_file in files: + if not os.path.exists(os.path.join(OPENOCD_DIR, ocd_file)): + if logger is not None: + logger.error("Missing file %s" % os.path.join(OPENOCD_DIR, ocd_file)) + return False + return True + +def check_fpga_state(which=0): + """ + Check if the FPGA is operational + :param which: the FPGA to check + """ + logger = get_logger('update_cpld') + try: + context = pyudev.Context() + fpga_mgrs = list(context.list_devices(subsystem="fpga_manager")) + if fpga_mgrs: + state = fpga_mgrs[which].attributes.asstring('state') + logger.trace("FPGA State: {}".format(state)) + return state == "operating" + return False + except OSError as ex: + logger.error("Error while checking FPGA status: {}".format(ex)) + return False + +def find_axi_bitq_uio(): + """ + Find the AXI Bitq UIO device + """ + label = 'jtag-0' + + logger = get_logger('update_cpld') + + try: + context = pyudev.Context() + for uio in context.list_devices(subsystem="uio"): + uio_label = uio.attributes.asstring('maps/map0/name') + logger.trace("UIO label: {}, match: {} number: {}".format( + uio_label, uio_label == label, uio.sys_number)) + if uio_label == label: + return int(uio.sys_number) + return None + except OSError as ex: + logger.error("Error while looking for axi_bitq uio nodes: {}".format(ex)) + return None + +def get_gpio_controls(): + """ + Instantiates an object to control JTAG related GPIO pins + Bank 3 - Pin 0: Allows toggle of JTAG Enable and additional signals + Bank 3 - Pin 1: JTAG Enable signal to the CPLD + """ + # Bank 3 starts at pin 78 + offset = 78 + mask = 0x03 + ddr = 0x03 + return GPIOBank({'label': 'zynqmp_gpio'}, offset, mask, ddr) + +def enable_jtag_gpio(gpios, disable=False): + """ + Toggle JTAG Enable line to the CPLD + """ + CPLD_JTAG_OE_n_pin = 0 + PL_CPLD_JTAGEN_pin = 1 + if not disable: + gpios.set(CPLD_JTAG_OE_n_pin, 0) # CPLD_JTAG_OE_n is active low + gpios.set(PL_CPLD_JTAGEN_pin, 1) # PL_CPLD_JTAGEN is active high + else: + gpios.set(CPLD_JTAG_OE_n_pin, 0) # CPLD_JTAG_OE_n is active low + gpios.set(PL_CPLD_JTAGEN_pin, 0) # PL_CPLD_JTAGEN is active high + +def do_update_cpld(filename, updater_mode): + """ + Carry out update process for the CPLD + :param filename: path (on device) to the new CPLD image + :param updater_mode: the updater method to use- Either flash or legacy + :return: True on success, False otherwise + """ + assert updater_mode in ('legacy', 'flash'), \ + f"Invalid updater method {updater_mode} given" + logger = get_logger('update_cpld') + logger.info("Programming CPLD of mboard with image {} using {} mode" + .format(filename, updater_mode)) + + if not os.path.exists(filename): + logger.error("CPLD image file {} not found".format(filename)) + return False + + if updater_mode == 'legacy': + return jtag_cpld_update(filename, logger) + if updater_mode == 'flash': + cpld_spi_node = dt_symbol_get_spidev('mb_cpld') + regs = MboardCPLD(cpld_spi_node, logger) + reconfig_engine_offset = 0x40 + cpld_min_revision = 0x19100108 + flash_control = Max10CpldFlashCtrl(logger, regs, reconfig_engine_offset, cpld_min_revision) + return flash_control.update(filename) + return False + +def jtag_cpld_update(filename, logger): + """ + Update the MB CPLD via dedicated JTAG lines in the FPGA + Note: To use this update mechanism, a FPGA image with JTAG + lines must be loaded. + """ + if logger is None: + logger = get_logger('update_cpld') + + if not check_fpga_state(): + logger.error("CPLD lines are routed through fabric, " + "FPGA is not programmed, giving up") + return False + + mode = 'axi_bitq' + config = CONFIGS[mode] + + if not filename.endswith('svf'): + logger.warning('The legacy JTAG programming mechanism expects ' + '.svf files. The CPLD file being used may be incorrect.') + + if check_openocd_files(config['files'], logger=logger): + logger.trace("Found required OpenOCD files.") + else: + # check_openocd_files logs errors + return False + + uio_id = find_axi_bitq_uio() + if uio_id is None or uio_id < 0: + logger.error('Failed to find axi_bitq uio devices. '\ + 'Make sure overlays are up to date') + return False + + try: + gpios = get_gpio_controls() + except RuntimeError as ex: + logger.error('Could not open GPIO required for JTAG programming!'\ + ' {}'.format(ex)) + return False + enable_jtag_gpio(gpios) + + cmd = ["openocd", + "-c", config['cmd'][0] % (uio_id, AXI_BITQ_BUS_CLK, AXI_BITQ_ADAPTER_SPEED), + "-f", (config['files'][0]).strip(), + "-c", config['cmd'][1] % filename] + + logger.trace("Update CPLD CMD: {}".format(" ".join(cmd))) + subprocess.call(cmd) + + # Disable JTAG dual-purpose pins to CPLD after reprogramming + enable_jtag_gpio(gpios, disable=True) + + logger.trace("Done programming CPLD...") + return True + + +def main(): + """ + Go, go, go! + """ + def parse_args(): + """Parse the command-line arguments""" + parser = argparse.ArgumentParser(description='Update the CPLD image on the X4xx') + parser.add_argument("--file", help="Filename of CPLD image", + default="/lib/firmware/ni/cpld-x410.rpd") + parser.add_argument("--updater", + help="The image updater method to use, either \"legacy\" or \"flash\"", + default="flash") + parser.add_argument( + '-v', + '--verbose', + help="Increase verbosity level", + action="count", + default=1 + ) + parser.add_argument( + '-q', + '--quiet', + help="Decrease verbosity level", + action="count", + default=0 + ) + return parser.parse_args() + + args = parse_args() + + # We need to make a logger if we're running stand-alone + from usrp_mpm.mpmlog import get_main_logger + log = get_main_logger(log_default_delta=args.verbose-args.quiet) + + return do_update_cpld(args.file, args.updater) + +if __name__ == "__main__": + sys.exit(not main()) diff --git a/mpm/python/usrp_mpm/sys_utils/CMakeLists.txt b/mpm/python/usrp_mpm/sys_utils/CMakeLists.txt index 71a06faf8..f3f3f40d4 100644 --- a/mpm/python/usrp_mpm/sys_utils/CMakeLists.txt +++ b/mpm/python/usrp_mpm/sys_utils/CMakeLists.txt @@ -15,6 +15,7 @@ set(USRP_MPM_SYSUTILS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/udev.py ${CMAKE_CURRENT_SOURCE_DIR}/uio.py ${CMAKE_CURRENT_SOURCE_DIR}/watchdog.py + ${CMAKE_CURRENT_SOURCE_DIR}/ectool.py ) list(APPEND USRP_MPM_FILES ${USRP_MPM_SYSUTILS_FILES}) set(USRP_MPM_FILES ${USRP_MPM_FILES} PARENT_SCOPE) diff --git a/mpm/python/usrp_mpm/sys_utils/ectool.py b/mpm/python/usrp_mpm/sys_utils/ectool.py new file mode 100644 index 000000000..0525d604a --- /dev/null +++ b/mpm/python/usrp_mpm/sys_utils/ectool.py @@ -0,0 +1,45 @@ +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Utilities for interfacing with ectool +""" + +import subprocess + +def run_cmd(cmd): + """Run ectool utility's command named cmd.""" + cmd_str = ' '.join(['ectool', cmd]) + try: + output = subprocess.check_output( + cmd_str, + stderr=subprocess.STDOUT, + shell=True, + ) + except subprocess.CalledProcessError as ex: + raise RuntimeError("Failed to execute {} ectool command".format(cmd)) + return output.decode("utf-8") + +def get_num_fans(): + """ Run ectool utility's command pwmgetnumfans to get number of fans.""" + output = run_cmd('pwmgetnumfans') + num_fans = [int(s) for s in output.split() if s.isdigit()][0] + return num_fans + +def get_fan_rpm(): + """Run ectool utility's command pwmgetfanrpm to get fan rpm.""" + num_fans = get_num_fans() + if num_fans == 0: + raise RuntimeError("Number of fans is zero.") + output = run_cmd('pwmgetfanrpm') + fan_rpm_info = [int(s) for s in output.split() if s.isdigit()] + if len(fan_rpm_info) == 2 * num_fans: + return { + "fan{}".format(fan) : fan_rpm_info[fan * 2 + 1] + for fan in range (0, num_fans) + } + else: + raise RuntimeError("Error getting fan rpm using ectool, at least one fan" \ + " may be stalled. Command output: {}".format(output)) diff --git a/mpm/python/usrp_mpm/sys_utils/gpio.py b/mpm/python/usrp_mpm/sys_utils/gpio.py index b609479f1..e864149f3 100644 --- a/mpm/python/usrp_mpm/sys_utils/gpio.py +++ b/mpm/python/usrp_mpm/sys_utils/gpio.py @@ -30,6 +30,7 @@ class Gpio: INPUT = gpiod.LINE_REQ_DIR_IN OUTPUT = gpiod.LINE_REQ_DIR_OUT + FALLING_EDGE = gpiod.LINE_REQ_EV_FALLING_EDGE def __init__(self, name, direction=INPUT, default_val=None): self._direction = direction @@ -58,3 +59,12 @@ class Gpio: with request_gpio(self._line, self._direction) as gpio: gpio.set_value(int(value)) self._out_value = bool(value) + + def event_wait(self): + """ + Wait for an event to happen on this line + """ + with request_gpio(self._line, self._direction) as gpio: + while True: + if gpio.event_wait(sec=1): + return True diff --git a/mpm/python/usrp_mpm/sys_utils/sysfs_gpio.py b/mpm/python/usrp_mpm/sys_utils/sysfs_gpio.py index b542123bc..056c28fd0 100644 --- a/mpm/python/usrp_mpm/sys_utils/sysfs_gpio.py +++ b/mpm/python/usrp_mpm/sys_utils/sysfs_gpio.py @@ -145,6 +145,7 @@ class SysFSGPIO(object): self._use_mask = use_mask self._ddr = ddr self._init_value = init_value + self._out_value = 0 self.log.trace("Generating SysFSGPIO object for identifiers `{}'..." .format(identifiers)) self._gpio_dev, self._map_info = \ @@ -182,6 +183,8 @@ class SysFSGPIO(object): open(os.path.join(GPIO_SYSFS_BASE_DIR, 'export'), 'w').write('{}'.format(gpio_num)) ddr_str = 'out' if ddr_out else 'in' ddr_str = 'high' if ini_v else ddr_str + if ini_v and ddr_out: + self._out_value |= 1 << gpio_idx self.log.trace("On GPIO path `{}', setting DDR mode to {}.".format(gpio_path, ddr_str)) open(os.path.join(GPIO_SYSFS_BASE_DIR, gpio_path, 'direction'), 'w').write(ddr_str) @@ -196,12 +199,18 @@ class SysFSGPIO(object): value = 1 assert (1<<gpio_idx) & self._use_mask assert (1<<gpio_idx) & self._ddr + assert int(value) in [0, 1] + value = int(value) gpio_num = self._base_gpio + gpio_idx gpio_path = os.path.join(GPIO_SYSFS_BASE_DIR, 'gpio{}'.format(gpio_num)) value_path = os.path.join(gpio_path, GPIO_SYSFS_VALUEFILE) self.log.trace("Writing value `{}' to `{}'...".format(value, value_path)) assert os.path.exists(value_path) open(value_path, 'w').write('{}'.format(value)) + if value: + self._out_value |= 1 << gpio_idx + else: + self._out_value &= ~(1 << gpio_idx) def reset(self, gpio_idx): """ @@ -216,19 +225,23 @@ class SysFSGPIO(object): """ Read back a GPIO at given index. - Note: The GPIO must be in the valid range, and it's DDR value must be - low (for "in"). + Note: The GPIO must be in the valid range. If it's DDR value is + low (for "in") then the register value is read from a local variable. """ assert (1<<gpio_idx) & self._use_mask - assert (1<<gpio_idx) & (~self._ddr) - gpio_num = self._base_gpio + gpio_idx - gpio_path = os.path.join(GPIO_SYSFS_BASE_DIR, 'gpio{}'.format(gpio_num)) - value_path = os.path.join(gpio_path, GPIO_SYSFS_VALUEFILE) - assert os.path.exists(value_path) - read_value = int(open(value_path, 'r').read().strip()) - self.log.trace("Reading value {} from `{}'...".format(read_value, value_path)) + if (1<<gpio_idx) & (~self._ddr): + gpio_num = self._base_gpio + gpio_idx + gpio_path = os.path.join(GPIO_SYSFS_BASE_DIR, 'gpio{}'.format(gpio_num)) + value_path = os.path.join(gpio_path, GPIO_SYSFS_VALUEFILE) + assert os.path.exists(value_path) + read_value = int(open(value_path, 'r').read().strip()) + self.log.trace("Reading value {} from `{}'...".format(read_value, value_path)) + else: + read_value = 1 if self._out_value & (1 << gpio_idx) else 0 + self.log.trace("Reading value {} from local var".format(read_value)) return read_value + class GPIOBank: """ Usability / convenience wrapper for GPIO banks accessed by SysFSGPIO diff --git a/mpm/python/usrp_mpm/sys_utils/uio.py b/mpm/python/usrp_mpm/sys_utils/uio.py index 84e4b2b64..f660c39b3 100644 --- a/mpm/python/usrp_mpm/sys_utils/uio.py +++ b/mpm/python/usrp_mpm/sys_utils/uio.py @@ -144,15 +144,15 @@ class UIO(object): else: self.log.trace("Using UIO device by label `{0}'".format(label)) self._path, map_info = find_uio_device(label, self.log) + if self._path is None or map_info is None: + self.log.error("Could not find a UIO device for label {0}".format(label)) + raise RuntimeError("Could not find a UIO device for label {0}".format(label)) # TODO If we ever support multiple maps, check if this is correct... offset = offset or map_info['offset'] assert offset == 0 # ...and then remove this line length = length or map_info['size'] self.log.trace("UIO device is being opened read-{0}.".format( "only" if read_only else "write")) - if self._path is None: - self.log.error("Could not find a UIO device for label {0}".format(label)) - raise RuntimeError("Could not find a UIO device for label {0}".format(label)) self._read_only = read_only # Our UIO objects are managed in C++ land, which gives us more granular control over # opening and closing diff --git a/mpm/python/x4xx_bist b/mpm/python/x4xx_bist new file mode 100644 index 000000000..adda5ecb0 --- /dev/null +++ b/mpm/python/x4xx_bist @@ -0,0 +1,1048 @@ +#!/usr/bin/env python3 +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +X4XX Built-In Self Test (BIST) + +Will work on all derivatives of the X4xx series. +""" + +import sys +import time +import subprocess +from usrp_mpm.sys_utils.udev import dt_symbol_get_spidev +from usrp_mpm import bist + +# Timeout values are in seconds: +GPS_WARMUP_TIMEOUT = 70 # Data sheet says "about a minute" +GPS_LOCKOK_TIMEOUT = 2 # Data sheet says about 15 minutes. Because our test + # does not necessarily require GPS lock to pass, we + # reduce this value in order for the BIST to pass faster + # by default. +DEFAULT_DB_ID = 0 + +# We will import stuff as late as possible, intentionally, so let's calm down +# PyLint +# pylint: disable=import-outside-toplevel + + +def get_rfdc_config(log): + """ + Return resampling, halfband settings of current FPGA image. + """ + from usrp_mpm.periph_manager import x4xx_rfdc_regs + from usrp_mpm.periph_manager import x4xx_rfdc_ctrl + rdfc_regs_control = x4xx_rfdc_regs.RfdcRegsControl( + x4xx_rfdc_ctrl.X4xxRfdcCtrl.rfdc_regs_label, log) + return rdfc_regs_control.get_rfdc_resampling_factor(0) + + +############################################################################## +# Bist class +############################################################################## +class X4XXBIST(bist.UsrpBIST): + """ + BIST Tool for the USRP X4xx series + """ + usrp_type = "X4XX" + # This defines special tests that are really collections of other tests. + collections = { + 'standard': ["gpsdo", "rtc", "temp", "fan"], + 'extended': "*", + } + # Default FPGA image type + DEFAULT_FPGA_TYPE = 'X4_200' + lv_compat_format = { + 'ddr3': { + 'throughput': -1, + }, + 'gpsdo': { + "class": "", + "time": "", + "ept": -1, + "lat": -1, + "lon": -1, + "alt": -1, + "epx": -1, + "epy": -1, + "epv": -1, + "track": -1, + "speed": -1, + "climb": -1, + "eps": -1, + "mode": -1, + }, + 'gpio': { + 'write_patterns': [], + 'read_patterns': [], + }, + 'temp': { + "DRAM PCB": 40000, + "EC Internal": 41000, + "PMBUS-0": 42000, + "PMBUS-1": 43000, + "Power Supply PCB": 44000, + "RFSoC": 45000, + "Sample Clock PCB": 46000, + "TMP464 Internal": 47000, + }, + 'fan': { + 'fan0': -1, + 'fan1': -1, + }, + } + device_args = "type=x4xx,mgmt_addr=127.0.0.1" + + def __init__(self): + bist.UsrpBIST.__init__(self) + + def get_mb_periph_mgr(self): + """Return reference to an x4xx periph manager""" + from usrp_mpm.periph_manager.x4xx import x4xx + return x4xx + + def get_product_id(self): + """Return the mboard product ID:""" + # TODO: use correct product ID, this is just a hack + # TODO: the path to the eeprom is not self-speaking like e.g. mb_eeprom + # return bist.get_product_id_from_eeprom(valid_ids=['x410'], eeprom='mb_eeprom') + return self.get_product_id_from_eeprom( + valid_ids=['0410'], + eeprom='/sys/bus/nvmem/devices/13-00500/nvmem') + + def get_product_id_from_eeprom(self, valid_ids, eeprom): + """Return the mboard product ID + Returns something like x410... + """ + # it is just here as override because eeprom-id installed on the + # x410 now needs a parameter while on n310 it runs without + # also the path to the eeprom is not self-speaking like e.g. mb_eeprom + # last problem is that the returned id is not 'x410' but '0410' + cmd = ['eeprom-id'] + cmd.append(eeprom) + cmd = ' '.join(cmd) + output = subprocess.check_output( + cmd, + stderr=subprocess.STDOUT, + shell=True, + ).decode('utf-8') + sys.stderr.write("product_id_from_eeprom: {}".format(str(output))) + for valid_id in valid_ids: + if valid_id in output: + return 'x410' + raise AssertionError("Cannot determine product ID.: `{}'".format(output)) + + def reload_fpga(self, fpga_type, sfp0_addrs): + """ + Loads the specified fpga and checks for sfp0 address, if the sfp0 address + existed, restores the original sfp0 address + """ + from usrp_mpm.sys_utils import net + from pyroute2 import IPRoute + import errno + bist.load_fpga_image( + fpga_type, + self.device_args, + 'x410', + ) + if sfp0_addrs: + ipr = IPRoute() + dev = ipr.link_lookup(ifname='sfp0')[0] + ipr.link('set', index=dev, state='down') + try: + ipr.addr('add', index=dev, address=sfp0_addrs[0], mask=24, label='sfp0') + except Exception as ex: + # If the addr already exists then ignore the error + if ex.code == errno.EEXIST: + pass + ipr.link('set', index=dev, state='up') + + def check_fpga_type_clkaux(self): + """ + clkaux BISTs require a OSS bitfile, check type and reimage if needed + If we have an OSS bitfile, we need to return a mcr that the clkaux + lmk can lock to. (Data clock rate of 122.88MHz) + """ + from usrp_mpm.periph_manager import x4xx, x4xx_periphs + + mboard_regs_control = x4xx_periphs.MboardRegsControl( + x4xx.x4xx.mboard_regs_label, self.log) + fpga_str = mboard_regs_control.get_fpga_type() + if not fpga_str or fpga_str == 'LV': + bist.load_fpga_image( + self.DEFAULT_FPGA_TYPE, + self.device_args, + 'x410', + ) + + rfdc_resamp, halfband = get_rfdc_config(self.log) + mcr = 122880000 * (8 / rfdc_resamp) + if halfband: + mcr = mcr / 2 + return mcr + +############################################################################# +# BISTS +# All bist_* methods must return True/False success values! +############################################################################# + def bist_gpsdo(self): + """ + BIST for GPSDO + Description: Returns GPS information + External Equipment: None; Recommend attaching an antenna or providing + fake GPS information + + Return dictionary: A TPV dictionary as returned by gpsd. + See also: http://www.catb.org/gpsd/gpsd_json.html + + Check for mode 2 or 3 to see if it's locked. + """ + assert 'gpsdo' in self.tests_to_run + if self.args.dry_run: + return True, { + "class": "TPV", + "time": "2017-04-30T11:48:20.10Z", + "ept": 0.005, + "lat": 30.407899, + "lon": -97.726634, + "alt": 1327.689, + "epx": 15.319, + "epy": 17.054, + "epv": 124.484, + "track": 10.3797, + "speed": 0.091, + "climb": -0.085, + "eps": 34.11, + "mode": 3 + } + from usrp_mpm.periph_manager import x4xx + # Turn on GPS, give some time to acclimatize + clk_aux_brd = x4xx.ClockingAuxBrdControl(default_source="gpsdo") + time.sleep(5) + gps_warmup_timeout = float( + self.args.option.get('gps_warmup_timeout', GPS_WARMUP_TIMEOUT)) + gps_lockok_timeout = float( + self.args.option.get('gps_lockok_timeout', GPS_LOCKOK_TIMEOUT)) + # Wait for WARMUP to go low + sys.stderr.write( + "Waiting for WARMUP to go low for up to {} seconds...\n".format( + gps_warmup_timeout)) + if not bist.poll_with_timeout( + lambda: not clk_aux_brd.get_gps_warmup(), + gps_warmup_timeout*1000, 1000 + ): + raise RuntimeError( + "GPS-WARMUP did not go low within {} seconds!".format( + gps_warmup_timeout)) + sys.stderr.write("Chip is warmed up.\n") + # Wait for LOCKOK. Data sheet says wait up to 15 minutes for GPS lock. + sys.stderr.write( + "Waiting for LOCKOK to go high for up to {} seconds...\n".format( + gps_lockok_timeout)) + if not bist.poll_with_timeout( + clk_aux_brd.get_gps_lock, + gps_lockok_timeout*1000, + 1000 + ): + sys.stderr.write("No GPS-LOCKOK!\n") + sys.stderr.write("GPS-SURVEY status: {}\n".format( + clk_aux_brd.get_gps_survey() + )) + sys.stderr.write("GPS-PHASELOCK status: {}\n".format( + clk_aux_brd.get_gps_phase_lock() + )) + sys.stderr.write("GPS-ALARM status: {}\n".format( + clk_aux_brd.get_gps_alarm() + )) + # Now the chip is on, read back the TPV result + result = bist.get_gpsd_tpv_result() + # If we reach this line, we have a valid result and the chip responded. + # However, it doesn't necessarily mean we had a GPS lock. + return True, result + + def bist_ref_clock_mboard(self): + """ + BIST for clock lock from mboard clock source (local to the motherboard) + + Description: Checks to see if the motherboard can lock to its internal + clock source. + + External Equipment: None + Return dictionary: + - <sensor-name>: + - locked: Boolean lock status + + There can be multiple ref lock sensors; for a pass condition they all + need to be asserted. + """ + assert 'ref_clock_mboard' in self.tests_to_run + if self.args.dry_run: + return True, {'ref_locked': True} + from usrp_mpm.periph_manager import x4xx_rfdc_ctrl + rfdc_resamp, fpga_halfband = get_rfdc_config(self.log) + mcrs_to_test = [] + for master_clock_rate, (_, decimation, _, halfband) in \ + x4xx_rfdc_ctrl.X4xxRfdcCtrl.master_to_sample_clk.items(): + if decimation == rfdc_resamp and fpga_halfband == halfband: + mcrs_to_test.append(master_clock_rate) + + for master_clock_rate in mcrs_to_test: + sys.stderr.write("Testing master_clock_rate {}".format(master_clock_rate)) + result = bist.get_ref_clock_prop( + 'mboard', + 'internal', + extra_args={ + 'addr': '169.254.0.2', + 'mgmt_addr': '127.0.0.1', + 'master_clock_rate': master_clock_rate, + } + ) + if 'error_msg' in result: + return False, result + return True, result + + def bist_ref_clock_ext(self): + """ + BIST for clock lock from external source. + + Description: Checks to see if the motherboard can lock to the external + reference clock. + + External Equipment: 10 MHz reference source connected to "ref in". + + Return dictionary: + - <sensor-name>: + - locked: Boolean lock status + + There can be multiple ref lock sensors; for a pass condition they all + need to be asserted. + """ + assert 'ref_clock_ext' in self.tests_to_run + if self.args.dry_run: + return True, {'ref_locked': True} + result = bist.get_ref_clock_prop( + 'external', + 'external', + extra_args={'addr': '169.254.0.2', 'mgmt_addr': '127.0.0.1'} + ) + return 'error_msg' not in result, result + + def bist_ref_clock_gpsdo(self): + """ + BIST for clock lock from gpsdo source. + + Description: Checks to see if the motherboard can lock to the gpsdo + reference clock. + + External Equipment: None + + Return dictionary: + - <sensor-name>: + - locked: Boolean lock status + + There can be multiple ref lock sensors; for a pass condition they all + need to be asserted. + """ + assert 'ref_clock_gpsdo' in self.tests_to_run + if self.args.dry_run: + return True, {'ref_locked': True} + result = bist.get_ref_clock_prop( + 'gpsdo', + 'gpsdo', + extra_args={} + ) + return 'error_msg' not in result, result + + def bist_ref_clock_int(self): + """ + BIST for clock lock from internal source. + + Description: Checks to see if the motherboard can lock to the + clocking aux board's internal reference clock. + + External Equipment: None + + Return dictionary: + - <sensor-name>: + - locked: Boolean lock status + + There can be multiple ref lock sensors; for a pass condition they all + need to be asserted. + """ + assert 'ref_clock_int' in self.tests_to_run + if self.args.dry_run: + return True, {'ref_locked': True} + result = bist.get_ref_clock_prop( + 'internal', + 'internal', + extra_args={'addr': '169.254.0.2', 'mgmt_addr': '127.0.0.1'} + ) + return 'error_msg' not in result, result + + def bist_ref_clock_nsync(self): + """ + BIST for clock lock from nsync source. + + Description: Checks to see if the motherboard can lock to the nsync + reference clock. + + External Equipment: None + + Return dictionary: + - <sensor-name>: + - locked: Boolean lock status + + There can be multiple ref lock sensors; for a pass condition they all + need to be asserted. + """ + assert 'ref_clock_nsync' in self.tests_to_run + if self.args.dry_run: + return True, {'ref_locked': True} + result = bist.get_ref_clock_prop( + 'nsync', + 'internal', + extra_args={'addr': '169.254.0.2', 'mgmt_addr': '127.0.0.1'} + ) + return 'error_msg' not in result, result + + def bist_nsync_fabric(self): + """ + BIST for testing the fabric_clk signal from the motherboard + + Description: Checks to see if the LMK on the clocking auxiliary board + can lock to the fabric clock signal output by the motherboard. We check + this by verifying that the pri_ref signal is being used, the dpll is locked, + the apll1 is locked, and apll2 is unlocked. + + External Equipment: None + + Return dictionary: + - fabric_clk pll lock: Did the clkaux lmk lock to the fabric_clk signal + """ + assert 'nsync_fabric' in self.tests_to_run + import uhd + from uhd.usrp import multi_usrp + + try: + mcr = self.check_fpga_type_clkaux() + usrp_dev = multi_usrp.MultiUSRP("type=x4xx,addr=localhost,clock_source=nsync," + "master_clock_rate={}".format(str(mcr))) + except Exception as ex: + return False, { + 'error_msg': "Failed to create usrp device: {}".format(str(ex)) + } + + mpm_c = usrp_dev.get_mpm_client() + + mpm_c.nsync_change_input_source('fabric_clk') + # The lock should happen fast, but give it half a second + time.sleep(0.5) + # Status 1 checks that the lmk is configured to use the priref signal + # True = secref, False = priref + using_pri_ref = not mpm_c.clkaux_get_nsync_status1() + # Status 0 checks dpll loss of lock + dpll_lock = not mpm_c.clkaux_get_nsync_status0() + # Reg 0xD checks several APLL statuses, we need to make sure APLL1 is + # locked and APLL2 is unlocked + apll_lock = mpm_c.peek_clkaux(0xD) == '0x8' + + result = using_pri_ref and dpll_lock and apll_lock + + mpm_c.enable_ecpri_clocks(False) + + return result, {"pri_ref_selected": using_pri_ref, + "dpll_locked": dpll_lock, + "apll1_locked_apll2_unlocked": apll_lock} + + def bist_nsync_gty(self): + """ + BIST for testing the gty_rcv_clk signal from the motherboard + + Description: Checks to see if the LMK on the clocking auxiliary board + can lock to the gty_rcv clock signal output by the motherboard. We check + this by verifying that the pri_ref signal is being used, the dpll is locked, + the apll1 is locked, and apll2 is unlocked. + + External Equipment: None + + Return dictionary: + - gty_rcv_clk pll lock: Did the clkaux lmk lock to the gty_rcv_clk signal + """ + assert 'nsync_gty' in self.tests_to_run + import uhd + from uhd.usrp import multi_usrp + + try: + mcr = self.check_fpga_type_clkaux() + usrp_dev = multi_usrp.MultiUSRP("type=x4xx,addr=localhost,clock_source=nsync," + "master_clock_rate={}".format(str(mcr))) + except Exception as ex: + return False, { + 'error_msg': "Failed to create usrp device: {}".format(str(ex)) + } + + mpm_c = usrp_dev.get_mpm_client() + + mpm_c.nsync_change_input_source('gty_rcv_clk') + # The lock should happen fast, but give it half a second + time.sleep(0.5) + # Status 1 checks that the lmk is configured to use the priref signal + # True = secref, False = priref + using_pri_ref = not mpm_c.clkaux_get_nsync_status1() + # Status 0 checks dpll loss of lock + dpll_lock = not mpm_c.clkaux_get_nsync_status0() + # Reg 0xD checks several APLL statuses, we need to make sure APLL1 is + # locked and APLL2 is unlocked + apll_lock = mpm_c.peek_clkaux(0xD) == '0x8' + + result = using_pri_ref and dpll_lock and apll_lock + + mpm_c.enable_ecpri_clocks(False) + # Set the clock source back to internal, as the register values + # for locking to the gty_rcv_clk cause the mboard to lose ref_lock + # to the nsync lmk. + mpm_c.set_clock_source('internal') + + return result, {"pri_ref_selected": using_pri_ref, + "dpll_locked": dpll_lock, + "apll1_locked_apll2_unlocked": apll_lock} + + def bist_clkaux_fpga_aux_ref(self): + """ + BIST for testing the fpga_aux_ref pps source + + Description: Checks to see if the fpga_aux_ref can output a valid pps signal + + External Equipment: None + + Return dictionary: + """ + assert 'clkaux_fpga_aux_ref' in self.tests_to_run + import uhd + from uhd.usrp import multi_usrp + + try: + mcr = self.check_fpga_type_clkaux() + usrp_dev = multi_usrp.MultiUSRP("type=x4xx,addr=localhost,clock_source=nsync," + "master_clock_rate={}".format(str(mcr))) + except Exception as ex: + return False, { + 'error_msg': "Failed to create usrp device: {}".format(str(ex)) + } + + mpm_c = usrp_dev.get_mpm_client() + + count = mpm_c.get_fpga_aux_ref_freq() + + # We expect a pps signal, pulse is reported in 40 MHz clock ticks, so + # 1 PPS is expected to return 40 million ticks, this gives 1% tolerance + return 39600000 < count < 40400000, {} + + def bist_nsync_rpll_config(self): + """ + BIST for testing that the LMK28PRIRefClk can be used as a source for the + motherboard RPLL + + Description: Enable the LMK on the clocking auxiliary board to output a signal + on the LMK28PRIRefClk line to the motherboard RPLL. Configure the RPLL to use + this source, and check to see if the RPLL can lock to the source. + + External Equipment: None + + Return dictionary: + """ + assert 'nsync_rpll_config' in self.tests_to_run + import uhd + from uhd.usrp import multi_usrp + from usrp_mpm.sys_utils import net + + try: + mcr = self.check_fpga_type_clkaux() + usrp_dev = multi_usrp.MultiUSRP("type=x4xx,addr=localhost,clock_source=nsync," + "master_clock_rate={}".format(str(mcr))) + except Exception as ex: + return False, { + 'error_msg': "Failed to create usrp device: {}".format(str(ex)) + } + + sfp0_addrs = net.get_iface_info('sfp0')['ip_addrs'] + + mpm_c = usrp_dev.get_mpm_client() + + mpm_c.config_rpll_to_nsync() + + ref_locked = mpm_c.get_ref_lock_sensor() + + result = ref_locked.get('value') == 'true' + + fpga_type = mpm_c.get_device_info()['fpga'] + + del usrp_dev + + # This is a brute-force way of making sure the device gets back into a clean state after + # we touched the rpll configuration + self.reload_fpga(fpga_type, sfp0_addrs) + + return result, ref_locked + + def bist_gpio(self): + """ + BIST for GPIO + Description: Writes and reads the values to the GPIO + + Needed Equipment: External loopback cable between port 0 and port 1 + + Notes: + - X410 has two FP-GPIO connectors (HDMI connectors) with 12 programmable + pins each + + Return dictionary: + - write_patterns: A list of patterns that were written + - read_patterns: A list of patterns that were read back + """ + assert 'gpio' in self.tests_to_run + if self.args.dry_run: + patterns = range(64) + return True, { + 'write_patterns': list(patterns), + 'read_patterns': list(patterns), + } + from usrp_mpm.periph_manager import x4xx, x4xx_periphs, x4xx_mb_cpld + mboard_regs_control = x4xx_periphs.MboardRegsControl( + x4xx.x4xx.mboard_regs_label, self.log) + cpld_spi_node = dt_symbol_get_spidev('mb_cpld') + cpld_control = x4xx_mb_cpld.MboardCPLD(cpld_spi_node, self.log) + gpio_diocontrol = x4xx_periphs.DioControl( + mboard_regs_control, + cpld_control, + self.log) + def _run_sub_test(inport, outport, pin_mode, voltage, pattern): + """ + Closure to run an actual test. The GPIO control object is enclosed. + + Arguments: + inport: "port" argument for DioControl, input port + outport: "port" argument for DioControl, input port + pin_mode: HDMI or DIO (see DioControl) + voltage: Valid arg for DioControl.set_voltage_level() + pattern: Bits to write to the inport, should be read back at outport + """ + gpio_diocontrol.set_port_mapping(pin_mode) + # We set all pins to be driven by the PS + # in HDMI mode not all pins can be accessed by the user + if pin_mode == "HDMI": + mask = 0xDB6D + else: + mask = 0xFFF + gpio_diocontrol.set_pin_masters(inport, mask) + gpio_diocontrol.set_pin_masters(outport, mask) + gpio_diocontrol.set_voltage_level(inport, voltage) + gpio_diocontrol.set_voltage_level(outport, voltage) + gpio_diocontrol.set_pin_directions(inport, 0x00000) + gpio_diocontrol.set_pin_directions(outport, 0xFFFFF) + gpio_diocontrol.set_pin_outputs(outport, pattern) + read_values = gpio_diocontrol.get_pin_inputs(inport) + if (pattern & mask) != read_values: + sys.stderr.write(gpio_diocontrol.status()) + return False, {'write_patterns': ["0x{:04X}".format(pattern)], + 'read_patterns': ["0x{:04X}".format(read_values)]} + return True, {'write_patterns': ["0x{:04X}".format(pattern)], + 'read_patterns': ["0x{:04X}".format(read_values)]} + # Now run tests: + for voltage in ["1V8", "2V5", "3V3"]: + for mode in ["DIO", "HDMI"]: + for pattern in [0xFFFF, 0xA5A5, 0x5A5A, 0x0000]: + sys.stderr.write("test: PortA -> PortB, {}, {}, 0x{:04X}" + .format(voltage, mode, pattern)) + status, data = _run_sub_test( + "PORTB", "PORTA", mode, voltage, pattern) + if not status: + return status, data + sys.stderr.write("test: PortB -> PortA, {}, {}, 0x{:04X}" + .format(voltage, mode, pattern)) + status, data = _run_sub_test( + "PORTA", "PORTB", mode, voltage, pattern) + if not status: + return status, data + return status, data + + + def bist_qsfp(self): + """ + BiST for QSFP status and property read out. + + Description: Tests MODSEL (write) and MODPRS (read) pin at QSFP port + and I2C communication with QSFP module. By default all + ports are tested. The user can add module to the option + argument when calling the test to select a specific + port out of [0,1]. A negative number will test all + ports (the default) + + Example: The following example will run the test on port 0: + > x4xx_bist qsfp --option module=0 + + Needed Equipment: None, for exhaustive test results the test should + be run with loopback test modules. + + Notes: The test ensures consistency of I2C communication and the + MODSEL and MODPRS pins. If MODPRS pin is active low + (module present) I2C communication must return valid values for + all QSFP properties. On the other hand when MODPRS pin is high + QSFP properties should report None as the only valid value. + The test also disables the I2C communication (unsetting MODSEL + pin) and checks that the low level I2C communication with the + module fails. The communication is reenabled after 3sec. This + window allows a tester to check whether a loopback adapter + signals the state of MODSEL correctly. + """ + + assert 'qsfp' in self.tests_to_run + if self.args.dry_run: + return True, {} + + from usrp_mpm.periph_manager import x4xx + from usrp_mpm.periph_manager.x4xx_periphs import QSFPModule + + def add_error_msg(msg): + # add error message to result map + if "error_msg" not in infos: + infos["error_msg"] = [] + infos["error_msg"].append(msg) + + def modules_to_test(): + module = self.args.option.get("module", -1) + try: + module = int(module) + except ValueError: + add_error_msg("Module '{}' is not an int".format(module)) + return None + if module < 0: + return x4xx.X400_QSFP_I2C_CONFIGS + if module not in [0, 1]: + add_error_msg("Module to test must be 0 or 1") + return None + return [x4xx.X400_QSFP_I2C_CONFIGS[module]] + + + infos = {} + modules_to_test = modules_to_test() + if not modules_to_test: + return False, infos + + result = True + + for config in modules_to_test: + qsfp = QSFPModule( + config.modprs, config.modsel, config.devsymbol, self.log) + info = {} + info["available"] = qsfp.is_available() + if info["available"]: + # if adapter is available, read out prop via I2C and + # verify they are all valid (not None) + props = [qsfp.decoded_status, + qsfp.vendor_name, + qsfp.connector_type] + for prop in props: + info[prop.__name__] = prop() + if not all(info.values()): + result = False + add_error_msg("QSFP adapter at {} is present but has " + "None property.".format(config.devsymbol)) + else: + # if adapter is not available + # reading a property *must* return None + if qsfp.connector_type() is not None: + result = False + add_error_msg("QSFP adapter at {} reports valid property " + "but is not present.".format(config.devsymbol)) + + infos[config.devsymbol] = info + + # disable i2c communication and check for failure on low level read + qsfp.enable_i2c(False) + try: + qsfp.qsfp_regs.peek8(0) + result = False + add_error_msg("Reading I2C when disabled should fail with " + "RuntimeError at {}\n".format(config.devsymbol)) + except RuntimeError: + pass + sys.stderr.write("A loopback QSFP adapter at {} should report MODSEL " + "inactive for 3 sec.".format(config.devsymbol)) + time.sleep(3) + qsfp.enable_i2c(True) + + return result, infos + + + def bist_temp(self): + """ + BIST for temperature sensors + Description: Reads the temperature sensors on the motherboards and + returns their values in mC + + Return dictionary: + - <thermal-zone-name>: temp in mC + """ + assert 'temp' in self.tests_to_run + if self.args.dry_run: + return True, {"DRAM PCB": 40000, + "EC Internal": 41000, + "PMBUS-0": 42000, + "PMBUS-1": 43000, + "Power Supply PCB": 44000, + "RFSoC": 45000, + "Sample Clock PCB": 46000, + "TMP464 Internal": 47000, + } + + result = bist.get_iio_temp_sensor_values() + + if len(result) < 1: + result['error_msg'] = "No temperature sensors found!" + + return 'error_msg' not in result, result + + def bist_fan(self): + """ + BIST for fans + Description: Reads the RPM values of the fans on the motherboard + + Return dictionary: + - <fan-name>: Fan speed in RPM + + External Equipment: None + """ + assert 'fan' in self.tests_to_run + if self.args.dry_run: + return True, {'fan0': 10000, 'fan1': 10000} + result = bist.get_ectool_fan_values() + return len(result) == 2, result + + def _db_flash_init(self, db_flash): + """ + Initialize the specified DB Flash and verify + its state + """ + db_flash.init() + if not db_flash.initialized: + raise RuntimeError() + + def _db_flash_deinit(self, db_flash): + """ + De-initialize the specified DB Flash and verify + its state + """ + db_flash.deinit() + if db_flash.initialized: + raise RuntimeError() + + def bist_spi_flash_integrity(self): + """ + BIST for SPI flash on DB + Description: Performs data integrity test on a section of + the flash memory. Stop the MPM service before running this + test using "systemctl stop usrp-hwd" command. + + External Equipment: None, but at least one daughterboard + should be installed in the X410 unit. + + Return dictionary: + + """ + assert 'spi_flash_integrity' in self.tests_to_run + if self.args.dry_run: + return True, {} + import os + from usrp_mpm.sys_utils.db_flash import DBFlash + FIXED_MEMORY_PATTERN = 'fixed' + RANDOM_MEMORY_PATTERN = 'random' + + db_id = int(self.args.option.get('db_id', DEFAULT_DB_ID)) + assert db_id in [0, 1] + + db_flash = DBFlash(db_id, log=None) + + buf_size = 100 + memory_pattern = str(self.args.option.get('memory_pattern', RANDOM_MEMORY_PATTERN)) + assert memory_pattern in (FIXED_MEMORY_PATTERN, RANDOM_MEMORY_PATTERN) + if memory_pattern == RANDOM_MEMORY_PATTERN: + buff = os.urandom(buf_size) + else: + buff = [0xA5] * buf_size + + data_valid = False + + try: + self._db_flash_init(db_flash) + except (ValueError, RuntimeError) as ex: + return False, {"error_msg": "Error while initializing flash storage: " + str(ex)} + + file_path = f'/mnt/db{db_id}_flash/test.bin' + + sys.stderr.write("Testing DB{} with {} memory pattern..".format(db_id, memory_pattern)) + with open(file_path, "wb") as f: + f.write(bytearray(buff)) + + try: + self._db_flash_deinit(db_flash) + self._db_flash_init(db_flash) + except (ValueError, RuntimeError) as ex: + return False, {"error_msg": "Error while init(deinit)ializing flash storage: " + str(ex)} + + with open(file_path, "rb") as f: + read_data = f.read() + data_valid = read_data == bytearray(buff) + os.remove(file_path) + self._db_flash_deinit(db_flash) + + return data_valid, {} + + def bist_spi_flash_speed(self): + """ + BIST for SPI flash on DB + Description: Performs read and write speed test on the SPI flash + memory on DB. Stop the MPM service before running this test + using "systemctl stop usrp-hwd" command. + + External Equipment: None, but at least one daughterboard + should be installed in the X410 unit. + + Return dictionary: + + """ + assert 'spi_flash_speed' in self.tests_to_run + if self.args.dry_run: + return True, {} + import os + import re + from usrp_mpm.sys_utils.db_flash import DBFlash + + MIN_WRITE_SPEED = 5000 #B/s + MIN_READ_SPEED = 5000 #B/s + def parse_speed(cmd_output): + mobj = re.search( + r"(.*records in\n)(.*records out\n)(.* (?P<speed>[0-9.]+) (?P<order>\S?)B\/s)", + cmd_output) + if mobj is None: + return 0 + scale = {'': 1, 'k': 1024, 'M': 1024*1024, 'G': 1024*1024*1024} + order = mobj.group('order') + if order not in scale: + raise ValueError(f"unsupported unit '{order}B/s'") + return float(mobj.group('speed')) * scale[order] + + db_id = int(self.args.option.get('db_id', DEFAULT_DB_ID)) + assert db_id in [0, 1] + + db_flash = DBFlash(db_id, log=None) + + file_path = f'/mnt/db{db_id}_flash/test.bin' + + try: + self._db_flash_init(db_flash) + except (ValueError, RuntimeError) as ex: + return False, { + "error_msg": "Error while initializing flash storage: {}".format(str(ex)) + } + + sys.stderr.write("Testing DB{}..".format(db_id)) + write_error_msg = None + sys.stderr.write("Write Speed Test:") + cmd = [ + 'dd', + 'if=/dev/zero', + 'of=' + file_path, + 'bs=512', + 'count=1000', + 'oflag=dsync' + ] + + try: + output = subprocess.check_output( + cmd, + stderr=subprocess.STDOUT, + ) + except subprocess.CalledProcessError as ex: + output = ex.output + write_error_msg = "Error during Write Test: {}".format(output) + write_test_output = output.decode("utf-8") + sys.stderr.write(write_test_output) + + # De-init and init flash here to mitigate any effects + # caching may have on the read speed. + try: + self._db_flash_deinit(db_flash) + self._db_flash_init(db_flash) + except (ValueError, RuntimeError) as ex: + return False, { + "error_msg": "Error while init(deinit)ializing flash storage: {}".format(str(ex))} + + sys.stderr.write("Read Speed Test:") + read_error_msg = None + cmd = [ + 'dd', + 'if=' + file_path, + 'of=/dev/null', + 'bs=512', + 'count=1000', + 'oflag=dsync' + ] + + try: + output = subprocess.check_output( + cmd, + stderr=subprocess.STDOUT, + ) + except subprocess.CalledProcessError as ex: + output = ex.output + read_error_msg = "Error during Read Test: {}".format(output) + read_test_output = output.decode("utf-8") + sys.stderr.write(read_test_output) + + # Clean up. + os.remove(file_path) + self._db_flash_deinit(db_flash) + + test_status = bool(write_error_msg is None and read_error_msg is None) + + if test_status: + write_speed = parse_speed(write_test_output) + read_speed = parse_speed(read_test_output) + + if write_speed == 0: + test_status = False + write_error_msg = "Write speed parse error" + elif write_speed < MIN_WRITE_SPEED: + test_status = False + write_error_msg = \ + "Write speed {} B/s is below minimum requirement of {} B/s".format( + write_speed, MIN_WRITE_SPEED) + + if read_speed == 0: + test_status = False + read_error_msg = "Read speed parse error" + elif read_speed < MIN_READ_SPEED: + test_status = False + read_error_msg = \ + "Read speed {} B/s is below minimum requirement of {} B/s".format( + read_speed, MIN_READ_SPEED) + + return test_status, { + "Write Test": write_test_output if write_error_msg is None else write_error_msg, + "Read Test": read_test_output if read_error_msg is None else read_error_msg + } + + +############################################################################## +# main +############################################################################## +def main(): + " Go, go, go! " + return X4XXBIST().run() + +if __name__ == '__main__': + sys.exit(not main()) diff --git a/mpm/systemd/CMakeLists.txt b/mpm/systemd/CMakeLists.txt index 8387c68cc..bc5b457a5 100644 --- a/mpm/systemd/CMakeLists.txt +++ b/mpm/systemd/CMakeLists.txt @@ -27,12 +27,32 @@ install(FILES DESTINATION ${SYSTEMD_SYSTEM_UNITDIR} ) +if(MPM_DEVICE STREQUAL "x4xx") + set(UHD_SELF_CAL_SERIES "X400") + set(UHD_SELF_CAL_ARGS "type=x4xx,addr=169.254.0.2,mgmt_addr=127.0.0.1") + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/system/usrp-adc-self-cal.service.in + ${CMAKE_CURRENT_BINARY_DIR}/system/usrp-adc-self-cal.service + ) + + install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/system/usrp-adc-self-cal.service + DESTINATION ${SYSTEMD_SYSTEM_UNITDIR} + ) +endif() + # network configuration files install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/network/eth0.network ${CMAKE_CURRENT_SOURCE_DIR}/network/int0.network ${CMAKE_CURRENT_SOURCE_DIR}/network/sfp0.network + ${CMAKE_CURRENT_SOURCE_DIR}/network/sfp0_1.network + ${CMAKE_CURRENT_SOURCE_DIR}/network/sfp0_2.network + ${CMAKE_CURRENT_SOURCE_DIR}/network/sfp0_3.network ${CMAKE_CURRENT_SOURCE_DIR}/network/sfp1.network + ${CMAKE_CURRENT_SOURCE_DIR}/network/sfp1_1.network + ${CMAKE_CURRENT_SOURCE_DIR}/network/sfp1_2.network + ${CMAKE_CURRENT_SOURCE_DIR}/network/sfp1_3.network DESTINATION ${SYSTEMD_SYSTEM_UNITDIR}/../network ) diff --git a/mpm/systemd/network/sfp0_1.network b/mpm/systemd/network/sfp0_1.network new file mode 100644 index 000000000..ee4e5a12b --- /dev/null +++ b/mpm/systemd/network/sfp0_1.network @@ -0,0 +1,8 @@ +[Match] +Name=sfp0_1 + +[Network] +Address=192.168.11.2/24 + +[Link] +MTUBytes=9000 diff --git a/mpm/systemd/network/sfp0_2.network b/mpm/systemd/network/sfp0_2.network new file mode 100644 index 000000000..9cc90f5af --- /dev/null +++ b/mpm/systemd/network/sfp0_2.network @@ -0,0 +1,8 @@ +[Match] +Name=sfp0_2 + +[Network] +Address=192.168.12.2/24 + +[Link] +MTUBytes=9000 diff --git a/mpm/systemd/network/sfp0_3.network b/mpm/systemd/network/sfp0_3.network new file mode 100644 index 000000000..928e06d5c --- /dev/null +++ b/mpm/systemd/network/sfp0_3.network @@ -0,0 +1,8 @@ +[Match] +Name=sfp0_3 + +[Network] +Address=192.168.13.2/24 + +[Link] +MTUBytes=9000 diff --git a/mpm/systemd/network/sfp1_1.network b/mpm/systemd/network/sfp1_1.network new file mode 100644 index 000000000..c7b134aea --- /dev/null +++ b/mpm/systemd/network/sfp1_1.network @@ -0,0 +1,8 @@ +[Match] +Name=sfp1_1 + +[Network] +Address=192.168.21.2/24 + +[Link] +MTUBytes=9000 diff --git a/mpm/systemd/network/sfp1_2.network b/mpm/systemd/network/sfp1_2.network new file mode 100644 index 000000000..8e67d4510 --- /dev/null +++ b/mpm/systemd/network/sfp1_2.network @@ -0,0 +1,8 @@ +[Match] +Name=sfp1_2 + +[Network] +Address=192.168.22.2/24 + +[Link] +MTUBytes=9000 diff --git a/mpm/systemd/network/sfp1_3.network b/mpm/systemd/network/sfp1_3.network new file mode 100644 index 000000000..da17ba86b --- /dev/null +++ b/mpm/systemd/network/sfp1_3.network @@ -0,0 +1,8 @@ +[Match] +Name=sfp1_3 + +[Network] +Address=192.168.23.2/24 + +[Link] +MTUBytes=9000 diff --git a/mpm/systemd/system/usrp-adc-self-cal.service.in b/mpm/systemd/system/usrp-adc-self-cal.service.in new file mode 100644 index 000000000..cb09c36dc --- /dev/null +++ b/mpm/systemd/system/usrp-adc-self-cal.service.in @@ -0,0 +1,15 @@ +[Unit] +Description=Boot-time self-calibration of @UHD_SELF_CAL_SERIES@ devices +Requires=usrp-hwd.service +After=usrp-hwd.service + +After=network.target +Requires=network.target + +[Service] +ExecStart=@CMAKE_INSTALL_PREFIX@/bin/uhd_adc_self_cal --args @UHD_SELF_CAL_ARGS@ +Type=simple +User=root + +[Install] +WantedBy=multi-user.target diff --git a/mpm/systemd/udev/x4xx/70-sfp-net.rules b/mpm/systemd/udev/x4xx/70-sfp-net.rules new file mode 100644 index 000000000..926e927c6 --- /dev/null +++ b/mpm/systemd/udev/x4xx/70-sfp-net.rules @@ -0,0 +1,9 @@ +SUBSYSTEM=="net", KERNELS=="1200070000.ethernet", NAME="sfp1_3" +SUBSYSTEM=="net", KERNELS=="1200060000.ethernet", NAME="sfp1_2" +SUBSYSTEM=="net", KERNELS=="1200050000.ethernet", NAME="sfp1_1" +SUBSYSTEM=="net", KERNELS=="1200040000.ethernet", NAME="sfp1" +SUBSYSTEM=="net", KERNELS=="1200030000.ethernet", NAME="sfp0_3" +SUBSYSTEM=="net", KERNELS=="1200020000.ethernet", NAME="sfp0_2" +SUBSYSTEM=="net", KERNELS=="1200010000.ethernet", NAME="sfp0_1" +SUBSYSTEM=="net", KERNELS=="1200000000.ethernet", NAME="sfp0" +SUBSYSTEM=="net", KERNELS=="10000a4000.ethernet", NAME="int0" diff --git a/mpm/tools/check-filesystem b/mpm/tools/check-filesystem index 1ceb7b993..37a69b858 100755 --- a/mpm/tools/check-filesystem +++ b/mpm/tools/check-filesystem @@ -9,6 +9,8 @@ import sys import traceback import unittest +from usrp_mpm.mpmlog import get_main_logger +from usrp_mpm.periph_manager.x4xx_periphs import MboardRegsControl from usrp_mpm.sys_utils.filesystem_status import * from usrp_mpm.sys_utils.dtoverlay import list_overlays from usrp_mpm.sys_utils.dtoverlay import list_available_overlays @@ -40,13 +42,16 @@ class SimpleResult(unittest.TextTestResult): % (flavour, self.getDescription(test), err)) class CheckFilesystem(unittest.TestCase): + mboard_regs_control = None + def _assert_filesystem_root_is_not_set(self): if self.args.filesystem_root != '/': raise ValueError("Changing the filesystem root is not possible for this test") def __init__(self, methodName='runTest', args=None): super(CheckFilesystem, self).__init__(methodName) - self.args=args + self.args = args + self.log = get_main_logger() def test_uhd_version(self): uhd_version = get_uhd_version(filesystem_root=self.args.filesystem_root) @@ -56,6 +61,25 @@ class CheckFilesystem(unittest.TestCase): uhd_version = get_uhd_version(filesystem_root=self.args.filesystem_root) self.assertIn(self.args.uhd_githash, uhd_version) + def test_fpga_type(self): + if self.mboard_regs_control is None: + self.mboard_regs_control = MboardRegsControl("mboard-regs", self.log) + fpga_type = self.mboard_regs_control.get_fpga_type() + self.assertEqual(fpga_type, self.args.fpga_type) + + def test_fpga_version(self): + if self.mboard_regs_control is None: + self.mboard_regs_control = MboardRegsControl("mboard-regs", self.log) + compat_number = self.mboard_regs_control.get_compat_number() + fpga_version = "{}.{}".format(*compat_number) + self.assertEqual(fpga_version, self.args.fpga_version) + + def test_fpga_githash(self): + if self.mboard_regs_control is None: + self.mboard_regs_control = MboardRegsControl("mboard-regs", self.log) + fpga_githash = "{:x}.{}".format(*self.mboard_regs_control.get_git_hash()) + self.assertEqual(fpga_githash, self.args.fpga_githash) + def test_mender_artifact(self): mender_artifact = get_mender_artifact(filesystem_root=self.args.filesystem_root) self.assertEqual(mender_artifact, self.args.mender_artifact) @@ -119,6 +143,9 @@ def args_parser(argv): parser = argparse.ArgumentParser() parser.add_argument("--uhd-version", help="Check UHD version") parser.add_argument("--uhd-githash", help="Check UHD githash") + parser.add_argument("--fpga-type", help="Check FPGA type") + parser.add_argument("--fpga-version", help="Check FPGA version") + parser.add_argument("--fpga-githash", help="Check FPGA githash") parser.add_argument("--mender-artifact", help="Check mender artifact") parser.add_argument("--fs-version", help="Check filesystem version string") parser.add_argument("--opkg-status-date", help="Check package management status file date") diff --git a/mpm/tools/program_x4xx_clkaux_lmk05318.py b/mpm/tools/program_x4xx_clkaux_lmk05318.py new file mode 100644 index 000000000..69222a14e --- /dev/null +++ b/mpm/tools/program_x4xx_clkaux_lmk05318.py @@ -0,0 +1,90 @@ +#!/usr/bin/python3 +""" +Script for programming lmk05318 on the clocking aux board +""" +# pylint: disable=no-member + +import time +import argparse +import re +from uhd.utils.mpmtools import MPMClient, MPM_RPC_PORT, InitMode + +DESCRIPTION_TEST = '''reads LMK register file generated by TICS Pro and writes via mpm_shell + the commands on the USRP given by the --host parameter.''' +parser = argparse.ArgumentParser(epilog=DESCRIPTION_TEST, + formatter_class=argparse.RawDescriptionHelpFormatter) +parser.add_argument('reg_file', help='LMK register config file') +parser.add_argument('--host', default="localhost", + help='USRP host to be programmed, usrp-hwd must run there') +parser.add_argument('-p', '--program_to_eeprom', action='store_true', + help='write config to eeprom, be careful, only 100 write-cycles are supported') +parser.add_argument('-v', '--verify_eeprom', action='store_true', + help='read config from eeprom and compares it to given reg_file') + +args = parser.parse_args() + +def parse_reg_file(file): + """ + Parse the register values from the provided register config file + """ + regfile_regex = re.compile(r"^R(\d*)\t(0x[0-9A-F]*)") + reg_dict = {} + for line in open(file, 'r').readlines(): + if regfile_regex.match(line): + [reg_str, reg_val] = line.strip().split('\t') + reg_val = reg_val.split('0x')[1] + reg_addr = reg_val[:4] + reg_data = reg_val[4:] + reg_dict[reg_str] = (hex(int(reg_addr, base=16)), hex(int(reg_data, base=16))) + return reg_dict + +mpm_c = MPMClient(InitMode.Claim, "localhost", MPM_RPC_PORT) + +# read the register file containing the actual values to be programmed +print("read register values from {}".format(args.reg_file)) +prog_regs = parse_reg_file(args.reg_file) + +print("start programming the LMK registers") +mpm_c.clkaux_set_nsync_lmk_power_en(1) +mpm_c.clkaux_set_nsync_tcxo_en(1) +if not mpm_c.clkaux_get_nsync_chip_id_valid(): + raise RuntimeError("chip ID of LMK is not valid, skip programming") +print('EEPROM has been programmed {} times'.format( + mpm_c.clkaux_get_nsync_lmk_eeprom_prog_cycles())) + +# now write the commands to program the LMK +for reg in prog_regs: + mpm_c.poke_clkaux(int(prog_regs[reg][0], 16), int(prog_regs[reg][1], 16)) + val = mpm_c.peek_clkaux(int(prog_regs[reg][0], 16)).lower() + if prog_regs[reg][1] != val: + print("warning, reg values are not as programmed: addr: {}, prog: {}, read: {}" + .format(prog_regs[reg][0], prog_regs[reg][1], val)) + +if args.program_to_eeprom: + print("programming register values into EEPROM") + print('EEPROM has been programmed {} times'.format( + mpm_c.clkaux_get_nsync_lmk_eeprom_prog_cycles())) + mpm_c.clkaux_write_nsync_lmk_cfg_regs_to_eeprom("register_commit") + mpm_c.clkaux_set_nsync_soft_reset(True) + mpm_c.clkaux_set_nsync_soft_reset(False) + mpm_c.clkaux_set_nsync_lmk_power_en(0) + print("programming of LMK done, chip is powered down") + +if args.verify_eeprom: + time.sleep(1) + VERIFY_OK = True + print("start verification of EEPROM, powering chip up") + mpm_c.clkaux_set_nsync_lmk_power_en(1) + mpm_c.clkaux_set_nsync_tcxo_en(1) + if not mpm_c.clkaux_get_nsync_chip_id_valid(): + raise RuntimeError("chip ID of LMK is not valid, skip verifying") + for reg in prog_regs: + val = mpm_c.peek_clkaux(int(prog_regs[reg][0], 16)).lower() + if str(prog_regs[reg][1]) != val: + VERIFY_OK = False + print("warning, reg values are not as programmed: addr: {}, prog: {}, read: {}" + .format(prog_regs[reg][0], prog_regs[reg][1], val)) + if VERIFY_OK: + print("SUCCESS: read back values are equal to programmed values") + else: + print("ERROR: EEPROM verification failed!") diff --git a/mpm/tools/tlv_eeprom/eeprom-pids.c b/mpm/tools/tlv_eeprom/eeprom-pids.c index d181133d7..b7d49f14d 100644 --- a/mpm/tools/tlv_eeprom/eeprom-pids.c +++ b/mpm/tools/tlv_eeprom/eeprom-pids.c @@ -8,10 +8,10 @@ #include <stddef.h> static struct pid_info pid_list[] = { - { 0x0410, "titanium", "X410 Motherboard", 0 }, + { 0x0410, "x410", "X410 Motherboard", 0 }, { 0x4000, NULL, "Power Aux Board", 0 }, { 0x4001, NULL, "Debug RF DB", 0 }, - { 0x4002, "zbx", "Zirconium RF DB", 0}, + { 0x4002, "zbx", "ZBX RF DB", 0}, { 0x4003, NULL, "HDMI SE DIO Aux Board", 0}, { 0x4004, NULL, "Clocking Aux Board with GPSDO", 0}, { 0x4005, NULL, "Clocking Aux Board (no GPSDO)", 0}, diff --git a/mpm/tools/x4xx_clkaux_lmk05318_regs_revB.txt b/mpm/tools/x4xx_clkaux_lmk05318_regs_revB.txt new file mode 100644 index 000000000..7a9a741d1 --- /dev/null +++ b/mpm/tools/x4xx_clkaux_lmk05318_regs_revB.txt @@ -0,0 +1,334 @@ +R0 0x000010 +R1 0x00010B +R2 0x000235 +R3 0x000311 +R4 0x000408 +R5 0x000516 +R6 0x000608 +R7 0x000700 +R8 0x000802 +R10 0x000AC8 +R11 0x000B00 +R12 0x000C3B +R13 0x000D08 +R14 0x000E00 +R15 0x000F00 +R16 0x001000 +R17 0x00111D +R18 0x0012FF +R19 0x001300 +R20 0x001400 +R21 0x001500 +R22 0x001600 +R23 0x001755 +R24 0x001855 +R25 0x001900 +R26 0x001A00 +R27 0x001B00 +R28 0x001C01 +R29 0x001D11 +R30 0x001E40 +R32 0x002044 +R35 0x002300 +R36 0x002403 +R37 0x002500 +R38 0x002600 +R39 0x002702 +R40 0x002800 +R41 0x002900 +R42 0x002A11 +R43 0x002B9B +R44 0x002C00 +R45 0x002D00 +R46 0x002E33 +R47 0x002F83 +R48 0x003050 +R49 0x003147 +R50 0x003218 +R51 0x003318 +R52 0x003400 +R53 0x0035F9 +R54 0x003618 +R55 0x003700 +R56 0x0038F9 +R57 0x003910 +R58 0x003A63 +R59 0x003B00 +R60 0x003C0F +R61 0x003D00 +R62 0x003E0F +R63 0x003F3B +R64 0x004095 +R65 0x004102 +R66 0x0042F8 +R67 0x0043FF +R68 0x004428 +R69 0x004500 +R70 0x004600 +R71 0x004700 +R72 0x00483F +R73 0x004900 +R74 0x004A00 +R75 0x004B00 +R76 0x004C00 +R77 0x004D0F +R78 0x004E00 +R79 0x004F11 +R80 0x005080 +R81 0x00510A +R82 0x005200 +R83 0x005305 +R84 0x0054DC +R85 0x005500 +R86 0x005600 +R87 0x00571E +R88 0x005884 +R89 0x005980 +R90 0x005A00 +R91 0x005B14 +R92 0x005C00 +R93 0x005D05 +R94 0x005EDC +R95 0x005F00 +R96 0x006000 +R97 0x00611E +R98 0x006284 +R99 0x006380 +R100 0x006429 +R101 0x006503 +R102 0x006611 +R103 0x00670F +R104 0x006818 +R105 0x006905 +R106 0x006A00 +R107 0x006B64 +R108 0x006C00 +R109 0x006D7D +R110 0x006E00 +R111 0x006F00 +R112 0x007000 +R113 0x007100 +R114 0x007200 +R115 0x007303 +R116 0x007401 +R117 0x007500 +R118 0x007600 +R119 0x007700 +R120 0x007800 +R121 0x007900 +R122 0x007A00 +R123 0x007B28 +R124 0x007C09 +R125 0x007DE5 +R126 0x007E35 +R127 0x007F2E +R128 0x008000 +R129 0x008106 +R130 0x008200 +R131 0x008301 +R132 0x008401 +R133 0x008577 +R134 0x008601 +R135 0x008713 +R136 0x008800 +R137 0x008900 +R138 0x008A00 +R139 0x008B03 +R140 0x008C02 +R141 0x008D00 +R142 0x008E01 +R143 0x008F01 +R144 0x009077 +R145 0x009101 +R146 0x009281 +R147 0x009320 +R149 0x00950D +R150 0x009600 +R151 0x009701 +R152 0x00980D +R153 0x009929 +R154 0x009A24 +R155 0x009B8B +R156 0x009C01 +R157 0x009D00 +R158 0x009E00 +R159 0x009F00 +R160 0x00A000 +R161 0x00A100 +R162 0x00A200 +R164 0x00A400 +R165 0x00A500 +R167 0x00A702 +R178 0x00B200 +R180 0x00B400 +R181 0x00B500 +R182 0x00B600 +R183 0x00B700 +R184 0x00B800 +R185 0x00B905 +R186 0x00BA01 +R187 0x00BB00 +R188 0x00BC00 +R189 0x00BD00 +R190 0x00BE01 +R191 0x00BF00 +R192 0x00C050 +R193 0x00C101 +R194 0x00C201 +R195 0x00C300 +R196 0x00C400 +R197 0x00C50D +R198 0x00C600 +R199 0x00C700 +R200 0x00C880 +R201 0x00C900 +R202 0x00CA00 +R203 0x00CB00 +R204 0x00CC07 +R205 0x00CD00 +R206 0x00CE00 +R207 0x00CF7A +R208 0x00D000 +R209 0x00D104 +R210 0x00D200 +R211 0x00D305 +R212 0x00D400 +R213 0x00D504 +R214 0x00D600 +R215 0x00D705 +R216 0x00D80F +R217 0x00D900 +R218 0x00DA01 +R219 0x00DB2C +R220 0x00DC00 +R221 0x00DD00 +R222 0x00DE03 +R223 0x00DF0D +R224 0x00E040 +R225 0x00E100 +R226 0x00E200 +R227 0x00E318 +R228 0x00E46A +R229 0x00E500 +R230 0x00E603 +R231 0x00E70D +R232 0x00E840 +R233 0x00E90A +R234 0x00EA07 +R235 0x00EB00 +R236 0x00EC00 +R237 0x00ED00 +R238 0x00EE00 +R239 0x00EF00 +R240 0x00F000 +R241 0x00F100 +R242 0x00F202 +R243 0x00F300 +R244 0x00F400 +R249 0x00F912 +R250 0x00FA00 +R251 0x00FB33 +R252 0x00FC29 +R253 0x00FD00 +R254 0x00FE00 +R255 0x00FF00 +R256 0x010006 +R257 0x010100 +R258 0x010200 +R259 0x01037D +R260 0x010402 +R261 0x010580 +R262 0x010600 +R263 0x010700 +R264 0x010800 +R265 0x0109F4 +R266 0x010A24 +R267 0x010BA0 +R268 0x010C0C +R269 0x010D00 +R270 0x010E03 +R271 0x010F7A +R272 0x01101F +R273 0x01111F +R274 0x01121F +R275 0x011313 +R276 0x011410 +R277 0x011513 +R278 0x011604 +R279 0x011708 +R280 0x011804 +R281 0x011902 +R282 0x011A07 +R283 0x011B02 +R284 0x011C1E +R285 0x011D1E +R286 0x011E02 +R287 0x011F6C +R288 0x012003 +R289 0x0121E7 +R290 0x012203 +R291 0x012325 +R292 0x012409 +R293 0x012501 +R294 0x012600 +R295 0x01272C +R296 0x012800 +R297 0x012906 +R298 0x012A00 +R299 0x012B01 +R300 0x012C00 +R301 0x012D17 +R302 0x012E1B +R303 0x012F00 +R304 0x013000 +R305 0x013100 +R306 0x013200 +R307 0x01331E +R308 0x013484 +R309 0x013580 +R310 0x013600 +R311 0x013700 +R312 0x013800 +R313 0x013900 +R314 0x013A00 +R315 0x013B00 +R316 0x013C00 +R317 0x013D00 +R318 0x013E00 +R319 0x013F03 +R320 0x014000 +R321 0x01410A +R322 0x014200 +R323 0x014300 +R324 0x014400 +R325 0x01459A +R326 0x014600 +R327 0x014703 +R328 0x01480F +R329 0x014949 +R330 0x014A00 +R331 0x014B14 +R332 0x014C00 +R333 0x014D00 +R334 0x014E00 +R335 0x014F9A +R336 0x015000 +R337 0x015103 +R338 0x01520F +R339 0x015349 +R340 0x015400 +R341 0x015500 +R342 0x015600 +R343 0x015700 +R344 0x015800 +R345 0x015900 +R346 0x015A02 +R347 0x015B00 +R348 0x015C00 +R349 0x015D00 +R350 0x015E00 +R351 0x015FB7 +R352 0x016000 +R357 0x016528 +R367 0x016F00 +R411 0x019B08 diff --git a/tools/json_to_zbx_dsa_cal.py b/tools/json_to_zbx_dsa_cal.py new file mode 100755 index 000000000..cc9bf265d --- /dev/null +++ b/tools/json_to_zbx_dsa_cal.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +import uhd +import json +import pathlib + +def convert(name): + print("Convert {} ... ".format(name), end='') + with open(name) as jsonfile: + data = json.load(jsonfile) + num_steps = len(data['freq_gain_dsa_map'][0]['gains'][0]['steps']) + metadata = data['metadata'] + if num_steps == 2: + cal = uhd.usrp.cal.ZbxTxDsaCal(metadata['name'], metadata.get('serial', ''), metadata['timestamp']) + elif num_steps == 3: + cal = uhd.usrp.cal.ZbxRxDsaCal(metadata['name'], metadata.get('serial', ''), metadata['timestamp']) + else: + raise ValueError("Invalid number of steps {}".format(num_steps)) + for band in data["freq_gain_dsa_map"]: + print(band['name'], end=',') + gain_indizes = [i['steps'] for i in band['gains']] + print(len(gain_indizes), end=' ') + cal.add_frequency_band(band['max_freq'], band['name'], gain_indizes) + + rc_file = pathlib.Path(name).stem + ".cal" + with open(rc_file, "wb") as f: + f.write(cal.serialize()) + print('Done') + + +if __name__ == "__main__": + for filename in pathlib.Path.cwd().glob('*.json'): + convert(filename) |