Re: [PATCH v3 1/3] iio: frequency: adf4360: Add support for ADF4360 PLLs

From: Andy Shevchenko
Date: Mon Feb 03 2020 - 09:11:06 EST


On Mon, Feb 3, 2020 at 1:47 PM Ardelean, Alexandru
<alexandru.Ardelean@xxxxxxxxxx> wrote:
> On Sun, 2020-02-02 at 14:45 +0000, Jonathan Cameron wrote:
> > On Tue, 28 Jan 2020 13:13:00 +0200
> > Alexandru Ardelean <alexandru.ardelean@xxxxxxxxxx> wrote:

Just few comments on the code in case either this will go, or to teach
an author about best practices in LK.

> > > +#include <linux/err.h>
> > > +#include <linux/gpio/consumer.h>
> > > +#include <linux/kernel.h>
> > > +#include <linux/module.h>
> > > +#include <linux/of.h>
> > > +#include <linux/regulator/consumer.h>
> > > +#include <linux/spi/spi.h>
> > > +#include <linux/util_macros.h>

...

> > > +enum {
> > > + ADF4360_CTRL,
> > > + ADF4360_RDIV,
> > > + ADF4360_NDIV,

> > > + ADF4360_REG_NUM,

Sounds line no need for comma (is it indeed a terminator line?).

> > > +};

...

> > > +static int adf4360_write_reg(struct adf4360_state *st, unsigned int reg,
> > > + unsigned int val)
> > > +{
> > > + int ret;
> > > +
> > > + val |= reg;

This is dangerous. Shouldn't be some mask applied?

> > > + st->spi_data[0] = (val >> 16) & 0xff;
> > > + st->spi_data[1] = (val >> 8) & 0xff;
> > > + st->spi_data[2] = val & 0xff;

All ' & 0xff' are redundant.

> > > + ret = spi_write(st->spi, st->spi_data, ARRAY_SIZE(st->spi_data));
> > > + if (ret == 0)
> > > + st->regs[reg] = val;
> > > +
> > > + return ret;

Please, use pattern:
if (ret)
return ret;

...
return 0;

> > > +}

...

> > > +static unsigned int adf4360_calc_prescaler(unsigned int pfd_freq,
> > > + unsigned int n,
> > > + unsigned int *out_p,
> > > + unsigned int *out_a,
> > > + unsigned int *out_b)
> > > +{
> > > + unsigned int rate = pfd_freq * n;
> > > + unsigned int p, a, b;
> > > +
> > > + /* Make sure divider counter input frequency is low enough */
> > > + p = 8;
> > > + while (p < 32 && rate / p > ADF4360_MAX_COUNTER_RATE)
> > > + p *= 2;
> > > +
> > > + /*
> > > + * The range of dividers that can be produced using the dual-modulus
> > > + * pre-scaler is not continuous for values of n < p*(p-1). If we end up
> > > + * with a non supported divider value, pick the next closest one.
> > > + */
> > > + a = n % p;
> > > + b = n / p;

You may avoid divisions if you use shifts.
Currently it's a bit hard to compiler to prove that p is power of 2.

> > > + if (b < 3) {
> > > + b = 3;
> > > + a = 0;
> > > + } else if (a > b) {

Does this guarantee p >= a?

> > > + if (a - b < p - a) {
> > > + a = b;
> > > + } else {
> > > + a = 0;
> > > + b++;
> > > + }
> > > + }

I guess above conditional tree can be a bit improved, although I don't
see right now in what way.

> > > + return p * b + a;
> > > +}

...

> > > + /* ADF4360-0 to AD4370-7 have an optional by two divider */
> > > + if (st->part_id <= ID_ADF4360_7) {
> > > + if (rate < st->vco_min / 2)
> > > + return st->vco_min / 2;
> > > + if (rate < st->vco_min && rate > st->vco_max / 2) {
> > > + if (st->vco_min - rate < rate - st->vco_max / 2)
> > > + return st->vco_min;

> > > + else

Redundant.

> > > + return st->vco_max / 2;
> > > + }

> > > + } else {
> > > + if (rate < st->vco_min)

} else if (...) {

> > > + return st->vco_min;
> > > + }

...

> > > + case ADF4360_POWER_DOWN_REGULATOR:
> > > + if (!st->vdd_reg)
> > > + return -ENODEV;
> > > +
> > > + if (st->chip_en_gpio)
> > > + ret = __adf4360_power_down(st, ADF4360_POWER_DOWN_CE);
> > > + else
> > > + ret = __adf4360_power_down(st,
> > > + ADF4360_POWER_DOWN_SOFT_ASYNC);

Missed error check.

> > > +
> > > + ret = regulator_disable(st->vdd_reg);
> > > + if (ret)
> > > + dev_err(dev, "Supply disable error: %d\n", ret);
> > > + break;
> > > + }

...

> > > + if (ret == 0)
> > > + st->power_down_mode = mode;
> > > +
> > > + return 0;

Looks like 'return ret;' but see one comment at the beginning of the letter.

...

