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