new file mode 100644
@@ -0,0 +1,405 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices Generic AXI ADC IP core
+ * Link: https://wiki.analog.com/resources/fpga/docs/axi_adc_ip
+ *
+ * Copyright 2012-2023 Analog Devices Inc.
+ */
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/limits.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include <linux/fpga/adi-axi-common.h>
+#include <linux/iio/addc/converter.h>
+
+/*
+ * Register definitions:
+ * https://wiki.analog.com/resources/fpga/docs/axi_adc_ip#register_map
+ */
+
+/* ADC systhesis configuration*/
+#define AXI_ADC_REG_CONFIG 0x000c
+#define AXI_ADC_DATAFORMAT_DISABLE_MASK BIT(2)
+
+/* ADC controls */
+#define AXI_ADC_REG_RSTN 0x0040
+#define AXI_ADC_RSTN_RESET_MASK GENMASK(1, 0)
+#define AXI_ADC_RSTN_MMCM_RSTN BIT(1)
+#define AXI_ADC_RSTN_RSTN BIT(0)
+
+#define AXI_ADC_REG_CTRL 0x0044
+#define AXI_ADC_CTRL_DDR_EDGESEL_MASK BIT(1)
+
+/* ADC Channel controls */
+#define AXI_ADC_REG_CHAN_CTRL(c) (0x0400 + (c) * 0x40)
+#define AXI_ADC_CHAN_CTRL_FMT_MASK GENMASK(6, 4)
+#define AXI_ADC_CHAN_CTRL_FMT_EN BIT(0)
+#define AXI_ADC_CHAN_CTRL_FMT_BIN_OFF BIT(1)
+#define AXI_ADC_CHAN_CTRL_FMT_SIGEXT BIT(2)
+#define AXI_ADC_CHAN_CTRL_EN_MASK BIT(0)
+
+#define AXI_ADC_REG_CHAN_STATUS(c) (0x0404 + (c) * 0x40)
+#define AXI_ADC_CHAN_STAT_PN_MASK GENMASK(2, 1)
+
+#define AXI_ADC_REG_CHAN_CTRL_3(c) (0x0418 + (c) * 0x40)
+#define AXI_ADC_CHAN_PN_SEL_MASK GENMASK(19, 16)
+
+/* IO Delays */
+#define AXI_ADC_REG_DELAY(l) (0x0800 + (l) * 0x4)
+#define AXI_ADC_DELAY_CTRL_MASK GENMASK(4, 0)
+
+enum {
+ AXI_ADC_PN9A,
+ AXI_ADC_PN23A,
+ AXI_ADC_PN7 = 0x4,
+ AXI_ADC_PN15,
+ AXI_ADC_PN23,
+ AXI_ADC_PN31,
+ AXI_ADC_PNX = 0x9,
+ AXI_ADC_RAMP_NIBBLE,
+ AXI_ADC_RAMP_16,
+};
+
+struct axi_adc_state {
+ struct regmap *regmap;
+ /* Protect against concurrent access to the device registers */
+ struct mutex lock;
+ struct device *dev;
+ u32 capabilities;
+};
+
+static int axi_adc_iodelay_set(struct converter_backend *conv,
+ unsigned int num_lanes, unsigned int delay)
+{
+ struct axi_adc_state *st = converter_get_drvdata(conv);
+ unsigned int l;
+ u32 val;
+ int ret;
+
+ if (delay > FIELD_MAX(AXI_ADC_DELAY_CTRL_MASK))
+ return -EINVAL;
+
+ mutex_lock(&st->lock);
+ for (l = 0; l < num_lanes; l++) {
+ ret = regmap_update_bits(st->regmap, AXI_ADC_REG_DELAY(l),
+ AXI_ADC_DELAY_CTRL_MASK, delay);
+ if (ret)
+ break;
+ /*
+ * If a readback is ~0, that means there are issues with the
+ * delay_clk
+ */
+ ret = regmap_read(st->regmap, AXI_ADC_REG_DELAY(l), &val);
+ if (val == U32_MAX) {
+ ret = -EIO;
+ break;
+ }
+ }
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static int axi_adc_ddr_edge_set(struct converter_backend *conv,
+ enum converter_edge edge)
+{
+ struct axi_adc_state *st = converter_get_drvdata(conv);
+
+ if (edge == CONVERTER_RISING_EDGE_SAMPLE)
+ return regmap_clear_bits(st->regmap, AXI_ADC_REG_CTRL,
+ AXI_ADC_CTRL_DDR_EDGESEL_MASK);
+ if (edge == CONVERTER_FALLING_EDGE_SAMPLE)
+ return regmap_set_bits(st->regmap, AXI_ADC_REG_CTRL,
+ AXI_ADC_CTRL_DDR_EDGESEL_MASK);
+
+ return -EINVAL;
+}
+
+static const struct converter_test_pattern_xlate axi_adc_test_pattern[] = {
+ {CONVERTER_PRBS_7, AXI_ADC_PN7},
+ {CONVERTER_PRBS_15, AXI_ADC_PN15},
+ {CONVERTER_PRBS_15, AXI_ADC_PN15},
+ {CONVERTER_PRBS_23, AXI_ADC_PN23},
+ {CONVERTER_PRBS_31, AXI_ADC_PN31},
+ {CONVERTER_ADI_PRBS_9A, AXI_ADC_PN9A},
+ {CONVERTER_ADI_PRBS_23A, AXI_ADC_PN23A},
+ {CONVERTER_ADI_PRBS_X, AXI_ADC_PNX},
+ {CONVERTER_RAMP_NIBBLE, AXI_ADC_RAMP_NIBBLE},
+ {CONVERTER_RAMP_16, AXI_ADC_RAMP_16},
+};
+
+static int axi_adc_test_pattern_set(struct converter_backend *conv,
+ unsigned int chan,
+ enum converter_test_pattern pattern)
+{
+ struct axi_adc_state *st = converter_get_drvdata(conv);
+ u32 val;
+
+ val = converter_test_pattern_xlate(pattern, axi_adc_test_pattern);
+ if (val < 0)
+ return val;
+
+ return regmap_update_bits(st->regmap, AXI_ADC_REG_CHAN_CTRL_3(chan),
+ AXI_ADC_CHAN_PN_SEL_MASK,
+ FIELD_PREP(AXI_ADC_CHAN_PN_SEL_MASK, val));
+}
+
+static int axi_adc_chan_status_get(struct converter_backend *conv,
+ unsigned int chan,
+ struct converter_chan_status *status)
+{
+ struct axi_adc_state *st = converter_get_drvdata(conv);
+ int ret;
+ u32 val;
+
+ mutex_lock(&st->lock);
+
+ /* reset test bits by setting them */
+ ret = regmap_set_bits(st->regmap, AXI_ADC_REG_CHAN_STATUS(chan),
+ AXI_ADC_CHAN_STAT_PN_MASK);
+ if (ret)
+ goto out_unlock;
+
+ fsleep(5000);
+
+ ret = regmap_read(st->regmap, AXI_ADC_REG_CHAN_STATUS(chan), &val);
+ if (ret)
+ goto out_unlock;
+
+ mutex_unlock(&st->lock);
+
+ if (AXI_ADC_CHAN_STAT_PN_MASK & val)
+ status->errors = true;
+
+ return 0;
+
+out_unlock:
+ mutex_unlock(&st->lock);
+ return ret;
+}
+
+static int axi_adc_chan_enable(struct converter_backend *conv,
+ unsigned int chan)
+{
+ struct axi_adc_state *st = converter_get_drvdata(conv);
+
+ return regmap_set_bits(st->regmap, AXI_ADC_REG_CHAN_CTRL(chan),
+ AXI_ADC_CHAN_CTRL_EN_MASK);
+}
+
+static int axi_adc_chan_disable(struct converter_backend *conv,
+ unsigned int chan)
+{
+ struct axi_adc_state *st = converter_get_drvdata(conv);
+
+ return regmap_clear_bits(st->regmap, AXI_ADC_REG_CHAN_CTRL(chan),
+ AXI_ADC_CHAN_CTRL_EN_MASK);
+}
+
+static int axi_adc_data_format_set(struct converter_backend *conv,
+ unsigned int chan,
+ const struct converter_data_fmt *data)
+{
+ struct axi_adc_state *st = converter_get_drvdata(conv);
+ u32 val = 0;
+
+ if (FIELD_GET(AXI_ADC_DATAFORMAT_DISABLE_MASK, st->capabilities))
+ /* data format not available */
+ return -ENOTSUPP;
+
+ if (!data->enable)
+ return regmap_clear_bits(st->regmap,
+ AXI_ADC_REG_CHAN_CTRL(chan),
+ AXI_ADC_CHAN_CTRL_FMT_MASK);
+
+ val = FIELD_PREP(AXI_ADC_CHAN_CTRL_FMT_MASK, AXI_ADC_CHAN_CTRL_FMT_EN);
+ if (data->sign_extend)
+ val |= FIELD_PREP(AXI_ADC_CHAN_CTRL_FMT_MASK,
+ AXI_ADC_CHAN_CTRL_FMT_SIGEXT);
+
+ if (data->type == CONVERTER_OFFSET_BINARY)
+ val |= FIELD_PREP(AXI_ADC_CHAN_CTRL_FMT_MASK,
+ AXI_ADC_CHAN_CTRL_FMT_BIN_OFF);
+
+ return regmap_update_bits(st->regmap, AXI_ADC_REG_CHAN_CTRL(chan),
+ AXI_ADC_CHAN_CTRL_FMT_MASK, val);
+}
+
+static void __axi_adc_disable(const struct axi_adc_state *st)
+{
+ regmap_clear_bits(st->regmap, AXI_ADC_REG_RSTN,
+ AXI_ADC_RSTN_RESET_MASK);
+}
+
+static int __axi_adc_enable(const struct axi_adc_state *st)
+{
+ return regmap_set_bits(st->regmap, AXI_ADC_REG_RSTN,
+ AXI_ADC_RSTN_RESET_MASK);
+}
+
+static int axi_adc_enable(struct converter_backend *conv)
+{
+ return __axi_adc_enable(converter_get_drvdata(conv));
+}
+
+static void axi_adc_disable(struct converter_backend *conv)
+{
+ __axi_adc_disable(converter_get_drvdata(conv));
+}
+
+static int axi_adc_reset(struct axi_adc_state *st)
+{
+ int ret;
+
+ __axi_adc_disable(st);
+ fsleep(10);
+ ret = __axi_adc_enable(st);
+ if (ret)
+ return ret;
+
+ fsleep(10);
+ return 0;
+}
+
+static const struct regmap_config axi_adc_regmap_config = {
+ .val_bits = 32,
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x0800,
+};
+
+static int axi_adc_generic_init(struct converter_backend *conv,
+ struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ unsigned int ver, *expected_ver, ret;
+ struct axi_adc_state *st;
+ void __iomem *base;
+ struct clk *clk;
+
+ st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL);
+ if (!st)
+ return -ENOMEM;
+
+ st->dev = dev;
+
+ expected_ver = (unsigned int *)device_get_match_data(dev);
+ if (!expected_ver)
+ return -ENODEV;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ st->regmap = devm_regmap_init_mmio(dev, base, &axi_adc_regmap_config);
+ if (IS_ERR(st->regmap))
+ return PTR_ERR(st->regmap);
+
+ converter_set_drvdata(conv, st);
+ converter_set_regmap(conv, st->regmap);
+
+ clk = devm_clk_get_enabled(&pdev->dev, NULL);
+ if (IS_ERR(clk)) {
+ dev_err(&pdev->dev, "clk_get failed with %ld\n", PTR_ERR(clk));
+ return PTR_ERR(clk);
+ }
+
+ ret = axi_adc_reset(st);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(st->regmap, ADI_AXI_REG_VERSION, &ver);
+ if (ret)
+ return ret;
+
+ if (*expected_ver > ver) {
+ dev_err(&pdev->dev,
+ "IP core version is too old. Expected %d.%.2d.%c, Reported %d.%.2d.%c\n",
+ ADI_AXI_PCORE_VER_MAJOR(*expected_ver),
+ ADI_AXI_PCORE_VER_MINOR(*expected_ver),
+ ADI_AXI_PCORE_VER_PATCH(*expected_ver),
+ ADI_AXI_PCORE_VER_MAJOR(ver),
+ ADI_AXI_PCORE_VER_MINOR(ver),
+ ADI_AXI_PCORE_VER_PATCH(ver));
+ return -ENODEV;
+ }
+
+ /* fetch synthesis capabilities */
+ ret = regmap_read(st->regmap, AXI_ADC_REG_CONFIG, &st->capabilities);
+ if (ret)
+ return ret;
+
+ dev_dbg(&pdev->dev, "AXI ADC IP core (%d.%.2d.%c) up\n",
+ ADI_AXI_PCORE_VER_MAJOR(ver),
+ ADI_AXI_PCORE_VER_MINOR(ver),
+ ADI_AXI_PCORE_VER_PATCH(ver));
+
+ /* up to the frontend to explicitly enable us */
+ __axi_adc_disable(st);
+ mutex_init(&st->lock);
+ return 0;
+}
+
+static const struct converter_ops adi_axi_adc_generic = {
+ .backend_init = axi_adc_generic_init,
+ .enable = axi_adc_enable,
+ .disable = axi_adc_disable,
+ .data_format_set = axi_adc_data_format_set,
+ .test_pattern_set = axi_adc_test_pattern_set,
+ .chan_enable = axi_adc_chan_enable,
+ .chan_disable = axi_adc_chan_disable,
+ .iodelay_set = axi_adc_iodelay_set,
+ .sample_edge_select = axi_adc_ddr_edge_set,
+ .chan_status = axi_adc_chan_status_get,
+};
+
+static int axi_adc_probe(struct platform_device *pdev)
+{
+ return converter_add(&pdev->dev, &adi_axi_adc_generic);
+}
+
+/*
+ * It actually matters to remove the converter in the .remove() hook. This means
+ * that the all the converters (an 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 .backend_init() and being sure those resources
+ * will be released before the frontend resources and before any devm_* used
+ * in .probe().
+ */
+static int axi_adc_remove(struct platform_device *pdev)
+{
+ converter_del(&pdev->dev);
+ return 0;
+}
+
+static unsigned int axi_adc_10_0_a = ADI_AXI_PCORE_VER(10, 0, 'a');
+
+/* Match table for of_platform binding */
+static const struct of_device_id axi_adc_of_match[] = {
+ { .compatible = "adi,axi-adc-10.0.a-new", .data = &axi_adc_10_0_a },
+ { /* end of list */ }
+};
+MODULE_DEVICE_TABLE(of, axi_adc_of_match);
+
+static struct platform_driver axi_adc_driver = {
+ .driver = {
+ .name = "axi-adc",
+ .of_match_table = axi_adc_of_match,
+ },
+ .probe = axi_adc_probe,
+ .remove = axi_adc_remove,
+};
+module_platform_driver(axi_adc_driver);
+
+MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
+MODULE_DESCRIPTION("Analog Devices Generic AXI ADC IP core driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(IIO_CONVERTER);
Signed-off-by: Nuno Sa <nuno.sa@analog.com> --- drivers/iio/adc/adi-axi-adc-new.c | 405 ++++++++++++++++++++++++++++++ 1 file changed, 405 insertions(+) create mode 100644 drivers/iio/adc/adi-axi-adc-new.c