From: Nuno Sa <nuno.sa@analog.com>
To: <linux-iio@vger.kernel.org>
Cc: Jonathan Cameron <jic23@kernel.org>
Subject: [RFC PATCH 1/3] iio: addac: add new converter framework
Date: Fri, 4 Aug 2023 16:53:39 +0200 [thread overview]
Message-ID: <20230804145342.1600136-2-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/addac/converter.c | 547 ++++++++++++++++++++++++++++
include/linux/iio/addac/converter.h | 485 ++++++++++++++++++++++++
2 files changed, 1032 insertions(+)
create mode 100644 drivers/iio/addac/converter.c
create mode 100644 include/linux/iio/addac/converter.h
diff --git a/drivers/iio/addac/converter.c b/drivers/iio/addac/converter.c
new file mode 100644
index 000000000000..31ac704255ad
--- /dev/null
+++ b/drivers/iio/addac/converter.c
@@ -0,0 +1,547 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Framework to handle complex IIO aggregate devices
+ *
+ * A note on some of the design expectations with regards to lifetimes and
+ * devices bringup/removal.
+ *
+ * The Framework is using, under the wood, the component API which makes it to
+ * easy treat a bunch of devices as one aggregate device. This means that the
+ * complete thing is only brought to live when all the deviced are probed. To do
+ * this, two callbacks are used that should in fact completely replace .probe()
+ * and .remove(). The formers should only be used the minimum needed. Ideally,
+ * only to call the functions to add and remove frontend annd backend devices.
+ *
+ * It is advised for frontend and backend drivers to use their .remove()
+ * callbacks (to use devres API during the frontend and backends initialization).
+ * See the comment in @converter_frontend_bind().
+ *
+ * It is also assumed that converter objects cannot be accessed once one of the
+ * devices of the aggregate device is removed (effectively bringing the all the
+ * devices down). Based on that assumption, these objects are not refcount which
+ * means accessing them will likely fail miserably.
+ *
+ * Copyright (C) 2023 Analog Devices Inc.
+ */
+
+#define dev_fmt(fmt) "Converter - " fmt
+
+#include <linux/component.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/iio/addc/converter.h>
+#include <linux/iio/iio.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+struct converter_backend {
+ struct list_head entry;
+ struct device *dev;
+ const struct converter_ops *ops;
+ const char *name;
+ void *drvdata;
+
+ struct regmap *regmap;
+ unsigned int cached_reg_addr;
+};
+
+struct converter_frontend {
+ struct list_head list;
+ const struct frontend_ops *ops;
+ struct device *dev;
+};
+
+static ssize_t converter_debugfs_read_reg(struct file *file,
+ char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct converter_backend *conv = file->private_data;
+ unsigned int val = 0;
+ char read_buf[20];
+ int ret, len;
+
+ ret = regmap_read(conv->regmap, conv->cached_reg_addr, &val);
+ if (ret) {
+ dev_err(conv->dev, "%s: read failed\n", __func__);
+ return ret;
+ }
+
+ len = scnprintf(read_buf, sizeof(read_buf), "0x%X\n", val);
+
+ return simple_read_from_buffer(userbuf, count, ppos, read_buf, len);
+}
+
+static ssize_t converter_debugfs_write_reg(struct file *file,
+ const char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ struct converter_backend *conv = file->private_data;
+ unsigned int val;
+ char buf[80];
+ ssize_t rc;
+ int ret;
+
+ rc = simple_write_to_buffer(buf, sizeof(buf), ppos, userbuf, count);
+ if (rc < 0)
+ return rc;
+
+ ret = sscanf(buf, "%i %i", &conv->cached_reg_addr, &val);
+
+ switch (ret) {
+ case 1:
+ break;
+ case 2:
+ ret = regmap_write(conv->regmap, conv->cached_reg_addr, val);
+ if (ret) {
+ dev_err(conv->dev, "%s: write failed\n", __func__);
+ return ret;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static const struct file_operations converter_debugfs_reg_fops = {
+ .open = simple_open,
+ .read = converter_debugfs_read_reg,
+ .write = converter_debugfs_write_reg,
+};
+
+static void __converter_add_direct_reg_access(struct converter_backend *conv,
+ struct iio_dev *indio_dev)
+{
+ struct dentry *d = iio_get_debugfs_dentry(indio_dev);
+ const char *name = conv->name;
+ char file_name[64];
+
+ if (!conv->regmap)
+ return;
+ if (!d)
+ return;
+
+ if (!conv->name)
+ name = "converter";
+
+ snprintf(file_name, sizeof(file_name), "%s_direct_reg_access", name);
+
+ debugfs_create_file(file_name, 0644, d, conv,
+ &converter_debugfs_reg_fops);
+}
+
+void converter_add_direct_reg_access(struct converter_backend *conv,
+ struct iio_dev *indio_dev)
+{
+ if (IS_ENABLED(CONFIG_DEBUG_FS))
+ __converter_add_direct_reg_access(conv, indio_dev);
+}
+EXPORT_SYMBOL_NS_GPL(converter_add_direct_reg_access, IIO_CONVERTER);
+
+static int converter_bind(struct device *dev, struct device *aggregate,
+ void *data)
+{
+ struct converter_frontend *frontend = dev_get_drvdata(aggregate);
+ struct converter_backend *conv = dev_get_drvdata(dev);
+ int ret;
+
+ ret = conv->ops->backend_init(conv, dev);
+ if (ret)
+ return ret;
+
+ list_add_tail(&conv->entry, &frontend->list);
+
+ return 0;
+}
+
+static void converter_unbind(struct device *dev, struct device *aggregate,
+ void *data)
+{
+ struct converter_backend *conv = dev_get_drvdata(dev);
+
+ if (conv->ops->backend_close)
+ conv->ops->backend_close(conv);
+
+ /* after this point the converter should not be used anymore */
+ converter_set_drvdata(conv, NULL);
+}
+
+static const struct component_ops converter_component_ops = {
+ .bind = converter_bind,
+ .unbind = converter_unbind,
+};
+
+static int converter_frontend_bind(struct device *dev)
+{
+ struct converter_frontend *frontend = dev_get_drvdata(dev);
+ int ret;
+
+ ret = component_bind_all(dev, NULL);
+ if (ret)
+ return ret;
+ /*
+ * We open a new group so that we can control when resources are
+ * released and still use device managed (devm_) calls. The expectations
+ * are that on probe, backend resources are allocated first followed by
+ * the frontend resources (where registering the IIO device must happen)
+ * Naturally we want the reverse order on the unbind path and that would
+ * not be possible without opening our own devres group.
+
+ * Note that the component API also opens it's own devres group when
+ * calling the .bind() callbacks for both the aggregate device
+ * (our frontend) and each of the components (our backends). On the
+ * unbind path, the aggregate .unbind() function is called
+ * (@converter_frontend_unbind()) which should be responsible to tear
+ * down all the components (effectively releasing all the resources
+ * allocated on each component devres group) and only then the aggregate
+ * devres group is released. Hence, the order we want to maintain for
+ * releasing resources would not be satisfied because backend resources
+ * would be freed first. With our own group, we can control when
+ * releasing the resources and we do it before @component_unbind_all().
+ *
+ * This also relies that internally the component API is releasing each
+ * of the component's devres group. That is likely not to change, but
+ * maybe we should not trust it and also open our own groups for backend
+ * devices?!
+ *
+ * Another very important thing to keep in mind is that this is only
+ * valid if frontend and backend driver's are implementing their
+ * .remove() callback to call @converter_frontend_del() and
+ * @converter_backend_del(). Calling those functions from
+ * devm_add_action* and use devm APIs in .frontend_init() and
+ * .backend_init() is not going to work. Not perfect but still better
+ * than having to tear everything down in .frontend_close() and
+ * .backend_close()
+ */
+ if (!devres_open_group(dev, frontend, GFP_KERNEL))
+ return -ENOMEM;
+
+ ret = frontend->ops->frontend_init(frontend, dev);
+ if (ret) {
+ devres_release_group(dev, frontend);
+ return ret;
+ }
+
+ devres_close_group(dev, NULL);
+ return 0;
+}
+
+static void converter_frontend_unbind(struct device *dev)
+{
+ struct converter_frontend *frontend = dev_get_drvdata(dev);
+
+ if (frontend->ops->frontend_close)
+ frontend->ops->frontend_close(frontend);
+
+ devres_release_group(dev, frontend);
+ component_unbind_all(dev, NULL);
+ list_del_init(&frontend->list);
+}
+
+static const struct component_master_ops frontend_component_ops = {
+ .bind = converter_frontend_bind,
+ .unbind = converter_frontend_unbind,
+};
+
+struct converter_backend *converter_get(const struct converter_frontend *frontend,
+ const char *name)
+{
+ struct converter_backend *iter, *conv = NULL;
+ struct device *dev = frontend->dev;
+ struct fwnode_handle *fwnode;
+ int index = 0;
+
+ if (list_empty(&frontend->list)) {
+ dev_err(dev, "Backend list is empty...\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ /* if no name given, we assume only one converter_backend exists */
+ if (!name)
+ return list_first_entry(&frontend->list,
+ struct converter_backend, entry);
+
+ index = device_property_match_string(frontend->dev, "converter-names",
+ name);
+ if (index < 0)
+ return ERR_PTR(index);
+
+ fwnode = fwnode_find_reference(dev_fwnode(dev), "converters", index);
+ if (IS_ERR(fwnode))
+ return ERR_CAST(fwnode);
+
+ list_for_each_entry(iter, &frontend->list, entry) {
+ if (device_match_fwnode(iter->dev, fwnode)) {
+ conv = iter;
+ break;
+ }
+ }
+
+ fwnode_handle_put(fwnode);
+
+ if (!conv) {
+ dev_err(dev, "Converter (%s) not found in the list\n", name);
+ return ERR_PTR(-ENODEV);
+ }
+
+ /* See if we can add device_property_string_read_index() */
+ conv->name = kstrdup_const(name, GFP_KERNEL);
+ if (!conv->name)
+ return ERR_PTR(-ENOMEM);
+
+ return conv;
+}
+EXPORT_SYMBOL_NS_GPL(converter_get, IIO_CONVERTER);
+
+static int converter_frontend_add_matches(struct converter_frontend *frontend,
+ struct component_match **match)
+{
+ struct device *dev = frontend->dev;
+ struct fwnode_handle *fwnode;
+ int index = 0;
+
+ do {
+ fwnode = fwnode_find_reference(dev_fwnode(dev), "converters",
+ index);
+ if (IS_ERR(fwnode))
+ break;
+
+ component_match_add_release(dev, match,
+ component_release_fwnode,
+ component_compare_fwnode, fwnode);
+ index++;
+ } while (true);
+
+ /* no devices?! */
+ if (!index) {
+ dev_err(dev, "No converters. Make sure the \"converters\" property is given!\n");
+ return -ENODEV;
+ }
+
+ if (PTR_ERR(fwnode) != -ENOENT)
+ return PTR_ERR(fwnode);
+
+ return 0;
+}
+
+int converter_test_pattern_set(struct converter_backend *conv,
+ unsigned int chan,
+ enum converter_test_pattern pattern)
+{
+ if (pattern >= CONVERTER_TEST_PATTERN_MAX)
+ return -EINVAL;
+ if (!conv->ops->test_pattern_set)
+ return -ENOTSUPP;
+
+ return conv->ops->test_pattern_set(conv, chan, pattern);
+}
+EXPORT_SYMBOL_NS_GPL(converter_test_pattern_set, IIO_CONVERTER);
+
+int converter_chan_status_get(struct converter_backend *conv,
+ unsigned int chan,
+ struct converter_chan_status *status)
+{
+ if (!conv->ops->chan_status)
+ return -ENOTSUPP;
+
+ return conv->ops->chan_status(conv, chan, status);
+}
+EXPORT_SYMBOL_NS_GPL(converter_chan_status_get, IIO_CONVERTER);
+
+int converter_iodelay_set(struct converter_backend *conv,
+ unsigned int num_lanes, unsigned int delay)
+{
+ if (!num_lanes)
+ return -EINVAL;
+ if (!conv->ops->iodelay_set)
+ return -ENOTSUPP;
+
+ return conv->ops->iodelay_set(conv, num_lanes, delay);
+}
+EXPORT_SYMBOL_NS_GPL(converter_iodelay_set, IIO_CONVERTER);
+
+int converter_data_format_set(struct converter_backend *conv,
+ unsigned int chan,
+ const struct converter_data_fmt *data)
+{
+ if (data->type >= CONVERTER_DATA_TYPE_MAX)
+ return -EINVAL;
+ if (!conv->ops->data_format_set)
+ return -ENOTSUPP;
+
+ return conv->ops->data_format_set(conv, chan, data);
+}
+EXPORT_SYMBOL_NS_GPL(converter_data_format_set, IIO_CONVERTER);
+
+int converter_sample_edge_select(struct converter_backend *conv,
+ enum converter_edge edge)
+{
+ if (edge >= CONVERTER_EDGE_MAX)
+ return -EINVAL;
+ if (conv->ops->sample_edge_select)
+ return -ENOTSUPP;
+
+ return conv->ops->sample_edge_select(conv, edge);
+}
+EXPORT_SYMBOL_NS_GPL(converter_sample_edge_select, IIO_CONVERTER);
+
+int converter_chan_enable(struct converter_backend *conv, unsigned int chan)
+{
+ if (!conv->ops->chan_enable)
+ return -ENOTSUPP;
+
+ return conv->ops->chan_enable(conv, chan);
+}
+EXPORT_SYMBOL_NS_GPL(converter_chan_enable, IIO_CONVERTER);
+
+int converter_chan_disable(struct converter_backend *conv, unsigned int chan)
+{
+ if (!conv->ops->disable)
+ return -ENOTSUPP;
+
+ return conv->ops->chan_disable(conv, chan);
+}
+EXPORT_SYMBOL_NS_GPL(converter_chan_disable, IIO_CONVERTER);
+
+int converter_enable(struct converter_backend *conv)
+{
+ if (!conv->ops->enable)
+ return -ENOTSUPP;
+
+ return conv->ops->enable(conv);
+}
+EXPORT_SYMBOL_NS_GPL(converter_enable, IIO_CONVERTER);
+
+void converter_disable(struct converter_backend *conv)
+{
+ if (!conv->ops->disable)
+ return;
+
+ conv->ops->disable(conv);
+}
+EXPORT_SYMBOL_NS_GPL(converter_disable, IIO_CONVERTER);
+
+int __converter_test_pattern_xlate(unsigned int pattern,
+ const struct converter_test_pattern_xlate *xlate,
+ int n_matches)
+{
+ unsigned int p = n_matches;
+
+ while (p--) {
+ if (pattern == xlate[p].pattern)
+ return xlate[p].reg_val;
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_NS_GPL(__converter_test_pattern_xlate, IIO_CONVERTER);
+
+void converter_set_regmap(struct converter_backend *conv,
+ struct regmap *regmap)
+{
+ conv->regmap = regmap;
+}
+EXPORT_SYMBOL_NS_GPL(converter_set_regmap, IIO_CONVERTER);
+
+void converter_set_drvdata(struct converter_backend *conv, void *drvdata)
+{
+ conv->drvdata = drvdata;
+}
+EXPORT_SYMBOL_NS_GPL(converter_set_drvdata, IIO_CONVERTER);
+
+void *converter_get_drvdata(const struct converter_backend *conv)
+{
+ WARN_ON(!conv->drvdata);
+ return conv->drvdata;
+}
+EXPORT_SYMBOL_NS_GPL(converter_get_drvdata, IIO_CONVERTER);
+
+void converter_del(struct device *dev)
+{
+ component_del(dev, &converter_component_ops);
+}
+EXPORT_SYMBOL_NS_GPL(converter_del, IIO_CONVERTER);
+
+static void converter_free(void *conv)
+{
+ struct converter_backend *__conv = conv;
+
+ if (__conv->name)
+ kfree_const(__conv->name);
+
+ kfree(__conv);
+}
+
+int converter_add(struct device *dev, const struct converter_ops *ops)
+{
+ struct converter_backend *conv;
+ int ret;
+
+ if (!ops || !ops->backend_init)
+ return -EINVAL;
+
+ conv = kzalloc(sizeof(*conv), GFP_KERNEL);
+ if (!conv)
+ return -ENOMEM;
+
+ /*
+ * The expectation is that everything goes up and down in
+ * .converter_bind() and .converter_unbind() respectively. Hence, it's
+ * not expected for converter objects to be accessed after unbind(). As
+ * soon as that does not stand anymore, we need to
+ * drop devm_add_action_or_reset() and properly refcount the objects.
+ */
+ ret = devm_add_action_or_reset(dev, converter_free, conv);
+ if (ret)
+ return ret;
+
+ conv->ops = ops;
+ dev_set_drvdata(dev, conv);
+ conv->dev = dev;
+
+ return component_add(dev, &converter_component_ops);
+}
+EXPORT_SYMBOL_NS_GPL(converter_add, IIO_CONVERTER);
+
+void converter_frontend_del(struct device *dev)
+{
+ component_master_del(dev, &frontend_component_ops);
+}
+EXPORT_SYMBOL_NS_GPL(converter_frontend_del, IIO_CONVERTER);
+
+int converter_frontend_add(struct device *dev, const struct frontend_ops *ops)
+{
+ struct converter_frontend *frontend;
+ struct component_match *match;
+ int ret;
+
+ if (!ops || !ops->frontend_init) {
+ dev_err(dev, "Mandatory ops missing\n");
+ return -EINVAL;
+ }
+
+ frontend = devm_kzalloc(dev, sizeof(*frontend), GFP_KERNEL);
+ if (!frontend)
+ return -ENOMEM;
+
+ frontend->ops = ops;
+ frontend->dev = dev;
+ INIT_LIST_HEAD(&frontend->list);
+ dev_set_drvdata(dev, frontend);
+
+ ret = converter_frontend_add_matches(frontend, &match);
+ if (ret)
+ return ret;
+
+ return component_master_add_with_match(dev, &frontend_component_ops,
+ match);
+}
+EXPORT_SYMBOL_NS_GPL(converter_frontend_add, IIO_CONVERTER);
+
+MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
+MODULE_DESCRIPTION("Framework to handle complex IIO aggregate devices");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/iio/addac/converter.h b/include/linux/iio/addac/converter.h
new file mode 100644
index 000000000000..09d9d491b2b8
--- /dev/null
+++ b/include/linux/iio/addac/converter.h
@@ -0,0 +1,485 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _CONVERTER_H
+#define _CONVERTER_H
+
+struct converter_frontend;
+struct converter_backend;
+struct iio_dev;
+struct device;
+struct regmap;
+
+enum converter_test_pattern {
+ CONVERTER_PRBS_7,
+ CONVERTER_PRBS_15,
+ CONVERTER_PRBS_23,
+ CONVERTER_PRBS_31,
+ CONVERTER_RAMP_NIBBLE,
+ CONVERTER_RAMP_16,
+ /* vendor specific from 32 */
+ CONVERTER_ADI_PRBS_9A = 32,
+ CONVERTER_ADI_PRBS_23A,
+ CONVERTER_ADI_PRBS_X,
+ CONVERTER_TEST_PATTERN_MAX
+};
+
+enum converter_data_type {
+ CONVERTER_TWOS_COMPLEMENT,
+ CONVERTER_OFFSET_BINARY,
+ CONVERTER_DATA_TYPE_MAX
+};
+
+enum converter_edge {
+ CONVERTER_RISING_EDGE_SAMPLE,
+ CONVERTER_FALLING_EDGE_SAMPLE,
+ CONVERTER_EDGE_MAX
+};
+
+struct converter_chan_status {
+ bool errors;
+};
+
+/**
+ * struct converter_data_fmt - Backend data format
+ * @type: Data type.
+ * @sign_extend: Bool to tell if the data is sign extended.
+ * @enable: Enable/Disable the data format module. If disabled,
+ * not formatting will happen.
+ */
+struct converter_data_fmt {
+ enum converter_data_type type;
+ bool sign_extend;
+ bool enable;
+};
+
+/**
+ * struct converter_test_pattern_xlate - Helper struct for test pattern handling
+ * @pattern: Pattern to configure.
+ * @reg_val: Register value for the pattern to configure.
+ */
+struct converter_test_pattern_xlate {
+ enum converter_test_pattern pattern;
+ unsigned int reg_val;
+};
+
+/**
+ * struct converter_ops - Backend supported operations
+ * @backend_init: Mandatory function to initialize the backend device. It
+ * should be a replacement for .probe() where the latest
+ * should only have to care about doing @converter_add().
+ * @backend_close: Optional function to tear down the device.
+ * @enable: Enable the backend device.
+ * @disable: Disable the backend device.
+ * @data_format_set: Configure the data format for a specific channel.
+ * @chan_enable: Enable one channel.
+ * @chan_disable: Disable one channel.
+ * @iodelay_set: Controls the IO delay for all the lanes at the interface
+ * (where data is actually transferred between frontend and
+ backend) level.
+ * @test_pattern_set: Set's a test pattern to be transmitted/received by the
+ * backend. Typically useful for debug or interface
+ * purposes calibration.
+ */
+struct converter_ops {
+ int (*backend_init)(struct converter_backend *conv, struct device *dev);
+ void (*backend_close)(struct converter_backend *conv);
+ int (*enable)(struct converter_backend *conv);
+ void (*disable)(struct converter_backend *conv);
+ int (*data_format_set)(struct converter_backend *conv,
+ unsigned int chan,
+ const struct converter_data_fmt *data);
+ int (*chan_enable)(struct converter_backend *conv, unsigned int chan);
+ int (*chan_disable)(struct converter_backend *conv, unsigned int chan);
+ int (*iodelay_set)(struct converter_backend *conv,
+ unsigned int num_lanes, unsigned int delay);
+ int (*test_pattern_set)(struct converter_backend *conv,
+ unsigned int chan,
+ enum converter_test_pattern pattern);
+ int (*chan_status)(struct converter_backend *conv, unsigned int chan,
+ struct converter_chan_status *status);
+ int (*sample_edge_select)(struct converter_backend *conv,
+ enum converter_edge edge);
+};
+
+/**
+ * struct frontend_ops - Frontend supported operations
+ * @frontend_init: Mandatory function to initialize the frontend device. It
+ * should be a replacement for .probe() where the latest
+ * should only have to care about doing @frontend_add().
+ * @frontend_close: Optional function to tear down the device.
+ */
+struct frontend_ops {
+ int (*frontend_init)(struct converter_frontend *frontend,
+ struct device *dev);
+ void (*frontend_close)(struct converter_frontend *frontend);
+};
+
+/**
+ * converter_test_pattern_xlate() - Helper macro for translatting test patterns
+ * @pattern: Pattern to translate.
+ * @xlate: List of @struct converter_test_pattern_xlate pairs.
+ *
+ * Simple helper to match a supported pattern and get the register value. Should
+ * only be called by backend devices. Automatically computes the number of
+ * @xlate entries.
+ */
+#define converter_test_pattern_xlate(pattern, xlate) \
+ __converter_test_pattern_xlate(pattern, xlate, ARRAY_SIZE(xlate));
+
+#if IS_ENABLED(CONFIG_IIO_CONVERTER)
+
+/**
+ * converter_get_drvdata - Get driver private data
+ * @conv: Converter device.
+ */
+void *converter_get_drvdata(const struct converter_backend *conv);
+
+/**
+ * converter_set_drvdata - Set driver private data
+ * @conv: Converter device.
+ * @drvdata: Driver private data.
+ */
+void converter_set_drvdata(struct converter_backend *conv, void *drvdata);
+
+/**
+ * converter_set_regmap - Add a regmap object to a converter
+ * @conv: Converter device.
+ * @regmap: Regmap object.
+ */
+void converter_set_regmap(struct converter_backend *conv,
+ struct regmap *regmap);
+
+/**
+ * __converter_test_pattern_xlate - Helper macro for translatting test patterns
+ * @pattern: Pattern to translate.
+ * @xlate: List of @struct converter_test_pattern_xlate pairs.
+ * @n_matches: Number of entries in @xlate.
+ *
+ * Simple helper to match a supported pattern and get the register value. Should
+ * only be called by backend devices.
+ */
+int __converter_test_pattern_xlate(unsigned int pattern,
+ const struct converter_test_pattern_xlate *xlate,
+ int n_matches);
+
+/**
+ *
+ */
+int converter_add(struct device *dev, const struct converter_ops *ops);
+
+/**
+ * converter_del - Remove the converter device
+ * @dev: device to remove from the aggregate
+ *
+ * Removes the converter from the aggregate device. This tears down the frontend
+ * and all the converters.
+ *
+ * Ideally, this should be called from the backend driver .remove() callback.
+ * 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 @backend_init() and being sure those
+ * resources will be released after the backend resources and before any devm_*
+ * used in @probe(). If that is not the case, one should likely not use any
+ * devm_ API in @backend_init(). That means .backend_close() should be
+ * provided to do all the necessary cleanups.
+ */
+void converter_del(struct device *dev);
+
+/**
+ * converter_enable - Enable the device
+ * @conv: Converter device.
+ *
+ * Enables the backend device.
+ *
+ * RETURNS:
+ * 0 on success, negative error number on failure.
+ */
+int converter_enable(struct converter_backend *conv);
+
+/**
+ * converter_disable - Disable the device
+ * @conv: Converter device.
+ *
+ * Disables the backend device.
+ */
+void converter_disable(struct converter_backend *conv);
+
+/**
+ * converter_test_pattern_set - Set a test pattern
+ * @conv: Converter device.
+ * @chan: Channel number.
+ * @pattern: Pattern to set.
+ *
+ * Set's a test pattern to be transmitted/received by the backend. Typically
+ * useful for debug or interface calibration purposes. A backend driver can
+ * call the @converter_test_pattern_xlate() helper to validate the pattern
+ * (given an array of @struct converter_test_pattern_xlate).
+ *
+ * Note that some patterns might be frontend specific. I.e, as far as the
+ * backend is concerned the pattern is valid (from a register point of view) but
+ * the actual support for the pattern is not implemented in the device for this
+ * specific frontend. It's up to the frontend to ask for a proper pattern
+ * (as it should know better).
+ *
+ * RETURNS:
+ * 0 on success, negative error number on failure.
+ */
+int converter_test_pattern_set(struct converter_backend *conv,
+ unsigned int chan,
+ enum converter_test_pattern pattern);
+
+int converter_chan_status_get(struct converter_backend *conv,
+ unsigned int chan,
+ struct converter_chan_status *status);
+
+/**
+ * converter_data_format_set - Configure the data format
+ * @conv: Converter device.
+ * @chan: Channel number.
+ * @data: Data format.
+ *
+ * Properly configure a channel with respect to the expected data format. A
+ * @struct converter_data_fmt must be passed with the settings.
+ *
+ * RETURNS:
+ * 0 on success, negative error number on failure.
+ */
+int converter_data_format_set(struct converter_backend *conv,
+ unsigned int chan,
+ const struct converter_data_fmt *data);
+
+int converter_sample_edge_select(struct converter_backend *conv,
+ enum converter_edge edge);
+
+static inline int
+converter_sample_on_falling_edge(struct converter_backend *conv)
+{
+ return converter_sample_edge_select(conv, CONVERTER_RISING_EDGE_SAMPLE);
+}
+
+static inline int
+converter_sample_on_rising_edge(struct converter_backend *conv)
+{
+ return converter_sample_edge_select(conv, CONVERTER_FALLING_EDGE_SAMPLE);
+}
+
+/**
+ * converter_chan_enable - Enable a backend channel
+ * @conv: Converter device.
+ * @chan: Channel number.
+ *
+ * Enables a channel on the backend device.
+ *
+ * RETURNS:
+ * 0 on success, negative error number on failure.
+ */
+int converter_chan_enable(struct converter_backend *conv, unsigned int chan);
+
+/**
+ * converter_chan_disable - Disable a backend channel
+ * @conv: Converter device.
+ * @chan: Channel number.
+ *
+ * Disables a channel on the backend device.
+ *
+ * RETURNS:
+ * 0 on success, negative error number on failure.
+ */
+int converter_chan_disable(struct converter_backend *conv, unsigned int chan);
+
+/**
+ * converter_iodelay_set - Set's the backend data interface IO delay
+ * @conv: Converter device.
+ * @num_lanes: Number of lanes in the data interface.
+ * @delay: Delay to set.
+ *
+ * Controls the IO delay for all the lanes at the data interface (where data is
+ * actually transferred between frontend and backend) level.
+ *
+ * RETURNS:
+ * 0 on success, negative error number on failure.
+ */
+int converter_iodelay_set(struct converter_backend *conv,
+ unsigned int num_lanes, unsigned int delay);
+
+/**
+ * converter_frontend_del - Remove the frontend device
+ * @dev: Device to remove from the aggregate
+ *
+ * Removes the frontend from the aggregate device. This tears down the frontend
+ * and all the converters.
+ *
+ * Ideally, this should be called from the frontend driver .remove() callback.
+ * 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(). If that is not the case, one should likely not
+ * use any devm_ API in .frontend_init(). That means .frontend_close() should be
+ * provided to do all the necessary cleanups.
+ */
+void converter_frontend_del(struct device *dev);
+
+/**
+ * converter_frontend_add - Allocate and add a frontend device
+ * @dev: Device to allocate frontend for.
+ * @ops: Frontend callbacks.
+ *
+ * This allocates the frontend device and looks for all converters needed
+ * so that, when they are available, all of the devices in the aggregate can be
+ * initialized.
+ *
+ * RETURNS:
+ * 0 on success, negative error number on failure.
+ */
+int converter_frontend_add(struct device *dev, const struct frontend_ops *ops);
+
+/**
+ * converter_get - Get a converter object
+ * @frontend: Frontend device.
+ * @name: Converter name.
+ *
+ * Get's a pointer to a converter device. If name is NULL, then it is assumed
+ * that only one backend device is bond with the frontend and the first element
+ * in the list is retrieved. Should only be called from the .frontend_init()
+ * callback.
+ *
+ * RETURNS:
+ * A converter pointer, negative error pointer otherwise.
+ */
+struct converter_backend *__must_check
+converter_get(const struct converter_frontend *frontend, const char *name);
+
+/**
+ * converter_add_direct_reg_access - Add debugfs direct register access
+ * @conv: Coverter device
+ * @indio_dev: IIO device
+ *
+ * This is analogous to the typical IIO direct register access in debugfs. The
+ * extra converter file will be added in the same debugs dir as @indio_dev.
+ * Moreover, if @conv->name is NULL, the file will be called
+ * converter_direct_reg_access. Otherwise, will be
+ * @conv->name_converter_direct_reg_access.
+ */
+void converter_add_direct_reg_access(struct converter_backend *conv,
+ struct iio_dev *indio_dev);
+
+#else
+
+static inline void *converter_get_drvdata(const struct converter_backend *conv)
+{
+ WARN_ONCE(1, "converter API is disabled");
+ return NULL;
+}
+
+static inline void converter_set_drvdata(struct converter_backend *conv,
+ void *drvdata)
+{
+ WARN_ONCE(1, "converter API is disabled");
+}
+
+static inline void converter_set_regmap(struct converter_backend *conv,
+ struct regmap *regmap)
+{
+ WARN_ONCE(1, "converter API is disabled");
+}
+
+static inline int
+__converter_test_pattern_xlate(unsigned int pattern,
+ const struct converter_test_pattern_xlate *xlate,
+ int n_matches)
+{
+ WARN_ONCE(1, "converter API is disabled");
+ return -ENOTSUPP;
+}
+
+static inline struct converter_backend *__must_check
+converter_get(const struct converter_frontend *frontend, const char *name)
+{
+ WARN_ONCE(1, "converter API is disabled");
+ return ERR_PTR(-ENOTSUPP);
+}
+
+static inline int converter_add(struct device *dev,
+ const struct converter_ops *ops)
+{
+ WARN_ONCE(1, "converter API is disabled");
+ return -ENOTSUPP;
+}
+
+static inline void converter_del(struct device *dev)
+{
+ WARN_ONCE(1, "converter API is disabled");
+}
+
+static inline int converter_enable(struct converter_backend *conv)
+{
+ WARN_ONCE(1, "converter API is disabled");
+ return -ENOTSUPP;
+}
+
+static inline void converter_disable(struct converter_backend *conv)
+{
+ WARN_ONCE(1, "converter API is disabled");
+}
+
+static inline int
+converter_test_pattern_set(struct converter_backend *conv,
+ unsigned int chan,
+ enum converter_test_pattern pattern)
+{
+ WARN_ONCE(1, "converter API is disabled");
+ return -ENOTSUPP;
+}
+
+static inline int
+converter_data_format_set(struct converter_backend *conv,
+ unsigned int chan,
+ const struct converter_data_fmt *data)
+{
+ WARN_ONCE(1, "converter API is disabled");
+ return -ENOTSUPP;
+}
+
+static inline int converter_chan_enable(struct converter_backend *conv,
+ unsigned int chan)
+{
+ WARN_ONCE(1, "converter API is disabled");
+ return -ENOTSUPP;
+}
+
+static inline int converter_chan_disable(struct converter_backend *conv,
+ unsigned int chan)
+{
+ WARN_ONCE(1, "converter API is disabled");
+ return -ENOTSUPP;
+}
+
+static inline int converter_iodelay_set(struct converter_backend *conv,
+ unsigned int num_lanes,
+ unsigned int val)
+{
+ WARN_ONCE(1, "converter API is disabled");
+ return -ENOTSUPP;
+}
+
+static inline void
+converter_add_direct_reg_access(struct converter_backend *conv,
+ struct iio_dev *indio_dev)
+{
+ WARN_ONCE(1, "converter API is disabled");
+}
+
+static inline int converter_frontend_add(struct device *dev,
+ const struct frontend_ops *ops)
+{
+ WARN_ONCE(1, "converter API is disabled");
+ return -ENOTSUPP;
+}
+
+static inline void converter_frontend_del(struct device *dev)
+{
+ WARN_ONCE(1, "converter API is disabled");
+}
+
+#endif
+#endif
--
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 ` Nuno Sa [this message]
2023-08-30 17:02 ` [RFC PATCH 1/3] iio: addac: add new " 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 ` [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-2-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox