Post Go back to editing

AD9545 outputs not being enabled

Category: Software
Product Number: AD9545

This is possibly a bit of a unique case, but I'm having trouble bringing up the outputs of the AD9545 on a custom board, running Linux on a Xilinx Zynq-7000 SoC, and utilising the clk-ad9545 driver from ADI.

My kernel version is Xilinx's 2021.2 release (5.10), which is slightly ahead of ADI's kernel (which is based on linux-xlnx 2021.1). I've added the drivers/clk/adi directory from ADI's kernel branch and updated my drivers/clk/Kconfig & drivers/clk/Makefile to build this module. I've added the necessary 'nshot' functions that are referenced by clk-ad9545.c, and have added some additional changes to catch divide-by-zero errors when reading from certain registers (happy to provide diffs if required).

I've been able to successfully build the module and lock PLL0 and PLL1, however the outputs appear to be muted and I cannot seem to enable them even when forcing a call to 'clk_enable' at the end of ad9545_setup. The relevant section of my dts looks like this:

ref_clk0: ref_clk_0 {
			compatible = "fixed-clock";
			#clock-cells = <1>;

			clock-frequency  = <100000000>;
			clock-output-names = "Ref-A";
		};

ref_clk1: ref_clk_1 {
	compatible = "fixed-clock";
	#clock-cells = <1>;

	clock-frequency  = <100000000>;
	clock-output-names = "Ref-AA";
};

ref_clk2: ref_clk_2 {
	compatible = "fixed-clock";
	#clock-cells = <1>;

	clock-frequency  = <100000000>;
	clock-output-names = "Ref-B";
};

ref_clk3: ref_clk_3 {
	compatible = "fixed-clock";
	#clock-cells = <1>;

	clock-frequency  = <100000000>;
	clock-output-names = "Ref-BB";
};
		
