From: Nuno Sa <nuno.sa@analog.com>
To: <linux-iio@vger.kernel.org>
Cc: Jonathan Cameron <jic23@kernel.org>
Subject: [RFC PATCH 2/3] iio: adc: ad9647: add based on converter framework
Date: Fri, 4 Aug 2023 16:53:40 +0200 [thread overview]
Message-ID: <20230804145342.1600136-3-nuno.sa@analog.com> (raw)
In-Reply-To: <20230804145342.1600136-1-nuno.sa@analog.com>
Signed-off-by: Nuno Sa <nuno.sa@analog.com>
---
drivers/iio/adc/ad9467_new.c | 830 +++++++++++++++++++++++++++++++++++
1 file changed, 830 insertions(+)
create mode 100644 drivers/iio/adc/ad9467_new.c
diff --git a/drivers/iio/adc/ad9467_new.c b/drivers/iio/adc/ad9467_new.c
new file mode 100644
index 000000000000..ccdd3a893beb
--- /dev/null
+++ b/drivers/iio/adc/ad9467_new.c
@@ -0,0 +1,830 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices AD9467 SPI ADC driver
+ *
+ * Copyright 2012-2023 Analog Devices Inc.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+
+#include <linux/iio/addc/converter.h>
+#include <linux/iio/buffer-dmaengine.h>
+#include <linux/iio/iio.h>
+
+/*
+ * ADI High-Speed ADC common spi interface registers
+ * See Application-Note AN-877:
+ * https://www.analog.com/media/en/technical-documentation/application-notes/AN-877.pdf
+ */
+
+#define AN877_ADC_REG_CHIP_ID 0x01
+#define AN877_ADC_REG_CHAN_INDEX 0x05
+#define AN877_ADC_REG_TEST_IO 0x0D
+#define AN877_ADC_REG_OUTPUT_MODE 0x14
+#define AN877_ADC_REG_OUTPUT_PHASE 0x16
+#define AN877_ADC_REG_OUTPUT_DELAY 0x17
+#define AN877_ADC_REG_VREF 0x18
+#define AN877_ADC_REG_TRANSFER 0xFF
+
+/* AN877_ADC_REG_TRANSFER */
+#define AN877_ADC_TRANSFER_SYNC 0x1
+
+/* AN877_ADC_REG_OUTPUT_MODE */
+#define AN877_ADC_OUTPUT_MODE_OFFSET_BINARY 0x0
+#define AN877_ADC_OUTPUT_MODE_TWOS_COMPLEMENT 0x1
+
+/* AN877_ADC_REG_OUTPUT_PHASE */
+#define AN877_ADC_OUTPUT_EVEN_ODD_MODE_EN 0x20
+#define AN877_ADC_INVERT_DCO_CLK 0x80
+
+/* AN877_ADC_REG_TEST_IO */
+#define AN877_ADC_TESTMODE_OFF 0x0
+#define AN877_ADC_TESTMODE_PN23_SEQ 0x5
+#define AN877_ADC_TESTMODE_PN9_SEQ 0x6
+
+#define AD9647_MAX_TEST_POINTS 32
+/*
+ * Analog Devices AD9265 16-Bit, 125/105/80 MSPS ADC
+ */
+
+#define CHIPID_AD9265 0x64
+#define AD9265_DEF_OUTPUT_MODE 0x40
+#define AD9265_REG_VREF_MASK 0xC0
+
+/*
+ * Analog Devices AD9434 12-Bit, 370/500 MSPS ADC
+ */
+
+#define CHIPID_AD9434 0x6A
+#define AD9434_DEF_OUTPUT_MODE 0x00
+#define AD9434_REG_VREF_MASK 0xC0
+
+/*
+ * Analog Devices AD9467 16-Bit, 200/250 MSPS ADC
+ */
+
+#define CHIPID_AD9467 0x50
+#define AD9467_DEF_OUTPUT_MODE 0x08
+#define AD9467_REG_VREF_MASK 0x0F
+
+struct ad9467_chip_info {
+ const char *name;
+ const struct iio_chan_spec *channels;
+ const unsigned int (*scale_table)[2];
+ unsigned int id;
+ int num_scales;
+ unsigned long max_rate;
+ unsigned int default_output_mode;
+ unsigned int vref_mask;
+ unsigned int num_channels;
+ unsigned int num_lanes;
+ bool has_dco;
+};
+
+struct ad9467_state {
+ const struct ad9467_chip_info *info;
+ struct converter_backend *conv;
+ struct spi_device *spi;
+ struct clk *clk;
+ unsigned int output_mode;
+ unsigned long adc_clk;
+};
+
+/*
+ * Infer about moving to regmap (looks pretty straight)...
+ * Moreover we need to make this DMA safe
+ */
+static int ad9467_spi_read(struct spi_device *spi, unsigned int reg)
+{
+ unsigned char tbuf[2], rbuf[1];
+ int ret;
+
+ tbuf[0] = 0x80 | (reg >> 8);
+ tbuf[1] = reg & 0xFF;
+
+ ret = spi_write_then_read(spi,
+ tbuf, ARRAY_SIZE(tbuf),
+ rbuf, ARRAY_SIZE(rbuf));
+
+ if (ret < 0)
+ return ret;
+
+ return rbuf[0];
+}
+
+static int ad9467_spi_write(struct spi_device *spi, unsigned int reg,
+ unsigned int val)
+{
+ unsigned char buf[3];
+
+ buf[0] = reg >> 8;
+ buf[1] = reg & 0xFF;
+ buf[2] = val;
+
+ return spi_write(spi, buf, ARRAY_SIZE(buf));
+}
+
+static void __ad9467_get_scale(struct ad9467_state *st, int index,
+ unsigned int *val, unsigned int *val2)
+{
+ const struct iio_chan_spec *chan = &st->info->channels[0];
+ unsigned int tmp;
+
+ tmp = (st->info->scale_table[index][0] * 1000000ULL) >> chan->scan_type.realbits;
+ *val = tmp / 1000000;
+ *val2 = tmp % 1000000;
+}
+
+/* needs to check for ret codes */
+static int ad9467_get_scale(struct ad9467_state *st, int *val, int *val2)
+{
+ unsigned int i, vref_val;
+
+ vref_val = ad9467_spi_read(st->spi, AN877_ADC_REG_VREF);
+
+ vref_val &= st->info->vref_mask;
+
+ for (i = 0; i < st->info->num_scales; i++) {
+ if (vref_val == st->info->scale_table[i][1])
+ break;
+ }
+
+ if (i == st->info->num_scales)
+ return -ERANGE;
+
+ __ad9467_get_scale(st, i, val, val2);
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+/* Needs mutex and check for ret codes */
+static int ad9467_set_scale(struct ad9467_state *st, int val, int val2)
+{
+ unsigned int scale_val[2];
+ unsigned int i;
+
+ if (val != 0)
+ return -EINVAL;
+
+ for (i = 0; i < st->info->num_scales; i++) {
+ __ad9467_get_scale(st, i, &scale_val[0], &scale_val[1]);
+ if (scale_val[0] != val || scale_val[1] != val2)
+ continue;
+
+ ad9467_spi_write(st->spi, AN877_ADC_REG_VREF,
+ st->info->scale_table[i][1]);
+ ad9467_spi_write(st->spi, AN877_ADC_REG_TRANSFER,
+ AN877_ADC_TRANSFER_SYNC);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int ad9467_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct ad9467_state *st = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ return ad9467_get_scale(st, val, val2);
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = clk_get_rate(st->clk);
+
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad9647_calibrate_prepare(const struct ad9467_state *st)
+{
+ int ret;
+
+ ret = ad9467_spi_write(st->spi, AN877_ADC_REG_TEST_IO,
+ AN877_ADC_TESTMODE_PN9_SEQ);
+ if (ret)
+ return ret;
+
+ ret = ad9467_spi_write(st->spi, AN877_ADC_REG_TRANSFER,
+ AN877_ADC_TRANSFER_SYNC);
+ if (ret)
+ return ret;
+
+ ret = converter_test_pattern_set(st->conv, 0, CONVERTER_ADI_PRBS_9A);
+ if (ret)
+ return ret;
+
+ return converter_chan_enable(st->conv, 0);
+}
+
+static int ad9647_calibrate_stop(const struct ad9467_state *st)
+{
+ int ret;
+
+ ret = ad9467_spi_write(st->spi, AN877_ADC_REG_TEST_IO,
+ AN877_ADC_TESTMODE_OFF);
+ if (ret)
+ return ret;
+
+ ret = ad9467_spi_write(st->spi, AN877_ADC_REG_TRANSFER,
+ AN877_ADC_TRANSFER_SYNC);
+ if (ret)
+ return ret;
+
+ return converter_chan_disable(st->conv, 0);
+}
+
+static int ad9467_calibrate_apply(const struct ad9467_state *st,
+ unsigned int val)
+{
+ if (st->info->has_dco) {
+ int ret;
+
+ ret = ad9467_spi_write(st->spi, AN877_ADC_REG_OUTPUT_DELAY,
+ val);
+ if (ret)
+ return ret;
+
+ return ad9467_spi_write(st->spi, AN877_ADC_REG_TRANSFER,
+ AN877_ADC_TRANSFER_SYNC);
+ }
+
+ return converter_iodelay_set(st->conv, st->info->num_lanes, val);
+}
+
+static int ad9467_calibrate_status_check(const struct ad9467_state *st)
+{
+ struct converter_chan_status status = {0};
+ int ret;
+
+ ret = converter_chan_status_get(st->conv, 0, &status);
+ if (ret)
+ return ret;
+
+ if (status.errors)
+ return 1;
+
+ return 0;
+}
+
+static void ad9467_dump_table(const unsigned char *err_field,
+ unsigned int size, unsigned int val)
+{
+ unsigned int cnt;
+
+ for (cnt = 0; cnt < size; cnt++) {
+ if (cnt == val) {
+ pr_debug("|");
+ continue;
+ }
+
+ pr_debug("%c", err_field[cnt] ? '-' : 'o');
+ if (cnt == size / 2)
+ pr_debug("\n");
+ }
+}
+
+static int ad9467_find_optimal_point(const unsigned char *err_field,
+ unsigned int size)
+{
+ unsigned int val, cnt = 0, max_cnt = 0, max_start = 0;
+ int start = -1;
+
+ for (val = 0; val < size; val++) {
+ if (!err_field[val]) {
+ if (start == -1)
+ start = val;
+ cnt++;
+ } else {
+ if (cnt > max_cnt) {
+ max_cnt = cnt;
+ max_start = start;
+ }
+
+ start = -1;
+ cnt = 0;
+ }
+ }
+
+ if (cnt > max_cnt) {
+ max_cnt = cnt;
+ max_start = start;
+ }
+
+ if (!max_cnt)
+ return -EIO;
+
+ val = max_start + max_cnt / 2;
+ ad9467_dump_table(err_field, size, val);
+
+ return val;
+}
+
+static int ad9467_do_calibrate(const struct ad9467_state *st)
+{
+ unsigned char err_field[AD9647_MAX_TEST_POINTS * 2] = {0};
+ unsigned int max_val = AD9647_MAX_TEST_POINTS, val;
+ bool inv_range = false;
+ int ret;
+
+ ret = ad9647_calibrate_prepare(st);
+ if (ret)
+ return ret;
+retune:
+ if (st->info->has_dco) {
+ unsigned int phase = AN877_ADC_OUTPUT_EVEN_ODD_MODE_EN;
+
+ if (inv_range)
+ phase |= AN877_ADC_INVERT_DCO_CLK;
+
+ ret = ad9467_spi_write(st->spi, AN877_ADC_REG_OUTPUT_PHASE,
+ phase);
+ if (ret)
+ return ret;
+ } else {
+ if (inv_range)
+ ret = converter_sample_on_falling_edge(st->conv);
+ else
+ ret = converter_sample_on_rising_edge(st->conv);
+
+ if (ret)
+ return ret;
+ }
+
+ for (val = 0; val < max_val; val++) {
+ ret = ad9467_calibrate_apply(st, val);
+ if (ret)
+ return ret;
+
+ ret = ad9467_calibrate_status_check(st);
+ if (ret < 0)
+ return ret;
+
+ err_field[val + inv_range * max_val] = ret;
+ }
+
+ if (!inv_range) {
+ inv_range = true;
+ goto retune;
+ }
+
+ val = ad9467_find_optimal_point(err_field, sizeof(err_field));
+ if (val < 0)
+ return val;
+
+ if (val < max_val) {
+ if (st->info->has_dco)
+ ret = ad9467_spi_write(st->spi,
+ AN877_ADC_REG_OUTPUT_PHASE,
+ AN877_ADC_OUTPUT_EVEN_ODD_MODE_EN);
+ else
+ ret = converter_sample_on_rising_edge(st->conv);
+ } else {
+ val -= max_val + 1;
+ /*
+ * inv_range = true is the last test to run. Hence, there's no
+ * need to re-do any configuration
+ */
+ inv_range = false;
+ }
+
+ if (st->info->has_dco)
+ dev_dbg(&st->spi->dev,
+ " %s DCO 0x%X CLK %lu Hz\n", inv_range ? "INVERT" : "",
+ val, st->adc_clk);
+ else
+ dev_dbg(&st->spi->dev,
+ " %s IDELAY 0x%x\n", inv_range ? "INVERT" : "", val);
+
+ ret = ad9647_calibrate_stop(st);
+ if (ret)
+ return ret;
+
+ /* finally apply the optimal value */
+ return ad9467_calibrate_apply(st, val);
+}
+
+static int ad9467_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct ad9467_state *st = iio_priv(indio_dev);
+ long r_clk;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ return ad9467_set_scale(st, val, val2);
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+
+ r_clk = clk_round_rate(st->clk, val);
+ if (r_clk < 0 || r_clk > st->info->max_rate) {
+ dev_warn(&st->spi->dev,
+ "Error setting ADC sample rate %ld", r_clk);
+ iio_device_release_direct_mode(indio_dev);
+ return -EINVAL;
+ }
+
+ if (st->adc_clk == r_clk) {
+ iio_device_release_direct_mode(indio_dev);
+ return 0;
+ }
+
+ ret = clk_set_rate(st->clk, r_clk);
+ if (ret) {
+ iio_device_release_direct_mode(indio_dev);
+ return ret;
+ }
+
+ st->adc_clk = r_clk;
+ ret = ad9467_do_calibrate(st);
+ iio_device_release_direct_mode(indio_dev);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad9467_read_available(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct ad9467_state *st = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ *vals = (const int *)st->info->scale_table;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ /* Values are stored in a 2D matrix */
+ *length = st->info->num_scales * 2;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad9467_update_scan_mode(struct iio_dev *indio_dev,
+ const unsigned long *scan_mask)
+{
+ struct ad9467_state *st = iio_priv(indio_dev);
+ unsigned int c;
+ int ret;
+
+ for (c = 0; c < st->info->num_channels; c++) {
+ if (test_bit(c, scan_mask))
+ ret = converter_chan_enable(st->conv, c);
+ else
+ ret = converter_chan_disable(st->conv, c);
+
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ad9467_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+ unsigned int writeval, unsigned int *readval)
+{
+ struct ad9467_state *st = iio_priv(indio_dev);
+ struct spi_device *spi = st->spi;
+ int ret;
+
+ if (!readval) {
+ ret = ad9467_spi_write(spi, reg, writeval);
+ if (ret)
+ return ret;
+
+ return ad9467_spi_write(spi, AN877_ADC_REG_TRANSFER,
+ AN877_ADC_TRANSFER_SYNC);
+ }
+
+ ret = ad9467_spi_read(spi, reg);
+ if (ret < 0)
+ return ret;
+
+ *readval = ret;
+
+ return 0;
+}
+
+/* missing available scales... */
+#define AD9467_CHAN(_chan, _si, _bits, _sign) \
+{ \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .channel = _chan, \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE), \
+ .scan_index = _si, \
+ .scan_type = { \
+ .sign = _sign, \
+ .realbits = _bits, \
+ .storagebits = 16, \
+ }, \
+}
+
+static const struct iio_chan_spec ad9434_channels[] = {
+ AD9467_CHAN(0, 0, 12, 'S'),
+};
+
+static const struct iio_chan_spec ad9467_channels[] = {
+ AD9467_CHAN(0, 0, 16, 'S'),
+};
+
+static const unsigned int ad9265_scale_table[][2] = {
+ {1250, 0x00}, {1500, 0x40}, {1750, 0x80}, {2000, 0xC0},
+};
+
+static const unsigned int ad9434_scale_table[][2] = {
+ {1600, 0x1C}, {1580, 0x1D}, {1550, 0x1E}, {1520, 0x1F}, {1500, 0x00},
+ {1470, 0x01}, {1440, 0x02}, {1420, 0x03}, {1390, 0x04}, {1360, 0x05},
+ {1340, 0x06}, {1310, 0x07}, {1280, 0x08}, {1260, 0x09}, {1230, 0x0A},
+ {1200, 0x0B}, {1180, 0x0C},
+};
+
+static const unsigned int ad9467_scale_table[][2] = {
+ {2000, 0}, {2100, 6}, {2200, 7},
+ {2300, 8}, {2400, 9}, {2500, 10},
+};
+
+static const struct ad9467_chip_info ad9467_chip_tbl = {
+ .name = "ad9467",
+ .id = CHIPID_AD9467,
+ .max_rate = 250000000UL,
+ .scale_table = ad9467_scale_table,
+ .num_scales = ARRAY_SIZE(ad9467_scale_table),
+ .channels = ad9467_channels,
+ .num_channels = ARRAY_SIZE(ad9467_channels),
+ .default_output_mode = AD9467_DEF_OUTPUT_MODE,
+ .vref_mask = AD9467_REG_VREF_MASK,
+ .num_lanes = 8,
+};
+
+static const struct ad9467_chip_info ad9265_chip_tbl = {
+ .name = "ad9265",
+ .id = CHIPID_AD9265,
+ .max_rate = 125000000UL,
+ .scale_table = ad9265_scale_table,
+ .num_scales = ARRAY_SIZE(ad9265_scale_table),
+ .channels = ad9467_channels,
+ .num_channels = ARRAY_SIZE(ad9467_channels),
+ .default_output_mode = AD9265_DEF_OUTPUT_MODE,
+ .vref_mask = AD9265_REG_VREF_MASK,
+ .has_dco = true,
+};
+
+static const struct ad9467_chip_info ad9434_chip_tbl = {
+ .name = "ad9434",
+ .id = CHIPID_AD9434,
+ .max_rate = 500000000UL,
+ .scale_table = ad9434_scale_table,
+ .num_scales = ARRAY_SIZE(ad9434_scale_table),
+ .channels = ad9434_channels,
+ .num_channels = ARRAY_SIZE(ad9434_channels),
+ .default_output_mode = AD9434_DEF_OUTPUT_MODE,
+ .vref_mask = AD9434_REG_VREF_MASK,
+ .num_lanes = 6,
+};
+
+static const struct iio_info ad9467_info = {
+ .read_raw = ad9467_read_raw,
+ .write_raw = ad9467_write_raw,
+ .update_scan_mode = ad9467_update_scan_mode,
+ .debugfs_reg_access = ad9467_reg_access,
+ .read_avail = ad9467_read_available,
+};
+
+static int ad9467_reset(struct device *dev)
+{
+ struct gpio_desc *gpio;
+
+ gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(gpio))
+ return PTR_ERR(gpio);
+ if (!gpio)
+ return 0;
+
+ fsleep(1);
+ gpiod_set_value_cansleep(gpio, 0);
+ fsleep(10);
+
+ return 0;
+}
+
+/*
+ * Also candidate for a generic helper...
+ *
+ * This is something that I don't like much because, hardwarewise, the dma is
+ * connected to the backend device so it would make sense for the dma
+ * properties to be in the platform device rather than the frontend. However,
+ * detaching the IIO DMA buffer like that from the place where the IIO
+ * device is handled would feel equally odd and, while doable, it would
+ * require some hacking and new converter ops to make sure that resources
+ * lifetime feel right (so also export the non devm_ @iio_dmaengine_buffer_alloc()).
+ */
+static int ad9467_buffer_get(struct iio_dev *indio_dev)
+{
+ struct device *dev = indio_dev->dev.parent;
+ const char *dma_name;
+
+ if (!device_property_present(dev, "dmas"))
+ return 0;
+
+ if (device_property_read_string(dev, "dma-names", &dma_name))
+ dma_name = "rx";
+
+ return devm_iio_dmaengine_buffer_setup(dev, indio_dev, dma_name);
+}
+
+static int ad9467_outputmode_set(struct spi_device *spi, unsigned int mode)
+{
+ int ret;
+
+ ret = ad9467_spi_write(spi, AN877_ADC_REG_OUTPUT_MODE, mode);
+ if (ret < 0)
+ return ret;
+
+ return ad9467_spi_write(spi, AN877_ADC_REG_TRANSFER,
+ AN877_ADC_TRANSFER_SYNC);
+}
+
+static int ad9467_channels_setup(const struct ad9467_state *st, bool test_mode)
+{
+ struct converter_data_fmt data;
+ unsigned int c, mode;
+ int ret;
+
+ if (test_mode) {
+ data.enable = false;
+ mode = st->info->default_output_mode;
+ } else {
+ mode = st->info->default_output_mode |
+ AN877_ADC_OUTPUT_MODE_TWOS_COMPLEMENT;
+ data.type = CONVERTER_TWOS_COMPLEMENT;
+ data.sign_extend = true;
+ data.enable = true;
+ }
+
+ ret = ad9467_outputmode_set(st->spi, mode);
+ if (ret)
+ return ret;
+
+ for (c = 0; c < st->info->num_channels; c++) {
+ ret = converter_data_format_set(st->conv, c, &data);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ad9467_calibrate(const struct ad9467_state *st)
+{
+ int ret;
+
+ ret = ad9467_channels_setup(st, true);
+ if (ret)
+ return ret;
+
+ ret = ad9467_do_calibrate(st);
+ if (ret)
+ return ret;
+
+ return ad9467_channels_setup(st, false);
+}
+
+static int ad9467_init(struct converter_frontend *frontend, struct device *dev)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct iio_dev *indio_dev;
+ struct ad9467_state *st;
+ unsigned int id;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->spi = spi;
+
+ st->info = spi_get_device_match_data(spi);
+ if (!st->info)
+ return -EINVAL;
+
+ st->conv = converter_get(frontend, NULL);
+ if (IS_ERR(st->conv))
+ return PTR_ERR(st->conv);
+
+ st->clk = devm_clk_get_enabled(dev, "adc-clk");
+ if (IS_ERR(st->clk))
+ return PTR_ERR(st->clk);
+
+ st->adc_clk = clk_get_rate(st->clk);
+
+ ret = ad9467_reset(dev);
+ if (ret)
+ return ret;
+
+ id = ad9467_spi_read(spi, AN877_ADC_REG_CHIP_ID);
+ if (id != st->info->id) {
+ dev_err(dev, "Mismatch CHIP_ID, got 0x%X, expected 0x%X\n",
+ id, st->info->id);
+ return -ENODEV;
+ }
+
+ indio_dev->name = st->info->name;
+ indio_dev->channels = st->info->channels;
+ indio_dev->num_channels = st->info->num_channels;
+ indio_dev->info = &ad9467_info;
+
+ ret = ad9467_buffer_get(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = converter_enable(st->conv);
+ if (ret)
+ return ret;
+
+ ret = ad9467_calibrate(st);
+ if (ret)
+ return ret;
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret)
+ return ret;
+
+ converter_add_direct_reg_access(st->conv, indio_dev);
+
+ return 0;
+}
+
+static const struct frontend_ops ad9467_ops = {
+ .frontend_init = ad9467_init,
+};
+
+static int ad9467_probe(struct spi_device *spi)
+{
+ return converter_frontend_add(&spi->dev, &ad9467_ops);
+}
+
+/*
+ * It actually matters to remove the frontend in the .remove() hook. This means
+ * that all the converters (and the frontend) will be tear down before running
+ * any specific devres cleanup (at the driver core level). What this all means is
+ * that we can use devm_ apis in .frontend_init() and being sure those resources
+ * will be released after the backend resources and before any devm_* used
+ * in .probe().
+ */
+static void ad9467_remove(struct spi_device *spi)
+{
+ converter_del(&spi->dev);
+}
+
+static const struct of_device_id ad9467_of_match[] = {
+ { .compatible = "adi,ad9265", .data = &ad9265_chip_tbl, },
+ { .compatible = "adi,ad9434", .data = &ad9434_chip_tbl, },
+ { .compatible = "adi,ad9467-new", .data = &ad9467_chip_tbl, },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ad9467_of_match);
+
+static const struct spi_device_id ad9467_ids[] = {
+ { "ad9265", (kernel_ulong_t)&ad9265_chip_tbl },
+ { "ad9434", (kernel_ulong_t)&ad9434_chip_tbl },
+ { "ad9467-new", (kernel_ulong_t)&ad9467_chip_tbl },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, ad9467_ids);
+
+static struct spi_driver ad9467_driver = {
+ .driver = {
+ .name = "ad9467",
+ .of_match_table = ad9467_of_match,
+ },
+ .probe = ad9467_probe,
+ .remove = ad9467_remove,
+ .id_table = ad9467_ids,
+};
+module_spi_driver(ad9467_driver);
+
+MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD9467 ADC driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(IIO_CONVERTER);
--
2.41.0
next prev parent reply other threads:[~2023-08-04 14:51 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-08-04 14:53 [RFC PATCH 0/3] Add converter framework Nuno Sa
2023-08-04 14:53 ` [RFC PATCH 1/3] iio: addac: add new " Nuno Sa
2023-08-30 17:02 ` Jonathan Cameron
2023-08-31 9:32 ` Nuno Sá
2023-09-03 10:56 ` Jonathan Cameron
2023-09-04 14:14 ` Nuno Sá
2023-09-04 15:31 ` Jonathan Cameron
2023-11-13 17:20 ` Olivier MOYSAN
2023-11-14 9:03 ` Nuno Sá
2023-11-16 15:42 ` Olivier MOYSAN
2023-08-04 14:53 ` Nuno Sa [this message]
2023-08-30 17:13 ` [RFC PATCH 2/3] iio: adc: ad9647: add based on " Jonathan Cameron
2023-08-04 14:53 ` [RFC PATCH 3/3] iio: adc: adi-axi-adc: add based on new " Nuno Sa
2023-08-30 17:15 ` Jonathan Cameron
2023-08-30 16:29 ` [RFC PATCH 0/3] Add " Jonathan Cameron
2023-08-30 16:30 ` Jonathan Cameron
2023-08-31 8:20 ` Nuno Sá
2023-08-31 9:28 ` Jonathan Cameron
2023-08-31 10:58 ` Nuno Sá
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20230804145342.1600136-3-nuno.sa@analog.com \
--to=nuno.sa@analog.com \
--cc=jic23@kernel.org \
--cc=linux-iio@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.