Skip to content
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 137 additions & 34 deletions drivers/iio/frequency/hmc7044.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -358,6 +366,8 @@ static const char * const hmc7044_input_clk_names[] = {
[3] = "clkin3",
};

static int hmc7044_setup(struct iio_dev *indio_dev);
Comment thread
nunojsa marked this conversation as resolved.

static int hmc7044_write(struct iio_dev *indio_dev,
unsigned int reg,
unsigned int val)
Expand Down Expand Up @@ -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;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we gain something from DIV_ROUND_CLOSEST()?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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;
}
Comment thread
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);
}

Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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 */
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
}
Expand Down