lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Date:   Sat, 8 Jun 2019 14:44:58 +0100
From:   Jonathan Cameron <jic23@...nel.org>
To:     Stefan Popa <stefan.popa@...log.com>
Cc:     <Michael.Hennerich@...log.com>, <knaack.h@....de>,
        <lars@...afoo.de>, <pmeerw@...erw.net>,
        <gregkh@...uxfoundation.org>, <linux-kernel@...r.kernel.org>,
        <linux-iio@...r.kernel.org>
Subject: Re: [PATCH v6 1/2] iio: frequency: adf4371: Add support for ADF4371
 PLL

On Tue, 4 Jun 2019 17:58:02 +0300
Stefan Popa <stefan.popa@...log.com> wrote:

> The ADF4371 is a frequency synthesizer with an integrated voltage
> controlled oscillator (VCO) for phase-locked loops (PLLs). The ADF4371
> has an integrated VCO with a fundamental output frequency ranging from
> 4000 MHz to 8000 MHz. In addition, the VCO frequency is connected to
> divide by 1, 2, 4, 8, 16, 32, or 64 circuits that allows the user to
> generate radio frequency (RF) output frequencies as low as 62.5 MHz at
> RF8x. A frequency multiplier at RF16x generates from 8 GHz to 16 GHz. A
> frequency quadrupler generates frequencies from 16 GHz to 32 GHz at RF32x.
> RFAUX8x duplicates the frequency range of RF8x or permits direct access to
> the VCO output.
> 
> The driver takes the reference input frequency from the device tree and
> uses it to calculate and maximize the PFD frequency (frequency of the phase
> frequency detector). The PFD frequency is further used to calculate the
> timeouts: synthesizer lock, VCO band selection, automatic level
> calibration (ALC) and PLL settling time.
> 
> This initial driver exposes the attributes for setting the frequency and
> enabling/disabling the different adf4371 channels.
> 
> Datasheet:
> Link: https://www.analog.com/media/en/technical-documentation/data-sheets/adf4371.pdf
> 
> Signed-off-by: Stefan Popa <stefan.popa@...log.com>
A few comments inline, but nothing to stop me taking it.

Applied to the togreg branch of iio.git.

Thanks,

Jonathan

> ---
> Changes in v2:
> 	- Added a new sysfs-bus-iio-frequency-adf4371 file which documents the ABI
> 	  changes.
> 	- Modified the ADF4371_REG() macro to take the reg values in hex as params
> 	- ADF4371_MAX_MODULUS2 macro is now defined as BIT(14)
> 	- regmap_bulk_write() can do DMA directly, so the buffer was forced into
> 	  it's own cacheline.
> 	- Fixed the multi line comment style.
> Changes in v3:
> 	- out_altvoltageY_frequency and out_altvoltageY_powerdown attributes are
> 	  treated as normal indexed channels.
> 	- out_altvoltageY_name attribute was added, from which the datasheet names
> 	  of the channels can be read.
> 	- Added more information in the documentation.
> 	- Documented the use of mutex lock.
> 	- As part of adf4371_write(), used a bool variable for power down and a
> 	  64 bit variable for the frequency.
> Changes in v4:
> 	- Misc style fixes.
> Changes in v5:
> 	- Added st->spi = spi; line of code in probe and fixing possible NULL
> 	  pointer dereference.
> Changes in v6:
> 	- Channel RF8AUX was accidentally duplicated.
> 
>  .../ABI/testing/sysfs-bus-iio-frequency-adf4371    |  44 ++
>  drivers/iio/frequency/Kconfig                      |  10 +
>  drivers/iio/frequency/Makefile                     |   1 +
>  drivers/iio/frequency/adf4371.c                    | 594 +++++++++++++++++++++
>  4 files changed, 649 insertions(+)
>  create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-frequency-adf4371
>  create mode 100644 drivers/iio/frequency/adf4371.c
> 
> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-frequency-adf4371 b/Documentation/ABI/testing/sysfs-bus-iio-frequency-adf4371
> new file mode 100644
> index 0000000..302de64
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-bus-iio-frequency-adf4371
> @@ -0,0 +1,44 @@
> +What:		/sys/bus/iio/devices/iio:deviceX/out_altvoltageY_frequency
> +KernelVersion:
> +Contact:	linux-iio@...r.kernel.org
> +Description:
> +		Stores the PLL frequency in Hz for channel Y.
> +		Reading returns the actual frequency in Hz.
> +		The ADF4371 has an integrated VCO with fundamendal output
> +		frequency ranging from 4000000000 Hz 8000000000 Hz.
> +
> +		out_altvoltage0_frequency:
> +			A divide by 1, 2, 4, 8, 16, 32 or circuit generates
> +			frequencies from 62500000 Hz to 8000000000 Hz.
> +		out_altvoltage1_frequency:
> +			This channel duplicates the channel 0 frequency
> +		out_altvoltage2_frequency:
> +			A frequency doubler generates frequencies from
> +			8000000000 Hz to 16000000000 Hz.
> +		out_altvoltage3_frequency:
> +			A frequency quadrupler generates frequencies from
> +			16000000000 Hz to 32000000000 Hz.
> +
> +		Note: writes to one of the channels will affect the frequency of
> +		all the other channels, since it involves changing the VCO
> +		fundamental output frequency.
> +
> +What:		/sys/bus/iio/devices/iio:deviceX/out_altvoltageY_name
> +KernelVersion:
> +Contact:	linux-iio@...r.kernel.org
> +Description:
> +		Reading returns the datasheet name for channel Y:
Hmm. The open question in my mind is whether _name is obviously enough
associated with the internal datasheet_name parameter...  

