From f1c89e4529d2e2453e9b0dad1c97e984aa200ce7 Mon Sep 17 00:00:00 2001 From: Brent Kowal Date: Thu, 29 Jan 2026 12:35:10 -0500 Subject: [PATCH 1/4] iio: frequency: hmc7044: Fix external VCO (PLL2) implementation This corrects the implementation of the external VCO functionality for the HMC7044. The existing implementation treated the external VCO mode similar to HMC7043, with CLKIN1 (FIN) being a reference clock for the dividers, bypassing PLL1 and PLL2. This is not the intended functionality of this device mode. With external VCO mode, PLL1 remains active, using the reference inputs as with normal operation. The PLL2 loop is also closed, with CLKIN1 (FIN) being the VCO, rather than the internal VCO. There were several changes required to accommodate this: - The external VCO has wider frequency limits. PLL2 bounds checks are based on the VCO source - CLKIN1 is no longer used as the PLL2 frequency, using the adi,pll2-output-frequency dt property for both nodes - PLL1 and PLL2 must be enabled in both modes - Corrected status reporting when external VCO is enabled - Remove external VCO as a condition for sync configuration Signed-off-by: Brent Kowal --- drivers/iio/frequency/hmc7044.c | 155 ++++++++++++++++---------------- 1 file changed, 77 insertions(+), 78 deletions(-) diff --git a/drivers/iio/frequency/hmc7044.c b/drivers/iio/frequency/hmc7044.c index a7560bfb45d638..b7a413a6011ab9 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -217,10 +217,13 @@ #define HMC7044_NUM_CHAN 14 -#define HMC7044_LOW_VCO_MIN 2150000 -#define HMC7044_LOW_VCO_MAX 2880000 -#define HMC7044_HIGH_VCO_MIN 2650000 -#define HMC7044_HIGH_VCO_MAX 3200000 +#define HMC7044_LOW_VCO_MIN_KHZ 2150000 +#define HMC7044_LOW_VCO_MAX_KHZ 2880000 +#define HMC7044_HIGH_VCO_MIN_KHZ 2650000 +#define HMC7044_HIGH_VCO_MAX_KHZ 3200000 +#define HMC7044_EXT_VCO_MIN_KHZ 800000 +#define HMC7044_EXT_VCO_MAX_KHZ 3200000 +#define HMC7044_EXT_VCO_LOW_THRESH_KHZ 1000000 #define HMC7044_RECOMM_LCM_MIN 30000 #define HMC7044_RECOMM_LCM_MAX 70000 @@ -890,7 +893,7 @@ static int hmc7044_info(struct iio_dev *indio_dev) return 0; } - if (hmc->device_id == HMC7044 && !hmc->clkin1_vcoin_en) { + if (hmc->device_id == HMC7044) { ret = hmc7044_read(indio_dev, HMC7044_REG_PLL1_STATUS, &pll1_stat); if (ret < 0) @@ -910,8 +913,6 @@ static int hmc7044_info(struct iio_dev *indio_dev) return ret; active = HMC7044_PLL1_ACTIVE_CLKIN(pll1_stat); - } else { - active = 1; } if (hmc->device_id == HMC7043) @@ -922,7 +923,7 @@ static int hmc7044_info(struct iio_dev *indio_dev) else clkin_freq = hmc->clkin_freq[active]; - if (hmc->device_id == HMC7044 && !hmc->clkin1_vcoin_en) + if (hmc->device_id == HMC7044) dev_info(&hmc->spi->dev, "PLL1: %s, CLKIN%u @ %u Hz, PFD: %u kHz - PLL2: %s @ %u.%06u MHz\n", pll1_fsm_states[HMC7044_PLL1_FSM_STATE(pll1_stat)], @@ -930,9 +931,40 @@ static int hmc7044_info(struct iio_dev *indio_dev) HMC7044_PLL2_LOCK_DETECT(alarm_stat) ? "Locked" : "Unlocked", hmc->pll2_freq / 1000000, hmc->pll2_freq % 1000000); - else - dev_info(&hmc->spi->dev, "CLKIN%u @ %u.%06u MHz\n", active, - clkin_freq / 1000000, clkin_freq % 1000000); + + return 0; +} + +static int hmc7044_validate_pll2_freq(struct hmc7044 *hmc, unsigned long pll2_freq_khz) +{ + unsigned long limit_max; + unsigned long limit_min; + + if (hmc->clkin1_vcoin_en) { + limit_max = HMC7044_EXT_VCO_MAX_KHZ; + limit_min = HMC7044_EXT_VCO_MIN_KHZ; + } else { + limit_max = HMC7044_HIGH_VCO_MAX_KHZ; + limit_min = HMC7044_LOW_VCO_MIN_KHZ; + } + + if (pll2_freq_khz < limit_min || + pll2_freq_khz > limit_max) { + if (hmc->ignore_vco_limits) { + /* + * Debug only, is at own risk! + * May fail across process, voltage and temperature + */ + dev_warn(&hmc->spi->dev, + "PLL2 frequency %lu kHz is out of range, ignoring limits\n", + pll2_freq_khz); + } else { + dev_err(&hmc->spi->dev, + "PLL2 frequency %lu kHz is out of range (%lu - %lu)\n", + pll2_freq_khz, limit_min, limit_max); + return -EINVAL; + } + } return 0; } @@ -948,12 +980,12 @@ static int hmc7044_setup(struct iio_dev *indio_dev) { struct hmc7044 *hmc = iio_priv(indio_dev); struct hmc7044_chan_spec *chan; - bool high_vco_en; bool pll2_freq_doubler_en; unsigned long vcxo_freq, pll2_freq; unsigned long clkin_freq[4]; unsigned long lcm_freq; unsigned int in_prescaler[5]; + unsigned int vco_sel; unsigned long pll1_lock_detect; unsigned long n1, r1; unsigned long n, r; @@ -961,6 +993,7 @@ static int hmc7044_setup(struct iio_dev *indio_dev) unsigned long vco_limit; unsigned long n2[2], r2[2]; unsigned int i, c, ref_en = 0; + u32 pll1_stat; int ret; vcxo_freq = hmc->vcxo_freq / 1000; @@ -1018,28 +1051,26 @@ static int hmc7044_setup(struct iio_dev *indio_dev) hmc->pll1_pfd = pfd1_freq; - if (pll2_freq < HMC7044_LOW_VCO_MIN || - pll2_freq > HMC7044_HIGH_VCO_MAX) { - if (hmc->ignore_vco_limits) { - /* Debug only, is at own risk! May fail across process, voltage and temperature */ - dev_warn(&hmc->spi->dev, - "PLL2 frequency %lu kHz is out of range, " - "ignoring limits\n", pll2_freq); - } else { - dev_err(&hmc->spi->dev, - "PLL2 frequency %lu kHz is out of range (%u - %u)\n", - pll2_freq, HMC7044_LOW_VCO_MIN / 1000, - HMC7044_HIGH_VCO_MAX / 1000); - return -EINVAL; + ret = hmc7044_validate_pll2_freq(hmc, pll2_freq); + if (ret) + return ret; + + if (hmc->clkin1_vcoin_en) { + vco_sel = HMC7044_VCO_EXT; + if (pll2_freq < HMC7044_EXT_VCO_LOW_THRESH_KHZ) { + ret = hmc7044_write(indio_dev, HMC7044_CLK_INPUT_CTRL, + HMC7044_LOW_FREQ_INPUT_MODE); + if (ret) + return ret; } + } else { + vco_limit = (HMC7044_LOW_VCO_MAX_KHZ + HMC7044_HIGH_VCO_MIN_KHZ) / 2; + if (pll2_freq >= vco_limit) + vco_sel = HMC7044_VCO_HIGH; + else + vco_sel = HMC7044_VCO_LOW; } - vco_limit = (HMC7044_LOW_VCO_MAX + HMC7044_HIGH_VCO_MIN) / 2; - if (pll2_freq >= vco_limit) - high_vco_en = true; - else - high_vco_en = false; - /* fVCO / N2 = fVCXO * doubler / R2 */ pll2_freq_doubler_en = true; rational_best_approximation(pll2_freq, vcxo_freq * 2, @@ -1110,40 +1141,18 @@ static int hmc7044_setup(struct iio_dev *indio_dev) return ret; /* Program PLL2 */ - - /* Select the VCO range */ + ret = hmc7044_write(indio_dev, HMC7044_REG_EN_CTRL_0, + (hmc->rf_reseeder_en ? HMC7044_RF_RESEEDER_EN : 0) | + HMC7044_VCO_SEL(vco_sel) | + HMC7044_SYSREF_TIMER_EN | HMC7044_PLL2_EN | + HMC7044_PLL1_EN); + if (ret) + return ret; if (hmc->clkin1_vcoin_en) { - hmc->pll2_freq = hmc->clkin_freq_ccf[1] ? - hmc->clkin_freq_ccf[1] : hmc->clkin_freq[1]; - - if (hmc->pll2_freq < 1000000000U) { - ret = hmc7044_write(indio_dev, HMC7044_CLK_INPUT_CTRL, - HMC7044_LOW_FREQ_INPUT_MODE); - if (ret) - return ret; - } - - ret = hmc7044_write(indio_dev, HMC7044_REG_EN_CTRL_0, - (hmc->rf_reseeder_en ? HMC7044_RF_RESEEDER_EN : 0) | - HMC7044_VCO_SEL(0) | - HMC7044_SYSREF_TIMER_EN); - if (ret) - return ret; - ret = hmc7044_write(indio_dev, HMC7044_REG_SYNC, HMC7044_SYNC_RETIME); if (ret) return ret; - } else { - ret = hmc7044_write(indio_dev, HMC7044_REG_EN_CTRL_0, - (hmc->rf_reseeder_en ? HMC7044_RF_RESEEDER_EN : 0) | - HMC7044_VCO_SEL(high_vco_en ? - HMC7044_VCO_HIGH : - HMC7044_VCO_LOW) | - HMC7044_SYSREF_TIMER_EN | HMC7044_PLL2_EN | - HMC7044_PLL1_EN); - if (ret) - return ret; } if (hmc->pll2_cap_bank_sel != ~0) { @@ -1354,17 +1363,11 @@ static int hmc7044_setup(struct iio_dev *indio_dev) if (ret) return ret; - if (!hmc->clkin1_vcoin_en) { - u32 pll1_stat; - - ret = hmc7044_read(indio_dev, HMC7044_REG_PLL1_STATUS, &pll1_stat); - if (ret < 0) - return ret; + ret = hmc7044_read(indio_dev, HMC7044_REG_PLL1_STATUS, &pll1_stat); + if (ret < 0) + return ret; - c = HMC7044_PLL1_ACTIVE_CLKIN(pll1_stat); - } else { - c = 1; /* CLKIN1 */ - } + c = HMC7044_PLL1_ACTIVE_CLKIN(pll1_stat); for (i = 0; i < hmc->num_channels; i++) { chan = &hmc->channels[i]; @@ -1918,10 +1921,7 @@ static int hmc7044_status_show(struct seq_file *file, void *offset) if (ret < 0) return ret; - if (hmc->clkin1_vcoin_en) - active = 1; - else - active = HMC7044_PLL1_ACTIVE_CLKIN(pll1_stat); + active = HMC7044_PLL1_ACTIVE_CLKIN(pll1_stat); if (hmc->clkin_freq_ccf[active]) clkin_freq = hmc->clkin_freq_ccf[active]; @@ -2139,7 +2139,7 @@ static int hmc7044_jesd204_clks_sync1(struct jesd204_dev *jdev, return ret; } } else { - if (hmc->device_id == HMC7044 && !hmc->clkin0_rfsync_en && !hmc->clkin1_vcoin_en) { + if (hmc->device_id == HMC7044 && !hmc->clkin0_rfsync_en) { ret = hmc7044_sync_pin_set(indio_dev, HMC7044_SYNC_PIN_SYNC); if (ret) return ret; @@ -2151,8 +2151,7 @@ static int hmc7044_jesd204_clks_sync1(struct jesd204_dev *jdev, } ret = hmc7044_toggle_bit(indio_dev, HMC7044_REG_REQ_MODE_0, - HMC7044_RESTART_DIV_FSM, (hmc->device_id == HMC7044 && - !hmc->clkin1_vcoin_en) ? 10000 : 1000); + HMC7044_RESTART_DIV_FSM, (hmc->device_id == HMC7044) ? 10000 : 1000); if (ret) return ret; @@ -2237,7 +2236,7 @@ static int hmc7044_jesd204_clks_sync3(struct jesd204_dev *jdev, __func__, val & 0xFF); } - if (hmc->device_id == HMC7044 && !hmc->clkin0_rfsync_en && !hmc->clkin1_vcoin_en) { + if (hmc->device_id == HMC7044 && !hmc->clkin0_rfsync_en) { ret = hmc7044_sync_pin_set(indio_dev, HMC7044_SYNC_PIN_PULSE_GEN_REQ); if (ret) return ret; From fe30f1b65dbc0a77438677fcdd93cebef00ff911 Mon Sep 17 00:00:00 2001 From: Brent Kowal Date: Thu, 29 Jan 2026 13:32:11 -0500 Subject: [PATCH 2/4] dt-bindings: iio: frequency: hmc7044: add adi,pll2-pfd-invert-enable Adds the adi,pll2-pfd-invert-enable flag to the bindings file. Signed-off-by: Brent Kowal --- Documentation/devicetree/bindings/iio/frequency/hmc7044.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/iio/frequency/hmc7044.txt b/Documentation/devicetree/bindings/iio/frequency/hmc7044.txt index 04f4a734a49640..f0b1f98d70bbac 100644 --- a/Documentation/devicetree/bindings/iio/frequency/hmc7044.txt +++ b/Documentation/devicetree/bindings/iio/frequency/hmc7044.txt @@ -86,6 +86,7 @@ Optional properties hmc7044: pulse generator. - adi,clkin0-rf-sync-enable: CLKIN0 input is used for external RF sync. - adi,clkin1-vco-in-enable: CLKIN1 input is used for external VCO. + - adi,pll2-pfd-invert-enable: Inverts the polarity of PLL2's PFD - adi,rf-reseeder-disable: Disables RF reseed for SYSREF. Optional properties hmc7043: From b2d4ba14afa1b49c94de3d60370746c2778a0c8c Mon Sep 17 00:00:00 2001 From: Brent Kowal Date: Thu, 29 Jan 2026 13:34:54 -0500 Subject: [PATCH 3/4] iio: frequency: hmc7044: Add PLL2 PFD inversion support Adds the adi,pll2-pfd-invert-enable device tree property to support changing the polarity of the PLL2 PFD. This is useful for utilizing an external VCO for PLL 2 which has an inversion in the loop filter. Signed-off-by: Brent Kowal --- drivers/iio/frequency/hmc7044.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/iio/frequency/hmc7044.c b/drivers/iio/frequency/hmc7044.c index b7a413a6011ab9..0d6471fdd431d4 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -126,6 +126,11 @@ #define HMC7044_REG_PLL2_N_MSB 0x0036 #define HMC7044_N2_MSB(x) (((x) & 0xff00) >> 8) +#define HMC7044_REG_PLL2_PFD_CTRL 0x0038 +#define HMC7044_PFD_UP_EN BIT(4) +#define HMC7044_PFD_DOWN_EN BIT(3) +#define HMC7044_PFD_POLARITY BIT(0) + #define HMC7044_REG_OSCOUT_PATH 0x0039 #define HMC7044_REG_OSCOUT_DRIVER_0 0x003A #define HMC7044_REG_OSCOUT_DRIVER_1 0x003B @@ -308,6 +313,7 @@ struct hmc7044 { bool pll1_ref_autorevert_en; bool clkin0_rfsync_en; bool clkin1_vcoin_en; + bool pll2_pfd_invert_en; bool high_performance_mode_clock_dist_en; bool rf_reseeder_en; bool oscout_path_en; @@ -1141,6 +1147,12 @@ static int hmc7044_setup(struct iio_dev *indio_dev) return ret; /* Program PLL2 */ + ret = hmc7044_write(indio_dev, HMC7044_REG_PLL2_PFD_CTRL, + HMC7044_PFD_UP_EN | HMC7044_PFD_DOWN_EN | + (hmc->pll2_pfd_invert_en ? HMC7044_PFD_POLARITY : 0)); + if (ret) + return ret; + ret = hmc7044_write(indio_dev, HMC7044_REG_EN_CTRL_0, (hmc->rf_reseeder_en ? HMC7044_RF_RESEEDER_EN : 0) | HMC7044_VCO_SEL(vco_sel) | @@ -1700,6 +1712,11 @@ static int hmc7044_parse_dt(struct device *dev, hmc->clkin1_vcoin_en = of_property_read_bool(np, "adi,clkin1-vco-in-enable"); + hmc->pll2_pfd_invert_en = + of_property_read_bool(np, "adi,pll2-pfd-invert-enable"); + + if (hmc->pll2_pfd_invert_en && !hmc->clkin1_vcoin_en) + dev_warn(dev, "PLL2 PFD inverted using internal VCO.\n"); hmc->ignore_vco_limits = of_property_read_bool(np, "adi,ignore-vco-limits"); hmc->sync_through_pll2_force_r2_eq_1 = From 949fce3d9e180487c7031fe676a0ac7ac5721deb Mon Sep 17 00:00:00 2001 From: George Mois Date: Mon, 16 Feb 2026 17:31:22 +0200 Subject: [PATCH 4/4] iio: frequency: hmc7044: Disable PLL1 when no CLKIN present When all four CLKIN inputs have zero frequency, PLL1 cannot achieve lock as there is no reference to lock against. In this configuration, disable PLL1 and allow the VCXO to free-run directly into PLL2. This enables use cases where the HMC7044 operates with an external VCXO only, without requiring a reference clock input. Signed-off-by: George Mois --- drivers/iio/frequency/hmc7044.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/iio/frequency/hmc7044.c b/drivers/iio/frequency/hmc7044.c index 0d6471fdd431d4..a5795d6168790c 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -1157,10 +1157,14 @@ static int hmc7044_setup(struct iio_dev *indio_dev) (hmc->rf_reseeder_en ? HMC7044_RF_RESEEDER_EN : 0) | HMC7044_VCO_SEL(vco_sel) | HMC7044_SYSREF_TIMER_EN | HMC7044_PLL2_EN | - HMC7044_PLL1_EN); + (ref_en ? HMC7044_PLL1_EN : 0)); if (ret) return ret; + if (!ref_en) + dev_info(&hmc->spi->dev, + "PLL1 disabled, no valid CLKIN reference\n"); + if (hmc->clkin1_vcoin_en) { ret = hmc7044_write(indio_dev, HMC7044_REG_SYNC, HMC7044_SYNC_RETIME); if (ret)