diff options
Diffstat (limited to 'drivers/iio/buffer')
-rw-r--r-- | drivers/iio/buffer/Kconfig | 24 | ||||
-rw-r--r-- | drivers/iio/buffer/Makefile | 8 | ||||
-rw-r--r-- | drivers/iio/buffer/industrialio-buffer-cb.c | 138 | ||||
-rw-r--r-- | drivers/iio/buffer/industrialio-triggered-buffer.c | 103 | ||||
-rw-r--r-- | drivers/iio/buffer/kfifo_buf.c | 221 |
5 files changed, 494 insertions, 0 deletions
diff --git a/drivers/iio/buffer/Kconfig b/drivers/iio/buffer/Kconfig new file mode 100644 index 000000000000..0a7b2fd3699b --- /dev/null +++ b/drivers/iio/buffer/Kconfig @@ -0,0 +1,24 @@ +# +# Industrial I/O generic buffer implementations +# +# When adding new entries keep the list in alphabetical order + +config IIO_BUFFER_CB + tristate "IIO callback buffer used for push in-kernel interfaces" + help + Should be selected by any drivers that do in-kernel push + usage. That is, those where the data is pushed to the consumer. + +config IIO_KFIFO_BUF + tristate "Industrial I/O buffering based on kfifo" + help + A simple fifo based on kfifo. Note that this currently provides + no buffer events so it is up to userspace to work out how + often to read from the buffer. + +config IIO_TRIGGERED_BUFFER + tristate + select IIO_TRIGGER + select IIO_KFIFO_BUF + help + Provides helper functions for setting up triggered buffers. diff --git a/drivers/iio/buffer/Makefile b/drivers/iio/buffer/Makefile new file mode 100644 index 000000000000..4d193b9a9123 --- /dev/null +++ b/drivers/iio/buffer/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the industrial I/O buffer implementations +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_IIO_BUFFER_CB) += industrialio-buffer-cb.o +obj-$(CONFIG_IIO_TRIGGERED_BUFFER) += industrialio-triggered-buffer.o +obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o diff --git a/drivers/iio/buffer/industrialio-buffer-cb.c b/drivers/iio/buffer/industrialio-buffer-cb.c new file mode 100644 index 000000000000..323079c3ccce --- /dev/null +++ b/drivers/iio/buffer/industrialio-buffer-cb.c @@ -0,0 +1,138 @@ +/* The industrial I/O callback buffer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/iio/buffer.h> +#include <linux/iio/consumer.h> + +struct iio_cb_buffer { + struct iio_buffer buffer; + int (*cb)(const void *data, void *private); + void *private; + struct iio_channel *channels; +}; + +static struct iio_cb_buffer *buffer_to_cb_buffer(struct iio_buffer *buffer) +{ + return container_of(buffer, struct iio_cb_buffer, buffer); +} + +static int iio_buffer_cb_store_to(struct iio_buffer *buffer, const void *data) +{ + struct iio_cb_buffer *cb_buff = buffer_to_cb_buffer(buffer); + return cb_buff->cb(data, cb_buff->private); +} + +static void iio_buffer_cb_release(struct iio_buffer *buffer) +{ + struct iio_cb_buffer *cb_buff = buffer_to_cb_buffer(buffer); + kfree(cb_buff->buffer.scan_mask); + kfree(cb_buff); +} + +static const struct iio_buffer_access_funcs iio_cb_access = { + .store_to = &iio_buffer_cb_store_to, + .release = &iio_buffer_cb_release, + + .modes = INDIO_BUFFER_SOFTWARE | INDIO_BUFFER_TRIGGERED, +}; + +struct iio_cb_buffer *iio_channel_get_all_cb(struct device *dev, + int (*cb)(const void *data, + void *private), + void *private) +{ + int ret; + struct iio_cb_buffer *cb_buff; + struct iio_dev *indio_dev; + struct iio_channel *chan; + + cb_buff = kzalloc(sizeof(*cb_buff), GFP_KERNEL); + if (cb_buff == NULL) + return ERR_PTR(-ENOMEM); + + iio_buffer_init(&cb_buff->buffer); + + cb_buff->private = private; + cb_buff->cb = cb; + cb_buff->buffer.access = &iio_cb_access; + INIT_LIST_HEAD(&cb_buff->buffer.demux_list); + + cb_buff->channels = iio_channel_get_all(dev); + if (IS_ERR(cb_buff->channels)) { + ret = PTR_ERR(cb_buff->channels); + goto error_free_cb_buff; + } + + indio_dev = cb_buff->channels[0].indio_dev; + cb_buff->buffer.scan_mask + = kcalloc(BITS_TO_LONGS(indio_dev->masklength), sizeof(long), + GFP_KERNEL); + if (cb_buff->buffer.scan_mask == NULL) { + ret = -ENOMEM; + goto error_release_channels; + } + chan = &cb_buff->channels[0]; + while (chan->indio_dev) { + if (chan->indio_dev != indio_dev) { + ret = -EINVAL; + goto error_free_scan_mask; + } + set_bit(chan->channel->scan_index, + cb_buff->buffer.scan_mask); + chan++; + } + + return cb_buff; + +error_free_scan_mask: + kfree(cb_buff->buffer.scan_mask); +error_release_channels: + iio_channel_release_all(cb_buff->channels); +error_free_cb_buff: + kfree(cb_buff); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(iio_channel_get_all_cb); + +int iio_channel_start_all_cb(struct iio_cb_buffer *cb_buff) +{ + return iio_update_buffers(cb_buff->channels[0].indio_dev, + &cb_buff->buffer, + NULL); +} +EXPORT_SYMBOL_GPL(iio_channel_start_all_cb); + +void iio_channel_stop_all_cb(struct iio_cb_buffer *cb_buff) +{ + iio_update_buffers(cb_buff->channels[0].indio_dev, + NULL, + &cb_buff->buffer); +} +EXPORT_SYMBOL_GPL(iio_channel_stop_all_cb); + +void iio_channel_release_all_cb(struct iio_cb_buffer *cb_buff) +{ + iio_channel_release_all(cb_buff->channels); + iio_buffer_put(&cb_buff->buffer); +} +EXPORT_SYMBOL_GPL(iio_channel_release_all_cb); + +struct iio_channel +*iio_channel_cb_get_channels(const struct iio_cb_buffer *cb_buffer) +{ + return cb_buffer->channels; +} +EXPORT_SYMBOL_GPL(iio_channel_cb_get_channels); + +MODULE_AUTHOR("Jonathan Cameron <jic23@kernel.org>"); +MODULE_DESCRIPTION("Industrial I/O callback buffer"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/buffer/industrialio-triggered-buffer.c b/drivers/iio/buffer/industrialio-triggered-buffer.c new file mode 100644 index 000000000000..4b2858ba1fd6 --- /dev/null +++ b/drivers/iio/buffer/industrialio-triggered-buffer.c @@ -0,0 +1,103 @@ + /* + * Copyright (c) 2012 Analog Devices, Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/module.h> +#include <linux/iio/iio.h> +#include <linux/iio/buffer.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/iio/triggered_buffer.h> +#include <linux/iio/trigger_consumer.h> + +static const struct iio_buffer_setup_ops iio_triggered_buffer_setup_ops = { + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, +}; + +/** + * iio_triggered_buffer_setup() - Setup triggered buffer and pollfunc + * @indio_dev: IIO device structure + * @h: Function which will be used as pollfunc top half + * @thread: Function which will be used as pollfunc bottom half + * @setup_ops: Buffer setup functions to use for this device. + * If NULL the default setup functions for triggered + * buffers will be used. + * + * This function combines some common tasks which will normally be performed + * when setting up a triggered buffer. It will allocate the buffer and the + * pollfunc. + * + * Before calling this function the indio_dev structure should already be + * completely initialized, but not yet registered. In practice this means that + * this function should be called right before iio_device_register(). + * + * To free the resources allocated by this function call + * iio_triggered_buffer_cleanup(). + */ +int iio_triggered_buffer_setup(struct iio_dev *indio_dev, + irqreturn_t (*h)(int irq, void *p), + irqreturn_t (*thread)(int irq, void *p), + const struct iio_buffer_setup_ops *setup_ops) +{ + struct iio_buffer *buffer; + int ret; + + buffer = iio_kfifo_allocate(); + if (!buffer) { + ret = -ENOMEM; + goto error_ret; + } + + iio_device_attach_buffer(indio_dev, buffer); + + indio_dev->pollfunc = iio_alloc_pollfunc(h, + thread, + IRQF_ONESHOT, + indio_dev, + "%s_consumer%d", + indio_dev->name, + indio_dev->id); + if (indio_dev->pollfunc == NULL) { + ret = -ENOMEM; + goto error_kfifo_free; + } + + /* Ring buffer functions - here trigger setup related */ + if (setup_ops) + indio_dev->setup_ops = setup_ops; + else + indio_dev->setup_ops = &iio_triggered_buffer_setup_ops; + + /* Flag that polled ring buffering is possible */ + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + + return 0; + +error_kfifo_free: + iio_kfifo_free(indio_dev->buffer); +error_ret: + return ret; +} +EXPORT_SYMBOL(iio_triggered_buffer_setup); + +/** + * iio_triggered_buffer_cleanup() - Free resources allocated by iio_triggered_buffer_setup() + * @indio_dev: IIO device structure + */ +void iio_triggered_buffer_cleanup(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_kfifo_free(indio_dev->buffer); +} +EXPORT_SYMBOL(iio_triggered_buffer_cleanup); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("IIO helper functions for setting up triggered buffers"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/buffer/kfifo_buf.c b/drivers/iio/buffer/kfifo_buf.c new file mode 100644 index 000000000000..c5b999f0c519 --- /dev/null +++ b/drivers/iio/buffer/kfifo_buf.c @@ -0,0 +1,221 @@ +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/workqueue.h> +#include <linux/kfifo.h> +#include <linux/mutex.h> +#include <linux/iio/kfifo_buf.h> +#include <linux/sched.h> +#include <linux/poll.h> + +struct iio_kfifo { + struct iio_buffer buffer; + struct kfifo kf; + struct mutex user_lock; + int update_needed; +}; + +#define iio_to_kfifo(r) container_of(r, struct iio_kfifo, buffer) + +static inline int __iio_allocate_kfifo(struct iio_kfifo *buf, + int bytes_per_datum, int length) +{ + if ((length == 0) || (bytes_per_datum == 0)) + return -EINVAL; + + return __kfifo_alloc((struct __kfifo *)&buf->kf, length, + bytes_per_datum, GFP_KERNEL); +} + +static int iio_request_update_kfifo(struct iio_buffer *r) +{ + int ret = 0; + struct iio_kfifo *buf = iio_to_kfifo(r); + + mutex_lock(&buf->user_lock); + if (buf->update_needed) { + kfifo_free(&buf->kf); + ret = __iio_allocate_kfifo(buf, buf->buffer.bytes_per_datum, + buf->buffer.length); + if (ret >= 0) + buf->update_needed = false; + } else { + kfifo_reset_out(&buf->kf); + } + mutex_unlock(&buf->user_lock); + + return ret; +} + +static int iio_mark_update_needed_kfifo(struct iio_buffer *r) +{ + struct iio_kfifo *kf = iio_to_kfifo(r); + kf->update_needed = true; + return 0; +} + +static int iio_set_bytes_per_datum_kfifo(struct iio_buffer *r, size_t bpd) +{ + if (r->bytes_per_datum != bpd) { + r->bytes_per_datum = bpd; + iio_mark_update_needed_kfifo(r); + } + return 0; +} + +static int iio_set_length_kfifo(struct iio_buffer *r, int length) +{ + /* Avoid an invalid state */ + if (length < 2) + length = 2; + if (r->length != length) { + r->length = length; + iio_mark_update_needed_kfifo(r); + } + return 0; +} + +static int iio_store_to_kfifo(struct iio_buffer *r, + const void *data) +{ + int ret; + struct iio_kfifo *kf = iio_to_kfifo(r); + ret = kfifo_in(&kf->kf, data, 1); + if (ret != 1) + return -EBUSY; + return 0; +} + +static int iio_read_first_n_kfifo(struct iio_buffer *r, + size_t n, char __user *buf) +{ + int ret, copied; + struct iio_kfifo *kf = iio_to_kfifo(r); + + if (mutex_lock_interruptible(&kf->user_lock)) + return -ERESTARTSYS; + + if (!kfifo_initialized(&kf->kf) || n < kfifo_esize(&kf->kf)) + ret = -EINVAL; + else + ret = kfifo_to_user(&kf->kf, buf, n, &copied); + mutex_unlock(&kf->user_lock); + if (ret < 0) + return ret; + + return copied; +} + +static size_t iio_kfifo_buf_data_available(struct iio_buffer *r) +{ + struct iio_kfifo *kf = iio_to_kfifo(r); + size_t samples; + + mutex_lock(&kf->user_lock); + samples = kfifo_len(&kf->kf); + mutex_unlock(&kf->user_lock); + + return samples; +} + +static void iio_kfifo_buffer_release(struct iio_buffer *buffer) +{ + struct iio_kfifo *kf = iio_to_kfifo(buffer); + + mutex_destroy(&kf->user_lock); + kfifo_free(&kf->kf); + kfree(kf); +} + +static const struct iio_buffer_access_funcs kfifo_access_funcs = { + .store_to = &iio_store_to_kfifo, + .read_first_n = &iio_read_first_n_kfifo, + .data_available = iio_kfifo_buf_data_available, + .request_update = &iio_request_update_kfifo, + .set_bytes_per_datum = &iio_set_bytes_per_datum_kfifo, + .set_length = &iio_set_length_kfifo, + .release = &iio_kfifo_buffer_release, + + .modes = INDIO_BUFFER_SOFTWARE | INDIO_BUFFER_TRIGGERED, +}; + +struct iio_buffer *iio_kfifo_allocate(void) +{ + struct iio_kfifo *kf; + + kf = kzalloc(sizeof(*kf), GFP_KERNEL); + if (!kf) + return NULL; + + kf->update_needed = true; + iio_buffer_init(&kf->buffer); + kf->buffer.access = &kfifo_access_funcs; + kf->buffer.length = 2; + mutex_init(&kf->user_lock); + + return &kf->buffer; +} +EXPORT_SYMBOL(iio_kfifo_allocate); + +void iio_kfifo_free(struct iio_buffer *r) +{ + iio_buffer_put(r); +} +EXPORT_SYMBOL(iio_kfifo_free); + +static void devm_iio_kfifo_release(struct device *dev, void *res) +{ + iio_kfifo_free(*(struct iio_buffer **)res); +} + +static int devm_iio_kfifo_match(struct device *dev, void *res, void *data) +{ + struct iio_buffer **r = res; + + if (WARN_ON(!r || !*r)) + return 0; + + return *r == data; +} + +/** + * devm_iio_fifo_allocate - Resource-managed iio_kfifo_allocate() + * @dev: Device to allocate kfifo buffer for + * + * RETURNS: + * Pointer to allocated iio_buffer on success, NULL on failure. + */ +struct iio_buffer *devm_iio_kfifo_allocate(struct device *dev) +{ + struct iio_buffer **ptr, *r; + + ptr = devres_alloc(devm_iio_kfifo_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return NULL; + + r = iio_kfifo_allocate(); + if (r) { + *ptr = r; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return r; +} +EXPORT_SYMBOL(devm_iio_kfifo_allocate); + +/** + * devm_iio_fifo_free - Resource-managed iio_kfifo_free() + * @dev: Device the buffer belongs to + * @r: The buffer associated with the device + */ +void devm_iio_kfifo_free(struct device *dev, struct iio_buffer *r) +{ + WARN_ON(devres_release(dev, devm_iio_kfifo_release, + devm_iio_kfifo_match, r)); +} +EXPORT_SYMBOL(devm_iio_kfifo_free); + +MODULE_LICENSE("GPL"); |