... 

I can't come up with anything short that does as well so let's go
with this.  I would think we'll actually move this into the core
shortly anyway as standard ABI, but let's do that when we have
a second reason to do so.

> +
> +		out_altvoltage0_name: RF8x
> +		out_altvoltage1_name: RFAUX8x
> +		out_altvoltage2_name: RF16x
> +		out_altvoltage3_name: RF32x
> +
> +What:		/sys/bus/iio/devices/iio:deviceX/out_altvoltageY_powerdown
> +KernelVersion:
> +Contact:	linux-iio@...r.kernel.org
> +Description:
> +		This attribute allows the user to power down the PLL and it's
> +		RFOut buffers.
> +		Writing 1 causes the specified channel to power down.
> +		Clearing returns to normal operation.
> diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig
> index dc5e0b7..e4a921f 100644
> --- a/drivers/iio/frequency/Kconfig
> +++ b/drivers/iio/frequency/Kconfig
> @@ -38,5 +38,15 @@ config ADF4350
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called adf4350.
>  
> +config ADF4371
> +	tristate "Analog Devices ADF4371 Wideband Synthesizer"
> +	depends on SPI
> +	select REGMAP_SPI
> +	help
> +	  Say yes here to build support for Analog Devices  ADF4371
> +	  Wideband Synthesizer. The driver provides direct access via sysfs.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called adf4371.
>  endmenu
>  endmenu
> diff --git a/drivers/iio/frequency/Makefile b/drivers/iio/frequency/Makefile
> index 2bca03f..2ddda77 100644
> --- a/drivers/iio/frequency/Makefile
> +++ b/drivers/iio/frequency/Makefile
> @@ -5,3 +5,4 @@
>  # When adding new entries keep the list in alphabetical order
>  obj-$(CONFIG_AD9523) += ad9523.o
>  obj-$(CONFIG_ADF4350) += adf4350.o
> +obj-$(CONFIG_ADF4371) += adf4371.o
> diff --git a/drivers/iio/frequency/adf4371.c b/drivers/iio/frequency/adf4371.c
> new file mode 100644
> index 0000000..d8c414b
> --- /dev/null
> +++ b/drivers/iio/frequency/adf4371.c
> @@ -0,0 +1,594 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Analog Devices ADF4371 SPI Wideband Synthesizer driver
> + *
> + * Copyright 2019 Analog Devices Inc.
> + */
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/gcd.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/sysfs.h>
> +#include <linux/spi/spi.h>
> +
> +#include <linux/iio/iio.h>
> +
> +/* Registers address macro */
> +#define ADF4371_REG(x)			(x)
I'm not all that keen on this which is basically just a macro to act
as documentation.