> > > +static ssize_t adf4360_read(struct iio_dev *indio_dev,
> > > + uintptr_t private,
> > > + const struct iio_chan_spec *chan,
> > > + char *buf)
> > > +{
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > + unsigned long val;

> > > + int ret = 0;

Redundant variable.

> > > +
> > > + switch ((u32)private) {
> > > + case ADF4360_FREQ_REFIN:
> > > + val = st->clkin_freq;
> > > + break;
> > > + case ADF4360_MTLD:
> > > + val = st->mtld;
> > > + break;
> > > + case ADF4360_FREQ_PFD:
> > > + val = st->pfd_freq;
> > > + break;
> > > + default:
> > > + ret = -EINVAL;
> > > + val = 0;
> > > + }
> > > +
> > > + return ret < 0 ? ret : sprintf(buf, "%lu\n", val);
> > > +}

...

> > > +static ssize_t adf4360_write(struct iio_dev *indio_dev,
> > > + uintptr_t private,
> > > + const struct iio_chan_spec *chan,
> > > + const char *buf, size_t len)
> > > +{
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > + unsigned long readin, tmp;
> > > + bool mtld;
> > > + int ret = 0;
> > > +
> > > + mutex_lock(&st->lock);
> > > + switch ((u32)private) {

Strange casting. Why?

> > > + if ((readin > ADF4360_MAX_REFIN_RATE) || (readin == 0)) {

Too many parentheses.

> > > + ret = -EINVAL;
> > > + break;
> > > + }
> > > +
> > > + if (st->clkin) {
> > > + tmp = clk_round_rate(st->clkin, readin);
> > > + if (tmp != readin) {
> > > + ret = -EINVAL;
> > > + break;
> > > + }
> > > +
> > > + ret = clk_set_rate(st->clkin, tmp);
> > > + if (ret)
> > > + break;
> > A bit odd to directly provide an interface to control and entirely different
> > bit of hardware. If there are specific demands on the input clock as a
> > result
> > of something to do with the outputs, then fair enough. Direct tweaking like
> > this seems like a very odd interface.
> >
> > > + }
> > > +
> > > + st->clkin_freq = readin;
> > > + break;

> > > + case ADF4360_FREQ_PFD:
> > > + ret = kstrtoul(buf, 10, &readin);
> > > + if (ret)
> > > + break;
> > > +


> > > + if ((readin > ADF4360_MAX_PFD_RATE) || (readin == 0)) {

Ditto.

> > > + ret = -EINVAL;
> > > + break;
> > > + }
> > > +
> > > + st->pfd_freq = readin;
> > > + break;
> > > + default:
> > > + ret = -EINVAL;

Nevertheless 'break;' is good to have even here.

> > > + }

> > > +
> > > + if (ret == 0)

Maybe this, or maybe

if (ret)
goto out_unlock;

> > > + ret = adf4360_set_freq(st, st->freq_req);
> > > + mutex_unlock(&st->lock);
> > > +
> > > + return ret ? ret : len;
> > > +}

...

> > > + int ret = 0;

Redundant assignment.

> > > + mutex_lock(&st->lock);
> > > + writeval = st->regs[ADF4360_CTRL] & ~ADF4360_ADDR_MUXOUT_MSK;
> > > + writeval |= ADF4360_MUXOUT(mode & 0x7);
> > > + ret = adf4360_write_reg(st, ADF4360_REG(ADF4360_CTRL), writeval);

> > > + if (ret == 0)
> > > + st->muxout_mode = mode & 0x7;
> > > + mutex_unlock(&st->lock);

Similar comment to the return check style as above.

> > > + return ret;
> > > +}

...

> > > +static const struct iio_chan_spec_ext_info adf4360_ext_info[] = {

> > > + IIO_ENUM("power_level", false, &adf4360_pwr_lvl_modes_available),
> > > + { },

No need to have comma for terminator line.

> > > +};

...

> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > + bool lk_det;
> > > +
> > > + switch (mask) {
> > > + case IIO_CHAN_INFO_FREQUENCY:
> > > + if (adf4360_is_powerdown(st))
> > > + return -EBUSY;
> > > +
> > > + lk_det = (ADF4360_MUXOUT_LOCK_DETECT | ADF4360_MUXOUT_OD_LD) &
> > > + st->muxout_mode;
> > > + if (lk_det && st->muxout_gpio) {
> > > + if (!gpiod_get_value(st->muxout_gpio)) {
> > > + dev_dbg(&st->spi->dev, "PLL un-locked\n");
> > > + return -EBUSY;
> > > + }
> > > + }
> > > +
> > > + *val = st->freq_req;

> > > + return IIO_VAL_INT;
> > > + default:
> > > + return -EINVAL;
> > > + }

> > > +
> > > + return 0;

How this is possible?

...

> > > + default:
> > > + ret = -EINVAL;

break;

...

> > > + struct adf4360_state *st = iio_priv(indio_dev);

> > > + int ret = 0;

Would be better to have this assignment...