spi@e0007000 {
            compatible = "xlnx,zynq-spi-r1p6";
            reg = <0xe0007000 0x1000>;
            status = "okay";
            interrupt-parent = <0x04>;
            interrupts = <0x00 0x31 0x04>;
            clocks = <0x01 0x1a 0x01 0x23>;
            clock-names = "ref_clk\0pclk";
            #address-cells = <0x01>;
            #size-cells = <0x00>;
            phandle = <0x22>;

            
            ad9545_clock: ad9545@4A {
                    compatible = "adi,ad9545";
                    reg = <0x00>;
                    #address-cells = <1>;
                    #size-cells = <0>;
                spi-max-frequency = <0xe4e1c0>;

                clock-names = "Ref-A", "Ref-AA", "Ref-B", "Ref-BB";
                clocks = <&ref_clk0 0>, <&ref_clk1 1>, <&ref_clk2 2>, <&ref_clk3 3>;

                #clock-cells = <2>;

                adi,ref-crystal;
                adi,ref-frequency-hz = <54000000>;

                assigned-clocks = <&ad9545_clock 0x02 0x00>, // AD9545_CLK_NCO, AD9545_NCO0
                                  <&ad9545_clock 0x01 0x00>, // AD9545_CLK_PLL, AD9545_PLL0
                                    <&ad9545_clock 0x01 0x01>, // AD9545_CLK_PLL, AD9545_PLL1
                                    <&ad9545_clock 0x00 0x00>, // AD9545_CLK_OUT, AD9545_Q0A
                                    <&ad9545_clock 0x00 0x02>, // AD9545_CLK_OUT, AD9545_Q0B
                                    <&ad9545_clock 0x00 0x04>, // AD9545_CLK_OUT, AD9545_Q0C
                                    <&ad9545_clock 0x00 0x08>; // AD9545_CLK_OUT, AD9545_Q1B

                assigned-clock-rates = <1000>,
                                       <1228800000>, // APLL0 must be in range 1.212GHz -> 1.616GHz, this value is 245.76MHz * 6
                                       <1875000000>, // 156.25MHz*12, the VCO for PLL0 is twice this val
                                          <122880000>,
                                       <153600000>,
                                       <153600000>,
                                       <156250000>;
        
                assigned-clock-phases = <0>,
                                        <0>,
                                        <0>,
                                        <0>,
                                        <0>,
                                        <0>,
                                        <0>;

                    // Ref-A (Pin 47)
                ref-input-clk@0 {
                    reg = <0>;
                    adi,single-ended-mode = <0>; // DRIVER_MODE_AC_COUPLED_IF
                    adi,r-divider-ratio = <508>;
                    adi,ref-dtol-pbb = <10000000>;
                    adi,ref-monitor-hysteresis-pbb = <87500>;
                    adi,ref-validation-timer-ms = <1>;
                    adi,freq-lock-threshold-ps = <0xFFFFFF>;
                    adi,phase-lock-threshold-ps = <0xFFFFFF>;
                    adi,freq-lock-fill-rate = <200>;
                    adi,freq-lock-drain-rate = <20>;
                    adi,phase-lock-fill-rate = <200>;
                    adi,phase-lock-drain-rate = <20>;
                };

                    // Ref-AA (Pin 46)
                ref-input-clk@1 {
                    reg = <1>;
                    adi,single-ended-mode = <0>; // DRIVER_MODE_AC_COUPLED_IF
                    adi,r-divider-ratio = <508>;
                    adi,ref-dtol-pbb = <10000000>;
                    adi,ref-monitor-hysteresis-pbb = <87500>;
                    adi,ref-validation-timer-ms = <1>;
                    adi,freq-lock-threshold-ps = <0xFFFFFF>;
                    adi,phase-lock-threshold-ps = <0xFFFFFF>;
                    adi,freq-lock-fill-rate = <200>;
                    adi,freq-lock-drain-rate = <20>;
                    adi,phase-lock-fill-rate = <200>;
                    adi,phase-lock-drain-rate = <20>;
                };

                    // Ref-B (Pin 38)
                ref-input-clk@2 {
                    reg = <2>;
                    adi,differential-mode = <0>; // DRIVER_MODE_AC_COUPLED
                    adi,r-divider-ratio = <508>;
                    adi,ref-dtol-pbb = <10000000>;
                    adi,ref-monitor-hysteresis-pbb = <87500>;
                    adi,ref-validation-timer-ms = <1>;
                    adi,freq-lock-threshold-ps = <0xFFFFFF>;
                    adi,phase-lock-threshold-ps = <0xFFFFFF>;
                    adi,freq-lock-fill-rate = <200>;
                    adi,freq-lock-drain-rate = <20>;
                    adi,phase-lock-fill-rate = <200>;
                    adi,phase-lock-drain-rate = <20>;
                };

                    // Ref-BB (Pin 38)
                ref-input-clk@3 {
                    reg = <3>;
                    adi,differential-mode = <0>; // DRIVER_MODE_AC_COUPLED
                    adi,r-divider-ratio = <508>;
                    adi,ref-dtol-pbb = <10000000>;
                    adi,ref-monitor-hysteresis-pbb = <87500>;
                    adi,ref-validation-timer-ms = <1>;
                    adi,freq-lock-threshold-ps = <0xFFFFFF>;
                    adi,phase-lock-threshold-ps = <0xFFFFFF>;
                    adi,freq-lock-fill-rate = <200>;
                    adi,freq-lock-drain-rate = <20>;
                    adi,phase-lock-fill-rate = <200>;
                    adi,phase-lock-drain-rate = <20>;
                };

                ad9545_apll0: pll-clk@0x00 {
                        reg = <0x00>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        profile@0 {
                               reg = <0>;
                               adi,pll-source = <2>;
                               adi,profile-priority = <0>;
                               adi,pll-loop-bandwidth-uhz = <200000000>;
                        };
                };

                ad9545_apll1: pll-clk@0x01 {
                        reg = <0x01>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        profile@0 {
                               reg = <0>;
                               adi,pll-source = <2>;
                               adi,profile-priority = <0>;
                               adi,pll-loop-bandwidth-uhz = <200000000>;
                        };
                };

                output-clk@0x00 {
                        reg = <0x00>;
                        adi,output-mode = <0x00>;
                        adi,current-source-microamp = <7500>;
                };
                output-clk@0x02 {
                        reg = <0x02>;
                        adi,output-mode = <0x00>;
                        adi,current-source-microamp = <7500>;
                };
                output-clk@0x04 {
                        reg = <0x04>;
                        adi,output-mode = <0x00>;
                        adi,current-source-microamp = <7500>;
                };
                
                output-clk@0x08 {
                        reg = <0x08>;
                        adi,output-mode = <0x00>;
                        adi,current-source-microamp = <7500>;
                };
            
            };
        };


