-
Notifications
You must be signed in to change notification settings - Fork 949
hmc7044: add dynamic PLL2 rate selection via CCF #3283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
b2082d6
6dfaa62
14dcf78
234a790
a85cc2f
d5bf979
5455436
1411922
2df009d
0be41d1
0b47a51
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -335,6 +341,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 +366,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) | ||
|
|
@@ -810,9 +820,74 @@ 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; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will we gain something from DIV_ROUND_CLOSEST()?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thin that wouldn't gain anything here. The frequencies are exact kHz multiples so it would give the same result. |
||
|
|
||
| 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; | ||
| } | ||
|
vai-tomme marked this conversation as resolved.
|
||
|
|
||
| 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) | ||
| { | ||
| 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 +924,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 +979,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); | ||
|
|
@@ -986,7 +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; | ||
| struct hmc7044_pll2_config pll2; | ||
| unsigned long vcxo_freq, pll2_freq; | ||
| unsigned long clkin_freq[4]; | ||
| unsigned long lcm_freq; | ||
|
|
@@ -997,7 +1119,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,32 +1198,9 @@ 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; | ||
| } | ||
| } | ||
|
|
||
| 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(&pll2, hmc->vcxo_freq, hmc->pll2_freq, | ||
| hmc->sync_through_pll2_force_r2_eq_1); | ||
| if (ret == 0) | ||
| return -EINVAL; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel that this patch could have been squashed in the patch adding the pll2 clock. Adding the helper in same patchset as adding the feature looks weird to me |
||
|
|
||
| /* Resets all registers to default values */ | ||
|
|
@@ -1181,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 */ | ||
|
|
@@ -1385,14 +1483,19 @@ 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]; | ||
|
|
||
| if (chan->num >= HMC7044_NUM_CHAN || chan->disable) | ||
| 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; | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.