Given you could just use c99 structure assignment to make the same point,
that might have been nicer.

.reg = XXXX, .bit = XXXX
etc
> +
> +/* ADF4371_REG0 */
> +#define ADF4371_ADDR_ASC_MSK		BIT(2)
> +#define ADF4371_ADDR_ASC(x)		FIELD_PREP(ADF4371_ADDR_ASC_MSK, x)
> +#define ADF4371_ADDR_ASC_R_MSK		BIT(5)
> +#define ADF4371_ADDR_ASC_R(x)		FIELD_PREP(ADF4371_ADDR_ASC_R_MSK, x)
> +#define ADF4371_RESET_CMD		0x81
> +
> +/* ADF4371_REG17 */
> +#define ADF4371_FRAC2WORD_L_MSK		GENMASK(7, 1)
> +#define ADF4371_FRAC2WORD_L(x)		FIELD_PREP(ADF4371_FRAC2WORD_L_MSK, x)
> +#define ADF4371_FRAC1WORD_MSK		BIT(0)
> +#define ADF4371_FRAC1WORD(x)		FIELD_PREP(ADF4371_FRAC1WORD_MSK, x)
> +
> +/* ADF4371_REG18 */
> +#define ADF4371_FRAC2WORD_H_MSK		GENMASK(6, 0)
> +#define ADF4371_FRAC2WORD_H(x)		FIELD_PREP(ADF4371_FRAC2WORD_H_MSK, x)
> +
> +/* ADF4371_REG1A */
> +#define ADF4371_MOD2WORD_MSK		GENMASK(5, 0)
> +#define ADF4371_MOD2WORD(x)		FIELD_PREP(ADF4371_MOD2WORD_MSK, x)
> +
> +/* ADF4371_REG24 */
> +#define ADF4371_RF_DIV_SEL_MSK		GENMASK(6, 4)
> +#define ADF4371_RF_DIV_SEL(x)		FIELD_PREP(ADF4371_RF_DIV_SEL_MSK, x)
> +
> +/* ADF4371_REG32 */
> +#define ADF4371_TIMEOUT_MSK		GENMASK(1, 0)
> +#define ADF4371_TIMEOUT(x)		FIELD_PREP(ADF4371_TIMEOUT_MSK, x)
> +
> +/* ADF4371_REG34 */
> +#define ADF4371_VCO_ALC_TOUT_MSK	GENMASK(4, 0)
> +#define ADF4371_VCO_ALC_TOUT(x)		FIELD_PREP(ADF4371_VCO_ALC_TOUT_MSK, x)
> +
> +/* Specifications */
> +#define ADF4371_MIN_VCO_FREQ		4000000000ULL /* 4000 MHz */
> +#define ADF4371_MAX_VCO_FREQ		8000000000ULL /* 8000 MHz */
> +#define ADF4371_MAX_OUT_RF8_FREQ	ADF4371_MAX_VCO_FREQ /* Hz */
> +#define ADF4371_MIN_OUT_RF8_FREQ	(ADF4371_MIN_VCO_FREQ / 64) /* Hz */
> +#define ADF4371_MAX_OUT_RF16_FREQ	(ADF4371_MAX_VCO_FREQ * 2) /* Hz */
> +#define ADF4371_MIN_OUT_RF16_FREQ	(ADF4371_MIN_VCO_FREQ * 2) /* Hz */
> +#define ADF4371_MAX_OUT_RF32_FREQ	(ADF4371_MAX_VCO_FREQ * 4) /* Hz */
> +#define ADF4371_MIN_OUT_RF32_FREQ	(ADF4371_MIN_VCO_FREQ * 4) /* Hz */
> +
> +#define ADF4371_MAX_FREQ_PFD		250000000UL /* Hz */
> +#define ADF4371_MAX_FREQ_REFIN		600000000UL /* Hz */
> +
> +/* MOD1 is a 24-bit primary modulus with fixed value of 2^25 */
> +#define ADF4371_MODULUS1		33554432ULL
> +/* MOD2 is the programmable, 14-bit auxiliary fractional modulus */
> +#define ADF4371_MAX_MODULUS2		BIT(14)
> +
> +#define ADF4371_CHECK_RANGE(freq, range) \
> +	((freq > ADF4371_MAX_ ## range) || (freq < ADF4371_MIN_ ## range))
> +
> +enum {
> +	ADF4371_FREQ,
> +	ADF4371_POWER_DOWN,
> +	ADF4371_CHANNEL_NAME
> +};
> +
> +enum {
> +	ADF4371_CH_RF8,
> +	ADF4371_CH_RFAUX8,
> +	ADF4371_CH_RF16,
> +	ADF4371_CH_RF32
> +};
> +
> +struct adf4371_pwrdown {
> +	unsigned int reg;
> +	unsigned int bit;
> +};
> +
> +static const char * const adf4371_ch_names[] = {
> +	"RF8x", "RFAUX8x", "RF16x", "RF32x"
> +};
> +
> +static const struct adf4371_pwrdown adf4371_pwrdown_ch[4] = {
> +	[ADF4371_CH_RF8] = { ADF4371_REG(0x25), 2 },
> +	[ADF4371_CH_RFAUX8] = { ADF4371_REG(0x72), 3 },
> +	[ADF4371_CH_RF16] = { ADF4371_REG(0x25), 3 },
> +	[ADF4371_CH_RF32] = { ADF4371_REG(0x25), 4 },
> +};
> +
> +static const struct reg_sequence adf4371_reg_defaults[] = {
> +	{ ADF4371_REG(0x0),  0x18 },
> +	{ ADF4371_REG(0x12), 0x40 },
> +	{ ADF4371_REG(0x1E), 0x48 },
> +	{ ADF4371_REG(0x20), 0x14 },
> +	{ ADF4371_REG(0x22), 0x00 },
> +	{ ADF4371_REG(0x23), 0x00 },
> +	{ ADF4371_REG(0x24), 0x80 },
> +	{ ADF4371_REG(0x25), 0x07 },
> +	{ ADF4371_REG(0x27), 0xC5 },
> +	{ ADF4371_REG(0x28), 0x83 },
> +	{ ADF4371_REG(0x2C), 0x44 },
> +	{ ADF4371_REG(0x2D), 0x11 },
> +	{ ADF4371_REG(0x2E), 0x12 },
> +	{ ADF4371_REG(0x2F), 0x94 },
> +	{ ADF4371_REG(0x32), 0x04 },
> +	{ ADF4371_REG(0x35), 0xFA },
> +	{ ADF4371_REG(0x36), 0x30 },
> +	{ ADF4371_REG(0x39), 0x07 },
> +	{ ADF4371_REG(0x3A), 0x55 },
> +	{ ADF4371_REG(0x3E), 0x0C },
> +	{ ADF4371_REG(0x3F), 0x80 },
> +	{ ADF4371_REG(0x40), 0x50 },
> +	{ ADF4371_REG(0x41), 0x28 },
> +	{ ADF4371_REG(0x47), 0xC0 },
> +	{ ADF4371_REG(0x52), 0xF4 },
> +	{ ADF4371_REG(0x70), 0x03 },
> +	{ ADF4371_REG(0x71), 0x60 },
> +	{ ADF4371_REG(0x72), 0x32 },
> +};
> +
> +static const struct regmap_config adf4371_regmap_config = {
> +	.reg_bits = 16,
> +	.val_bits = 8,
> +	.read_flag_mask = BIT(7),
> +};
> +
> +struct adf4371_state {
> +	struct spi_device *spi;
> +	struct regmap *regmap;
> +	struct clk *clkin;
> +	/*
> +	 * Lock for accessing device registers. Some operations require
> +	 * multiple consecutive R/W operations, during which the device
> +	 * shouldn't be interrupted. The buffers are also shared across
> +	 * all operations so need to be protected on stand alone reads and
> +	 * writes.
> +	 */
> +	struct mutex lock;
> +	unsigned long clkin_freq;
> +	unsigned long fpfd;
> +	unsigned int integer;
> +	unsigned int fract1;
> +	unsigned int fract2;
> +	unsigned int mod2;
> +	unsigned int rf_div_sel;
> +	unsigned int ref_div_factor;
> +	u8 buf[10] ____cacheline_aligned;
> +};
> +
> +static unsigned long long adf4371_pll_fract_n_get_rate(struct adf4371_state *st,
> +						       u32 channel)
> +{
> +	unsigned long long val, tmp;
> +	unsigned int ref_div_sel;
> +
> +	val = (((u64)st->integer * ADF4371_MODULUS1) + st->fract1) * st->fpfd;
> +	tmp = (u64)st->fract2 * st->fpfd;
> +	do_div(tmp, st->mod2);
> +	val += tmp + ADF4371_MODULUS1 / 2;
> +
> +	if (channel == ADF4371_CH_RF8 || channel == ADF4371_CH_RFAUX8)
> +		ref_div_sel = st->rf_div_sel;
> +	else
> +		ref_div_sel = 0;
> +
> +	do_div(val, ADF4371_MODULUS1 * (1 << ref_div_sel));
> +
> +	if (channel == ADF4371_CH_RF16)
> +		val <<= 1;
> +	else if (channel == ADF4371_CH_RF32)
> +		val <<= 2;
> +
> +	return val;
> +}
> +
> +static void adf4371_pll_fract_n_compute(unsigned long long vco,
> +				       unsigned long long pfd,
> +				       unsigned int *integer,
> +				       unsigned int *fract1,
> +				       unsigned int *fract2,
> +				       unsigned int *mod2)
> +{
> +	unsigned long long tmp;
> +	u32 gcd_div;
> +
> +	tmp = do_div(vco, pfd);
> +	tmp = tmp * ADF4371_MODULUS1;
> +	*fract2 = do_div(tmp, pfd);
> +
> +	*integer = vco;
> +	*fract1 = tmp;
> +
> +	*mod2 = pfd;
> +
> +	while (*mod2 > ADF4371_MAX_MODULUS2) {
> +		*mod2 >>= 1;
> +		*fract2 >>= 1;
> +	}
> +
> +	gcd_div = gcd(*fract2, *mod2);
> +	*mod2 /= gcd_div;
> +	*fract2 /= gcd_div;
> +}
> +
> +static int adf4371_set_freq(struct adf4371_state *st, unsigned long long freq,
> +			    unsigned int channel)
> +{
> +	u32 cp_bleed;
> +	u8 int_mode = 0;
> +	int ret;
> +
> +	switch (channel) {
> +	case ADF4371_CH_RF8:
> +	case ADF4371_CH_RFAUX8:
> +		if (ADF4371_CHECK_RANGE(freq, OUT_RF8_FREQ))
> +			return -EINVAL;
> +
> +		st->rf_div_sel = 0;
> +
> +		while (freq < ADF4371_MIN_VCO_FREQ) {
> +			freq <<= 1;
> +			st->rf_div_sel++;
> +		}
> +		break;
> +	case ADF4371_CH_RF16:
> +		/* ADF4371 RF16 8000...16000 MHz */
> +		if (ADF4371_CHECK_RANGE(freq, OUT_RF16_FREQ))
> +			return -EINVAL;
> +
> +		freq >>= 1;
> +		break;
> +	case ADF4371_CH_RF32:
> +		/* ADF4371 RF32 16000...32000 MHz */
> +		if (ADF4371_CHECK_RANGE(freq, OUT_RF32_FREQ))
> +			return -EINVAL;
> +
> +		freq >>= 2;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	adf4371_pll_fract_n_compute(freq, st->fpfd, &st->integer, &st->fract1,
> +				    &st->fract2, &st->mod2);
> +	st->buf[0] = st->integer >> 8;
> +	st->buf[1] = 0x40; /* REG12 default */
> +	st->buf[2] = 0x00;
> +	st->buf[3] = st->fract2 & 0xFF;
> +	st->buf[4] = st->fract2 >> 7;
> +	st->buf[5] = st->fract2 >> 15;
> +	st->buf[6] = ADF4371_FRAC2WORD_L(st->fract2 & 0x7F) |
> +		     ADF4371_FRAC1WORD(st->fract1 >> 23);
> +	st->buf[7] = ADF4371_FRAC2WORD_H(st->fract2 >> 7);
> +	st->buf[8] = st->mod2 & 0xFF;
> +	st->buf[9] = ADF4371_MOD2WORD(st->mod2 >> 8);
> +
> +	ret = regmap_bulk_write(st->regmap, ADF4371_REG(0x11), st->buf, 10);
> +	if (ret < 0)
> +		return ret;
> +	/*
> +	 * The R counter allows the input reference frequency to be
> +	 * divided down to produce the reference clock to the PFD
> +	 */
> +	ret = regmap_write(st->regmap, ADF4371_REG(0x1F), st->ref_div_factor);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = regmap_update_bits(st->regmap, ADF4371_REG(0x24),
> +				 ADF4371_RF_DIV_SEL_MSK,
> +				 ADF4371_RF_DIV_SEL(st->rf_div_sel));
> +	if (ret < 0)
> +		return ret;
> +
> +	cp_bleed = DIV_ROUND_UP(400 * 1750, st->integer * 375);
> +	cp_bleed = clamp(cp_bleed, 1U, 255U);
> +	ret = regmap_write(st->regmap, ADF4371_REG(0x26), cp_bleed);
> +	if (ret < 0)
> +		return ret;
> +	/*
> +	 * Set to 1 when in INT mode (when FRAC1 = FRAC2 = 0),
> +	 * and set to 0 when in FRAC mode.
> +	 */
> +	if (st->fract1 == 0 && st->fract2 == 0)
> +		int_mode = 0x01;
> +
> +	ret = regmap_write(st->regmap, ADF4371_REG(0x2B), int_mode);
> +	if (ret < 0)
> +		return ret;
> +
> +	return regmap_write(st->regmap, ADF4371_REG(0x10), st->integer & 0xFF);
> +}
> +
> +static ssize_t adf4371_read(struct iio_dev *indio_dev,
> +			    uintptr_t private,
> +			    const struct iio_chan_spec *chan,
> +			    char *buf)
> +{
> +	struct adf4371_state *st = iio_priv(indio_dev);
> +	unsigned long long val = 0;
> +	unsigned int readval, reg, bit;
> +	int ret;
> +
> +	switch ((u32)private) {
> +	case ADF4371_FREQ:
> +		val = adf4371_pll_fract_n_get_rate(st, chan->channel);
> +		ret = regmap_read(st->regmap, ADF4371_REG(0x7C), &readval);
> +		if (ret < 0)
> +			break;
> +
> +		if (readval == 0x00) {
> +			dev_dbg(&st->spi->dev, "PLL un-locked\n");
> +			ret = -EBUSY;
> +		}
> +		break;
> +	case ADF4371_POWER_DOWN:
> +		reg = adf4371_pwrdown_ch[chan->channel].reg;
> +		bit = adf4371_pwrdown_ch[chan->channel].bit;
> +
> +		ret = regmap_read(st->regmap, reg, &readval);
> +		if (ret < 0)
> +			break;
> +
> +		val = !(readval & BIT(bit));
> +		break;
> +	case ADF4371_CHANNEL_NAME:
> +		return sprintf(buf, "%s\n", adf4371_ch_names[chan->channel]);
> +	default:
> +		ret = -EINVAL;
> +		val = 0;
> +		break;
> +	}
> +
> +	return ret < 0 ? ret : sprintf(buf, "%llu\n", val);
> +}
> +
> +static ssize_t adf4371_write(struct iio_dev *indio_dev,
> +			     uintptr_t private,
> +			     const struct iio_chan_spec *chan,
> +			     const char *buf, size_t len)
> +{
> +	struct adf4371_state *st = iio_priv(indio_dev);
> +	unsigned long long freq;
> +	bool power_down;
> +	unsigned int bit, readval, reg;
> +	int ret;
> +
> +	mutex_lock(&st->lock);
> +	switch ((u32)private) {
> +	case ADF4371_FREQ:
> +		ret = kstrtoull(buf, 10, &freq);
> +		if (ret)
> +			break;
> +
> +		ret = adf4371_set_freq(st, freq, chan->channel);
> +		break;
> +	case ADF4371_POWER_DOWN:
> +		ret = kstrtobool(buf, &power_down);
> +		if (ret)
> +			break;
> +
> +		reg = adf4371_pwrdown_ch[chan->channel].reg;
> +		bit = adf4371_pwrdown_ch[chan->channel].bit;
> +		ret = regmap_read(st->regmap, reg, &readval);
> +		if (ret < 0)
> +			break;
> +
> +		readval &= ~BIT(bit);
> +		readval |= (!power_down << bit);
> +
> +		ret = regmap_write(st->regmap, reg, readval);
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +	mutex_unlock(&st->lock);
> +
> +	return ret ? ret : len;
> +}
> +
> +#define _ADF4371_EXT_INFO(_name, _ident) { \
> +		.name = _name, \
> +		.read = adf4371_read, \
> +		.write = adf4371_write, \
> +		.private = _ident, \
> +		.shared = IIO_SEPARATE, \
> +}
> +
> +static const struct iio_chan_spec_ext_info adf4371_ext_info[] = {
> +	/*
> +	 * Ideally we use IIO_CHAN_INFO_FREQUENCY, but there are
> +	 * values > 2^32 in order to support the entire frequency range
> +	 * in Hz. Using scale is a bit ugly.
> +	 */
> +	_ADF4371_EXT_INFO("frequency", ADF4371_FREQ),
> +	_ADF4371_EXT_INFO("powerdown", ADF4371_POWER_DOWN),
> +	_ADF4371_EXT_INFO("name", ADF4371_CHANNEL_NAME),
> +	{ },
> +};
> +
> +#define ADF4371_CHANNEL(index) { \
> +		.type = IIO_ALTVOLTAGE, \
> +		.output = 1, \
> +		.channel = index, \
> +		.ext_info = adf4371_ext_info, \
> +		.indexed = 1, \
> +	}
> +
> +static const struct iio_chan_spec adf4371_chan[] = {
> +	ADF4371_CHANNEL(ADF4371_CH_RF8),
> +	ADF4371_CHANNEL(ADF4371_CH_RFAUX8),
> +	ADF4371_CHANNEL(ADF4371_CH_RF16),
> +	ADF4371_CHANNEL(ADF4371_CH_RF32),
> +};
> +
> +static int adf4371_reg_access(struct iio_dev *indio_dev,
> +			      unsigned int reg,
> +			      unsigned int writeval,
> +			      unsigned int *readval)
> +{
> +	struct adf4371_state *st = iio_priv(indio_dev);
> +
> +	if (readval)
> +		return regmap_read(st->regmap, reg, readval);
> +	else
> +		return regmap_write(st->regmap, reg, writeval);
> +}
> +
> +static const struct iio_info adf4371_info = {
> +	.debugfs_reg_access = &adf4371_reg_access,
> +};
> +
> +static int adf4371_setup(struct adf4371_state *st)
> +{
> +	unsigned int synth_timeout = 2, timeout = 1, vco_alc_timeout = 1;
> +	unsigned int vco_band_div, tmp;
> +	int ret;
> +
> +	/* Perform a software reset */
> +	ret = regmap_write(st->regmap, ADF4371_REG(0x0), ADF4371_RESET_CMD);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = regmap_multi_reg_write(st->regmap, adf4371_reg_defaults,
> +				     ARRAY_SIZE(adf4371_reg_defaults));
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Set address in ascending order, so the bulk_write() will work */
> +	ret = regmap_update_bits(st->regmap, ADF4371_REG(0x0),
> +				 ADF4371_ADDR_ASC_MSK | ADF4371_ADDR_ASC_R_MSK,
> +				 ADF4371_ADDR_ASC(1) | ADF4371_ADDR_ASC_R(1));
> +	if (ret < 0)
> +		return ret;
> +	/*
> +	 * Calculate and maximize PFD frequency
> +	 * fPFD = REFIN × ((1 + D)/(R × (1 + T)))
> +	 * Where D is the REFIN doubler bit, T is the reference divide by 2,
> +	 * R is the reference division factor
> +	 * TODO: it is assumed D and T equal 0.
> +	 */
> +	do {
> +		st->ref_div_factor++;
> +		st->fpfd = st->clkin_freq / st->ref_div_factor;
> +	} while (st->fpfd > ADF4371_MAX_FREQ_PFD);
> +
> +	/* Calculate Timeouts */
> +	vco_band_div = DIV_ROUND_UP(st->fpfd, 2400000U);
> +
> +	tmp = DIV_ROUND_CLOSEST(st->fpfd, 1000000U);
> +	do {
> +		timeout++;
> +		if (timeout > 1023) {
> +			timeout = 2;
> +			synth_timeout++;
> +		}
> +	} while (synth_timeout * 1024 + timeout <= 20 * tmp);
> +
> +	do {
> +		vco_alc_timeout++;
> +	} while (vco_alc_timeout * 1024 - timeout <= 50 * tmp);
> +
> +	st->buf[0] = vco_band_div;
> +	st->buf[1] = timeout & 0xFF;
> +	st->buf[2] = ADF4371_TIMEOUT(timeout >> 8) | 0x04;
> +	st->buf[3] = synth_timeout;
> +	st->buf[4] = ADF4371_VCO_ALC_TOUT(vco_alc_timeout);
> +
> +	return regmap_bulk_write(st->regmap, ADF4371_REG(0x30), st->buf, 5);
> +}
> +
> +static void adf4371_clk_disable(void *data)
> +{
> +	struct adf4371_state *st = data;
> +
> +	clk_disable_unprepare(st->clkin);
> +}
> +
> +static int adf4371_probe(struct spi_device *spi)
> +{
> +	const struct spi_device_id *id = spi_get_device_id(spi);
> +	struct iio_dev *indio_dev;
> +	struct adf4371_state *st;
> +	struct regmap *regmap;
> +	int ret;
> +
> +	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	regmap = devm_regmap_init_spi(spi, &adf4371_regmap_config);
> +	if (IS_ERR(regmap)) {
> +		dev_err(&spi->dev, "Error initializing spi regmap: %ld\n",
> +			PTR_ERR(regmap));
> +		return PTR_ERR(regmap);
> +	}
> +
> +	st = iio_priv(indio_dev);
> +	spi_set_drvdata(spi, indio_dev);
> +	st->spi = spi;
> +	st->regmap = regmap;
> +	mutex_init(&st->lock);
> +
> +	indio_dev->dev.parent = &spi->dev;
> +	indio_dev->name = id->name;
> +	indio_dev->info = &adf4371_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = adf4371_chan;
> +	indio_dev->num_channels = ARRAY_SIZE(adf4371_chan);
> +
> +	st->clkin = devm_clk_get(&spi->dev, "clkin");
> +	if (IS_ERR(st->clkin))
> +		return PTR_ERR(st->clkin);
> +
> +	ret = clk_prepare_enable(st->clkin);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = devm_add_action_or_reset(&spi->dev, adf4371_clk_disable, st);
> +	if (ret)
> +		return ret;
> +
> +	st->clkin_freq = clk_get_rate(st->clkin);
> +
> +	ret = adf4371_setup(st);
> +	if (ret < 0) {
> +		dev_err(&spi->dev, "ADF4371 setup failed\n");
> +		return ret;
> +	}
> +
> +	return devm_iio_device_register(&spi->dev, indio_dev);
> +}
> +
> +static const struct spi_device_id adf4371_id_table[] = {
> +	{ "adf4371", 0 },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(spi, adf4371_id_table);
> +
> +static const struct of_device_id adf4371_of_match[] = {
> +	{ .compatible = "adi,adf4371" },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, adf4371_of_match);
> +
> +static struct spi_driver adf4371_driver = {
> +	.driver = {
> +		.name = "adf4371",
> +		.of_match_table = adf4371_of_match,
> +	},
> +	.probe = adf4371_probe,
> +	.id_table = adf4371_id_table,
> +};
> +module_spi_driver(adf4371_driver);
> +
> +MODULE_AUTHOR("Stefan Popa <stefan.popa@...log.com>");
> +MODULE_DESCRIPTION("Analog Devices ADF4371 SPI PLL");
> +MODULE_LICENSE("GPL");

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