And here's the output from /sys/kernel/debug/clk_summary:



...and the output showing both PLLs locked:

root@target-zynq7:~# cat /sys/kernel/debug/clk/PLL0/PLL0
PLL0:
PLL status: Locked
Freerun Mode: Off
Holdover Mode: Off
PLL Profile: On
Profile Number: 0
Temperature: 46 C
root@target-zynq7:~# cat /sys/kernel/debug/clk/PLL1/PLL1
PLL1:
PLL status: Locked
Freerun Mode: Off
Holdover Mode: Off
PLL Profile: On
Profile Number: 0
Temperature: 46 C
root@target-zynq7:~#vav


When I attempt to force a call to 'clk_enable'  I hit a 'scheduling while atomic' bug, which I believe is caused by the call to 'regmap_read' within 'ad9545_out_clk_get_nshot', as this function eventually calls  'spi_write_then_read' which later hits  '__schedule' - since this call to '__schedule' comes between calls to 'clk_enable_lock' and 'clk_enable_unlock' (I can provide the exception stack for this if required, but it's probably not necessary just yet).

What I'm trying to determine is how the callback to 'ad9545_out_clk_enable' from ops->enable is supposed to be invoked during the driver's standard operation. I'm sure I'm missing something within the clock framework, but as far as I can tell, this callback will never be invoked during the probe of the device and there is no interface available to force it to be called from userspace. If I do enable the debugfs clk_prepare_enable interface, I end up with the same 'scheduling while atomic' bug I encounter when forcing the call within 'ad9545_setup'.

I'm going to attempt forcing the unmute from outside of the clock API (i.e. I'm going to directly call 'ad9545_out_clk_enable'), but I would be keen to understand if there's anything I'm doing wrong here.

  • Hi CCurry,

    I can't comment on much of your question, but a common problem enabling the AD9545 outputs can be traced to a proper termination of the outputs.  You mention that you are using a "custom" board.  I didn't know if you are referring to our "customer" eval board or truly a custom board of your creation.  Regardless, the default condition for the AD9545 outputs is to act as a current source into a 50 ohm load to ground (HCSL mode).  This is shown in Figure 38 of the Rev C Datasheet.  However, our customer eval board is typically shipped with jumpers set for 50 ohm resistors to the 1.8V supply.  This configuration is shown in Figure 40 of the Rev C Datasheet.  This requires the AD9545's outputs to act as a current sink (CML mode) for proper operation.  As an example, the polarity (source or sink) is set by bit 0 of 0x10D7 for Out0A as an example.  

    Please check to make sure that the AD9545 is properly configured to match your board termination.  If you provide your board termination and register settings (0x10D7, 0x10D8, 0x10D9, 0x14D7, 0x14D8), I can review for proper configuration.

  • Hi ad9020,

    To answer your first point - this is not an ADI eval board, it's a custom board produced by a third-party that we are using as a development board.

    Your point on the driver configuration has cracked it though - after reading back these registers I noticed that all were 0x0 - indicating the driver is set to sink mode, which is not correct for this hardware (all outputs have external pulldown resistors). After updating my device-tree to include the 'adi,current-source' field on each output, It looks like this (in combination with my forced enable call) produces valid clocks, so thank you for that (you've saved me a lot of pain).

    My next steps will be to remove my forced call and see if I still get clock outputs, so I'll leave the question open for now, but it may be that this is the key...

    Cheers,

    Chris