From: Nuno Sa <nuno.sa@analog.com>
To: <linux-iio@vger.kernel.org>
Cc: Jonathan Cameron <jic23@kernel.org>
Subject: [RFC PATCH 3/3] iio: adc: adi-axi-adc: add based on new converter framework
Date: Fri, 4 Aug 2023 16:53:41 +0200 [thread overview]
Message-ID: <20230804145342.1600136-4-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/adi-axi-adc-new.c | 405 ++++++++++++++++++++++++++++++
1 file changed, 405 insertions(+)
create mode 100644 drivers/iio/adc/adi-axi-adc-new.c
diff --git a/drivers/iio/adc/adi-axi-adc-new.c b/drivers/iio/adc/adi-axi-adc-new.c
new file mode 100644
index 000000000000..7ee24f765d07
--- /dev/null
+++ b/drivers/iio/adc/adi-axi-adc-new.c
@@ -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);
--
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 ` [RFC PATCH 2/3] iio: adc: ad9647: add based on " Nuno Sa
2023-08-30 17:13 ` Jonathan Cameron
2023-08-04 14:53 ` Nuno Sa [this message]
2023-08-30 17:15 ` [RFC PATCH 3/3] iio: adc: adi-axi-adc: add based on new " 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-4-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.