> > > +
> > > + if (reg >= ADF4360_REG_NUM)
> > > + return -EFAULT;
> > > +
> > > + mutex_lock(&st->lock);
> > > + if (readval) {
> > > + *readval = st->regs[reg];

...here.

> > > + } else {
> > > + writeval &= 0xFFFFFC;

What this magic does? Make a define using GENMASK().

> > > + ret = adf4360_write_reg(st, reg, writeval);
> > > + }
> > > + mutex_unlock(&st->lock);
> > > +
> > > + return ret;
> > > +}

...

> > > + /* ADF4360 PLLs are write only devices, try to probe using GPIO. */
> > > + for (i = 0; i < 4; i++) {
> > > + if (i & 1)
> > > + val = ADF4360_MUXOUT(ADF4360_MUXOUT_DVDD);
> > > + else
> > > + val = ADF4360_MUXOUT(ADF4360_MUXOUT_GND);
> > > +
> > > + ret = adf4360_write_reg(st, ADF4360_REG(ADF4360_CTRL), val);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = gpiod_get_value(st->muxout_gpio);

> > > + if (ret ^ (i & 1)) {

I guess == or != is better than ^ here.

> > > + dev_err(dev, "Probe failed (muxout)");
> > > + return -ENODEV;
> > > + }
> > > + }
> > > +
> > > + return 0;
> > > +}

...

> > Hmm. This makes me wonder why this is an IIO driver rather than a clk
> > driver? Definitely needs some more information in the patch description
> > or a cover letter.

+1.

...

> > > + ret = device_property_read_string(dev, "clock-output-names",
> > > + &st->clk_out_name);

Your driver is OF dependent, why to bother with device property API?

> > > + if ((ret < 0) && dev->of_node)

Oh, this is bad. Why do you need to check for OF node at all?!

> > > + st->clk_out_name = dev->of_node->name;

...

> > > + /*
> > > + * ADF4360-7 to ADF4360-9 have a VCO that is tuned to a specific
> > > + * range using an external inductor. These properties describe
> > > + * the range selected by the external inductor.
> > > + */
> > > + ret = device_property_read_u32(dev,
> > > + "adi,vco-minimum-frequency-hz",
> > > + &tmp);
> > > + if (ret == 0)
> > > + st->vco_min = max(st->info->vco_min, tmp);
> > > + else
> > > + st->vco_min = st->info->vco_min;

Why to use tmp at all?

&st->vco_min);
if (ret)
st->vco_min = st->info->vco_min;

st->vco_min = max(st->info->vco_min, st->vco_min);

> > > + ret = device_property_read_u32(dev,
> > > + "adi,vco-maximum-frequency-hz",
> > > + &tmp);
> > > + if (ret == 0)
> > > + st->vco_max = min(st->info->vco_max, tmp);
> > > + else
> > > + st->vco_max = st->info->vco_max;

Ditto.

> > > + ret = device_property_read_u32(dev,
> > > + "adi,loop-filter-pfd-frequency-hz",
> > > + &tmp);
> > > + if (ret == 0) {
> > > + st->pfd_freq = tmp;

Ditto!

> > > + } else {
> > > + dev_err(dev, "PFD frequency property missing\n");
> > > + return ret;
> > > + }

> > > +
> > > + ret = device_property_read_u32(dev,
> > > + "adi,loop-filter-charge-pump-current-microamp",
> > > + &tmp);
> > > + if (ret == 0) {
> > > + st->cpi = find_closest(tmp, adf4360_cpi_modes,
> > > + ARRAY_SIZE(adf4360_cpi_modes));

Ditto!

> > > + } else {
> > > + dev_err(dev, "CPI property missing\n");
> > > + return ret;
> > > + }
> > > +
> > > + ret = device_property_read_u32(dev, "adi,power-up-frequency-hz", &tmp);
> > > + if (ret == 0)
> > > + st->freq_req = tmp;
> > > + else
> > > + st->freq_req = st->vco_min;

Ditto.

> > > + ret = device_property_read_u32(dev, "adi,power-out-level-microamp",
> > > + &tmp);
> > > + if (ret == 0)
> > > + st->power_level = find_closest(tmp, adf4360_power_lvl_microamp,
> > > + ARRAY_SIZE(adf4360_power_lvl_microamp));
> > > + else
> > > + st->power_level = ADF4360_PL_5;

Ditto.

...

> > > + if (spi->dev.of_node)
> > > + indio_dev->name = spi->dev.of_node->name;

Use %pOFn instead

> > > + else
> > > + indio_dev->name = spi_get_device_id(spi)->name;

...

> > > +static const struct of_device_id adf4360_of_match[] = {
> > > + { .compatible = "adi,adf4360-0", },
> > > + { .compatible = "adi,adf4360-1", },
> > > + { .compatible = "adi,adf4360-2", },
> > > + { .compatible = "adi,adf4360-3", },
> > > + { .compatible = "adi,adf4360-4", },
> > > + { .compatible = "adi,adf4360-5", },
> > > + { .compatible = "adi,adf4360-6", },
> > > + { .compatible = "adi,adf4360-7", },
> > > + { .compatible = "adi,adf4360-8", },
> > > + { .compatible = "adi,adf4360-9", },

> > > + {},

No comma here.

> > > +};

--
With Best Regards,
Andy Shevchenko