diff options
Diffstat (limited to 'drivers/iio/adc/ad7405.c')
-rw-r--r-- | drivers/iio/adc/ad7405.c | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/drivers/iio/adc/ad7405.c b/drivers/iio/adc/ad7405.c new file mode 100644 index 000000000000..9adf85a732ce --- /dev/null +++ b/drivers/iio/adc/ad7405.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices AD7405 driver + * + * Copyright 2025 Analog Devices Inc. + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> +#include <linux/util_macros.h> + +#include <linux/iio/backend.h> +#include <linux/iio/iio.h> + +static const unsigned int ad7405_dec_rates_range[] = { + 32, 1, 4096, +}; + +struct ad7405_chip_info { + const char *name; + const unsigned int full_scale_mv; +}; + +struct ad7405_state { + struct iio_backend *back; + const struct ad7405_chip_info *info; + unsigned int ref_frequency; + unsigned int dec_rate; +}; + +static int ad7405_set_dec_rate(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int dec_rate) +{ + struct ad7405_state *st = iio_priv(indio_dev); + int ret; + + if (dec_rate > 4096 || dec_rate < 32) + return -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = iio_backend_oversampling_ratio_set(st->back, chan->scan_index, dec_rate); + iio_device_release_direct(indio_dev); + + if (ret < 0) + return ret; + + st->dec_rate = dec_rate; + + return 0; +} + +static int ad7405_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, int *val, + int *val2, long info) +{ + struct ad7405_state *st = iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_SCALE: + *val = st->info->full_scale_mv; + *val2 = indio_dev->channels[0].scan_type.realbits - 1; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = st->dec_rate; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = DIV_ROUND_CLOSEST_ULL(st->ref_frequency, st->dec_rate); + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + *val = -(1 << (indio_dev->channels[0].scan_type.realbits - 1)); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad7405_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long info) +{ + switch (info) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (val < 0) + return -EINVAL; + return ad7405_set_dec_rate(indio_dev, chan, val); + default: + return -EINVAL; + } +} + +static int ad7405_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long info) +{ + switch (info) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = ad7405_dec_rates_range; + *type = IIO_VAL_INT; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +static const struct iio_info ad7405_iio_info = { + .read_raw = &ad7405_read_raw, + .write_raw = &ad7405_write_raw, + .read_avail = &ad7405_read_avail, +}; + +static const struct iio_chan_spec ad7405_channel = { + .type = IIO_VOLTAGE, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET), + .info_mask_shared_by_all = IIO_CHAN_INFO_SAMP_FREQ | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .indexed = 1, + .channel = 0, + .channel2 = 1, + .differential = 1, + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + }, +}; + +static const struct ad7405_chip_info ad7405_chip_info = { + .name = "ad7405", + .full_scale_mv = 320, +}; + +static const struct ad7405_chip_info adum7701_chip_info = { + .name = "adum7701", + .full_scale_mv = 320, +}; + +static const struct ad7405_chip_info adum7702_chip_info = { + .name = "adum7702", + .full_scale_mv = 64, +}; + +static const struct ad7405_chip_info adum7703_chip_info = { + .name = "adum7703", + .full_scale_mv = 320, +}; + +static const char * const ad7405_power_supplies[] = { + "vdd1", "vdd2", +}; + +static int ad7405_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct ad7405_state *st; + struct clk *clk; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->info = device_get_match_data(dev); + if (!st->info) + return dev_err_probe(dev, -EINVAL, "no chip info\n"); + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad7405_power_supplies), + ad7405_power_supplies); + if (ret) + return dev_err_probe(dev, ret, "failed to get and enable supplies"); + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + st->ref_frequency = clk_get_rate(clk); + if (!st->ref_frequency) + return -EINVAL; + + indio_dev->name = st->info->name; + indio_dev->channels = &ad7405_channel; + indio_dev->num_channels = 1; + indio_dev->info = &ad7405_iio_info; + + st->back = devm_iio_backend_get(dev, NULL); + if (IS_ERR(st->back)) + return dev_err_probe(dev, PTR_ERR(st->back), + "failed to get IIO backend"); + + ret = iio_backend_chan_enable(st->back, 0); + if (ret) + return ret; + + ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev); + if (ret) + return ret; + + ret = devm_iio_backend_enable(dev, st->back); + if (ret) + return ret; + + /* + * Set 256 decimation rate. The default value in the AXI_ADC register + * is 0, so we set the register with a decimation rate value that is + * functional for all parts. + */ + ret = ad7405_set_dec_rate(indio_dev, &indio_dev->channels[0], 256); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad7405_of_match[] = { + { .compatible = "adi,ad7405", .data = &ad7405_chip_info, }, + { .compatible = "adi,adum7701", .data = &adum7701_chip_info, }, + { .compatible = "adi,adum7702", .data = &adum7702_chip_info, }, + { .compatible = "adi,adum7703", .data = &adum7703_chip_info, }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7405_of_match); + +static struct platform_driver ad7405_driver = { + .driver = { + .name = "ad7405", + .of_match_table = ad7405_of_match, + }, + .probe = ad7405_probe, +}; +module_platform_driver(ad7405_driver); + +MODULE_AUTHOR("Dragos Bogdan <dragos.bogdan@analog.com>"); +MODULE_AUTHOR("Pop Ioan Daniel <pop.ioan-daniel@analog.com>"); +MODULE_DESCRIPTION("Analog Devices AD7405 driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BACKEND"); |