[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20200203112736.00001241@Huawei.com>
Date: Mon, 3 Feb 2020 11:27:36 +0000
From: Jonathan Cameron <Jonathan.Cameron@...wei.com>
To: "Ardelean, Alexandru" <alexandru.Ardelean@...log.com>
CC: "jic23@...nel.org" <jic23@...nel.org>,
"ekigwana@...il.com" <ekigwana@...il.com>,
"devicetree@...r.kernel.org" <devicetree@...r.kernel.org>,
"linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>,
"linux-iio@...r.kernel.org" <linux-iio@...r.kernel.org>,
"robh+dt@...nel.org" <robh+dt@...nel.org>,
"lars@...afoo.de" <lars@...afoo.de>
Subject: Re: [PATCH v3 1/3] iio: frequency: adf4360: Add support for ADF4360
PLLs
On Mon, 3 Feb 2020 11:18:51 +0000
"Ardelean, Alexandru" <alexandru.Ardelean@...log.com> wrote:
> On Sun, 2020-02-02 at 14:45 +0000, Jonathan Cameron wrote:
> > [External]
> >
> > On Tue, 28 Jan 2020 13:13:00 +0200
> > Alexandru Ardelean <alexandru.ardelean@...log.com> wrote:
> >
> > > From: Edward Kigwana <ekigwana@...il.com>
> > >
> > > The ADF4360-N (N is 0..9) are a family of integrated integer-N synthesizer
> > > and voltage controlled oscillator (VCO).
> > >
> > > Control of all the on-chip registers is through a simple 3-wire SPI
> > > interface. The device operates with a power supply ranging from 3.0 V to
> > > 3.6 V and can be powered down when not in use.
> > >
> > > The initial draft-work was done by Lars-Peter Clausen <lars@...afoo.de>
> > > The current/latest/complete version was implemented by
> > > Edward Kigwana <ekigwana@...il.com>.
> > >
> > > The ADF4360-9 is also present on the Analog Devices 'Advanced Active
> > > Learning Module 2000' (ADALM-2000).
> > >
> > > Link:
> > > https://www.analog.com/media/en/technical-documentation/data-sheets/ADF4360-0.pdf
> > > Link:
> > > https://www.analog.com/media/en/technical-documentation/data-sheets/ADF4360-1.pdf
> > > Link:
> > > https://www.analog.com/media/en/technical-documentation/data-sheets/ADF4360-2.pdf
> > > Link:
> > > https://www.analog.com/media/en/technical-documentation/data-sheets/ADF4360-3.pdf
> > > Link:
> > > https://www.analog.com/media/en/technical-documentation/data-sheets/ADF4360-4.pdf
> > > Link:
> > > https://www.analog.com/media/en/technical-documentation/data-sheets/ADF4360-5.pdf
> > > Link:
> > > https://www.analog.com/media/en/technical-documentation/data-sheets/ADF4360-6.pdf
> > > Link:
> > > https://www.analog.com/media/en/technical-documentation/data-sheets/ADF4360-7.pdf
> > > Link:
> > > https://www.analog.com/media/en/technical-documentation/data-sheets/ADF4360-8.pdf
> > > Link:
> > > https://www.analog.com/media/en/technical-documentation/data-sheets/ADF4360-9.pdf
> > > Link:
> > > https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/ADALM2000.html
> > >
> > > Signed-off-by: Lars-Peter Clausen <lars@...afoo.de>
> > > Signed-off-by: Edward Kigwana <ekigwana@...il.com>
> > > Signed-off-by: Alexandru Ardelean <alexandru.ardelean@...log.com>
> >
> > Superficially this looks like you really have a clock source driver.
> > You then wrap it in IIO in order to provide convenient userspace interfaces.
> >
> > I'm not sure we want to do that and I definitely need agreement from those
> > responsible for clock source drivers before I take anything that does do
> > this sort of combination of the two types of driver.
> >
>
> I'll admit that I am a bit fuzzy about these frequency-generators/clock-
> sources/synthesizer chips.
> In the sense, I don't know where to draw the line between when to consider it
> [primarly] an IIO device [for the IIO subsystem] or when to consider it a clock-
> device [primarily, for the clk subsystem].
> Obviously there's an inertia [for me at least] to go IIO, as I know it a bit
> better.
>
> We've had some quick/short discussions [internally] about this driver, and also
> about the LTC6952. We didn't have a bigger one, where we try to discuss them
> more in-detail; but we do have a plan to do it.
>
> So, then maybe [until then] a question: how do we decide this? [generally
> speaking, not just adf4360 & ltc6952]
> i.e. when to consider it clk-first or iio-first;
> I'm not sure if there is a clear-cut rule, but maybe some guidelines/thoughts?
> Obviously, I'll have to read-up more on the clk framework code [as well] to get
> a feel for it.
>
> We've done some things internally [up until now] with some of these clock-chips
> that's been mostly IIO-centric. Now, much of it may not be correct, but it is
> what we use as template when writing new driver, which [of course] is not good.
> And, I also don't want to push/force our mistakes upstream, because that is
> [well...] bad. Hence this/these question(s).
Clocks aren't an area I know much about either. I'd suggest an RFC with
a description of the types of devices and some examples. Send that to
linux-iio and linux-clk and see what responses you get.
Make sure to highlight the fuzzy boundary when we get into waveform generators
etc rather than straight forward clocks. + include some of the devices that
definitely aren't suitable for use clocking normal SoCs etc.
Will be interesting to see where this one goes.
Jonathan
>
> Thanks
> Alex
>
> > I can see that, for these high speed devices, the intent is normally to
> > provide
> > an input signal to some external circuitry rather than some internal clock
> > signal, but given this is registered as a clock source the argument that it is
> > somehow special doesn't seem to hold.
> >
> > A few more specific comments inline.
> >
> > Thanks,
> >
> > Jonathan
> >
> > Jonathan
> >
> > > ---
> > >
> > > Changelog v2 -> v3:
> > > * dropped patch about adf4371.yaml; added by accident since it was used to
> > > compare against
> > > * addressed Rob's review comments for DT schema
> > >
> > > Changelog v1 -> v2:
> > > * validate dt-bindings file with
> > > make dt_binding_check
> > > DT_SCHEMA_FILES=Documentation/devicetree/bindings/iio/frequency/adi,adf4360.
> > > yaml
> > >
> > > drivers/iio/frequency/Kconfig | 11 +
> > > drivers/iio/frequency/Makefile | 1 +
> > > drivers/iio/frequency/adf4360.c | 1263 +++++++++++++++++++++++++++++++
> > > 3 files changed, 1275 insertions(+)
> > > create mode 100644 drivers/iio/frequency/adf4360.c
> > >
> > > diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig
> > > index 240b81502512..781236496661 100644
> > > --- a/drivers/iio/frequency/Kconfig
> > > +++ b/drivers/iio/frequency/Kconfig
> > > @@ -39,6 +39,17 @@ config ADF4350
> > > To compile this driver as a module, choose M here: the
> > > module will be called adf4350.
> > >
> > > +config ADF4360
> > > + tristate "Analog Devices ADF4360 Wideband Synthesizers"
> > > + depends on SPI
> > > + depends on COMMON_CLK
> > > + help
> > > + Say yes here to build support for Analog Devices ADF4360
> > > + Wideband Synthesizers. The driver provides direct access via sysfs.
> > > +
> > > + To compile this driver as a module, choose M here: the
> > > + module will be called adf4360.
> > > +
> > > config ADF4371
> > > tristate "Analog Devices ADF4371/ADF4372 Wideband Synthesizers"
> > > depends on SPI
> > > diff --git a/drivers/iio/frequency/Makefile b/drivers/iio/frequency/Makefile
> > > index 518b1e50caef..85f2f81da662 100644
> > > --- a/drivers/iio/frequency/Makefile
> > > +++ b/drivers/iio/frequency/Makefile
> > > @@ -6,4 +6,5 @@
> > > # When adding new entries keep the list in alphabetical order
> > > obj-$(CONFIG_AD9523) += ad9523.o
> > > obj-$(CONFIG_ADF4350) += adf4350.o
> > > +obj-$(CONFIG_ADF4360) += adf4360.o
> > > obj-$(CONFIG_ADF4371) += adf4371.o
> > > diff --git a/drivers/iio/frequency/adf4360.c
> > > b/drivers/iio/frequency/adf4360.c
> > > new file mode 100644
> > > index 000000000000..49ad58092171
> > > --- /dev/null
> > > +++ b/drivers/iio/frequency/adf4360.c
> > > @@ -0,0 +1,1263 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * ADF4360 PLL with Integrated Synthesizer and VCO
> > > + *
> > > + * Copyright 2014-2019 Analog Devices Inc.
> > > + * Copyright 2019-2020 Edward Kigwana.
> > > + */
> > > +
> > > +#include <linux/bitfield.h>
> > > +#include <linux/clk.h>
> > > +#include <linux/clk-provider.h>
> > > +#include <linux/delay.h>
> > > +#include <linux/device.h>
> > > +#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>
> > > +
> > > +#include <linux/iio/iio.h>
> > > +
> > > +/* Register address macro */
> > > +#define ADF4360_REG(x) (x)
> > > +
> > > +/* ADF4360_CTRL */
> > > +#define ADF4360_ADDR_CPL_MSK GENMASK(3, 2)
> > > +#define ADF4360_CPL(x) FIELD_PREP(ADF4360_ADDR_CPL_MSK,
> > > x)
> > > +#define ADF4360_ADDR_MUXOUT_MSK GENMASK(7, 5)
> > > +#define ADF4360_MUXOUT(x) FIELD_PREP(ADF4360_ADDR_MUXOUT_MSK, x)
> > > +#define ADF4360_ADDR_PDP_MSK BIT(8)
> > > +#define ADF4360_PDP(x) FIELD_PREP(ADF4360_ADDR_PDP_MSK,
> > > x)
> > > +#define ADF4360_ADDR_MTLD_MSK BIT(11)
> > > +#define ADF4360_MTLD(x) FIELD_PREP(ADF4360_ADDR_MTLD_MSK
> > > , x)
> > > +#define ADF4360_ADDR_OPL_MSK GENMASK(13, 12)
> > > +#define ADF4360_OPL(x) FIELD_PREP(ADF4360_ADDR_OPL_MSK,
> > > x)
> > > +#define ADF4360_ADDR_CPI1_MSK GENMASK(16, 14)
> > > +#define ADF4360_CPI1(x) FIELD_PREP(ADF4360_ADDR_CPI1_MSK
> > > , x)
> > > +#define ADF4360_ADDR_CPI2_MSK GENMASK(19, 17)
> > > +/* ADF4360_RDIV */
> > > +#define ADF4360_CPI2(x) FIELD_PREP(ADF4360_ADDR_CPI2_MSK
> > > , x)
> > > +#define ADF4360_ADDR_PWR_DWN_MSK GENMASK(21, 20)
> > > +#define ADF4360_POWERDOWN(x) FIELD_PREP(ADF4360_ADDR_PWR_DWN_
> > > MSK, x)
> > > +#define ADF4360_ADDR_PRESCALER_MSK GENMASK(23, 22)
> > > +#define ADF4360_PRESCALER(x) FIELD_PREP(ADF4360_ADDR_PRESCALE
> > > R_MSK, x)
> > > +
> > > +/* ADF4360_NDIV */
> > > +#define ADF4360_ADDR_A_CNTR_MSK GENMASK(6, 2)
> > > +#define ADF4360_A_COUNTER(x) FIELD_PREP(ADF4360_ADDR_A_CNTR_M
> > > SK, x)
> > > +#define ADF4360_ADDR_B_CNTR_MSK GENMASK(20, 8)
> > > +#define ADF4360_B_COUNTER(x) FIELD_PREP(ADF4360_ADDR_B_CNTR_M
> > > SK, x)
> > > +#define ADF4360_ADDR_OUT_DIV2_MSK BIT(22)
> > > +#define ADF4360_OUT_DIV2(x) FIELD_PREP(ADF4360_ADDR_OUT_DIV2
> > > _MSK, x)
> > > +#define ADF4360_ADDR_DIV2_SEL_MSK BIT(23)
> > > +#define ADF4360_PRESCALER_DIV2(x) FIELD_PREP(ADF4360_ADDR_DIV2_SEL_MSK, x)
> > > +
> > > +#define ADF4360_ADDR_R_CNTR_MSK GENMASK(15, 2)
> > > +#define ADF4360_R_COUNTER(x) FIELD_PREP(ADF4360_ADDR_R_CNTR_M
> > > SK, x)
> > > +#define ADF4360_ADDR_ABP_MSK GENMASK(17, 16)
> > > +#define ADF4360_ABP(x) FIELD_PREP(ADF4360_ADDR_ABP_MSK,
> > > x)
> > > +#define ADF4360_ADDR_BSC_MSK GENMASK(21, 20)
> > > +#define ADF4360_BSC(x) FIELD_PREP(ADF4360_ADDR_BSC_MSK,
> > > x)
> > > +
> > > +/* Specifications */
> > > +#define ADF4360_MAX_PFD_RATE 8000000 /* 8 MHz */
> > > +#define ADF4360_MAX_COUNTER_RATE 300000000 /* 300 MHz */
> > > +#define ADF4360_MAX_REFIN_RATE 250000000 /* 250 MHz */
> > > +
> > > +enum {
> > > + ADF4360_CTRL,
> > > + ADF4360_RDIV,
> > > + ADF4360_NDIV,
> > > + ADF4360_REG_NUM,
> > > +};
> > > +
> > > +enum {
> > > + ADF4360_GEN1_PC_5,
> > > + ADF4360_GEN1_PC_10,
> > > + ADF4360_GEN1_PC_15,
> > > + ADF4360_GEN1_PC_20,
> > > +};
> > > +
> > > +enum {
> > > + ADF4360_GEN2_PC_2_5,
> > > + ADF4360_GEN2_PC_5,
> > > + ADF4360_GEN2_PC_7_5,
> > > + ADF4360_GEN2_PC_10,
> > > +};
> > > +
> > > +enum {
> > > + ADF4360_MUXOUT_THREE_STATE,
> > > + ADF4360_MUXOUT_LOCK_DETECT,
> > > + ADF4360_MUXOUT_NDIV,
> > > + ADF4360_MUXOUT_DVDD,
> > > + ADF4360_MUXOUT_RDIV,
> > > + ADF4360_MUXOUT_OD_LD,
> > > + ADF4360_MUXOUT_SDO,
> > > + ADF4360_MUXOUT_GND,
> > > +};
> > > +
> > > +enum {
> > > + ADF4360_PL_3_5,
> > > + ADF4360_PL_5,
> > > + ADF4360_PL_7_5,
> > > + ADF4360_PL_11,
> > > +};
> > > +
> > > +enum {
> > > + ADF4360_CPI_0_31,
> > > + ADF4360_CPI_0_62,
> > > + ADF4360_CPI_0_93,
> > > + ADF4360_CPI_1_25,
> > > + ADF4360_CPI_1_56,
> > > + ADF4360_CPI_1_87,
> > > + ADF4360_CPI_2_18,
> > > + ADF4360_CPI_2_50,
> > > +};
> > > +
> > > +enum {
> > > + ADF4360_POWER_DOWN_NORMAL,
> > > + ADF4360_POWER_DOWN_SOFT_ASYNC,
> > > + ADF4360_POWER_DOWN_CE,
> > > + ADF4360_POWER_DOWN_SOFT_SYNC,
> > > + ADF4360_POWER_DOWN_REGULATOR,
> > > +};
> > > +
> > > +enum {
> > > + ADF4360_PRESCALER_8,
> > > + ADF4360_PRESCALER_16,
> > > + ADF4360_PRESCALER_32,
> > > +};
> > > +
> > > +enum {
> > > + ADF4360_ABP_3_0NS,
> > > + ADF4360_ABP_1_3NS,
> > > + ADF4360_ABP_6_0NS,
> > > +};
> > > +
> > > +enum {
> > > + ADF4360_BSC_1,
> > > + ADF4360_BSC_2,
> > > + ADF4360_BSC_4,
> > > + ADF4360_BSC_8,
> > > +};
> > > +
> > > +enum {
> > > + ID_ADF4360_0,
> > > + ID_ADF4360_1,
> > > + ID_ADF4360_2,
> > > + ID_ADF4360_3,
> > > + ID_ADF4360_4,
> > > + ID_ADF4360_5,
> > > + ID_ADF4360_6,
> > > + ID_ADF4360_7,
> > > + ID_ADF4360_8,
> > > + ID_ADF4360_9,
> > > +};
> > > +
> > > +enum {
> > > + ADF4360_FREQ_REFIN,
> > > + ADF4360_MTLD,
> > > + ADF4360_FREQ_PFD,
> > > +};
> > > +
> > > +static const char * const adf4360_power_level_modes[] = {
> > This isn't an enum. These are real values in sensible units not
> > magic strings. Handle them as integers.
> >
> > We may need to define additional ABI for this but it should be
> > possible to 'fit' it in the normal structure of ABI.
> >
> > > + [ADF4360_PL_3_5] = "3500-uA",
> > > + [ADF4360_PL_5] = "5000-uA",
> > > + [ADF4360_PL_7_5] = "7500-uA",
> > > + [ADF4360_PL_11] = "11000-uA",
> > > +};
> > > +
> > > +static const unsigned int adf4360_power_lvl_microamp[] = {
> > > + [ADF4360_PL_3_5] = 3500,
> > > + [ADF4360_PL_5] = 5000,
> > > + [ADF4360_PL_7_5] = 7500,
> > > + [ADF4360_PL_11] = 11000,
> > > +};
> > > +
> > > +static const unsigned int adf4360_cpi_modes[] = {
> > > + [ADF4360_CPI_0_31] = 310,
> > > + [ADF4360_CPI_0_62] = 620,
> > > + [ADF4360_CPI_0_93] = 930,
> > > + [ADF4360_CPI_1_25] = 1250,
> > > + [ADF4360_CPI_1_56] = 1560,
> > > + [ADF4360_CPI_1_87] = 1870,
> > > + [ADF4360_CPI_2_18] = 2180,
> > > + [ADF4360_CPI_2_50] = 2500,
> > > +};
> > > +
> > > +static const char * const adf4360_muxout_modes[] = {
> > Superficially from glancing at the datasheet I thing this is debug
> > functionality? Perhaps move it to debugfs so as to avoid creating
> > complex new ABI in sysfs.
> >
> > > + [ADF4360_MUXOUT_THREE_STATE] = "three-state",
> > > + [ADF4360_MUXOUT_LOCK_DETECT] = "lock-detect",
> > > + [ADF4360_MUXOUT_NDIV] = "ndiv",
> > > + [ADF4360_MUXOUT_DVDD] = "dvdd",
> > > + [ADF4360_MUXOUT_RDIV] = "rdiv",
> > > + [ADF4360_MUXOUT_OD_LD] = "od-ld",
> > > + [ADF4360_MUXOUT_SDO] = "sdo",
> > > + [ADF4360_MUXOUT_GND] = "gnd",
> > > +};
> > > +
> > > +static const char * const adf4360_power_down_modes[] = {
> > > + [ADF4360_POWER_DOWN_NORMAL] = "normal",
> > > + [ADF4360_POWER_DOWN_SOFT_ASYNC] = "soft-async",
> > > + [ADF4360_POWER_DOWN_CE] = "ce",
> > > + [ADF4360_POWER_DOWN_SOFT_SYNC] = "soft-sync",
> > > + [ADF4360_POWER_DOWN_REGULATOR] = "regulator",
> > This seems to map rather oddly to the datasheet. Perhaps some
> > comments to explain what is going on here would help/
> > > +};
> > > +
> > > +struct adf4360_output {
> > > + struct clk_hw hw;
> > > + struct iio_dev *indio_dev;
> > > +};
> > > +
> > > +#define to_output(_hw) container_of(_hw, struct adf4360_output, hw)
> > > +
> > > +struct adf4360_chip_info {
> > > + unsigned int vco_min;
> > > + unsigned int vco_max;
> > > + unsigned int default_cpl;
> > > +};
> > > +
> > > +struct adf4360_state {
> > > + struct spi_device *spi;
> > > + const struct adf4360_chip_info *info;
> > > + struct adf4360_output output;
> > > + struct clk *clkin;
> > > + struct gpio_desc *muxout_gpio;
> > > + struct gpio_desc *chip_en_gpio;
> > > + struct regulator *vdd_reg;
> > > + struct mutex lock; /* Protect PLL state. */
> > > + unsigned int part_id;
> > > + unsigned long clkin_freq;
> > > + unsigned long freq_req;
> > > + unsigned long r;
> > > + unsigned long n;
> > > + unsigned int vco_min;
> > > + unsigned int vco_max;
> > > + unsigned int pfd_freq;
> > > + unsigned int cpi;
> > > + bool pdp;
> > > + bool mtld;
> > > + unsigned int power_level;
> > > + unsigned int muxout_mode;
> > > + unsigned int power_down_mode;
> > > + bool initial_reg_seq;
> > > + const char *clk_out_name;
> > > + unsigned int regs[ADF4360_REG_NUM];
> > > + u8 spi_data[3] ____cacheline_aligned;
> > > +};
> > > +
> > > +static const struct adf4360_chip_info adf4360_chip_info_tbl[] = {
> > > + { /* ADF4360-0 */
> > > + .vco_min = 2400000000U,
> > > + .vco_max = 2725000000U,
> > > + .default_cpl = ADF4360_GEN1_PC_10,
> > > + }, { /* ADF4360-1 */
> > > + .vco_min = 2050000000U,
> > > + .vco_max = 2450000000U,
> > > + .default_cpl = ADF4360_GEN1_PC_15,
> > > + }, { /* ADF4360-2 */
> > > + .vco_min = 1850000000U,
> > > + .vco_max = 2170000000U,
> > > + .default_cpl = ADF4360_GEN1_PC_15,
> > > + }, { /* ADF4360-3 */
> > > + .vco_min = 1600000000U,
> > > + .vco_max = 1950000000U,
> > > + .default_cpl = ADF4360_GEN1_PC_15,
> > > + }, { /* ADF4360-4 */
> > > + .vco_min = 1450000000U,
> > > + .vco_max = 1750000000U,
> > > + .default_cpl = ADF4360_GEN1_PC_15,
> > > + }, { /* ADF4360-5 */
> > > + .vco_min = 1200000000U,
> > > + .vco_max = 1400000000U,
> > > + .default_cpl = ADF4360_GEN1_PC_10,
> > > + }, { /* ADF4360-6 */
> > > + .vco_min = 1050000000U,
> > > + .vco_max = 1250000000U,
> > > + .default_cpl = ADF4360_GEN1_PC_10,
> > > + }, { /* ADF4360-7 */
> > > + .vco_min = 350000000U,
> > > + .vco_max = 1800000000U,
> > > + .default_cpl = ADF4360_GEN1_PC_5,
> > > + }, { /* ADF4360-8 */
> > > + .vco_min = 65000000U,
> > > + .vco_max = 400000000U,
> > > + .default_cpl = ADF4360_GEN2_PC_5,
> > > + }, { /* ADF4360-9 */
> > > + .vco_min = 65000000U,
> > > + .vco_max = 400000000U,
> > > + .default_cpl = ADF4360_GEN2_PC_5,
> > > + }
> > > +};
> > > +
> > > +static int adf4360_write_reg(struct adf4360_state *st, unsigned int reg,
> > > + unsigned int val)
> > > +{
> > > + int ret;
> > > +
> > > + val |= reg;
> > > +
> > > + st->spi_data[0] = (val >> 16) & 0xff;
> > > + st->spi_data[1] = (val >> 8) & 0xff;
> > > + st->spi_data[2] = val & 0xff;
> > > +
> > > + ret = spi_write(st->spi, st->spi_data, ARRAY_SIZE(st->spi_data));
> > > + if (ret == 0)
> > > + st->regs[reg] = val;
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +/* fVCO = B * fREFIN / R */
> > > +
> > > +static unsigned long adf4360_clk_recalc_rate(struct clk_hw *hw,
> > > + unsigned long parent_rate)
> > > +{
> > > + struct iio_dev *indio_dev = to_output(hw)->indio_dev;
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > +
> > > + if (st->r == 0)
> > > + return 0;
> > > +
> > > + /*
> > > + * The result is guaranteed to fit in 32-bit, but the intermediate
> > > + * result might require 64-bit.
> > > + */
> > > + return DIV_ROUND_CLOSEST_ULL((uint64_t)parent_rate * st->n, st->r);
> > > +}
> > > +
> > > +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;
> > > +
> > > + if (b < 3) {
> > > + b = 3;
> > > + a = 0;
> > > + } else if (a > b) {
> > > + if (a - b < p - a) {
> > > + a = b;
> > > + } else {
> > > + a = 0;
> > > + b++;
> > > + }
> > > + }
> > > +
> > > + if (out_p)
> > > + *out_p = p;
> > > + if (out_a)
> > > + *out_a = a;
> > > + if (out_b)
> > > + *out_b = b;
> > > +
> > > + return p * b + a;
> > > +}
> > > +
> > > +static long adf4360_clk_round_rate(struct clk_hw *hw,
> > > + unsigned long rate,
> > > + unsigned long *parent_rate)
> > > +{
> > > + struct iio_dev *indio_dev = to_output(hw)->indio_dev;
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > + unsigned int r, n;
> > > + unsigned int pfd_freq;
> > > +
> > > + if (*parent_rate == 0)
> > > + return 0;
> > > +
> > > + if (st->part_id == ID_ADF4360_9)
> > > + return *parent_rate * st->n / st->r;
> > > +
> > > + if (rate > st->vco_max)
> > > + return st->vco_max;
> > > +
> > > + /* 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
> > > + return st->vco_max / 2;
> > > + }
> > > + } else {
> > > + if (rate < st->vco_min)
> > > + return st->vco_min;
> > > + }
> > > +
> > > + r = DIV_ROUND_CLOSEST(*parent_rate, st->pfd_freq);
> > > + pfd_freq = *parent_rate / r;
> > > + n = DIV_ROUND_CLOSEST(rate, pfd_freq);
> > > +
> > > + if (st->part_id <= ID_ADF4360_7)
> > > + n = adf4360_calc_prescaler(pfd_freq, n, NULL, NULL, NULL);
> > > +
> > > + return pfd_freq * n;
> > > +}
> > > +
> > > +static inline bool adf4360_is_powerdown(struct adf4360_state *st)
> > > +{
> > > + return (st->power_down_mode != ADF4360_POWER_DOWN_NORMAL);
> > > +}
> > > +
> > > +static int adf4360_set_freq(struct adf4360_state *st, unsigned long rate)
> > > +{
> > > + unsigned int val_r, val_n, val_ctrl;
> > > + unsigned int pfd_freq;
> > > + unsigned long r, n;
> > > + int ret;
> > > +
> > > + if (!st->clkin_freq || (st->clkin_freq > ADF4360_MAX_REFIN_RATE))
> > > + return -EINVAL;
> > > +
> > > + if ((rate < st->vco_min) || (rate > st->vco_max))
> > > + return -EINVAL;
> > > +
> > > + if (adf4360_is_powerdown(st))
> > > + ret = -EBUSY;
> > > +
> > > + r = DIV_ROUND_CLOSEST(st->clkin_freq, st->pfd_freq);
> > > + pfd_freq = st->clkin_freq / r;
> > > + n = DIV_ROUND_CLOSEST(rate, pfd_freq);
> > > +
> > > + val_ctrl = ADF4360_CPL(st->info->default_cpl) |
> > > + ADF4360_MUXOUT(st->muxout_mode) |
> > > + ADF4360_PDP(!st->pdp) |
> > > + ADF4360_MTLD(st->mtld) |
> > > + ADF4360_OPL(st->power_level) |
> > > + ADF4360_CPI1(st->cpi) |
> > > + ADF4360_CPI2(st->cpi) |
> > > + ADF4360_POWERDOWN(st->power_down_mode);
> > > +
> > > + /* ADF4360-0 to ADF4360-7 have a dual-modulous prescaler */
> > > + if (st->part_id <= ID_ADF4360_7) {
> > > + unsigned int p, a, b;
> > > +
> > > + n = adf4360_calc_prescaler(pfd_freq, n, &p, &a, &b);
> > > +
> > > + switch (p) {
> > > + case 8:
> > > + val_ctrl |= ADF4360_PRESCALER(ADF4360_PRESCALER_8);
> > > + break;
> > > + case 16:
> > > + val_ctrl |= ADF4360_PRESCALER(ADF4360_PRESCALER_16);
> > > + break;
> > > + default:
> > > + val_ctrl |= ADF4360_PRESCALER(ADF4360_PRESCALER_32);
> > > + break;
> > > + }
> > > +
> > > + val_n = ADF4360_A_COUNTER(a) |
> > > + ADF4360_B_COUNTER(b);
> > > +
> > > + if (rate < st->vco_min)
> > > + val_n |= ADF4360_OUT_DIV2(true) |
> > > + ADF4360_PRESCALER_DIV2(true);
> > > + } else {
> > > + val_n = ADF4360_B_COUNTER(n);
> > > + }
> > > +
> > > + /*
> > > + * Always use BSC divider of 8, see Analog Devices AN-1347.
> > > + *
> > > http://www.analog.com/media/en/technical-documentation/application-notes/AN-1347.pdf
> > > + */
> > > + val_r = ADF4360_R_COUNTER(r) |
> > > + ADF4360_ABP(ADF4360_ABP_3_0NS) |
> > > + ADF4360_BSC(ADF4360_BSC_8);
> > > +
> > > + ret = adf4360_write_reg(st, ADF4360_REG(ADF4360_RDIV), val_r);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = adf4360_write_reg(st, ADF4360_REG(ADF4360_CTRL), val_ctrl);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + /*
> > > + * Allow the transient behavior of the ADF4360-7 during initial
> > > + * power-up to settle.
> > > + */
> > > + if (st->initial_reg_seq) {
> > > + usleep_range(15000, 20000);
> > > + st->initial_reg_seq = false;
> > > + }
> > > +
> > > + ret = adf4360_write_reg(st, ADF4360_REG(ADF4360_NDIV), val_n);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + st->freq_req = rate;
> > > + st->n = n;
> > > + st->r = r;
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int adf4360_clk_set_rate(struct clk_hw *hw,
> > > + unsigned long rate,
> > > + unsigned long parent_rate)
> > > +{
> > > + struct iio_dev *indio_dev = to_output(hw)->indio_dev;
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > + int ret;
> > > +
> > > + if ((parent_rate == 0) || (parent_rate > ADF4360_MAX_REFIN_RATE))
> > > + return -EINVAL;
> > > +
> > > + mutex_lock(&st->lock);
> > > + if (st->clkin_freq != parent_rate)
> > > + st->clkin_freq = parent_rate;
> > > +
> > > + ret = adf4360_set_freq(st, rate);
> > > + mutex_unlock(&st->lock);
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static int __adf4360_power_down(struct adf4360_state *st, unsigned int
> > > mode)
> > > +{
> > > + struct device *dev = &st->spi->dev;
> > > + unsigned int val;
> > > + int ret = 0;
> > > +
> > > + switch (mode) {
> > > + case ADF4360_POWER_DOWN_NORMAL:
> > > + if (st->vdd_reg) {
> > > + ret = regulator_enable(st->vdd_reg);
> > > + if (ret) {
> > > + dev_err(dev, "Supply enable error: %d\n", ret);
> > > + return ret;
> > > + }
> > > + }
> > > +
> > > + st->initial_reg_seq = true;
> > > + st->power_down_mode = mode;
> > > + ret = adf4360_set_freq(st, st->freq_req);
> > > + break;
> > > + case ADF4360_POWER_DOWN_SOFT_ASYNC: /* fallthrough */
> > > + case ADF4360_POWER_DOWN_SOFT_SYNC:
> > > + val = st->regs[ADF4360_CTRL] & ~ADF4360_ADDR_MUXOUT_MSK;
> > > + val |= ADF4360_POWERDOWN(mode);
> > > + ret = adf4360_write_reg(st, ADF4360_REG(ADF4360_CTRL), val);
> > > + break;
> > > + case ADF4360_POWER_DOWN_CE:
> > > + if (st->chip_en_gpio)
> > > + gpiod_set_value(st->chip_en_gpio, 0x0);
> > > + else
> > > + return -ENODEV;
> > > + break;
> > > + 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);
> > > +
> > > + 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;
> > > +}
> > > +
> > > +static int adf4360_power_down(struct adf4360_state *st, unsigned int mode)
> > > +{
> > > + int ret;
> > > +
> > > + mutex_lock(&st->lock);
> > > + ret = __adf4360_power_down(st, mode);
> > > + mutex_unlock(&st->lock);
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static int adf4360_clk_prepare(struct clk_hw *hw)
> > > +{
> > > + struct iio_dev *indio_dev = to_output(hw)->indio_dev;
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > +
> > > + return adf4360_power_down(st, ADF4360_POWER_DOWN_NORMAL);
> > > +}
> > > +
> > > +static void adf4360_clk_unprepare(struct clk_hw *hw)
> > > +{
> > > + struct iio_dev *indio_dev = to_output(hw)->indio_dev;
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > +
> > > + adf4360_power_down(st, ADF4360_POWER_DOWN_SOFT_ASYNC);
> > > +}
> > > +
> > > +static int adf4360_clk_enable(struct clk_hw *hw)
> > > +{
> > > + struct iio_dev *indio_dev = to_output(hw)->indio_dev;
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > +
> > > + if (st->chip_en_gpio)
> > > + gpiod_set_value(st->chip_en_gpio, 0x1);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static void adf4360_clk_disable(struct clk_hw *hw)
> > > +{
> > > + struct iio_dev *indio_dev = to_output(hw)->indio_dev;
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > +
> > > + if (st->chip_en_gpio)
> > > + adf4360_power_down(st, ADF4360_POWER_DOWN_CE);
> > > +}
> > > +
> > > +static int adf4360_clk_is_enabled(struct clk_hw *hw)
> > > +{
> > > + struct iio_dev *indio_dev = to_output(hw)->indio_dev;
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > +
> > > + return adf4360_is_powerdown(st);
> > > +}
> > > +
> > > +static const struct clk_ops adf4360_clk_ops = {
> > > + .recalc_rate = adf4360_clk_recalc_rate,
> > > + .round_rate = adf4360_clk_round_rate,
> > > + .set_rate = adf4360_clk_set_rate,
> > > + .prepare = adf4360_clk_prepare,
> > > + .unprepare = adf4360_clk_unprepare,
> > > + .enable = adf4360_clk_enable,
> > > + .disable = adf4360_clk_disable,
> > > + .is_enabled = adf4360_clk_is_enabled,
> > > +};
> > > +
> > > +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;
> > > +
> > > + 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) {
> > > + case ADF4360_FREQ_REFIN:
> > > + ret = kstrtoul(buf, 10, &readin);
> > > + if (ret)
> > > + break;
> > > +
> > > + if ((readin > ADF4360_MAX_REFIN_RATE) || (readin == 0)) {
> > > + 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_MTLD:
> > > + ret = kstrtobool(buf, &mtld);
> > > + if (ret)
> > > + break;
> > > +
> > > + st->mtld = mtld;
> > > + break;
> > > + case ADF4360_FREQ_PFD:
> > > + ret = kstrtoul(buf, 10, &readin);
> > > + if (ret)
> > > + break;
> > > +
> > > + if ((readin > ADF4360_MAX_PFD_RATE) || (readin == 0)) {
> > > + ret = -EINVAL;
> > > + break;
> > > + }
> > > +
> > > + st->pfd_freq = readin;
> > > + break;
> > > + default:
> > > + ret = -EINVAL;
> > > + }
> > > +
> > > + if (ret == 0)
> > > + ret = adf4360_set_freq(st, st->freq_req);
> > > + mutex_unlock(&st->lock);
> > > +
> > > + return ret ? ret : len;
> > > +}
> > > +
> > > +static int adf4360_get_muxout_mode(struct iio_dev *indio_dev,
> > > + const struct iio_chan_spec *chan)
> > > +{
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > +
> > > + return st->muxout_mode;
> > > +}
> > > +
> > > +static int adf4360_set_muxout_mode(struct iio_dev *indio_dev,
> > > + const struct iio_chan_spec *chan,
> > > + unsigned int mode)
> > > +{
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > + unsigned int writeval;
> > > + int ret = 0;
> > > +
> > > + 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);
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static const struct iio_enum adf4360_muxout_modes_available = {
> > > + .items = adf4360_muxout_modes,
> > > + .num_items = ARRAY_SIZE(adf4360_muxout_modes),
> > > + .get = adf4360_get_muxout_mode,
> > > + .set = adf4360_set_muxout_mode,
> > > +};
> > > +
> > > +static int adf4360_get_power_down(struct iio_dev *indio_dev,
> > > + const struct iio_chan_spec *chan)
> > > +{
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > +
> > > + return st->power_down_mode;
> > > +}
> > > +
> > > +static int adf4360_set_power_down(struct iio_dev *indio_dev,
> > > + const struct iio_chan_spec *chan,
> > > + unsigned int mode)
> > > +{
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > +
> > > + return adf4360_power_down(st, mode);
> > > +}
> > > +
> > > +static const struct iio_enum adf4360_pwr_dwn_modes_available = {
> > > + .items = adf4360_power_down_modes,
> > > + .num_items = ARRAY_SIZE(adf4360_power_down_modes),
> > > + .get = adf4360_get_power_down,
> > > + .set = adf4360_set_power_down,
> > > +};
> > > +
> > > +static int adf4360_get_power_level(struct iio_dev *indio_dev,
> > > + const struct iio_chan_spec *chan)
> > > +{
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > +
> > > + return st->power_level;
> > > +}
> > > +
> > > +static int adf4360_set_power_level(struct iio_dev *indio_dev,
> > > + const struct iio_chan_spec *chan,
> > > + unsigned int level)
> > > +{
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > + unsigned int val;
> > > + int ret;
> > > +
> > > + if (adf4360_is_powerdown(st))
> > > + return -EBUSY;
> > > +
> > > + mutex_lock(&st->lock);
> > > + val = st->regs[ADF4360_CTRL] & ~ADF4360_ADDR_OPL_MSK;
> > > + val |= ADF4360_OPL(level);
> > > + ret = adf4360_write_reg(st, ADF4360_REG(ADF4360_CTRL), val);
> > > + st->power_level = level;
> > > + mutex_unlock(&st->lock);
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static const struct iio_enum adf4360_pwr_lvl_modes_available = {
> > > + .items = adf4360_power_level_modes,
> > > + .num_items = ARRAY_SIZE(adf4360_power_level_modes),
> > > + .get = adf4360_get_power_level,
> > > + .set = adf4360_set_power_level,
> > > +};
> > > +
> > > +#define _ADF4360_EXT_INFO(_name, _ident) { \
> > > + .name = _name, \
> > > + .read = adf4360_read, \
> > > + .write = adf4360_write, \
> > > + .private = _ident, \
> > > + .shared = IIO_SEPARATE, \
> > > +}
> > > +
> > > +static const struct iio_chan_spec_ext_info adf4360_ext_info[] = {
> >
> > This is a wide range of new ABI. These all need documentation
> > in Documentation/ABI/testing/sysfs-bus-iio-*
> >
> > Without docs, it's hard to discuss if these are appropriate but a few initial
> > comments...
> > > + _ADF4360_EXT_INFO("refin_frequency", ADF4360_FREQ_REFIN),
> > Looks like a control that should be matched to some clock source and
> > not change at runtime?
> >
> > > + _ADF4360_EXT_INFO("mute_till_lock_detect", ADF4360_MTLD),
> > > + _ADF4360_EXT_INFO("pfd_frequency", ADF4360_FREQ_PFD),
> > > + IIO_ENUM_AVAILABLE("muxout_mode", &adf4360_muxout_modes_available),
> > > + IIO_ENUM("muxout_mode", false, &adf4360_muxout_modes_available),
> > > + IIO_ENUM_AVAILABLE("power_down", &adf4360_pwr_dwn_modes_available),
> > > + IIO_ENUM("power_down", false, &adf4360_pwr_dwn_modes_available),
> > > + IIO_ENUM_AVAILABLE("power_level", &adf4360_pwr_lvl_modes_available),
> > > + IIO_ENUM("power_level", false, &adf4360_pwr_lvl_modes_available),
> > > + { },
> > > +};
> > > +
> > > +static const struct iio_chan_spec adf4360_chan = {
> > > + .type = IIO_ALTVOLTAGE,
> > > + .indexed = 1,
> > > + .output = 1,
> > > + .info_mask_separate = BIT(IIO_CHAN_INFO_FREQUENCY),
> > > + .ext_info = adf4360_ext_info,
> > > +};
> > > +
> > > +static int adf4360_read_raw(struct iio_dev *indio_dev,
> > > + struct iio_chan_spec const *chan,
> > > + int *val,
> > > + int *val2,
> > > + long mask)
> > > +{
> > > + 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;
> > > +};
> > > +
> > > +static int adf4360_write_raw(struct iio_dev *indio_dev,
> > > + struct iio_chan_spec const *chan,
> > > + int val,
> > > + int val2,
> > > + long mask)
> > > +{
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > + int ret;
> > > +
> > > + mutex_lock(&st->lock);
> > > + switch (mask) {
> > > + case IIO_CHAN_INFO_FREQUENCY:
> > > + ret = adf4360_set_freq(st, val);
> > > + break;
> > > + default:
> > > + ret = -EINVAL;
> > > + }
> > > + mutex_unlock(&st->lock);
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static int adf4360_reg_access(struct iio_dev *indio_dev,
> > > + unsigned int reg,
> > > + unsigned int writeval,
> > > + unsigned int *readval)
> > > +{
> > > + struct adf4360_state *st = iio_priv(indio_dev);
> > > + int ret = 0;
> > > +
> > > + if (reg >= ADF4360_REG_NUM)
> > > + return -EFAULT;
> > > +
> > > + mutex_lock(&st->lock);
> > > + if (readval) {
> > > + *readval = st->regs[reg];
> > > + } else {
> > > + writeval &= 0xFFFFFC;
> > > + ret = adf4360_write_reg(st, reg, writeval);
> > > + }
> > > + mutex_unlock(&st->lock);
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static const struct iio_info adf4360_iio_info = {
> > > + .read_raw = &adf4360_read_raw,
> > > + .write_raw = &adf4360_write_raw,
> > > + .debugfs_reg_access = &adf4360_reg_access,
> > > +};
> > > +
> > > +static int adf4360_get_gpio(struct adf4360_state *st)
> > > +{
> > > + struct device *dev = &st->spi->dev;
> > > + unsigned int val;
> > > + int ret, i;
> > > +
> > > + st->chip_en_gpio = devm_gpiod_get_optional(dev, "enable",
> > > + GPIOD_OUT_HIGH);
> > > + if (IS_ERR(st->chip_en_gpio)) {
> > > + dev_err(dev, "Chip enable GPIO error\n");
> >
> > Put handling in here to prevent an error message of DEFER.
> > Same for the other routes where this might happen.
> >
> > > + return PTR_ERR(st->chip_en_gpio);
> > > + }
> > > +
> > > + if (st->chip_en_gpio)
> > > + st->power_down_mode = ADF4360_POWER_DOWN_CE;
> > > +
> > > + st->muxout_gpio = devm_gpiod_get_optional(dev, "adi,muxout", GPIOD_IN);
> > > + if (IS_ERR(st->muxout_gpio)) {
> > > + dev_err(dev, "Muxout GPIO error\n");
> > > + return PTR_ERR(st->muxout_gpio);
> > > + }
> > > +
> > > + if (!st->muxout_gpio)
> > > + return 0;
> > > +
> > > + /* 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)) {
> > > + dev_err(dev, "Probe failed (muxout)");
> > > + return -ENODEV;
> > > + }
> > > + }
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static void adf4360_clkin_disable(void *data)
> > > +{
> > > + struct adf4360_state *st = data;
> > > +
> > > + clk_disable_unprepare(st->clkin);
> > > +}
> > > +
> > > +static int adf4360_get_clkin(struct adf4360_state *st)
> > > +{
> > > + struct device *dev = &st->spi->dev;
> > > + struct clk *clk;
> > > + int ret;
> > > +
> > > + clk = devm_clk_get(dev, "clkin");
> > > + if (IS_ERR(clk))
> > > + return PTR_ERR(clk);
> > > +
> > > + ret = clk_prepare_enable(clk);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = devm_add_action_or_reset(dev, adf4360_clkin_disable, st);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + st->clkin = clk;
> > > + st->clkin_freq = clk_get_rate(clk);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static void adf4360_clk_del_provider(void *data)
> > > +{
> > > + struct adf4360_state *st = data;
> > > +
> > > + of_clk_del_provider(st->spi->dev.of_node);
> > > +}
> > > +
> > > +static int adf4360_clk_register(struct adf4360_state *st)
> > > +{
> >
> > 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.
> >
> > > + struct spi_device *spi = st->spi;
> > > + struct clk_init_data init;
> > > + struct clk *clk;
> > > + const char *parent_name;
> > > + int ret;
> > > +
> > > + parent_name = of_clk_get_parent_name(spi->dev.of_node, 0);
> > > + if (!parent_name)
> > > + return -EINVAL;
> > > +
> > > + init.name = st->clk_out_name;
> > > + init.ops = &adf4360_clk_ops;
> > > + init.flags = CLK_SET_RATE_GATE;
> > > + init.parent_names = &parent_name;
> > > + init.num_parents = 1;
> > > +
> > > + st->output.hw.init = &init;
> > > +
> > > + clk = devm_clk_register(&spi->dev, &st->output.hw);
> > > + if (IS_ERR(clk))
> > > + return PTR_ERR(clk);
> > > +
> > > + ret = of_clk_add_provider(spi->dev.of_node, of_clk_src_simple_get, clk);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return devm_add_action_or_reset(&spi->dev, adf4360_clk_del_provider,
> > > st);
> > > +}
> > > +
> > > +static int adf4360_parse_dt(struct adf4360_state *st)
> > > +{
> > > + struct device *dev = &st->spi->dev;
> > > + u32 tmp;
> > > + int ret;
> > > +
> > > + ret = device_property_read_string(dev, "clock-output-names",
> > > + &st->clk_out_name);
> > > + if ((ret < 0) && dev->of_node)
> > > + st->clk_out_name = dev->of_node->name;
> > > +
> > > + if (st->part_id >= ID_ADF4360_7) {
> > > + /*
> > > + * 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;
> > > +
> > > + 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;
> > > + } else {
> > > + st->vco_min = st->info->vco_min;
> > > + st->vco_max = st->info->vco_max;
> > > + }
> > > +
> > > + st->pdp = device_property_read_bool(dev, "adi,loop-filter-inverting");
> > > +
> > > + ret = device_property_read_u32(dev,
> > > + "adi,loop-filter-pfd-frequency-hz",
> > > + &tmp);
> > > + if (ret == 0) {
> > > + st->pfd_freq = tmp;
> > > + } 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));
> > > + } 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;
> > > +
> > > + 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;
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int adf4360_probe(struct spi_device *spi)
> > > +{
> > > + struct iio_dev *indio_dev;
> > > + const struct spi_device_id *id = spi_get_device_id(spi);
> >
> > Given we require various dt parameters to be present, might as well
> > associate the id with the of_ structures instead and use the dt
> > calls throughout. Even better if you use the firmware type independent
> > versions.
> >
> > > + struct adf4360_state *st;
> > > + int ret;
> > > +
> > > + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> > > + if (!indio_dev)
> > > + return -ENOMEM;
> > > +
> > > + st = iio_priv(indio_dev);
> > > +
> > > + mutex_init(&st->lock);
> > > +
> > > + spi_set_drvdata(spi, indio_dev);
> > > +
> > > + st->spi = spi;
> > > + st->info = &adf4360_chip_info_tbl[id->driver_data];
> > > + st->part_id = id->driver_data;
> > > + st->muxout_mode = ADF4360_MUXOUT_LOCK_DETECT;
> > > + st->mtld = true;
> > > +
> > > + ret = adf4360_parse_dt(st);
> > > + if (ret) {
> > > + dev_err(&spi->dev, "Parsing properties failed (%d)\n", ret);
> > > + return -ENODEV;
> > > + }
> > > +
> > > + indio_dev->dev.parent = &spi->dev;
> > > +
> > > + if (spi->dev.of_node)
> > > + indio_dev->name = spi->dev.of_node->name;
> > > + else
> > > + indio_dev->name = spi_get_device_id(spi)->name;
> > > +
> > > + indio_dev->info = &adf4360_iio_info;
> > > + indio_dev->modes = INDIO_DIRECT_MODE;
> > > + indio_dev->channels = &adf4360_chan;
> > > + indio_dev->num_channels = 1;
> > > + st->output.indio_dev = indio_dev;
> > > +
> > > + ret = adf4360_get_gpio(st);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = adf4360_get_clkin(st);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + st->vdd_reg = devm_regulator_get_optional(&spi->dev, "vdd");
> > > + if (IS_ERR(st->vdd_reg)) {
> > > + if (PTR_ERR(st->vdd_reg) != -ENODEV) {
> > > + dev_err(&spi->dev, "Regulator error\n");
> > > + return PTR_ERR(st->vdd_reg);
> > > + }
> > > +
> > > + st->vdd_reg = NULL;
> > > + }
> > > +
> > > + ret = adf4360_power_down(st, ADF4360_POWER_DOWN_NORMAL);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + ret = adf4360_clk_register(st);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + return devm_iio_device_register(&spi->dev, indio_dev);
> > > +}
> > > +
> > > +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", },
> > > + {},
> > > +};
> > > +MODULE_DEVICE_TABLE(of, adf4360_of_match);
> > > +
> > > +static const struct spi_device_id adf4360_id[] = {
> >
> > As mentioned above, you can't actually probe this device
> > without a pile of dt stuff. So this fallback doesn't
> > make much sense. Use the data field in the of table
> > above and get rid of this table entirely.
> >
> > > + {"adf4360-0", ID_ADF4360_0},
> > > + {"adf4360-1", ID_ADF4360_1},
> > > + {"adf4360-2", ID_ADF4360_2},
> > > + {"adf4360-3", ID_ADF4360_3},
> > > + {"adf4360-4", ID_ADF4360_4},
> > > + {"adf4360-5", ID_ADF4360_5},
> > > + {"adf4360-6", ID_ADF4360_6},
> > > + {"adf4360-7", ID_ADF4360_7},
> > > + {"adf4360-8", ID_ADF4360_8},
> > > + {"adf4360-9", ID_ADF4360_9},
> > > + {}
> > > +};
> > > +
> > > +static struct spi_driver adf4360_driver = {
> > > + .driver = {
> > > + .name = "adf4360",
> > > + .of_match_table = adf4360_of_match,
> > > + .owner = THIS_MODULE,
> >
> > It's a long time since we had to set the .owner field for each driver.
> >
> > Follow through what happens in module_spi_driver, spi_register_driver
> > etc and you'll find it's set automatically during driver registration.
> >
> > > + },
> > > + .probe = adf4360_probe,
> > > + .id_table = adf4360_id,
> > > +};
> > > +module_spi_driver(adf4360_driver);
> > > +
> > > +MODULE_LICENSE("GPL v2");
> > > +MODULE_AUTHOR("Lars-Peter Clausen <lars@...afoo.de>");
> > > +MODULE_AUTHOR("Edward Kigwana <ekigwana@...il.com>");
> > > +MODULE_DESCRIPTION("Analog Devices ADF4360 PLL");
Powered by blists - more mailing lists