root/drivers/iio/frequency/admv4420.c
// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
/*
 * ADMV4420
 *
 * Copyright 2021 Analog Devices Inc.
 */

#include <linux/bitfield.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include <linux/units.h>

#include <linux/unaligned.h>

/* ADMV4420 Register Map */
#define ADMV4420_SPI_CONFIG_1                   0x00
#define ADMV4420_SPI_CONFIG_2                   0x01
#define ADMV4420_CHIPTYPE                       0x03
#define ADMV4420_PRODUCT_ID_L                   0x04
#define ADMV4420_PRODUCT_ID_H                   0x05
#define ADMV4420_SCRATCHPAD                     0x0A
#define ADMV4420_SPI_REV                        0x0B
#define ADMV4420_ENABLES                        0x103
#define ADMV4420_SDO_LEVEL                      0x108
#define ADMV4420_INT_L                          0x200
#define ADMV4420_INT_H                          0x201
#define ADMV4420_FRAC_L                         0x202
#define ADMV4420_FRAC_M                         0x203
#define ADMV4420_FRAC_H                         0x204
#define ADMV4420_MOD_L                          0x208
#define ADMV4420_MOD_M                          0x209
#define ADMV4420_MOD_H                          0x20A
#define ADMV4420_R_DIV_L                        0x20C
#define ADMV4420_R_DIV_H                        0x20D
#define ADMV4420_REFERENCE                      0x20E
#define ADMV4420_VCO_DATA_READBACK1             0x211
#define ADMV4420_VCO_DATA_READBACK2             0x212
#define ADMV4420_PLL_MUX_SEL                    0x213
#define ADMV4420_LOCK_DETECT                    0x214
#define ADMV4420_BAND_SELECT                    0x215
#define ADMV4420_VCO_ALC_TIMEOUT                0x216
#define ADMV4420_VCO_MANUAL                     0x217
#define ADMV4420_ALC                            0x219
#define ADMV4420_VCO_TIMEOUT1                   0x21C
#define ADMV4420_VCO_TIMEOUT2                   0x21D
#define ADMV4420_VCO_BAND_DIV                   0x21E
#define ADMV4420_VCO_READBACK_SEL               0x21F
#define ADMV4420_AUTOCAL                        0x226
#define ADMV4420_CP_STATE                       0x22C
#define ADMV4420_CP_BLEED_EN                    0x22D
#define ADMV4420_CP_CURRENT                     0x22E
#define ADMV4420_CP_BLEED                       0x22F

#define ADMV4420_SPI_CONFIG_1_SDOACTIVE         (BIT(4) | BIT(3))
#define ADMV4420_SPI_CONFIG_1_ENDIAN            (BIT(5) | BIT(2))
#define ADMV4420_SPI_CONFIG_1_SOFTRESET         (BIT(7) | BIT(1))

#define ADMV4420_REFERENCE_DIVIDE_BY_2_MASK     BIT(0)
#define ADMV4420_REFERENCE_MODE_MASK            BIT(1)
#define ADMV4420_REFERENCE_DOUBLER_MASK         BIT(2)

#define ADMV4420_REF_DIVIDER_MAX_VAL            GENMASK(9, 0)
#define ADMV4420_N_COUNTER_INT_MAX              GENMASK(15, 0)
#define ADMV4420_N_COUNTER_FRAC_MAX             GENMASK(23, 0)
#define ADMV4420_N_COUNTER_MOD_MAX              GENMASK(23, 0)

#define ENABLE_PLL                              BIT(6)
#define ENABLE_LO                               BIT(5)
#define ENABLE_VCO                              BIT(3)
#define ENABLE_IFAMP                            BIT(2)
#define ENABLE_MIXER                            BIT(1)
#define ENABLE_LNA                              BIT(0)

#define ADMV4420_SCRATCH_PAD_VAL_1              0xAD
#define ADMV4420_SCRATCH_PAD_VAL_2              0xEA

#define ADMV4420_REF_FREQ_HZ                    50000000
#define MAX_N_COUNTER                           655360UL
#define MAX_R_DIVIDER                           1024
#define ADMV4420_DEFAULT_LO_FREQ_HZ             16750000000ULL

enum admv4420_mux_sel {
        ADMV4420_LOW = 0,
        ADMV4420_LOCK_DTCT = 1,
        ADMV4420_R_COUNTER_PER_2 = 4,
        ADMV4420_N_CONUTER_PER_2 = 5,
        ADMV4420_HIGH = 8,
};

struct admv4420_reference_block {
        bool doubler_en;
        bool divide_by_2_en;
        bool ref_single_ended;
        u32 divider;
};

struct admv4420_n_counter {
        u32 int_val;
        u32 frac_val;
        u32 mod_val;
        u32 n_counter;
};

struct admv4420_state {
        struct spi_device               *spi;
        struct regmap                   *regmap;
        u64                             vco_freq_hz;
        u64                             lo_freq_hz;
        struct admv4420_reference_block ref_block;
        struct admv4420_n_counter       n_counter;
        enum admv4420_mux_sel           mux_sel;
        struct mutex                    lock;
        u8                              transf_buf[4] __aligned(IIO_DMA_MINALIGN);
};

static const struct regmap_config admv4420_regmap_config = {
        .reg_bits = 16,
        .val_bits = 8,
        .read_flag_mask = BIT(7),
};

static int admv4420_reg_access(struct iio_dev *indio_dev,
                               u32 reg, u32 writeval,
                               u32 *readval)
{
        struct admv4420_state *st = iio_priv(indio_dev);

        if (readval)
                return regmap_read(st->regmap, reg, readval);
        else
                return regmap_write(st->regmap, reg, writeval);
}

static int admv4420_set_n_counter(struct admv4420_state *st, u32 int_val,
                                  u32 frac_val, u32 mod_val)
{
        int ret;

        put_unaligned_le32(frac_val, st->transf_buf);
        ret = regmap_bulk_write(st->regmap, ADMV4420_FRAC_L, st->transf_buf, 3);
        if (ret)
                return ret;

        put_unaligned_le32(mod_val, st->transf_buf);
        ret = regmap_bulk_write(st->regmap, ADMV4420_MOD_L, st->transf_buf, 3);
        if (ret)
                return ret;

        put_unaligned_le32(int_val, st->transf_buf);
        return regmap_bulk_write(st->regmap, ADMV4420_INT_L, st->transf_buf, 2);
}

static int admv4420_read_raw(struct iio_dev *indio_dev,
                             struct iio_chan_spec const *chan,
                             int *val, int *val2, long info)
{
        struct admv4420_state *st = iio_priv(indio_dev);

        switch (info) {
        case IIO_CHAN_INFO_FREQUENCY:

                *val = div_u64_rem(st->lo_freq_hz, MICRO, val2);

                return IIO_VAL_INT_PLUS_MICRO;
        default:
                return -EINVAL;
        }
}

static const struct iio_info admv4420_info = {
        .read_raw = admv4420_read_raw,
        .debugfs_reg_access = &admv4420_reg_access,
};

static const struct iio_chan_spec admv4420_channels[] = {
        {
                .type = IIO_ALTVOLTAGE,
                .output = 0,
                .indexed = 1,
                .channel = 0,
                .info_mask_separate = BIT(IIO_CHAN_INFO_FREQUENCY),
        },
};

static void admv4420_fw_parse(struct admv4420_state *st)
{
        struct device *dev = &st->spi->dev;
        u32 tmp;
        int ret;

        ret = device_property_read_u32(dev, "adi,lo-freq-khz", &tmp);
        if (!ret)
                st->lo_freq_hz = (u64)tmp * KILO;

        st->ref_block.ref_single_ended = device_property_read_bool(dev,
                                                                   "adi,ref-ext-single-ended-en");
}

static inline uint64_t admv4420_calc_pfd_vco(struct admv4420_state *st)
{
        return div_u64(st->vco_freq_hz * 10, st->n_counter.n_counter);
}

static inline uint32_t admv4420_calc_pfd_ref(struct admv4420_state *st)
{
        uint32_t tmp;
        u8 doubler, divide_by_2;

        doubler = st->ref_block.doubler_en ? 2 : 1;
        divide_by_2 = st->ref_block.divide_by_2_en ? 2 : 1;
        tmp = ADMV4420_REF_FREQ_HZ * doubler;

        return (tmp / (st->ref_block.divider * divide_by_2));
}

static int admv4420_calc_parameters(struct admv4420_state *st)
{
        u64 pfd_ref, pfd_vco;
        bool sol_found = false;

        st->ref_block.doubler_en = false;
        st->ref_block.divide_by_2_en = false;
        st->vco_freq_hz = div_u64(st->lo_freq_hz, 2);

        for (st->ref_block.divider = 1; st->ref_block.divider < MAX_R_DIVIDER;
            st->ref_block.divider++) {
                pfd_ref = admv4420_calc_pfd_ref(st);
                for (st->n_counter.n_counter = 1; st->n_counter.n_counter < MAX_N_COUNTER;
                    st->n_counter.n_counter++) {
                        pfd_vco = admv4420_calc_pfd_vco(st);
                        if (pfd_ref == pfd_vco) {
                                sol_found = true;
                                break;
                        }
                }

                if (sol_found)
                        break;

                st->n_counter.n_counter = 1;
        }
        if (!sol_found)
                return -1;

        st->n_counter.int_val = div_u64_rem(st->n_counter.n_counter, 10, &st->n_counter.frac_val);
        st->n_counter.mod_val = 10;

        return 0;
}

static int admv4420_setup(struct iio_dev *indio_dev)
{
        struct admv4420_state *st = iio_priv(indio_dev);
        struct device *dev = indio_dev->dev.parent;
        u32 val;
        int ret;

        ret = regmap_write(st->regmap, ADMV4420_SPI_CONFIG_1,
                           ADMV4420_SPI_CONFIG_1_SOFTRESET);
        if (ret)
                return ret;

        ret = regmap_write(st->regmap, ADMV4420_SPI_CONFIG_1,
                           ADMV4420_SPI_CONFIG_1_SDOACTIVE |
                           ADMV4420_SPI_CONFIG_1_ENDIAN);
        if (ret)
                return ret;

        ret = regmap_write(st->regmap,
                           ADMV4420_SCRATCHPAD,
                           ADMV4420_SCRATCH_PAD_VAL_1);
        if (ret)
                return ret;

        ret = regmap_read(st->regmap, ADMV4420_SCRATCHPAD, &val);
        if (ret)
                return ret;

        if (val != ADMV4420_SCRATCH_PAD_VAL_1) {
                dev_err(dev, "Failed ADMV4420 to read/write scratchpad %x ", val);
                return -EIO;
        }

        ret = regmap_write(st->regmap,
                           ADMV4420_SCRATCHPAD,
                           ADMV4420_SCRATCH_PAD_VAL_2);
        if (ret)
                return ret;

        ret = regmap_read(st->regmap, ADMV4420_SCRATCHPAD, &val);
        if (ret)
                return ret;

        if (val != ADMV4420_SCRATCH_PAD_VAL_2) {
                dev_err(dev, "Failed to read/write scratchpad %x ", val);
                return -EIO;
        }

        st->mux_sel = ADMV4420_LOCK_DTCT;
        st->lo_freq_hz = ADMV4420_DEFAULT_LO_FREQ_HZ;

        admv4420_fw_parse(st);

        ret = admv4420_calc_parameters(st);
        if (ret) {
                dev_err(dev, "Failed calc parameters for %lld ", st->vco_freq_hz);
                return ret;
        }

        ret = regmap_write(st->regmap, ADMV4420_R_DIV_L,
                           FIELD_GET(0xFF, st->ref_block.divider));
        if (ret)
                return ret;

        ret = regmap_write(st->regmap, ADMV4420_R_DIV_H,
                           FIELD_GET(0xFF00, st->ref_block.divider));
        if (ret)
                return ret;

        ret = regmap_write(st->regmap, ADMV4420_REFERENCE,
                           st->ref_block.divide_by_2_en |
                           FIELD_PREP(ADMV4420_REFERENCE_MODE_MASK, st->ref_block.ref_single_ended) |
                           FIELD_PREP(ADMV4420_REFERENCE_DOUBLER_MASK, st->ref_block.doubler_en));
        if (ret)
                return ret;

        ret = admv4420_set_n_counter(st, st->n_counter.int_val,
                                     st->n_counter.frac_val,
                                     st->n_counter.mod_val);
        if (ret)
                return ret;

        ret = regmap_write(st->regmap, ADMV4420_PLL_MUX_SEL, st->mux_sel);
        if (ret)
                return ret;

        return regmap_write(st->regmap, ADMV4420_ENABLES,
                            ENABLE_PLL | ENABLE_LO | ENABLE_VCO |
                            ENABLE_IFAMP | ENABLE_MIXER | ENABLE_LNA);
}

static int admv4420_probe(struct spi_device *spi)
{
        struct iio_dev *indio_dev;
        struct admv4420_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, &admv4420_regmap_config);
        if (IS_ERR(regmap))
                return dev_err_probe(&spi->dev, PTR_ERR(regmap),
                                     "Failed to initializing spi regmap\n");

        st = iio_priv(indio_dev);
        st->spi = spi;
        st->regmap = regmap;

        indio_dev->name = "admv4420";
        indio_dev->info = &admv4420_info;
        indio_dev->channels = admv4420_channels;
        indio_dev->num_channels = ARRAY_SIZE(admv4420_channels);

        ret = admv4420_setup(indio_dev);
        if (ret) {
                dev_err(&spi->dev, "Setup ADMV4420 failed (%d)\n", ret);
                return ret;
        }

        return devm_iio_device_register(&spi->dev, indio_dev);
}

static const struct of_device_id admv4420_of_match[] = {
        { .compatible = "adi,admv4420" },
        { }
};

MODULE_DEVICE_TABLE(of, admv4420_of_match);

static struct spi_driver admv4420_driver = {
        .driver = {
                .name = "admv4420",
                .of_match_table = admv4420_of_match,
        },
        .probe = admv4420_probe,
};

module_spi_driver(admv4420_driver);

MODULE_AUTHOR("Cristian Pop <cristian.pop@analog.com>");
MODULE_DESCRIPTION("Analog Devices ADMV44200 K Band Downconverter");
MODULE_LICENSE("Dual BSD/GPL");