From b2082d60c29a07ee15b10d206b319d25c129910e Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Mon, 18 May 2026 14:32:06 +0000 Subject: [PATCH 01/11] iio: frequency: hmc7044: register pll2 clock Register pll2 as parent in the case device is hmc7044. This will enable control and propagation of clock changes to sibling channels and parent clock within the device. Support clock framework operations for the added pll2 clock. Signed-off-by: Tomas Melin --- drivers/iio/frequency/hmc7044.c | 118 +++++++++++++++++++++++++------- 1 file changed, 92 insertions(+), 26 deletions(-) diff --git a/drivers/iio/frequency/hmc7044.c b/drivers/iio/frequency/hmc7044.c index 3fb1608049743b..d45cf02b477791 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -335,6 +335,8 @@ struct hmc7044 { struct iio_chan_spec iio_channels[HMC7044_NUM_CHAN]; struct hmc7044_output outputs[HMC7044_NUM_CHAN]; struct clk *clks[HMC7044_NUM_CHAN]; + struct clk *pll2_clk; + struct hmc7044_output pll2_output; struct clk_onecell_data clk_data; struct clk *clk_input[4]; struct mutex lock; @@ -358,6 +360,8 @@ static const char * const hmc7044_input_clk_names[] = { [3] = "clkin3", }; +static int hmc7044_setup(struct iio_dev *indio_dev); + static int hmc7044_write(struct iio_dev *indio_dev, unsigned int reg, unsigned int val) @@ -813,6 +817,13 @@ static long hmc7044_set_clk_attr(struct clk_hw *hw, static unsigned long hmc7044_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { + struct hmc7044_output *out = to_output(hw); + struct iio_dev *indio_dev = out->indio_dev; + struct hmc7044 *hmc = iio_priv(indio_dev); + + if (out->address == hmc->pll2_output.address) + return hmc->pll2_freq; + return hmc7044_get_clk_attr(hw, IIO_CHAN_INFO_FREQUENCY); } @@ -849,6 +860,23 @@ static int hmc7044_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { + struct hmc7044_output *out = to_output(hw); + struct iio_dev *indio_dev = out->indio_dev; + struct hmc7044 *hmc = iio_priv(indio_dev); + unsigned int address; + int ret; + + address = to_output(hw)->address; + + if (address == hmc->pll2_output.address) { + mutex_lock(&hmc->lock); + /* request rate, setup() will update pll2_freq if not usable */ + hmc->pll2_freq = rate; + ret = hmc7044_setup(indio_dev); + mutex_unlock(&hmc->lock); + return ret; + } + return hmc7044_set_clk_attr(hw, IIO_CHAN_INFO_FREQUENCY, rate); } @@ -887,6 +915,36 @@ static int hmc7044_clk_register(struct iio_dev *indio_dev, return 0; } +static int hmc7044_pll2_register(struct iio_dev *indio_dev, + const char *parent_name) +{ + struct hmc7044 *hmc = iio_priv(indio_dev); + struct device *dev = &hmc->spi->dev; + struct clk_init_data init; + struct clk *clk; + + init.name = devm_kasprintf(dev, GFP_KERNEL, "%s-pll2", + fwnode_get_name(dev_fwnode(dev))); + if (!init.name) + return -ENOMEM; + init.ops = &hmc7044_clk_ops; + init.flags = CLK_GET_RATE_NOCACHE; + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + + hmc->pll2_output.hw.init = &init; + hmc->pll2_output.indio_dev = indio_dev; + hmc->pll2_output.address = HMC7044_NUM_CHAN; + + clk = devm_clk_register(dev, &hmc->pll2_output.hw); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + hmc->pll2_clk = clk; + + return 0; +} + static int hmc7044_info(struct iio_dev *indio_dev) { struct hmc7044 *hmc = iio_priv(indio_dev); @@ -987,6 +1045,7 @@ static int hmc7044_setup(struct iio_dev *indio_dev) struct hmc7044 *hmc = iio_priv(indio_dev); struct hmc7044_chan_spec *chan; bool pll2_freq_doubler_en; + unsigned long n2[2], r2[2]; unsigned long vcxo_freq, pll2_freq; unsigned long clkin_freq[4]; unsigned long lcm_freq; @@ -997,7 +1056,6 @@ static int hmc7044_setup(struct iio_dev *indio_dev) unsigned long n, r; unsigned long pfd1_freq; unsigned long vco_limit; - unsigned long n2[2], r2[2]; unsigned int i, c, ref_en = 0; u32 pll1_stat; int ret; @@ -1077,33 +1135,36 @@ static int hmc7044_setup(struct iio_dev *indio_dev) vco_sel = HMC7044_VCO_LOW; } - /* fVCO / N2 = fVCXO * doubler / R2 */ - pll2_freq_doubler_en = true; - rational_best_approximation(pll2_freq, vcxo_freq * 2, - HMC7044_N2_MAX, - hmc->sync_through_pll2_force_r2_eq_1 ? 1 : HMC7044_R2_MAX, - &n2[0], &r2[0]); - - if (pll2_freq != vcxo_freq * n2[0] / r2[0]) { - rational_best_approximation(pll2_freq, vcxo_freq, - HMC7044_N2_MAX, - hmc->sync_through_pll2_force_r2_eq_1 ? 1 : HMC7044_R2_MAX, - &n2[1], &r2[1]); - - if (abs((int)pll2_freq - (int)(vcxo_freq * 2 * n2[0] / r2[0])) > - abs((int)pll2_freq - (int)(vcxo_freq * n2[1] / r2[1]))) { - n2[0] = n2[1]; - r2[0] = r2[1]; - pll2_freq_doubler_en = false; + /* Calculate PLL2 */ + { + unsigned long r2_max = hmc->sync_through_pll2_force_r2_eq_1 ? + 1 : HMC7044_R2_MAX; + + pll2_freq_doubler_en = true; + rational_best_approximation(pll2_freq, vcxo_freq * 2, + HMC7044_N2_MAX, r2_max, + &n2[0], &r2[0]); + + if (pll2_freq != vcxo_freq * n2[0] / r2[0]) { + rational_best_approximation(pll2_freq, vcxo_freq, + HMC7044_N2_MAX, r2_max, + &n2[1], &r2[1]); + + if (abs((int)pll2_freq - (int)(vcxo_freq * 2 * n2[0] / r2[0])) > + abs((int)pll2_freq - (int)(vcxo_freq * n2[1] / r2[1]))) { + n2[0] = n2[1]; + r2[0] = r2[1]; + pll2_freq_doubler_en = false; + } } - } - while ((n2[0] < HMC7044_N2_MIN) && (r2[0] <= HMC7044_R2_MAX / 2)) { - n2[0] *= 2; - r2[0] *= 2; + while ((n2[0] < HMC7044_N2_MIN) && (r2[0] <= HMC7044_R2_MAX / 2)) { + n2[0] *= 2; + r2[0] *= 2; + } + if (n2[0] < HMC7044_N2_MIN) + return -EINVAL; } - if (n2[0] < HMC7044_N2_MIN) - return -EINVAL; /* Resets all registers to default values */ ret = hmc7044_toggle_bit(indio_dev, HMC7044_REG_SOFT_RESET, @@ -1385,6 +1446,11 @@ static int hmc7044_setup(struct iio_dev *indio_dev) c = HMC7044_PLL1_ACTIVE_CLKIN(pll1_stat); + ret = hmc7044_pll2_register(indio_dev, + __clk_get_name(hmc->clk_input[c])); + if (ret) + return ret; + for (i = 0; i < hmc->num_channels; i++) { chan = &hmc->channels[i]; @@ -1392,7 +1458,7 @@ static int hmc7044_setup(struct iio_dev *indio_dev) continue; ret = hmc7044_clk_register(indio_dev, chan->num, i, - __clk_get_name(hmc->clk_input[c])); + __clk_get_name(hmc->pll2_clk)); if (ret) return ret; } From 6dfaa628e2ca82786f89ba6e6eadbc97fad58d74 Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Tue, 19 May 2026 04:05:16 +0000 Subject: [PATCH 02/11] iio: frequency: hmc7044: split pll2 configuration to separate function Make the pll2 setting calculations a separate function. Calculating appropriate settings and achievable frequencies as a separate request is needed when controlling the clock via the clock framework. Signed-off-by: Tomas Melin --- drivers/iio/frequency/hmc7044.c | 111 +++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 37 deletions(-) diff --git a/drivers/iio/frequency/hmc7044.c b/drivers/iio/frequency/hmc7044.c index d45cf02b477791..a8bc62b06b38e7 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -279,6 +279,12 @@ struct hmc7044_output { #define to_output(_hw) container_of(_hw, struct hmc7044_output, hw) +struct hmc7044_pll2_config { + bool pll2_freq_doubler_en; + unsigned long n2; + unsigned long r2; +}; + struct hmc7044_chan_spec { unsigned int num; bool disable; @@ -814,6 +820,64 @@ static long hmc7044_set_clk_attr(struct clk_hw *hw, return hmc7044_write_raw(indio_dev, chan, val, 0, mask); } +static unsigned long hmc7044_pll2_recalc_rate(struct hmc7044_pll2_config *pll2, + unsigned long vcxo_freq, + unsigned long pll2_freq, + bool force_r2_eq_1) +{ + bool freq_doubler_en; + unsigned long n2[2], r2[2]; + unsigned long pll2_act; + unsigned long r2_max; + + vcxo_freq = vcxo_freq / 1000; + pll2_freq = pll2_freq / 1000; + + if (pll2_freq < HMC7044_LOW_VCO_MIN_KHZ || pll2_freq > HMC7044_HIGH_VCO_MAX_KHZ) + return 0; + + r2_max = force_r2_eq_1 ? 1 : HMC7044_R2_MAX; + + /* fVCO / N2 = fVCXO * doubler / R2 */ + freq_doubler_en = true; + rational_best_approximation(pll2_freq, vcxo_freq * 2, + HMC7044_N2_MAX, r2_max, + &n2[0], &r2[0]); + + if (pll2_freq != vcxo_freq * n2[0] / r2[0]) { + rational_best_approximation(pll2_freq, vcxo_freq, + HMC7044_N2_MAX, r2_max, + &n2[1], &r2[1]); + + if (abs((int)pll2_freq - (int)(vcxo_freq * 2 * n2[0] / r2[0])) > + abs((int)pll2_freq - (int)(vcxo_freq * n2[1] / r2[1]))) { + n2[0] = n2[1]; + r2[0] = r2[1]; + freq_doubler_en = false; + } + } + + while ((n2[0] < HMC7044_N2_MIN) && (r2[0] <= HMC7044_R2_MAX / 2)) { + n2[0] *= 2; + r2[0] *= 2; + } + + if (n2[0] < HMC7044_N2_MIN) + return 0; + + if (pll2) { + pll2->n2 = n2[0]; + pll2->r2 = r2[0]; + pll2->pll2_freq_doubler_en = freq_doubler_en; + } + + pll2_act = vcxo_freq * n2[0] / r2[0]; + if (freq_doubler_en) + pll2_act *= 2; + + return pll2_act * 1000; +} + static unsigned long hmc7044_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { @@ -1044,8 +1108,7 @@ static int hmc7044_setup(struct iio_dev *indio_dev) { struct hmc7044 *hmc = iio_priv(indio_dev); struct hmc7044_chan_spec *chan; - bool pll2_freq_doubler_en; - unsigned long n2[2], r2[2]; + struct hmc7044_pll2_config pll2; unsigned long vcxo_freq, pll2_freq; unsigned long clkin_freq[4]; unsigned long lcm_freq; @@ -1135,36 +1198,10 @@ static int hmc7044_setup(struct iio_dev *indio_dev) vco_sel = HMC7044_VCO_LOW; } - /* Calculate PLL2 */ - { - unsigned long r2_max = hmc->sync_through_pll2_force_r2_eq_1 ? - 1 : HMC7044_R2_MAX; - - pll2_freq_doubler_en = true; - rational_best_approximation(pll2_freq, vcxo_freq * 2, - HMC7044_N2_MAX, r2_max, - &n2[0], &r2[0]); - - if (pll2_freq != vcxo_freq * n2[0] / r2[0]) { - rational_best_approximation(pll2_freq, vcxo_freq, - HMC7044_N2_MAX, r2_max, - &n2[1], &r2[1]); - - if (abs((int)pll2_freq - (int)(vcxo_freq * 2 * n2[0] / r2[0])) > - abs((int)pll2_freq - (int)(vcxo_freq * n2[1] / r2[1]))) { - n2[0] = n2[1]; - r2[0] = r2[1]; - pll2_freq_doubler_en = false; - } - } - - while ((n2[0] < HMC7044_N2_MIN) && (r2[0] <= HMC7044_R2_MAX / 2)) { - n2[0] *= 2; - r2[0] *= 2; - } - if (n2[0] < HMC7044_N2_MIN) - return -EINVAL; - } + ret = hmc7044_pll2_recalc_rate(&pll2, hmc->vcxo_freq, hmc->pll2_freq, + hmc->sync_through_pll2_force_r2_eq_1); + if (ret == 0) + return -EINVAL; /* Resets all registers to default values */ ret = hmc7044_toggle_bit(indio_dev, HMC7044_REG_SOFT_RESET, @@ -1242,25 +1279,25 @@ static int hmc7044_setup(struct iio_dev *indio_dev) /* Program the dividers */ ret = hmc7044_write(indio_dev, HMC7044_REG_PLL2_R_LSB, - HMC7044_R2_LSB(r2[0])); + HMC7044_R2_LSB(pll2.r2)); if (ret) return ret; ret = hmc7044_write(indio_dev, HMC7044_REG_PLL2_R_MSB, - HMC7044_R2_MSB(r2[0])); + HMC7044_R2_MSB(pll2.r2)); if (ret) return ret; ret = hmc7044_write(indio_dev, HMC7044_REG_PLL2_N_LSB, - HMC7044_N2_LSB(n2[0])); + HMC7044_N2_LSB(pll2.n2)); if (ret) return ret; ret = hmc7044_write(indio_dev, HMC7044_REG_PLL2_N_MSB, - HMC7044_N2_MSB(n2[0])); + HMC7044_N2_MSB(pll2.n2)); if (ret) return ret; /* Program the reference doubler */ ret = hmc7044_write(indio_dev, HMC7044_REG_PLL2_FREQ_DOUBLER, - pll2_freq_doubler_en ? 0 : HMC7044_PLL2_FREQ_DOUBLER_DIS); + pll2.pll2_freq_doubler_en ? 0 : HMC7044_PLL2_FREQ_DOUBLER_DIS); if (ret) return ret; /* Program PLL1 */ From 14dcf78ca87457c6ca4db7abbab9346442eb52be Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Mon, 4 May 2026 14:22:20 +0000 Subject: [PATCH 03/11] dt-bindings: iio: frequency: hmc7044: add adi,set-rate-parent Add new set-rate-parent property. Signed-off-by: Tomas Melin --- Documentation/devicetree/bindings/iio/frequency/hmc7044.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/frequency/hmc7044.txt b/Documentation/devicetree/bindings/iio/frequency/hmc7044.txt index f0b1f98d70bbac..0f3e6025e0aeef 100644 --- a/Documentation/devicetree/bindings/iio/frequency/hmc7044.txt +++ b/Documentation/devicetree/bindings/iio/frequency/hmc7044.txt @@ -143,6 +143,9 @@ Adding channels: 0 - Internal resistor disable. 1 - Internal 100 Ω resistor enable per output pin. 3 - Internal 50 Ω resistor enable per output pin. + - adi,set-rate-parent: Propagate clock rate change to parent. If set, + this will, if needed, change rate of parent (PLL2) to fulfill + the frequency rate request. Example: From 234a7905c95fe19659fcd75ab2724d42146b61bf Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Mon, 4 May 2026 14:23:24 +0000 Subject: [PATCH 04/11] iio: frequency: hmc7044: support set rate parent Add a new property to make channel rate setting possible to propagate to parent. The escalation happens when a frequency that is not directly achievable is requested. Since not all requested frequencies are necessarily achievable by all channels at once, this feature allows to describe which channels should be prioritized. Channels that lack this property will work as before. That is, only operate within the range of divider settings available, and with constant parent frequency rate. Signed-off-by: Tomas Melin --- drivers/iio/frequency/hmc7044.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/iio/frequency/hmc7044.c b/drivers/iio/frequency/hmc7044.c index a8bc62b06b38e7..fbf48433ca14d6 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -300,6 +300,7 @@ struct hmc7044_chan_spec { unsigned int fine_delay; unsigned int out_mux_mode; const char *extended_name; + bool set_rate_parent; }; struct hmc7044 { @@ -959,10 +960,15 @@ static int hmc7044_clk_register(struct iio_dev *indio_dev, struct hmc7044 *hmc = iio_priv(indio_dev); struct clk_init_data init; struct clk *clk; + unsigned long flags; + + flags = CLK_GET_RATE_NOCACHE; + if (hmc->channels[address].set_rate_parent) + flags |= CLK_SET_RATE_PARENT; init.name = hmc->clk_out_names[num]; init.ops = &hmc7044_clk_ops; - init.flags = 0; + init.flags = flags; init.parent_names = (parent_name ? &parent_name : NULL); init.num_parents = (parent_name ? 1 : 0); @@ -1977,7 +1983,8 @@ static int hmc7044_parse_dt(struct device *dev, &hmc->channels[cnt].out_mux_mode); hmc->channels[cnt].is_sysref = of_property_read_bool(chan_np,"adi,jesd204-sysref-chan"); - + hmc->channels[cnt].set_rate_parent = + of_property_read_bool(chan_np, "adi,set-rate-parent"); cnt++; } From a85cc2f4598ad5c75c99ff11a6a75348090d5433 Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Wed, 8 Apr 2026 12:41:41 +0000 Subject: [PATCH 05/11] iio: frequency: hmc7044: drop determine rate implementation Implementation for both determine_rate and round_rate operations are typically not required. In this case supporting both is not needed and also would require extending current implementations to both support parent rate setting. To avoid any conflicts with this, drop determine rate in favor of the round rate implementation. Signed-off-by: Tomas Melin --- drivers/iio/frequency/hmc7044.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/drivers/iio/frequency/hmc7044.c b/drivers/iio/frequency/hmc7044.c index fbf48433ca14d6..fe8b2a8f184a5f 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -906,21 +906,6 @@ static long hmc7044_clk_round_rate(struct clk_hw *hw, return DIV_ROUND_CLOSEST(hmc->pll2_freq, div); } -static int hmc7044_clk_determine_rate(struct clk_hw *hw, - struct clk_rate_request *req) -{ - struct hmc7044_output *out = to_output(hw); - struct iio_dev *indio_dev = out->indio_dev; - struct hmc7044 *hmc = iio_priv(indio_dev); - unsigned int div; - - div = hmc7044_calc_out_div(hmc->pll2_freq, req->rate); - - req->rate = DIV_ROUND_CLOSEST(hmc->pll2_freq, div); - - return 0; -} - static int hmc7044_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) @@ -948,7 +933,6 @@ static int hmc7044_clk_set_rate(struct clk_hw *hw, static const struct clk_ops hmc7044_clk_ops = { .recalc_rate = hmc7044_clk_recalc_rate, .round_rate = hmc7044_clk_round_rate, - .determine_rate = hmc7044_clk_determine_rate, .set_rate = hmc7044_clk_set_rate, }; From d5bf979b5d54ddcf190b8e4e60de1d522dee61df Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Tue, 21 Apr 2026 13:39:56 +0000 Subject: [PATCH 06/11] iio: frequency: hmc7044: split clk register to separate function Split functionality to register clocks separately in preparation to support pll2 clock operations dynamically. Signed-off-by: Tomas Melin --- drivers/iio/frequency/hmc7044.c | 130 ++++++++++++++++---------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/drivers/iio/frequency/hmc7044.c b/drivers/iio/frequency/hmc7044.c index fe8b2a8f184a5f..a55073153ff92e 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -1094,6 +1094,64 @@ static void hcm7044_clk_del_provider(void *dev) of_clk_del_provider(spi->dev.of_node); } +static int hmc704x_register_clocks(struct iio_dev *indio_dev) +{ + struct hmc7044 *hmc = iio_priv(indio_dev); + struct hmc7044_chan_spec *chan; + int i, ret; + unsigned int parent_clkin; + const char *parent_name; + + if (hmc->device_id == HMC7044) { + /* Get active clkin */ + if (!hmc->clkin1_vcoin_en) { + u32 pll1_stat; + + ret = hmc7044_read(indio_dev, HMC7044_REG_PLL1_STATUS, &pll1_stat); + if (ret < 0) + return ret; + + parent_clkin = HMC7044_PLL1_ACTIVE_CLKIN(pll1_stat); + } else { + parent_clkin = 1; /* CLKIN1 */ + } + + ret = hmc7044_pll2_register(indio_dev, + __clk_get_name(hmc->clk_input[parent_clkin])); + if (ret) + return ret; + + parent_name = __clk_get_name(hmc->pll2_clk); + } else { + parent_clkin = 0; + parent_name = __clk_get_name(hmc->clk_input[parent_clkin]); + hmc->pll2_output.address = UINT_MAX; /* Not used */ + } + + for (i = 0; i < hmc->num_channels; i++) { + chan = &hmc->channels[i]; + + if (chan->num >= HMC7044_NUM_CHAN || chan->disable) + continue; + + ret = hmc7044_clk_register(indio_dev, chan->num, i, parent_name); + if (ret) + return ret; + } + + hmc->clk_data.clks = hmc->clks; + hmc->clk_data.clk_num = HMC7044_NUM_CHAN; + + ret = of_clk_add_provider(hmc->spi->dev.of_node, + of_clk_src_onecell_get, + &hmc->clk_data); + if (ret) + return ret; + + return devm_add_action_or_reset(&hmc->spi->dev, + hcm7044_clk_del_provider, hmc->spi); +} + static int hmc7044_setup(struct iio_dev *indio_dev) { struct hmc7044 *hmc = iio_priv(indio_dev); @@ -1109,8 +1167,7 @@ static int hmc7044_setup(struct iio_dev *indio_dev) unsigned long n, r; unsigned long pfd1_freq; unsigned long vco_limit; - unsigned int i, c, ref_en = 0; - u32 pll1_stat; + unsigned int i, ref_en = 0; int ret; vcxo_freq = hmc->vcxo_freq / 1000; @@ -1467,32 +1524,6 @@ static int hmc7044_setup(struct iio_dev *indio_dev) if (ret) 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); - - ret = hmc7044_pll2_register(indio_dev, - __clk_get_name(hmc->clk_input[c])); - if (ret) - return ret; - - for (i = 0; i < hmc->num_channels; i++) { - chan = &hmc->channels[i]; - - if (chan->num >= HMC7044_NUM_CHAN || chan->disable) - continue; - - ret = hmc7044_clk_register(indio_dev, chan->num, i, - __clk_get_name(hmc->pll2_clk)); - if (ret) - return ret; - } - - hmc->clk_data.clks = hmc->clks; - hmc->clk_data.clk_num = HMC7044_NUM_CHAN; - if (hmc->oscout_path_en) { ret = hmc7044_write(indio_dev, HMC7044_REG_OSCOUT_PATH, HMC7044_OSCOUT_DIVIDER(hmc->oscout_divider_ratio) | @@ -1519,17 +1550,7 @@ static int hmc7044_setup(struct iio_dev *indio_dev) return ret; } - ret = hmc7044_info(indio_dev); - if (ret) - return ret; - - ret = of_clk_add_provider(hmc->spi->dev.of_node, - of_clk_src_onecell_get, - &hmc->clk_data); - if (ret) - return ret; - - return devm_add_action_or_reset(&hmc->spi->dev, hcm7044_clk_del_provider, hmc->spi); + return hmc7044_info(indio_dev); } static int hmc7043_setup(struct iio_dev *indio_dev) @@ -1699,32 +1720,7 @@ static int hmc7043_setup(struct iio_dev *indio_dev) if (ret) return ret; - for (i = 0; i < hmc->num_channels; i++) { - chan = &hmc->channels[i]; - - if (chan->num >= HMC7044_NUM_CHAN || chan->disable) - continue; - - ret = hmc7044_clk_register(indio_dev, chan->num, i, - __clk_get_name(hmc->clk_input[0])); - if (ret) - return ret; - } - - hmc->clk_data.clks = hmc->clks; - hmc->clk_data.clk_num = HMC7044_NUM_CHAN; - - ret = hmc7044_info(indio_dev); - if (ret) - return ret; - - ret = of_clk_add_provider(hmc->spi->dev.of_node, - of_clk_src_onecell_get, - &hmc->clk_data); - if (ret) - return ret; - - return devm_add_action_or_reset(&hmc->spi->dev, hcm7044_clk_del_provider, hmc->spi); + return hmc7044_info(indio_dev); } static int hmc7044_parse_dt(struct device *dev, @@ -2530,6 +2526,10 @@ static int hmc7044_probe(struct spi_device *spi) if (ret) return ret; + ret = hmc704x_register_clocks(indio_dev); + if (ret) + return ret; + ret = devm_iio_device_register(&spi->dev, indio_dev); if (ret) return ret; From 5455436d9f02bcaf9736a2512e6588d5b8cc6831 Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Wed, 8 Apr 2026 12:44:03 +0000 Subject: [PATCH 07/11] iio: frequency: hmc7044: support round rate with parent rate Round rate operation will be called to determine whether a requested frequency can be fulfilled. Here we add support to also calculate a suitable new parent rate for the case where parent rate propagation has been set for the specific clock being changed. Signed-off-by: Tomas Melin --- drivers/iio/frequency/hmc7044.c | 62 +++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/drivers/iio/frequency/hmc7044.c b/drivers/iio/frequency/hmc7044.c index a55073153ff92e..cad913039d3f3e 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -226,6 +226,8 @@ #define HMC7044_LOW_VCO_MAX_KHZ 2880000 #define HMC7044_HIGH_VCO_MIN_KHZ 2650000 #define HMC7044_HIGH_VCO_MAX_KHZ 3200000 +#define HMC7044_RECOMM_VCO_MIN_KHZ 2400000 +#define HMC7044_RECOMM_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 @@ -899,11 +901,67 @@ static long hmc7044_clk_round_rate(struct clk_hw *hw, struct hmc7044_output *out = to_output(hw); struct iio_dev *indio_dev = out->indio_dev; struct hmc7044 *hmc = iio_priv(indio_dev); - unsigned int div; + unsigned long min_dev = ULONG_MAX; + unsigned long oldrate, newrate; + unsigned int div, div_avail; + unsigned long vcxo_freq; + unsigned long rate_best = 0; + unsigned long vco_best = 0; + + mutex_lock(&hmc->lock); + vcxo_freq = hmc->vcxo_freq; div = hmc7044_calc_out_div(hmc->pll2_freq, rate); + oldrate = DIV_ROUND_CLOSEST(hmc->pll2_freq, div); + mutex_unlock(&hmc->lock); + + if (out->address == hmc->pll2_output.address) + return hmc7044_pll2_recalc_rate(NULL, vcxo_freq, rate, + hmc->sync_through_pll2_force_r2_eq_1); + + if (oldrate == rate) + return rate; + + /* don't propagate rate change to parent*/ + if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) + return oldrate; + + /* find the best parent rate */ + for (div = HMC7044_OUT_DIV_MIN; div <= HMC7044_OUT_DIV_MAX; div++) { + unsigned long vco_freq; + unsigned long deviation; + + vco_freq = rate * div; + if (vco_freq < (HMC7044_RECOMM_VCO_MIN_KHZ * 1000UL)) + continue; + if (vco_freq > (HMC7044_RECOMM_VCO_MAX_KHZ * 1000UL)) + break; + + div_avail = hmc7044_calc_out_div(vco_freq, rate); + if (div != div_avail) + continue; + + /* get achievable freq. */ + vco_freq = hmc7044_pll2_recalc_rate(NULL, vcxo_freq, vco_freq, + hmc->sync_through_pll2_force_r2_eq_1); + div = hmc7044_calc_out_div(vco_freq, rate); + newrate = DIV_ROUND_CLOSEST(vco_freq, div); + if (newrate == rate) { + *parent_rate = vco_freq; + return newrate; + } + + deviation = abs(rate - newrate); + if (deviation < min_dev) { + min_dev = deviation; + vco_best = vco_freq; + rate_best = newrate; + } + } + + *parent_rate = vco_best; - return DIV_ROUND_CLOSEST(hmc->pll2_freq, div); + return rate_best; } static int hmc7044_clk_set_rate(struct clk_hw *hw, From 141192296354869e8437dfa771c12883c3d85e10 Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Wed, 8 Apr 2026 12:44:49 +0000 Subject: [PATCH 08/11] iio: frequency: hmc7044: use clk framework for rate changes Relay writes to common clock framework when channel frequency settings are altered via sysfs interface. This allows to escalate frequency changes to parent clocks and do other sanity checks to the frequency being requested, in a common fashion. Signed-off-by: Tomas Melin --- drivers/iio/frequency/hmc7044.c | 50 +++++++++++++++------------------ 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/drivers/iio/frequency/hmc7044.c b/drivers/iio/frequency/hmc7044.c index cad913039d3f3e..869755513f98ca 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -515,6 +515,7 @@ static int hmc7044_write_raw(struct iio_dev *indio_dev, struct hmc7044 *hmc = iio_priv(indio_dev); struct hmc7044_chan_spec *ch; unsigned int code, tmp; + long round_rate; if (chan->address >= hmc->num_channels) return -EINVAL; @@ -523,14 +524,10 @@ static int hmc7044_write_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_FREQUENCY: - ch->divider = hmc7044_calc_out_div(hmc->pll2_freq, val); - mutex_lock(&hmc->lock); - hmc7044_write(indio_dev, HMC7044_REG_CH_OUT_CRTL_1(ch->num), - HMC7044_DIV_LSB(ch->divider)); - hmc7044_write(indio_dev, HMC7044_REG_CH_OUT_CRTL_2(ch->num), - HMC7044_DIV_MSB(ch->divider)); - mutex_unlock(&hmc->lock); - break; + round_rate = clk_round_rate(hmc->clks[ch->num], val); + if (round_rate < 0) + return -EINVAL; + return clk_set_rate(hmc->clks[ch->num], val); case IIO_CHAN_INFO_PHASE: mutex_lock(&hmc->lock); code = val * 1000000 + val2 % 1000000; @@ -805,24 +802,6 @@ static long hmc7044_get_clk_attr(struct clk_hw *hw, return ret; } -static long hmc7044_set_clk_attr(struct clk_hw *hw, - long mask, - unsigned long val) -{ - struct iio_dev *indio_dev = to_output(hw)->indio_dev; - struct hmc7044 *hmc = iio_priv(indio_dev); - struct iio_chan_spec *chan; - unsigned int address; - - address = to_output(hw)->address; - if (address >= hmc->num_channels) - return -EINVAL; - - chan = &hmc->iio_channels[address]; - - return hmc7044_write_raw(indio_dev, chan, val, 0, mask); -} - static unsigned long hmc7044_pll2_recalc_rate(struct hmc7044_pll2_config *pll2, unsigned long vcxo_freq, unsigned long pll2_freq, @@ -971,6 +950,7 @@ static int hmc7044_clk_set_rate(struct clk_hw *hw, struct hmc7044_output *out = to_output(hw); struct iio_dev *indio_dev = out->indio_dev; struct hmc7044 *hmc = iio_priv(indio_dev); + struct hmc7044_chan_spec *ch; unsigned int address; int ret; @@ -985,7 +965,23 @@ static int hmc7044_clk_set_rate(struct clk_hw *hw, return ret; } - return hmc7044_set_clk_attr(hw, IIO_CHAN_INFO_FREQUENCY, rate); + if (address >= hmc->num_channels) + return -EINVAL; + + ch = &hmc->channels[address]; + + mutex_lock(&hmc->lock); + + ch->divider = hmc7044_calc_out_div(hmc->pll2_freq, rate); + ret = hmc7044_write(indio_dev, HMC7044_REG_CH_OUT_CRTL_1(ch->num), + HMC7044_DIV_LSB(ch->divider)); + if (ret) + goto out; + ret = hmc7044_write(indio_dev, HMC7044_REG_CH_OUT_CRTL_2(ch->num), + HMC7044_DIV_MSB(ch->divider)); +out: + mutex_unlock(&hmc->lock); + return ret; } static const struct clk_ops hmc7044_clk_ops = { From 2df009d14d30970b99df1f50126798e5bff62ac1 Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Wed, 8 Apr 2026 12:47:28 +0000 Subject: [PATCH 09/11] iio: frequency: hmc7044: support zero output frequency When requesting zero as output frequency for a specific channel, set the channel register to disabled. Also add the support codes for avoiding division by zero. Setting a frequency different than zero re-enables the channels with other settings restored. Signed-off-by: Tomas Melin --- drivers/iio/frequency/hmc7044.c | 35 ++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/drivers/iio/frequency/hmc7044.c b/drivers/iio/frequency/hmc7044.c index 869755513f98ca..555836e3243145 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -290,6 +290,7 @@ struct hmc7044_pll2_config { struct hmc7044_chan_spec { unsigned int num; bool disable; + bool mute; bool high_performance_mode_dis; bool start_up_mode_dynamic_enable; bool dynamic_driver_enable; @@ -491,7 +492,10 @@ static int hmc7044_read_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_FREQUENCY: - *val = hmc->pll2_freq / ch->divider; + if (ch->mute) + *val = 0; + else + *val = hmc->pll2_freq / ch->divider; return IIO_VAL_INT; case IIO_CHAN_INFO_PHASE: hmc7044_read(indio_dev, HMC7044_REG_CH_OUT_CRTL_4(ch->num), @@ -887,6 +891,8 @@ static long hmc7044_clk_round_rate(struct clk_hw *hw, unsigned long rate_best = 0; unsigned long vco_best = 0; + if (!rate) + return 0; mutex_lock(&hmc->lock); vcxo_freq = hmc->vcxo_freq; @@ -952,7 +958,8 @@ static int hmc7044_clk_set_rate(struct clk_hw *hw, struct hmc7044 *hmc = iio_priv(indio_dev); struct hmc7044_chan_spec *ch; unsigned int address; - int ret; + unsigned int val; + int ret = 0; address = to_output(hw)->address; @@ -971,6 +978,23 @@ static int hmc7044_clk_set_rate(struct clk_hw *hw, ch = &hmc->channels[address]; mutex_lock(&hmc->lock); + ret = hmc7044_read(indio_dev, HMC7044_REG_CH_OUT_CRTL_0(ch->num), + &val); + if (ret) + goto out; + + val &= ~HMC7044_CH_EN; + ret = hmc7044_write(indio_dev, HMC7044_REG_CH_OUT_CRTL_0(ch->num), + val | (rate ? HMC7044_CH_EN : 0)); + if (ret) + goto out; + + ch->mute = !rate; + + if (!rate) { + mutex_unlock(&hmc->lock); + return 0; + } ch->divider = hmc7044_calc_out_div(hmc->pll2_freq, rate); ret = hmc7044_write(indio_dev, HMC7044_REG_CH_OUT_CRTL_1(ch->num), @@ -1546,7 +1570,7 @@ static int hmc7044_setup(struct iio_dev *indio_dev) HMC7044_START_UP_MODE_DYN_EN : 0) | BIT(4) | (chan->high_performance_mode_dis ? 0 : HMC7044_HI_PERF_MODE) | HMC7044_SYNC_EN | - HMC7044_CH_EN); + (chan->mute ? 0 : HMC7044_CH_EN)); if (ret) return ret; hmc->iio_channels[i].type = IIO_ALTVOLTAGE; @@ -1741,7 +1765,7 @@ static int hmc7043_setup(struct iio_dev *indio_dev) HMC7044_START_UP_MODE_DYN_EN : 0) | BIT(4) | (chan->high_performance_mode_dis ? 0 : HMC7044_HI_PERF_MODE) | HMC7044_SYNC_EN | - HMC7044_CH_EN); + (chan->mute ? 0 : HMC7044_CH_EN)); if (ret) return ret; @@ -2138,7 +2162,8 @@ static int hmc7044_continuous_chan_sync_enable(struct iio_dev *indio_dev, bool e (chan->high_performance_mode_dis ? 0 : HMC7044_HI_PERF_MODE) | ((enable || chan->start_up_mode_dynamic_enable) ? - HMC7044_SYNC_EN : 0) | HMC7044_CH_EN); + HMC7044_SYNC_EN : 0) | + (chan->mute ? 0 : HMC7044_CH_EN)); if (ret < 0) return ret; } From 0be41d17deaa5173c8de273e157e8f3088227d29 Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Mon, 18 May 2026 12:33:32 +0000 Subject: [PATCH 10/11] dt-bindings: iio: frequency: hmc7044: add adi,max-deviation-ppm Add the adi,max-deviation-ppm property. When set on a channel, any PLL2 rate change requested by a CLK_SET_RATE_PARENT channel is rejected if it would shift this channel's output frequency by more than the specified number of parts per million from its current rate. Signed-off-by: Tomas Melin --- Documentation/devicetree/bindings/iio/frequency/hmc7044.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/frequency/hmc7044.txt b/Documentation/devicetree/bindings/iio/frequency/hmc7044.txt index 0f3e6025e0aeef..c10e2e564e96af 100644 --- a/Documentation/devicetree/bindings/iio/frequency/hmc7044.txt +++ b/Documentation/devicetree/bindings/iio/frequency/hmc7044.txt @@ -146,6 +146,10 @@ Adding channels: - adi,set-rate-parent: Propagate clock rate change to parent. If set, this will, if needed, change rate of parent (PLL2) to fulfill the frequency rate request. + - adi,max-deviation-ppm: Maximum allowed deviation in parts-per-million + when changing channel output frequencies. A rate change request + that would cause deviation larger than allowed value for any + output channel with this property, is blocked. Example: From 0b47a5155992e73f69b74cefa9b5c983d127def7 Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Mon, 18 May 2026 12:33:36 +0000 Subject: [PATCH 11/11] iio: frequency: hmc7044: support channel frequency deviation setting Account for maximum allowed deviations when setting frequency. Logic works accordingly: If set-rate-parent is enabled for a specific channel, a new parent pll2 rate will be looked for when the frequency does not match an allowed divider. If the parent clock rate changes, this implies that the accuracy of other channels might change. Therefore, check all other channel's allowed deviations. If a potential parent rate that fulfills the requested rate for the channel does not meet deviation requirements of the other channels, skip it and go to next candidate. This check applies to all channels that are not currently disabled. If any of the channel deviation requirements fail, the requested frequency will be rejected. Signed-off-by: Tomas Melin --- drivers/iio/frequency/hmc7044.c | 76 ++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 11 deletions(-) diff --git a/drivers/iio/frequency/hmc7044.c b/drivers/iio/frequency/hmc7044.c index 555836e3243145..127e7abaea9039 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -304,6 +304,7 @@ struct hmc7044_chan_spec { unsigned int out_mux_mode; const char *extended_name; bool set_rate_parent; + unsigned int max_deviation_ppm; }; struct hmc7044 { @@ -877,6 +878,42 @@ static unsigned long hmc7044_clk_recalc_rate(struct clk_hw *hw, return hmc7044_get_clk_attr(hw, IIO_CHAN_INFO_FREQUENCY); } +static int hmc7044_verify_deviation(struct clk_hw *hw, + unsigned long pll2_candidate) +{ + struct hmc7044_output *out = to_output(hw); + struct iio_dev *indio_dev = out->indio_dev; + struct hmc7044 *hmc = iio_priv(indio_dev); + int ret = 0; + int i; + + mutex_lock(&hmc->lock); + for (i = 0; i < hmc->num_channels; i++) { + struct hmc7044_chan_spec *ch = &hmc->channels[i]; + unsigned int div; + unsigned long current_rate; + unsigned long candidate_rate; + unsigned int deviation_ppm; + + if (out->address == i || ch->mute || ch->disable) + continue; + + current_rate = hmc->pll2_freq / ch->divider; + div = hmc7044_calc_out_div(pll2_candidate, current_rate); + candidate_rate = DIV_ROUND_CLOSEST(pll2_candidate, div); + deviation_ppm = abs(current_rate - candidate_rate) * 1000000 / current_rate; + if (deviation_ppm > ch->max_deviation_ppm) { + dev_dbg(&hmc->spi->dev, + "Rejecting PLL2 freq. change to %lu Hz due to output %u deviation %u ppm\n", + pll2_candidate, ch->num, deviation_ppm); + ret = -EINVAL; + break; + } + } + mutex_unlock(&hmc->lock); + return ret; +} + static long hmc7044_clk_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) @@ -884,12 +921,13 @@ static long hmc7044_clk_round_rate(struct clk_hw *hw, struct hmc7044_output *out = to_output(hw); struct iio_dev *indio_dev = out->indio_dev; struct hmc7044 *hmc = iio_priv(indio_dev); - unsigned long min_dev = ULONG_MAX; + unsigned long min_dev; + unsigned int div, div_avail, div_out; unsigned long oldrate, newrate; - unsigned int div, div_avail; unsigned long vcxo_freq; unsigned long rate_best = 0; unsigned long vco_best = 0; + bool found = false; if (!rate) return 0; @@ -911,10 +949,13 @@ static long hmc7044_clk_round_rate(struct clk_hw *hw, if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) return oldrate; - /* find the best parent rate */ + min_dev = hmc->channels[out->address].max_deviation_ppm; + + /* find the best parent rate or fail */ for (div = HMC7044_OUT_DIV_MIN; div <= HMC7044_OUT_DIV_MAX; div++) { unsigned long vco_freq; - unsigned long deviation; + unsigned long deviation_ppm; + int ret; vco_freq = rate * div; if (vco_freq < (HMC7044_RECOMM_VCO_MIN_KHZ * 1000UL)) @@ -929,24 +970,34 @@ static long hmc7044_clk_round_rate(struct clk_hw *hw, /* get achievable freq. */ vco_freq = hmc7044_pll2_recalc_rate(NULL, vcxo_freq, vco_freq, hmc->sync_through_pll2_force_r2_eq_1); - div = hmc7044_calc_out_div(vco_freq, rate); - newrate = DIV_ROUND_CLOSEST(vco_freq, div); + div_out = hmc7044_calc_out_div(vco_freq, rate); + newrate = DIV_ROUND_CLOSEST(vco_freq, div_out); + + ret = hmc7044_verify_deviation(hw, vco_freq); + if (ret < 0) + continue; + if (newrate == rate) { *parent_rate = vco_freq; return newrate; } - deviation = abs(rate - newrate); - if (deviation < min_dev) { - min_dev = deviation; + deviation_ppm = abs(rate - newrate) * 1000000 / rate; + + if (deviation_ppm < min_dev) { + min_dev = deviation_ppm; vco_best = vco_freq; rate_best = newrate; + found = true; } } - *parent_rate = vco_best; + if (found) { + *parent_rate = vco_best; + return rate_best; + } - return rate_best; + return -EINVAL; } static int hmc7044_clk_set_rate(struct clk_hw *hw, @@ -2043,6 +2094,9 @@ static int hmc7044_parse_dt(struct device *dev, of_property_read_bool(chan_np,"adi,jesd204-sysref-chan"); hmc->channels[cnt].set_rate_parent = of_property_read_bool(chan_np, "adi,set-rate-parent"); + hmc->channels[cnt].max_deviation_ppm = UINT_MAX; + of_property_read_u32(chan_np, "adi,max-deviation-ppm", + &hmc->channels[cnt].max_deviation_ppm); cnt++; }