| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * ADMV8818 driver |
| * |
| * Copyright 2021 Analog Devices Inc. |
| */ |
| |
| #include <linux/bitfield.h> |
| #include <linux/bits.h> |
| #include <linux/clk.h> |
| #include <linux/device.h> |
| #include <linux/iio/iio.h> |
| #include <linux/module.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/mutex.h> |
| #include <linux/notifier.h> |
| #include <linux/regmap.h> |
| #include <linux/spi/spi.h> |
| #include <linux/units.h> |
| |
| /* ADMV8818 Register Map */ |
| #define ADMV8818_REG_SPI_CONFIG_A 0x0 |
| #define ADMV8818_REG_SPI_CONFIG_B 0x1 |
| #define ADMV8818_REG_CHIPTYPE 0x3 |
| #define ADMV8818_REG_PRODUCT_ID_L 0x4 |
| #define ADMV8818_REG_PRODUCT_ID_H 0x5 |
| #define ADMV8818_REG_FAST_LATCH_POINTER 0x10 |
| #define ADMV8818_REG_FAST_LATCH_STOP 0x11 |
| #define ADMV8818_REG_FAST_LATCH_START 0x12 |
| #define ADMV8818_REG_FAST_LATCH_DIRECTION 0x13 |
| #define ADMV8818_REG_FAST_LATCH_STATE 0x14 |
| #define ADMV8818_REG_WR0_SW 0x20 |
| #define ADMV8818_REG_WR0_FILTER 0x21 |
| #define ADMV8818_REG_WR1_SW 0x22 |
| #define ADMV8818_REG_WR1_FILTER 0x23 |
| #define ADMV8818_REG_WR2_SW 0x24 |
| #define ADMV8818_REG_WR2_FILTER 0x25 |
| #define ADMV8818_REG_WR3_SW 0x26 |
| #define ADMV8818_REG_WR3_FILTER 0x27 |
| #define ADMV8818_REG_WR4_SW 0x28 |
| #define ADMV8818_REG_WR4_FILTER 0x29 |
| #define ADMV8818_REG_LUT0_SW 0x100 |
| #define ADMV8818_REG_LUT0_FILTER 0x101 |
| #define ADMV8818_REG_LUT127_SW 0x1FE |
| #define ADMV8818_REG_LUT127_FILTER 0x1FF |
| |
| /* ADMV8818_REG_SPI_CONFIG_A Map */ |
| #define ADMV8818_SOFTRESET_N_MSK BIT(7) |
| #define ADMV8818_LSB_FIRST_N_MSK BIT(6) |
| #define ADMV8818_ENDIAN_N_MSK BIT(5) |
| #define ADMV8818_SDOACTIVE_N_MSK BIT(4) |
| #define ADMV8818_SDOACTIVE_MSK BIT(3) |
| #define ADMV8818_ENDIAN_MSK BIT(2) |
| #define ADMV8818_LSBFIRST_MSK BIT(1) |
| #define ADMV8818_SOFTRESET_MSK BIT(0) |
| |
| /* ADMV8818_REG_SPI_CONFIG_B Map */ |
| #define ADMV8818_SINGLE_INSTRUCTION_MSK BIT(7) |
| #define ADMV8818_CSB_STALL_MSK BIT(6) |
| #define ADMV8818_MASTER_SLAVE_RB_MSK BIT(5) |
| #define ADMV8818_MASTER_SLAVE_TRANSFER_MSK BIT(0) |
| |
| /* ADMV8818_REG_WR0_SW Map */ |
| #define ADMV8818_SW_IN_SET_WR0_MSK BIT(7) |
| #define ADMV8818_SW_OUT_SET_WR0_MSK BIT(6) |
| #define ADMV8818_SW_IN_WR0_MSK GENMASK(5, 3) |
| #define ADMV8818_SW_OUT_WR0_MSK GENMASK(2, 0) |
| |
| /* ADMV8818_REG_WR0_FILTER Map */ |
| #define ADMV8818_HPF_WR0_MSK GENMASK(7, 4) |
| #define ADMV8818_LPF_WR0_MSK GENMASK(3, 0) |
| |
| enum { |
| ADMV8818_BW_FREQ, |
| ADMV8818_CENTER_FREQ |
| }; |
| |
| enum { |
| ADMV8818_AUTO_MODE, |
| ADMV8818_MANUAL_MODE, |
| }; |
| |
| struct admv8818_state { |
| struct spi_device *spi; |
| struct regmap *regmap; |
| struct clk *clkin; |
| struct notifier_block nb; |
| /* Protect against concurrent accesses to the device and data content*/ |
| struct mutex lock; |
| unsigned int filter_mode; |
| u64 cf_hz; |
| }; |
| |
| static const unsigned long long freq_range_hpf[4][2] = { |
| {1750000000ULL, 3550000000ULL}, |
| {3400000000ULL, 7250000000ULL}, |
| {6600000000, 12000000000}, |
| {12500000000, 19900000000} |
| }; |
| |
| static const unsigned long long freq_range_lpf[4][2] = { |
| {2050000000ULL, 3850000000ULL}, |
| {3350000000ULL, 7250000000ULL}, |
| {7000000000, 13000000000}, |
| {12550000000, 18500000000} |
| }; |
| |
| static const struct regmap_config admv8818_regmap_config = { |
| .reg_bits = 16, |
| .val_bits = 8, |
| .read_flag_mask = 0x80, |
| .max_register = 0x1FF, |
| }; |
| |
| static const char * const admv8818_modes[] = { |
| [0] = "auto", |
| [1] = "manual" |
| }; |
| |
| static int __admv8818_hpf_select(struct admv8818_state *st, u64 freq) |
| { |
| unsigned int hpf_step = 0, hpf_band = 0, i, j; |
| u64 freq_step; |
| int ret; |
| |
| if (freq < freq_range_hpf[0][0]) |
| goto hpf_write; |
| |
| if (freq > freq_range_hpf[3][1]) { |
| hpf_step = 15; |
| hpf_band = 4; |
| |
| goto hpf_write; |
| } |
| |
| for (i = 0; i < 4; i++) { |
| freq_step = div_u64((freq_range_hpf[i][1] - |
| freq_range_hpf[i][0]), 15); |
| |
| if (freq > freq_range_hpf[i][0] && |
| (freq < freq_range_hpf[i][1] + freq_step)) { |
| hpf_band = i + 1; |
| |
| for (j = 1; j <= 16; j++) { |
| if (freq < (freq_range_hpf[i][0] + (freq_step * j))) { |
| hpf_step = j - 1; |
| break; |
| } |
| } |
| break; |
| } |
| } |
| |
| /* Close HPF frequency gap between 12 and 12.5 GHz */ |
| if (freq >= 12000 * HZ_PER_MHZ && freq <= 12500 * HZ_PER_MHZ) { |
| hpf_band = 3; |
| hpf_step = 15; |
| } |
| |
| hpf_write: |
| ret = regmap_update_bits(st->regmap, ADMV8818_REG_WR0_SW, |
| ADMV8818_SW_IN_SET_WR0_MSK | |
| ADMV8818_SW_IN_WR0_MSK, |
| FIELD_PREP(ADMV8818_SW_IN_SET_WR0_MSK, 1) | |
| FIELD_PREP(ADMV8818_SW_IN_WR0_MSK, hpf_band)); |
| if (ret) |
| return ret; |
| |
| return regmap_update_bits(st->regmap, ADMV8818_REG_WR0_FILTER, |
| ADMV8818_HPF_WR0_MSK, |
| FIELD_PREP(ADMV8818_HPF_WR0_MSK, hpf_step)); |
| } |
| |
| static int admv8818_hpf_select(struct admv8818_state *st, u64 freq) |
| { |
| int ret; |
| |
| mutex_lock(&st->lock); |
| ret = __admv8818_hpf_select(st, freq); |
| mutex_unlock(&st->lock); |
| |
| return ret; |
| } |
| |
| static int __admv8818_lpf_select(struct admv8818_state *st, u64 freq) |
| { |
| unsigned int lpf_step = 0, lpf_band = 0, i, j; |
| u64 freq_step; |
| int ret; |
| |
| if (freq > freq_range_lpf[3][1]) |
| goto lpf_write; |
| |
| if (freq < freq_range_lpf[0][0]) { |
| lpf_band = 1; |
| |
| goto lpf_write; |
| } |
| |
| for (i = 0; i < 4; i++) { |
| if (freq > freq_range_lpf[i][0] && freq < freq_range_lpf[i][1]) { |
| lpf_band = i + 1; |
| freq_step = div_u64((freq_range_lpf[i][1] - freq_range_lpf[i][0]), 15); |
| |
| for (j = 0; j <= 15; j++) { |
| if (freq < (freq_range_lpf[i][0] + (freq_step * j))) { |
| lpf_step = j; |
| break; |
| } |
| } |
| break; |
| } |
| } |
| |
| lpf_write: |
| ret = regmap_update_bits(st->regmap, ADMV8818_REG_WR0_SW, |
| ADMV8818_SW_OUT_SET_WR0_MSK | |
| ADMV8818_SW_OUT_WR0_MSK, |
| FIELD_PREP(ADMV8818_SW_OUT_SET_WR0_MSK, 1) | |
| FIELD_PREP(ADMV8818_SW_OUT_WR0_MSK, lpf_band)); |
| if (ret) |
| return ret; |
| |
| return regmap_update_bits(st->regmap, ADMV8818_REG_WR0_FILTER, |
| ADMV8818_LPF_WR0_MSK, |
| FIELD_PREP(ADMV8818_LPF_WR0_MSK, lpf_step)); |
| } |
| |
| static int admv8818_lpf_select(struct admv8818_state *st, u64 freq) |
| { |
| int ret; |
| |
| mutex_lock(&st->lock); |
| ret = __admv8818_lpf_select(st, freq); |
| mutex_unlock(&st->lock); |
| |
| return ret; |
| } |
| |
| static int admv8818_rfin_band_select(struct admv8818_state *st) |
| { |
| int ret; |
| |
| st->cf_hz = clk_get_rate(st->clkin); |
| |
| mutex_lock(&st->lock); |
| |
| ret = __admv8818_hpf_select(st, st->cf_hz); |
| if (ret) |
| goto exit; |
| |
| ret = __admv8818_lpf_select(st, st->cf_hz); |
| exit: |
| mutex_unlock(&st->lock); |
| return ret; |
| } |
| |
| static int __admv8818_read_hpf_freq(struct admv8818_state *st, u64 *hpf_freq) |
| { |
| unsigned int data, hpf_band, hpf_state; |
| int ret; |
| |
| ret = regmap_read(st->regmap, ADMV8818_REG_WR0_SW, &data); |
| if (ret) |
| return ret; |
| |
| hpf_band = FIELD_GET(ADMV8818_SW_IN_WR0_MSK, data); |
| if (!hpf_band) { |
| *hpf_freq = 0; |
| return ret; |
| } |
| |
| ret = regmap_read(st->regmap, ADMV8818_REG_WR0_FILTER, &data); |
| if (ret) |
| return ret; |
| |
| hpf_state = FIELD_GET(ADMV8818_HPF_WR0_MSK, data); |
| |
| *hpf_freq = div_u64(freq_range_hpf[hpf_band - 1][1] - freq_range_hpf[hpf_band - 1][0], 15); |
| *hpf_freq = freq_range_hpf[hpf_band - 1][0] + (*hpf_freq * hpf_state); |
| |
| return ret; |
| } |
| |
| static int admv8818_read_hpf_freq(struct admv8818_state *st, u64 *hpf_freq) |
| { |
| int ret; |
| |
| mutex_lock(&st->lock); |
| ret = __admv8818_read_hpf_freq(st, hpf_freq); |
| mutex_unlock(&st->lock); |
| |
| return ret; |
| } |
| |
| static int __admv8818_read_lpf_freq(struct admv8818_state *st, u64 *lpf_freq) |
| { |
| unsigned int data, lpf_band, lpf_state; |
| int ret; |
| |
| ret = regmap_read(st->regmap, ADMV8818_REG_WR0_SW, &data); |
| if (ret) |
| return ret; |
| |
| lpf_band = FIELD_GET(ADMV8818_SW_OUT_WR0_MSK, data); |
| if (!lpf_band) { |
| *lpf_freq = 0; |
| return ret; |
| } |
| |
| ret = regmap_read(st->regmap, ADMV8818_REG_WR0_FILTER, &data); |
| if (ret) |
| return ret; |
| |
| lpf_state = FIELD_GET(ADMV8818_LPF_WR0_MSK, data); |
| |
| *lpf_freq = div_u64(freq_range_lpf[lpf_band - 1][1] - freq_range_lpf[lpf_band - 1][0], 15); |
| *lpf_freq = freq_range_lpf[lpf_band - 1][0] + (*lpf_freq * lpf_state); |
| |
| return ret; |
| } |
| |
| static int admv8818_read_lpf_freq(struct admv8818_state *st, u64 *lpf_freq) |
| { |
| int ret; |
| |
| mutex_lock(&st->lock); |
| ret = __admv8818_read_lpf_freq(st, lpf_freq); |
| mutex_unlock(&st->lock); |
| |
| return ret; |
| } |
| |
| static int admv8818_write_raw(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, |
| int val, int val2, long info) |
| { |
| struct admv8818_state *st = iio_priv(indio_dev); |
| |
| u64 freq = ((u64)val2 << 32 | (u32)val); |
| |
| switch (info) { |
| case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: |
| return admv8818_lpf_select(st, freq); |
| case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: |
| return admv8818_hpf_select(st, freq); |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int admv8818_read_raw(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, |
| int *val, int *val2, long info) |
| { |
| struct admv8818_state *st = iio_priv(indio_dev); |
| int ret; |
| u64 freq; |
| |
| switch (info) { |
| case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: |
| ret = admv8818_read_lpf_freq(st, &freq); |
| if (ret) |
| return ret; |
| |
| *val = (u32)freq; |
| *val2 = (u32)(freq >> 32); |
| |
| return IIO_VAL_INT_64; |
| case IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY: |
| ret = admv8818_read_hpf_freq(st, &freq); |
| if (ret) |
| return ret; |
| |
| *val = (u32)freq; |
| *val2 = (u32)(freq >> 32); |
| |
| return IIO_VAL_INT_64; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int admv8818_reg_access(struct iio_dev *indio_dev, |
| unsigned int reg, |
| unsigned int write_val, |
| unsigned int *read_val) |
| { |
| struct admv8818_state *st = iio_priv(indio_dev); |
| |
| if (read_val) |
| return regmap_read(st->regmap, reg, read_val); |
| else |
| return regmap_write(st->regmap, reg, write_val); |
| } |
| |
| static int admv8818_get_mode(struct iio_dev *indio_dev, |
| const struct iio_chan_spec *chan) |
| { |
| struct admv8818_state *st = iio_priv(indio_dev); |
| |
| return st->filter_mode; |
| } |
| |
| static int admv8818_set_mode(struct iio_dev *indio_dev, |
| const struct iio_chan_spec *chan, |
| unsigned int mode) |
| { |
| struct admv8818_state *st = iio_priv(indio_dev); |
| int ret = 0; |
| |
| if (!st->clkin) { |
| if (mode == ADMV8818_MANUAL_MODE) |
| return 0; |
| |
| return -EINVAL; |
| } |
| |
| switch (mode) { |
| case ADMV8818_AUTO_MODE: |
| if (!st->filter_mode) |
| return 0; |
| |
| ret = clk_prepare_enable(st->clkin); |
| if (ret) |
| return ret; |
| |
| ret = clk_notifier_register(st->clkin, &st->nb); |
| if (ret) { |
| clk_disable_unprepare(st->clkin); |
| |
| return ret; |
| } |
| |
| break; |
| case ADMV8818_MANUAL_MODE: |
| if (st->filter_mode) |
| return 0; |
| |
| clk_disable_unprepare(st->clkin); |
| |
| ret = clk_notifier_unregister(st->clkin, &st->nb); |
| if (ret) |
| return ret; |
| |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| st->filter_mode = mode; |
| |
| return ret; |
| } |
| |
| static const struct iio_info admv8818_info = { |
| .write_raw = admv8818_write_raw, |
| .read_raw = admv8818_read_raw, |
| .debugfs_reg_access = &admv8818_reg_access, |
| }; |
| |
| static const struct iio_enum admv8818_mode_enum = { |
| .items = admv8818_modes, |
| .num_items = ARRAY_SIZE(admv8818_modes), |
| .get = admv8818_get_mode, |
| .set = admv8818_set_mode, |
| }; |
| |
| static const struct iio_chan_spec_ext_info admv8818_ext_info[] = { |
| IIO_ENUM("filter_mode", IIO_SHARED_BY_ALL, &admv8818_mode_enum), |
| IIO_ENUM_AVAILABLE("filter_mode", IIO_SHARED_BY_ALL, &admv8818_mode_enum), |
| { }, |
| }; |
| |
| #define ADMV8818_CHAN(_channel) { \ |
| .type = IIO_ALTVOLTAGE, \ |
| .output = 1, \ |
| .indexed = 1, \ |
| .channel = _channel, \ |
| .info_mask_separate = \ |
| BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ |
| BIT(IIO_CHAN_INFO_HIGH_PASS_FILTER_3DB_FREQUENCY) \ |
| } |
| |
| #define ADMV8818_CHAN_BW_CF(_channel, _admv8818_ext_info) { \ |
| .type = IIO_ALTVOLTAGE, \ |
| .output = 1, \ |
| .indexed = 1, \ |
| .channel = _channel, \ |
| .ext_info = _admv8818_ext_info, \ |
| } |
| |
| static const struct iio_chan_spec admv8818_channels[] = { |
| ADMV8818_CHAN(0), |
| ADMV8818_CHAN_BW_CF(0, admv8818_ext_info), |
| }; |
| |
| static int admv8818_freq_change(struct notifier_block *nb, unsigned long action, void *data) |
| { |
| struct admv8818_state *st = container_of(nb, struct admv8818_state, nb); |
| |
| if (action == POST_RATE_CHANGE) |
| return notifier_from_errno(admv8818_rfin_band_select(st)); |
| |
| return NOTIFY_OK; |
| } |
| |
| static void admv8818_clk_notifier_unreg(void *data) |
| { |
| struct admv8818_state *st = data; |
| |
| if (st->filter_mode == 0) |
| clk_notifier_unregister(st->clkin, &st->nb); |
| } |
| |
| static void admv8818_clk_disable(void *data) |
| { |
| struct admv8818_state *st = data; |
| |
| if (st->filter_mode == 0) |
| clk_disable_unprepare(st->clkin); |
| } |
| |
| static int admv8818_init(struct admv8818_state *st) |
| { |
| int ret; |
| struct spi_device *spi = st->spi; |
| unsigned int chip_id; |
| |
| ret = regmap_update_bits(st->regmap, ADMV8818_REG_SPI_CONFIG_A, |
| ADMV8818_SOFTRESET_N_MSK | |
| ADMV8818_SOFTRESET_MSK, |
| FIELD_PREP(ADMV8818_SOFTRESET_N_MSK, 1) | |
| FIELD_PREP(ADMV8818_SOFTRESET_MSK, 1)); |
| if (ret) { |
| dev_err(&spi->dev, "ADMV8818 Soft Reset failed.\n"); |
| return ret; |
| } |
| |
| ret = regmap_update_bits(st->regmap, ADMV8818_REG_SPI_CONFIG_A, |
| ADMV8818_SDOACTIVE_N_MSK | |
| ADMV8818_SDOACTIVE_MSK, |
| FIELD_PREP(ADMV8818_SDOACTIVE_N_MSK, 1) | |
| FIELD_PREP(ADMV8818_SDOACTIVE_MSK, 1)); |
| if (ret) { |
| dev_err(&spi->dev, "ADMV8818 SDO Enable failed.\n"); |
| return ret; |
| } |
| |
| ret = regmap_read(st->regmap, ADMV8818_REG_CHIPTYPE, &chip_id); |
| if (ret) { |
| dev_err(&spi->dev, "ADMV8818 Chip ID read failed.\n"); |
| return ret; |
| } |
| |
| if (chip_id != 0x1) { |
| dev_err(&spi->dev, "ADMV8818 Invalid Chip ID.\n"); |
| return -EINVAL; |
| } |
| |
| ret = regmap_update_bits(st->regmap, ADMV8818_REG_SPI_CONFIG_B, |
| ADMV8818_SINGLE_INSTRUCTION_MSK, |
| FIELD_PREP(ADMV8818_SINGLE_INSTRUCTION_MSK, 1)); |
| if (ret) { |
| dev_err(&spi->dev, "ADMV8818 Single Instruction failed.\n"); |
| return ret; |
| } |
| |
| if (st->clkin) |
| return admv8818_rfin_band_select(st); |
| else |
| return 0; |
| } |
| |
| static int admv8818_clk_setup(struct admv8818_state *st) |
| { |
| struct spi_device *spi = st->spi; |
| int ret; |
| |
| st->clkin = devm_clk_get_optional(&spi->dev, "rf_in"); |
| if (IS_ERR(st->clkin)) |
| return dev_err_probe(&spi->dev, PTR_ERR(st->clkin), |
| "failed to get the input clock\n"); |
| else if (!st->clkin) |
| return 0; |
| |
| ret = clk_prepare_enable(st->clkin); |
| if (ret) |
| return ret; |
| |
| ret = devm_add_action_or_reset(&spi->dev, admv8818_clk_disable, st); |
| if (ret) |
| return ret; |
| |
| st->nb.notifier_call = admv8818_freq_change; |
| ret = clk_notifier_register(st->clkin, &st->nb); |
| if (ret < 0) |
| return ret; |
| |
| return devm_add_action_or_reset(&spi->dev, admv8818_clk_notifier_unreg, st); |
| } |
| |
| static int admv8818_probe(struct spi_device *spi) |
| { |
| struct iio_dev *indio_dev; |
| struct regmap *regmap; |
| struct admv8818_state *st; |
| int ret; |
| |
| indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); |
| if (!indio_dev) |
| return -ENOMEM; |
| |
| regmap = devm_regmap_init_spi(spi, &admv8818_regmap_config); |
| if (IS_ERR(regmap)) |
| return PTR_ERR(regmap); |
| |
| st = iio_priv(indio_dev); |
| st->regmap = regmap; |
| |
| indio_dev->info = &admv8818_info; |
| indio_dev->name = "admv8818"; |
| indio_dev->channels = admv8818_channels; |
| indio_dev->num_channels = ARRAY_SIZE(admv8818_channels); |
| |
| st->spi = spi; |
| |
| ret = admv8818_clk_setup(st); |
| if (ret) |
| return ret; |
| |
| mutex_init(&st->lock); |
| |
| ret = admv8818_init(st); |
| if (ret) |
| return ret; |
| |
| return devm_iio_device_register(&spi->dev, indio_dev); |
| } |
| |
| static const struct spi_device_id admv8818_id[] = { |
| { "admv8818", 0 }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(spi, admv8818_id); |
| |
| static const struct of_device_id admv8818_of_match[] = { |
| { .compatible = "adi,admv8818" }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(of, admv8818_of_match); |
| |
| static struct spi_driver admv8818_driver = { |
| .driver = { |
| .name = "admv8818", |
| .of_match_table = admv8818_of_match, |
| }, |
| .probe = admv8818_probe, |
| .id_table = admv8818_id, |
| }; |
| module_spi_driver(admv8818_driver); |
| |
| MODULE_AUTHOR("Antoniu Miclaus <antoniu.miclaus@analog.com"); |
| MODULE_DESCRIPTION("Analog Devices ADMV8818"); |
| MODULE_LICENSE("GPL v2"); |