RE: [PATCH v2 1/2] iio: frequency: admv1013: add support for ADMV1013

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello Jonathan,

Thanks for the review!

Regarding the interface for the Mixer Offset adjustments: 
ADMV1013_MIXER_OFF_ADJ_P
ADMV1013_MIXER_OFF_ADJ_N

These parameters are related to the LO feedthrough offset calibration.
(LO and sideband suppression)

On this matter, my suggestion would be to add IIO calibration types, something like:
IIO_CHAN_INFO_CALIBFEEDTROUGH_POS
IIO_CHAN_INFO_CALIBFEEDTROUGH_NEG

Looking forward to your feedback.

Regards,
--
Antoniu Miclăuş

> -----Original Message-----
> From: Jonathan Cameron <jic23@xxxxxxxxxx>
> Sent: Wednesday, October 27, 2021 8:43 PM
> To: Miclaus, Antoniu <Antoniu.Miclaus@xxxxxxxxxx>
> Cc: robh+dt@xxxxxxxxxx; linux-iio@xxxxxxxxxxxxxxx;
> devicetree@xxxxxxxxxxxxxxx; linux-kernel@xxxxxxxxxxxxxxx; Sa, Nuno
> <Nuno.Sa@xxxxxxxxxx>
> Subject: Re: [PATCH v2 1/2] iio: frequency: admv1013: add support for
> ADMV1013
> 
> [External]
> 
> On Wed, 27 Oct 2021 12:23:32 +0300
> Antoniu Miclaus <antoniu.miclaus@xxxxxxxxxx> wrote:
> 
> > The ADMV1013 is a wideband, microwave upconverter optimized
> > for point to point microwave radio designs operating in the
> > 24 GHz to 44 GHz radio frequency (RF) range.
> >
> > Datasheet:
> > https://www.analog.com/media/en/technical-documentation/data-
> sheets/ADMV1013.pdf
> >
> > Signed-off-by: Antoniu Miclaus <antoniu.miclaus@xxxxxxxxxx>
> Hi Antoniu.
> 
> A few small things inline, but main issue in here is the use of the
> IIO_VAL_INT_MULTIPLE
> There are very very few uses for that type (1 IIRC) and it isn't to combine
> multiple
> values into a single sysfs attribute as shown here.  As such those offset
> interfaces
> need a rethink.  We may well have to define some new ABI to support them
> but I don't
> see a reason to not maintain the normal sysfs rule of one value per attribute.
> 
> 
> ..
> 
> > diff --git a/drivers/iio/frequency/Makefile
> b/drivers/iio/frequency/Makefile
> > index 518b1e50caef..559922a8196e 100644
> > --- a/drivers/iio/frequency/Makefile
> > +++ b/drivers/iio/frequency/Makefile
> > @@ -7,3 +7,4 @@
> >  obj-$(CONFIG_AD9523) += ad9523.o
> >  obj-$(CONFIG_ADF4350) += adf4350.o
> >  obj-$(CONFIG_ADF4371) += adf4371.o
> > +obj-$(CONFIG_ADMV1013) += admv1013.o
> > diff --git a/drivers/iio/frequency/admv1013.c
> b/drivers/iio/frequency/admv1013.c
> > new file mode 100644
> > index 000000000000..91254605013c
> > --- /dev/null
> > +++ b/drivers/iio/frequency/admv1013.c
> > @@ -0,0 +1,578 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * ADMV1013 driver
> > + *
> > + * Copyright 2021 Analog Devices Inc.
> > + */
> > +
> > +#include <linux/bitfield.h>
> > +#include <linux/bitops.h>
> > +#include <linux/bits.h>
> > +#include <linux/clk.h>
> > +#include <linux/clkdev.h>
> > +#include <linux/clk-provider.h>
> > +#include <linux/device.h>
> > +#include <linux/iio/iio.h>
> > +#include <linux/module.h>
> Recheck this list.  Should have
> property.h and mod_devicetable.h
> 
> > +#include <linux/notifier.h>
> > +#include <linux/regmap.h>
> 
> and not regmap as you aren't using it.
> 
> > +#include <linux/regulator/consumer.h>
> > +#include <linux/spi/spi.h>
> > +
> > +#include <asm/unaligned.h>
> 
> ...
> 
> > +/* ADMV1013_REG_OFFSET_ADJUST_Q Map */
> > +#define ADMV1013_MIXER_OFF_ADJ_Q_P_MSK
> 	GENMASK(15, 9)
> > +#define ADMV1013_MIXER_OFF_ADJ_Q_N_MSK		GENMASK(8,
> 2)
> Given these two registers have the same form, could you use generic naming
> and just have them defined once?
> 
> > +
> > +/* ADMV1013_REG_QUAD Map */
> > +#define ADMV1013_QUAD_SE_MODE_MSK		GENMASK(9,
> 6)
> > +#define ADMV1013_QUAD_FILTERS_MSK		GENMASK(3, 0)
> > +
> > +/* ADMV1013_REG_VVA_TEMP_COMP Map */
> > +#define ADMV1013_VVA_TEMP_COMP_MSK
> 	GENMASK(15, 0)
> > +
> > +struct admv1013_state {
> > +	struct spi_device	*spi;
> > +	struct clk		*clkin;
> > +	/* Protect against concurrent accesses to the device */
> 
> Also against concurrent access to data.  Maybe other state in software as
> well?
> This comment needs to cover everything the lock protects - if it were just
> serialization of accesses to the device then the spi subsystem would handle
> that fine for us.
> 
> > +	struct mutex		lock;
> > +	struct regulator	*reg;
> > +	struct notifier_block	nb;
> > +	unsigned int		quad_se_mode;
> > +	bool			vga_pd;
> > +	bool			mixer_pd;
> > +	bool			quad_pd;
> > +	bool			bg_pd;
> > +	bool			mixer_if_en;
> > +	bool			det_en;
> > +	u8			data[3] ____cacheline_aligned;
> > +};
> 
> ...
> 
> > +static int admv1013_read_raw(struct iio_dev *indio_dev,
> > +			     struct iio_chan_spec const *chan,
> > +			     int *val, int *val2, long info)
> > +{
> > +	struct admv1013_state *st = iio_priv(indio_dev);
> > +	unsigned int data;
> > +	int ret;
> > +
> > +	switch (info) {
> > +	case IIO_CHAN_INFO_OFFSET:
> > +		if (chan->channel2 == IIO_MOD_I) {
> > +			ret = admv1013_spi_read(st,
> ADMV1013_REG_OFFSET_ADJUST_I, &data);
> > +			if (ret)
> > +				return ret;
> > +
> > +			*val =
> FIELD_GET(ADMV1013_MIXER_OFF_ADJ_I_P_MSK, data);
> > +			*val2 =
> FIELD_GET(ADMV1013_MIXER_OFF_ADJ_I_N_MSK, data);
> 
> I mention above about generic naming for these masks.  Advantage is then
> that this
> code can be
> 
> 		if (chan->channel2 == IIO_MOD_I)
> 			addr = ADMV1013_REG_OFFSET_ADJUST_I;
> 		else
> 			addr = ADMV1013_REG_OFFSET_ADJUST_Q;
> 
> 		ret = admv1013_spi_read(st, addr, &data);
> 		if (ret)
> 			return ret;
> 
> 		*val = FIELD_GET(ADMV1013_MIXER_OFF_ADJ_P_MSK,
> data);
> 		*val2 = FIELD_GET(ADMV1013_MIXER_OFF_ADJ_Q_MSK,
> data);
> 
> 		return IIO_VAL_INT_MULTIPLE;
> 
> However, returning multiple integers is normally reserved for things like
> quaternions where the individual parts have no meaning except when they
> are all present.
> It's not intended for pairs like this. It should also only be used with the special
> read_raw_multi callback.
> 
> So we need to rethink this interface. I'm not entirely sure what it is
> doing so I'm open to suggestions on what the interface should be!
> The description on the datasheet suggest to me these are for calibration
> tweaking
> in which case they should be related to calibbias not offset as they aren't
> something
> we should apply to a raw value in userspace (which is what offset is for).
> 
> 
> > +		} else {
> > +			ret = admv1013_spi_read(st,
> ADMV1013_REG_OFFSET_ADJUST_Q, &data);
> > +			if (ret)
> > +				return ret;
> > +
> > +			*val =
> FIELD_GET(ADMV1013_MIXER_OFF_ADJ_Q_P_MSK, data);
> > +			*val2 =
> FIELD_GET(ADMV1013_MIXER_OFF_ADJ_Q_N_MSK, data);
> > +		}
> > +
> > +		return IIO_VAL_INT_MULTIPLE;
> > +	case IIO_CHAN_INFO_PHASE:
> > +		if (chan->channel2 == IIO_MOD_I) {
> > +			ret = admv1013_spi_read(st,
> ADMV1013_REG_LO_AMP_I, &data);
> > +			if (ret)
> > +				return ret;
> > +
> > +			*val =
> FIELD_GET(ADMV1013_LOAMP_PH_ADJ_I_FINE_MSK, data);
> 
> As above, the masks match for these two branches of if / else, so with a
> generic
> name you should be able to share more code and only have to select the
> right register.
> 
> > +		} else {
> > +			ret = admv1013_spi_read(st,
> ADMV1013_REG_LO_AMP_Q, &data);
> > +			if (ret)
> > +				return ret;
> > +
> > +			*val =
> FIELD_GET(ADMV1013_LOAMP_PH_ADJ_Q_FINE_MSK, data);
> > +		}
> > +
> > +		return IIO_VAL_INT;
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +}
> > +
> > +static int admv1013_write_raw(struct iio_dev *indio_dev,
> > +			      struct iio_chan_spec const *chan,
> > +			      int val, int val2, long info)
> > +{
> > +	struct admv1013_state *st = iio_priv(indio_dev);
> > +	int ret;
> > +
> > +	switch (info) {
> > +	case IIO_CHAN_INFO_OFFSET:
> > +		val2 /= 100000;
> > +
> > +		if (chan->channel2 == IIO_MOD_I)
> > +			ret = admv1013_spi_update_bits(st,
> ADMV1013_REG_OFFSET_ADJUST_I,
> > +
> ADMV1013_MIXER_OFF_ADJ_I_P_MSK |
> > +
> ADMV1013_MIXER_OFF_ADJ_I_N_MSK,
> > +
> FIELD_PREP(ADMV1013_MIXER_OFF_ADJ_I_P_MSK, val) |
> > +
> FIELD_PREP(ADMV1013_MIXER_OFF_ADJ_I_N_MSK, val2));
> 
> As above, this isn't in inline with the normal ABI conventions so needs a
> rethink.  As far as I can
> establish these two values are independent though the datasheet provides
> limited information.
> 
> > +		else
> > +			ret = admv1013_spi_update_bits(st,
> ADMV1013_REG_OFFSET_ADJUST_Q,
> > +
> ADMV1013_MIXER_OFF_ADJ_Q_P_MSK |
> > +
> ADMV1013_MIXER_OFF_ADJ_Q_N_MSK,
> > +
> FIELD_PREP(ADMV1013_MIXER_OFF_ADJ_Q_P_MSK, val) |
> > +
> FIELD_PREP(ADMV1013_MIXER_OFF_ADJ_Q_N_MSK, val2));
> > +
> > +		return ret;
> > +	case IIO_CHAN_INFO_PHASE:
> > +		if (chan->channel2 == IIO_MOD_I)
> > +			return admv1013_spi_update_bits(st,
> ADMV1013_REG_LO_AMP_I,
> > +
> 	ADMV1013_LOAMP_PH_ADJ_I_FINE_MSK,
> > +
> 	FIELD_PREP(ADMV1013_LOAMP_PH_ADJ_I_FINE_MSK, val));
> > +		else
> > +			return admv1013_spi_update_bits(st,
> ADMV1013_REG_LO_AMP_Q,
> > +
> 	ADMV1013_LOAMP_PH_ADJ_Q_FINE_MSK,
> > +
> 	FIELD_PREP(ADMV1013_LOAMP_PH_ADJ_Q_FINE_MSK, val));
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +}
> > +
> > +static int admv1013_update_quad_filters(struct admv1013_state *st)
> > +{
> > +	unsigned int filt_raw;
> > +	u64 rate = clk_get_rate(st->clkin);
> > +
> > +	if (rate >= 5400000000 && rate <= 7000000000)
> 
> To reduce chance of 0s issues you could use the HZ_PER_MHZ definition in
> include/linux/units.h
> Nice to avoid counting so many zeros when reviewing.
> 
> > +		filt_raw = 15;
> > +	else if (rate >= 5400000000 && rate <= 8000000000)
> > +		filt_raw = 10;
> > +	else if (rate >= 6600000000 && rate <= 9200000000)
> > +		filt_raw = 5;
> > +	else
> > +		filt_raw = 0;
> > +
> > +	return __admv1013_spi_update_bits(st, ADMV1013_REG_QUAD,
> > +					ADMV1013_QUAD_FILTERS_MSK,
> > +
> 	FIELD_PREP(ADMV1013_QUAD_FILTERS_MSK, filt_raw));
> > +}
> > +
> 
> > +static int admv1013_reg_access(struct iio_dev *indio_dev,
> > +			       unsigned int reg,
> > +			       unsigned int write_val,
> > +			       unsigned int *read_val)
> > +{
> > +	struct admv1013_state *st = iio_priv(indio_dev);
> > +	int ret;
> > +
> > +	if (read_val)
> > +		ret = admv1013_spi_read(st, reg, read_val);
> 
> 		return amdv1013_spi_read() etc is a bit more concise for now
> loss of readability.
> 
> > +	else
> > +		ret = admv1013_spi_write(st, reg, write_val);
> > +
> > +	return ret;
> > +}
> > +
> 
> ...
> 
> 
> > +
> > +#define ADMV1013_CHAN(_channel, rf_comp) {			\
> > +	.type = IIO_ALTVOLTAGE,					\
> > +	.modified = 1,						\
> > +	.output = 1,						\
> > +	.indexed = 1,						\
> > +	.channel2 = IIO_MOD_##rf_comp,				\
> > +	.channel = _channel,					\
> > +	.info_mask_separate = BIT(IIO_CHAN_INFO_PHASE) |	\
> > +		BIT(IIO_CHAN_INFO_OFFSET)			\
> > +	}
> > +
> > +static const struct iio_chan_spec admv1013_channels[] = {
> > +	ADMV1013_CHAN(0, I),
> > +	ADMV1013_CHAN(0, Q),
> > +};
> 
> ...
> 
> > +
> > +static int admv1013_properties_parse(struct admv1013_state *st)
> > +{
> > +	int ret;
> > +	struct spi_device *spi = st->spi;
> > +
> > +	st->vga_pd = device_property_read_bool(&spi->dev, "adi,vga-pd");
> > +	st->mixer_pd = device_property_read_bool(&spi->dev, "adi,mixer-
> pd");
> > +	st->quad_pd = device_property_read_bool(&spi->dev, "adi,quad-
> pd");
> > +	st->bg_pd = device_property_read_bool(&spi->dev, "adi,bg-pd");
> > +	st->mixer_if_en = device_property_read_bool(&spi->dev,
> "adi,mixer-if-en");
> > +	st->det_en = device_property_read_bool(&spi->dev, "adi,det-en");
> 
> Comments on these in the binding document.
> 
> > +
> > +	ret = device_property_read_u32(&spi->dev, "adi,quad-se-mode",
> &st->quad_se_mode);
> > +	if (ret)
> > +		st->quad_se_mode = 12;
> > +
> > +	st->reg = devm_regulator_get(&spi->dev, "vcm");
> > +	if (IS_ERR(st->reg))
> > +		return dev_err_probe(&spi->dev, PTR_ERR(st->reg),
> > +				     "failed to get the common-mode
> voltage\n");
> > +
> > +	st->clkin = devm_clk_get(&spi->dev, "lo_in");
> > +	if (IS_ERR(st->clkin))
> > +		return dev_err_probe(&spi->dev, PTR_ERR(st->clkin),
> > +				     "failed to get the LO input clock\n");
> > +
> > +	return 0;
> > +}
> > +
> > +static int admv1013_probe(struct spi_device *spi)
> > +{
> > +	struct iio_dev *indio_dev;
> > +	struct admv1013_state *st;
> > +	int ret;
> > +
> > +	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> > +	if (!indio_dev)
> > +		return -ENOMEM;
> > +
> > +	st = iio_priv(indio_dev);
> > +
> > +	indio_dev->info = &admv1013_info;
> > +	indio_dev->name = "admv1013";
> > +	indio_dev->channels = admv1013_channels;
> > +	indio_dev->num_channels = ARRAY_SIZE(admv1013_channels);
> > +
> > +	st->spi = spi;
> > +
> > +	ret = admv1013_properties_parse(st);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = regulator_enable(st->reg);
> > +	if (ret) {
> > +		dev_err(&spi->dev, "Failed to enable specified Common-
> Mode Voltage!\n");
> > +		return ret;
> > +	}
> > +
> > +	ret = devm_add_action_or_reset(&spi->dev,
> admv1013_reg_disable,
> > +				       st->reg);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = clk_prepare_enable(st->clkin);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = devm_add_action_or_reset(&spi->dev, admv1013_clk_disable,
> st->clkin);
> > +	if (ret)
> > +		return ret;
> > +
> > +	st->nb.notifier_call = admv1013_freq_change;
> > +	ret = clk_notifier_register(st->clkin, &st->nb);
> 
> There seems to be a devm_clk_notifier_registers() which you should use.
> 
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = devm_add_action_or_reset(&spi->dev,
> admv1013_clk_notifier_unreg, st);
> > +	if (ret)
> > +		return ret;
> > +
> > +	mutex_init(&st->lock);
> > +
> > +	ret = admv1013_init(st);
> > +	if (ret) {
> > +		dev_err(&spi->dev, "admv1013 init failed\n");
> > +		return ret;
> > +	}
> > +
> > +	ret = devm_add_action_or_reset(&spi->dev,
> admv1013_powerdown, st);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return devm_iio_device_register(&spi->dev, indio_dev);
> > +}
> > +
> 
> ...





[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux