#define dev_fmt(fmt) "iio-backend: " fmt
#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/stringify.h>
#include <linux/types.h>
#include <linux/iio/backend.h>
#include <linux/iio/iio.h>
struct iio_backend {
struct list_head entry;
const struct iio_backend_ops *ops;
struct device *frontend_dev;
struct device *dev;
struct module *owner;
void *priv;
const char *name;
unsigned int cached_reg_addr;
u8 idx;
};
struct iio_backend_buffer_pair {
struct iio_backend *back;
struct iio_buffer *buffer;
};
static LIST_HEAD(iio_back_list);
static DEFINE_MUTEX(iio_back_lock);
#define iio_backend_check_op(back, op) ({ \
struct iio_backend *____back = back; \
int ____ret = 0; \
\
if (!____back->ops->op) \
____ret = -EOPNOTSUPP; \
\
____ret; \
})
#define iio_backend_op_call(back, op, args...) ({ \
struct iio_backend *__back = back; \
int __ret; \
\
__ret = iio_backend_check_op(__back, op); \
if (!__ret) \
__ret = __back->ops->op(__back, ##args); \
\
__ret; \
})
#define iio_backend_ptr_op_call(back, op, args...) ({ \
struct iio_backend *__back = back; \
void *ptr_err; \
int __ret; \
\
__ret = iio_backend_check_op(__back, op); \
if (__ret) \
ptr_err = ERR_PTR(__ret); \
else \
ptr_err = __back->ops->op(__back, ##args); \
\
ptr_err; \
})
#define iio_backend_void_op_call(back, op, args...) { \
struct iio_backend *__back = back; \
int __ret; \
\
__ret = iio_backend_check_op(__back, op); \
if (!__ret) \
__back->ops->op(__back, ##args); \
else \
dev_dbg(__back->dev, "Op(%s) not implemented\n",\
__stringify(op)); \
}
static ssize_t iio_backend_debugfs_read_reg(struct file *file,
char __user *userbuf,
size_t count, loff_t *ppos)
{
struct iio_backend *back = file->private_data;
char read_buf[20];
unsigned int val;
int ret, len;
ret = iio_backend_op_call(back, debugfs_reg_access,
back->cached_reg_addr, 0, &val);
if (ret)
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 iio_backend_debugfs_write_reg(struct file *file,
const char __user *userbuf,
size_t count, loff_t *ppos)
{
struct iio_backend *back = file->private_data;
unsigned int val;
char buf[80];
ssize_t rc;
int ret;
if (count >= sizeof(buf))
return -ENOSPC;
rc = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, userbuf, count);
if (rc < 0)
return rc;
buf[rc] = '\0';
ret = sscanf(buf, "%i %i", &back->cached_reg_addr, &val);
switch (ret) {
case 1:
return count;
case 2:
ret = iio_backend_op_call(back, debugfs_reg_access,
back->cached_reg_addr, val, NULL);
if (ret)
return ret;
return count;
default:
return -EINVAL;
}
}
static const struct file_operations iio_backend_debugfs_reg_fops = {
.open = simple_open,
.read = iio_backend_debugfs_read_reg,
.write = iio_backend_debugfs_write_reg,
};
static ssize_t iio_backend_debugfs_read_name(struct file *file,
char __user *userbuf,
size_t count, loff_t *ppos)
{
struct iio_backend *back = file->private_data;
char name[128];
int len;
len = scnprintf(name, sizeof(name), "%s\n", back->name);
return simple_read_from_buffer(userbuf, count, ppos, name, len);
}
static const struct file_operations iio_backend_debugfs_name_fops = {
.open = simple_open,
.read = iio_backend_debugfs_read_name,
};
void iio_backend_debugfs_add(struct iio_backend *back,
struct iio_dev *indio_dev)
{
struct dentry *d = iio_get_debugfs_dentry(indio_dev);
struct dentry *back_d;
char name[128];
if (!IS_ENABLED(CONFIG_DEBUG_FS) || !d)
return;
if (!back->ops->debugfs_reg_access && !back->name)
return;
snprintf(name, sizeof(name), "backend%d", back->idx);
back_d = debugfs_create_dir(name, d);
if (IS_ERR(back_d))
return;
if (back->ops->debugfs_reg_access)
debugfs_create_file("direct_reg_access", 0600, back_d, back,
&iio_backend_debugfs_reg_fops);
if (back->name)
debugfs_create_file("name", 0400, back_d, back,
&iio_backend_debugfs_name_fops);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_debugfs_add, "IIO_BACKEND");
ssize_t iio_backend_debugfs_print_chan_status(struct iio_backend *back,
unsigned int chan, char *buf,
size_t len)
{
if (!IS_ENABLED(CONFIG_DEBUG_FS))
return -ENODEV;
return iio_backend_op_call(back, debugfs_print_chan_status, chan, buf,
len);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_debugfs_print_chan_status, "IIO_BACKEND");
int iio_backend_chan_enable(struct iio_backend *back, unsigned int chan)
{
return iio_backend_op_call(back, chan_enable, chan);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_chan_enable, "IIO_BACKEND");
int iio_backend_chan_disable(struct iio_backend *back, unsigned int chan)
{
return iio_backend_op_call(back, chan_disable, chan);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_chan_disable, "IIO_BACKEND");
static void __iio_backend_disable(void *back)
{
iio_backend_void_op_call(back, disable);
}
void iio_backend_disable(struct iio_backend *back)
{
__iio_backend_disable(back);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_disable, "IIO_BACKEND");
int iio_backend_enable(struct iio_backend *back)
{
return iio_backend_op_call(back, enable);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_enable, "IIO_BACKEND");
int devm_iio_backend_enable(struct device *dev, struct iio_backend *back)
{
int ret;
ret = iio_backend_enable(back);
if (ret)
return ret;
return devm_add_action_or_reset(dev, __iio_backend_disable, back);
}
EXPORT_SYMBOL_NS_GPL(devm_iio_backend_enable, "IIO_BACKEND");
int iio_backend_data_format_set(struct iio_backend *back, unsigned int chan,
const struct iio_backend_data_fmt *data)
{
if (!data || data->type >= IIO_BACKEND_DATA_TYPE_MAX)
return -EINVAL;
return iio_backend_op_call(back, data_format_set, chan, data);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_data_format_set, "IIO_BACKEND");
int iio_backend_data_source_set(struct iio_backend *back, unsigned int chan,
enum iio_backend_data_source data)
{
if (data >= IIO_BACKEND_DATA_SOURCE_MAX)
return -EINVAL;
return iio_backend_op_call(back, data_source_set, chan, data);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_data_source_set, "IIO_BACKEND");
int iio_backend_data_source_get(struct iio_backend *back, unsigned int chan,
enum iio_backend_data_source *data)
{
int ret;
ret = iio_backend_op_call(back, data_source_get, chan, data);
if (ret)
return ret;
if (*data >= IIO_BACKEND_DATA_SOURCE_MAX)
return -EINVAL;
return 0;
}
EXPORT_SYMBOL_NS_GPL(iio_backend_data_source_get, "IIO_BACKEND");
int iio_backend_set_sampling_freq(struct iio_backend *back, unsigned int chan,
u64 sample_rate_hz)
{
return iio_backend_op_call(back, set_sample_rate, chan, sample_rate_hz);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_set_sampling_freq, "IIO_BACKEND");
int iio_backend_test_pattern_set(struct iio_backend *back,
unsigned int chan,
enum iio_backend_test_pattern pattern)
{
if (pattern >= IIO_BACKEND_TEST_PATTERN_MAX)
return -EINVAL;
return iio_backend_op_call(back, test_pattern_set, chan, pattern);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_test_pattern_set, "IIO_BACKEND");
int iio_backend_chan_status(struct iio_backend *back, unsigned int chan,
bool *error)
{
return iio_backend_op_call(back, chan_status, chan, error);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_chan_status, "IIO_BACKEND");
int iio_backend_iodelay_set(struct iio_backend *back, unsigned int lane,
unsigned int taps)
{
return iio_backend_op_call(back, iodelay_set, lane, taps);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_iodelay_set, "IIO_BACKEND");
int iio_backend_data_sample_trigger(struct iio_backend *back,
enum iio_backend_sample_trigger trigger)
{
if (trigger >= IIO_BACKEND_SAMPLE_TRIGGER_MAX)
return -EINVAL;
return iio_backend_op_call(back, data_sample_trigger, trigger);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_data_sample_trigger, "IIO_BACKEND");
static void iio_backend_free_buffer(void *arg)
{
struct iio_backend_buffer_pair *pair = arg;
iio_backend_void_op_call(pair->back, free_buffer, pair->buffer);
}
int devm_iio_backend_request_buffer(struct device *dev,
struct iio_backend *back,
struct iio_dev *indio_dev)
{
struct iio_backend_buffer_pair *pair;
struct iio_buffer *buffer;
pair = devm_kzalloc(dev, sizeof(*pair), GFP_KERNEL);
if (!pair)
return -ENOMEM;
buffer = iio_backend_ptr_op_call(back, request_buffer, indio_dev);
if (IS_ERR(buffer))
return PTR_ERR(buffer);
pair->back = back;
pair->buffer = buffer;
return devm_add_action_or_reset(dev, iio_backend_free_buffer, pair);
}
EXPORT_SYMBOL_NS_GPL(devm_iio_backend_request_buffer, "IIO_BACKEND");
int iio_backend_read_raw(struct iio_backend *back,
struct iio_chan_spec const *chan, int *val, int *val2,
long mask)
{
return iio_backend_op_call(back, read_raw, chan, val, val2, mask);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_read_raw, "IIO_BACKEND");
static struct iio_backend *iio_backend_from_indio_dev_parent(const struct device *dev)
{
struct iio_backend *back = ERR_PTR(-ENODEV), *iter;
guard(mutex)(&iio_back_lock);
list_for_each_entry(iter, &iio_back_list, entry) {
if (dev == iter->frontend_dev) {
if (!IS_ERR(back)) {
dev_warn(dev,
"Multiple backends! get_iio_backend() needs to be implemented");
return ERR_PTR(-ENODEV);
}
back = iter;
}
}
return back;
}
ssize_t iio_backend_ext_info_get(struct iio_dev *indio_dev, uintptr_t private,
const struct iio_chan_spec *chan, char *buf)
{
struct iio_backend *back;
back = iio_backend_from_indio_dev_parent(indio_dev->dev.parent);
if (IS_ERR(back))
return PTR_ERR(back);
return iio_backend_op_call(back, ext_info_get, private, chan, buf);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_ext_info_get, "IIO_BACKEND");
ssize_t iio_backend_ext_info_set(struct iio_dev *indio_dev, uintptr_t private,
const struct iio_chan_spec *chan,
const char *buf, size_t len)
{
struct iio_backend *back;
back = iio_backend_from_indio_dev_parent(indio_dev->dev.parent);
if (IS_ERR(back))
return PTR_ERR(back);
return iio_backend_op_call(back, ext_info_set, private, chan, buf, len);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_ext_info_set, "IIO_BACKEND");
int iio_backend_interface_type_get(struct iio_backend *back,
enum iio_backend_interface_type *type)
{
int ret;
ret = iio_backend_op_call(back, interface_type_get, type);
if (ret)
return ret;
if (*type >= IIO_BACKEND_INTERFACE_MAX)
return -EINVAL;
return 0;
}
EXPORT_SYMBOL_NS_GPL(iio_backend_interface_type_get, "IIO_BACKEND");
int iio_backend_data_size_set(struct iio_backend *back, unsigned int size)
{
if (!size)
return -EINVAL;
return iio_backend_op_call(back, data_size_set, size);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_data_size_set, "IIO_BACKEND");
int iio_backend_oversampling_ratio_set(struct iio_backend *back,
unsigned int chan,
unsigned int ratio)
{
return iio_backend_op_call(back, oversampling_ratio_set, chan, ratio);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_oversampling_ratio_set, "IIO_BACKEND");
int iio_backend_extend_chan_spec(struct iio_backend *back,
struct iio_chan_spec *chan)
{
const struct iio_chan_spec_ext_info *frontend_ext_info = chan->ext_info;
const struct iio_chan_spec_ext_info *back_ext_info;
int ret;
ret = iio_backend_op_call(back, extend_chan_spec, chan);
if (ret)
return ret;
if (frontend_ext_info && chan->ext_info != frontend_ext_info)
return -EOPNOTSUPP;
if (!chan->ext_info)
return 0;
for (back_ext_info = chan->ext_info; back_ext_info->name; back_ext_info++) {
if (back_ext_info->read != iio_backend_ext_info_get)
return -EINVAL;
if (back_ext_info->write != iio_backend_ext_info_set)
return -EINVAL;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(iio_backend_extend_chan_spec, "IIO_BACKEND");
static void iio_backend_release(void *arg)
{
struct iio_backend *back = arg;
module_put(back->owner);
}
static int __devm_iio_backend_get(struct device *dev, struct iio_backend *back)
{
struct device_link *link;
int ret;
if (!try_module_get(back->owner))
return dev_err_probe(dev, -ENODEV,
"Cannot get module reference\n");
ret = devm_add_action_or_reset(dev, iio_backend_release, back);
if (ret)
return ret;
link = device_link_add(dev, back->dev, DL_FLAG_AUTOREMOVE_CONSUMER);
if (!link)
return dev_err_probe(dev, -EINVAL,
"Could not link to supplier(%s)\n",
dev_name(back->dev));
back->frontend_dev = dev;
dev_dbg(dev, "Found backend(%s) device\n", dev_name(back->dev));
return 0;
}
int iio_backend_filter_type_set(struct iio_backend *back,
enum iio_backend_filter_type type)
{
if (type >= IIO_BACKEND_FILTER_TYPE_MAX)
return -EINVAL;
return iio_backend_op_call(back, filter_type_set, type);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_filter_type_set, "IIO_BACKEND");
int iio_backend_interface_data_align(struct iio_backend *back, u32 timeout_us)
{
if (!timeout_us)
return -EINVAL;
return iio_backend_op_call(back, interface_data_align, timeout_us);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_interface_data_align, "IIO_BACKEND");
int iio_backend_num_lanes_set(struct iio_backend *back, unsigned int num_lanes)
{
if (!num_lanes)
return -EINVAL;
return iio_backend_op_call(back, num_lanes_set, num_lanes);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_num_lanes_set, "IIO_BACKEND");
int iio_backend_ddr_enable(struct iio_backend *back)
{
return iio_backend_op_call(back, ddr_enable);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_ddr_enable, "IIO_BACKEND");
int iio_backend_ddr_disable(struct iio_backend *back)
{
return iio_backend_op_call(back, ddr_disable);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_ddr_disable, "IIO_BACKEND");
int iio_backend_data_stream_enable(struct iio_backend *back)
{
return iio_backend_op_call(back, data_stream_enable);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_data_stream_enable, "IIO_BACKEND");
int iio_backend_data_stream_disable(struct iio_backend *back)
{
return iio_backend_op_call(back, data_stream_disable);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_data_stream_disable, "IIO_BACKEND");
int iio_backend_data_transfer_addr(struct iio_backend *back, u32 address)
{
return iio_backend_op_call(back, data_transfer_addr, address);
}
EXPORT_SYMBOL_NS_GPL(iio_backend_data_transfer_addr, "IIO_BACKEND");
static struct iio_backend *__devm_iio_backend_fwnode_get(struct device *dev, const char *name,
struct fwnode_handle *fwnode)
{
struct fwnode_handle *fwnode_back;
struct iio_backend *back;
unsigned int index;
int ret;
if (name) {
ret = device_property_match_string(dev, "io-backend-names",
name);
if (ret < 0)
return ERR_PTR(ret);
index = ret;
} else {
index = 0;
}
fwnode_back = fwnode_find_reference(fwnode, "io-backends", index);
if (IS_ERR(fwnode_back))
return dev_err_cast_probe(dev, fwnode_back,
"Cannot get Firmware reference\n");
guard(mutex)(&iio_back_lock);
list_for_each_entry(back, &iio_back_list, entry) {
if (!device_match_fwnode(back->dev, fwnode_back))
continue;
fwnode_handle_put(fwnode_back);
ret = __devm_iio_backend_get(dev, back);
if (ret)
return ERR_PTR(ret);
if (name)
back->idx = index;
return back;
}
fwnode_handle_put(fwnode_back);
return ERR_PTR(-EPROBE_DEFER);
}
struct iio_backend *devm_iio_backend_get(struct device *dev, const char *name)
{
return __devm_iio_backend_fwnode_get(dev, name, dev_fwnode(dev));
}
EXPORT_SYMBOL_NS_GPL(devm_iio_backend_get, "IIO_BACKEND");
struct iio_backend *devm_iio_backend_fwnode_get(struct device *dev,
const char *name,
struct fwnode_handle *fwnode)
{
return __devm_iio_backend_fwnode_get(dev, name, fwnode);
}
EXPORT_SYMBOL_NS_GPL(devm_iio_backend_fwnode_get, "IIO_BACKEND");
struct iio_backend *
__devm_iio_backend_get_from_fwnode_lookup(struct device *dev,
struct fwnode_handle *fwnode)
{
struct iio_backend *back;
int ret;
guard(mutex)(&iio_back_lock);
list_for_each_entry(back, &iio_back_list, entry) {
if (!device_match_fwnode(back->dev, fwnode))
continue;
ret = __devm_iio_backend_get(dev, back);
if (ret)
return ERR_PTR(ret);
return back;
}
return ERR_PTR(-EPROBE_DEFER);
}
EXPORT_SYMBOL_NS_GPL(__devm_iio_backend_get_from_fwnode_lookup, "IIO_BACKEND");
void *iio_backend_get_priv(const struct iio_backend *back)
{
return back->priv;
}
EXPORT_SYMBOL_NS_GPL(iio_backend_get_priv, "IIO_BACKEND");
static void iio_backend_unregister(void *arg)
{
struct iio_backend *back = arg;
guard(mutex)(&iio_back_lock);
list_del(&back->entry);
}
int devm_iio_backend_register(struct device *dev,
const struct iio_backend_info *info, void *priv)
{
struct iio_backend *back;
if (!info || !info->ops)
return dev_err_probe(dev, -EINVAL, "No backend ops given\n");
back = devm_kzalloc(dev, sizeof(*back), GFP_KERNEL);
if (!back)
return -ENOMEM;
back->ops = info->ops;
back->name = info->name;
back->owner = dev->driver->owner;
back->dev = dev;
back->priv = priv;
scoped_guard(mutex, &iio_back_lock)
list_add(&back->entry, &iio_back_list);
return devm_add_action_or_reset(dev, iio_backend_unregister, back);
}
EXPORT_SYMBOL_NS_GPL(devm_iio_backend_register, "IIO_BACKEND");
MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
MODULE_DESCRIPTION("Framework to handle complex IIO aggregate devices");
MODULE_LICENSE("GPL");