From 5a0b5185f59dd1094d99664148b074bc41481503 Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Wed, 8 Apr 2026 12:29:46 +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 | 61 ++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/drivers/iio/frequency/hmc7044.c b/drivers/iio/frequency/hmc7044.c index 2da87b7b002db2..792443c7588e97 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -326,6 +326,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; @@ -349,6 +351,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) @@ -804,6 +808,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); } @@ -840,6 +851,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); } @@ -878,6 +906,32 @@ 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 clk_init_data init; + struct clk *clk; + + init.name = "hmc7044_pll2"; + 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(&hmc->spi->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); @@ -1366,6 +1420,11 @@ static int hmc7044_setup(struct iio_dev *indio_dev) c = 1; /* CLKIN1 */ } + 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]; @@ -1373,7 +1432,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 98d9c126a441fd1bbefb9f2b89b2d20125a76088 Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Wed, 8 Apr 2026 12:33:00 +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 | 106 ++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 33 deletions(-) diff --git a/drivers/iio/frequency/hmc7044.c b/drivers/iio/frequency/hmc7044.c index 792443c7588e97..8ed4ee08d8e165 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -271,6 +271,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; @@ -805,6 +811,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(unsigned long vcxo_freq, + unsigned long pll2_freq, + bool force_r2_eq_1, + struct hmc7044_pll2_config *pll2) +{ + 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 || + pll2_freq > HMC7044_HIGH_VCO_MAX) + 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) { @@ -1002,8 +1066,8 @@ static int hmc7044_setup(struct iio_dev *indio_dev) { struct hmc7044 *hmc = iio_priv(indio_dev); struct hmc7044_chan_spec *chan; + struct hmc7044_pll2_config pll2; bool high_vco_en; - bool pll2_freq_doubler_en; unsigned long vcxo_freq, pll2_freq; unsigned long clkin_freq[4]; unsigned long lcm_freq; @@ -1013,7 +1077,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; int ret; @@ -1094,32 +1157,9 @@ static int hmc7044_setup(struct iio_dev *indio_dev) else high_vco_en = false; - /* 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; - } - } - - while ((n2[0] < HMC7044_N2_MIN) && (r2[0] <= HMC7044_R2_MAX / 2)) { - n2[0] *= 2; - r2[0] *= 2; - } - if (n2[0] < HMC7044_N2_MIN) + ret = hmc7044_pll2_recalc_rate(hmc->vcxo_freq, hmc->pll2_freq, + hmc->sync_through_pll2_force_r2_eq_1, &pll2); + if (ret == 0) return -EINVAL; /* Resets all registers to default values */ @@ -1210,25 +1250,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 540d57d4a7ca91aba1d478c34f6898bacdf3a84a 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 04f4a734a49640..4509b134244398 100644 --- a/Documentation/devicetree/bindings/iio/frequency/hmc7044.txt +++ b/Documentation/devicetree/bindings/iio/frequency/hmc7044.txt @@ -142,6 +142,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 9a5b8f297a0ddc48d0f3addbff1b8d37c8398967 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 8ed4ee08d8e165..e92a0c6c0a6cf9 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -292,6 +292,7 @@ struct hmc7044_chan_spec { unsigned int fine_delay; unsigned int out_mux_mode; const char *extended_name; + bool set_rate_parent; }; struct hmc7044 { @@ -950,10 +951,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); @@ -1949,7 +1955,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 389d0ad9e4ab8fde3f501127b64cc0d0cfc20aa8 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 e92a0c6c0a6cf9..2c88516e582f24 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -897,21 +897,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) @@ -939,7 +924,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 e0403643f7eccef9d0017704b90df39f425ed708 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 | 135 +++++++++++++++----------------- 1 file changed, 65 insertions(+), 70 deletions(-) diff --git a/drivers/iio/frequency/hmc7044.c b/drivers/iio/frequency/hmc7044.c index 2c88516e582f24..281a0be5071b5e 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -1052,6 +1052,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); @@ -1067,7 +1125,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; + unsigned int i, ref_en = 0; int ret; vcxo_freq = hmc->vcxo_freq / 1000; @@ -1438,38 +1496,6 @@ 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; - - c = HMC7044_PLL1_ACTIVE_CLKIN(pll1_stat); - } else { - c = 1; /* CLKIN1 */ - } - - 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) | @@ -1496,17 +1522,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) @@ -1676,32 +1692,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, @@ -2506,6 +2497,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 d7d2a4de8e92181d16925a05fccb39d8e0fb6fa0 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 281a0be5071b5e..0a892fbb89cffe 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -221,6 +221,8 @@ #define HMC7044_LOW_VCO_MAX 2880000 #define HMC7044_HIGH_VCO_MIN 2650000 #define HMC7044_HIGH_VCO_MAX 3200000 +#define HMC7044_RECOMM_VCO_MIN 2400000 +#define HMC7044_RECOMM_VCO_MAX 3200000 #define HMC7044_RECOMM_LCM_MIN 30000 #define HMC7044_RECOMM_LCM_MAX 70000 @@ -890,11 +892,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(vcxo_freq, rate, + hmc->sync_through_pll2_force_r2_eq_1, NULL); + + 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 * 1000UL)) + continue; + if (vco_freq > (HMC7044_RECOMM_VCO_MAX * 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(vcxo_freq, vco_freq, + hmc->sync_through_pll2_force_r2_eq_1, NULL); + 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 b9ef87993a5d2c98d7f03236a48c883a27ed0b2f 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 0a892fbb89cffe..9f500773dfa5d5 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -506,6 +506,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; @@ -514,14 +515,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; @@ -796,24 +793,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(unsigned long vcxo_freq, unsigned long pll2_freq, bool force_r2_eq_1, @@ -962,6 +941,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; @@ -976,7 +956,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 68fc04d41a20c99e946c3e56a6ff760beeccf6f2 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 9f500773dfa5d5..026d283abc1e4e 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -282,6 +282,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; @@ -482,7 +483,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), @@ -878,6 +882,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; @@ -943,7 +949,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; @@ -962,6 +969,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), @@ -1518,7 +1542,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; @@ -1713,7 +1737,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; @@ -2108,7 +2132,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 e42adadae70ee960f7b707dbd75474e67311d4fc Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Sat, 6 Apr 2024 04:29:09 +0000 Subject: [PATCH 10/11] Documentation: bindings: 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 4509b134244398..31e970da2b69dc 100644 --- a/Documentation/devicetree/bindings/iio/frequency/hmc7044.txt +++ b/Documentation/devicetree/bindings/iio/frequency/hmc7044.txt @@ -145,6 +145,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 30964aa355b3aed92a3c32121286127d42cd7e44 Mon Sep 17 00:00:00 2001 From: Tomas Melin Date: Wed, 8 Apr 2026 12:48:50 +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 | 78 ++++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 11 deletions(-) diff --git a/drivers/iio/frequency/hmc7044.c b/drivers/iio/frequency/hmc7044.c index 026d283abc1e4e..513bcde54a584d 100644 --- a/drivers/iio/frequency/hmc7044.c +++ b/drivers/iio/frequency/hmc7044.c @@ -296,6 +296,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 { @@ -868,6 +869,43 @@ 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_err(&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) @@ -875,12 +913,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; @@ -902,10 +941,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 * 1000UL)) @@ -920,24 +962,35 @@ static long hmc7044_clk_round_rate(struct clk_hw *hw, /* get achievable freq. */ vco_freq = hmc7044_pll2_recalc_rate(vcxo_freq, vco_freq, hmc->sync_through_pll2_force_r2_eq_1, NULL); - 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, @@ -2010,6 +2063,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++; }