summaryrefslogtreecommitdiffstats
path: root/drivers/iio/buffer/industrialio-hw-consumer.c
blob: 95165697d8aea06d07c673c5593500cbb8f5683b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright 2017 Analog Devices Inc.
 *  Author: Lars-Peter Clausen <lars@metafoo.de>
 */

#include <linux/err.h>
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/module.h>

#include <linux/iio/iio.h>
#include <linux/iio/consumer.h>
#include <linux/iio/hw-consumer.h>
#include <linux/iio/buffer_impl.h>

/**
 * struct iio_hw_consumer - IIO hw consumer block
 * @buffers: hardware buffers list head.
 * @channels: IIO provider channels.
 */
struct iio_hw_consumer {
	struct list_head buffers;
	struct iio_channel *channels;
};

struct hw_consumer_buffer {
	struct list_head head;
	struct iio_dev *indio_dev;
	struct iio_buffer buffer;
	long scan_mask[];
};

static struct hw_consumer_buffer *iio_buffer_to_hw_consumer_buffer(
	struct iio_buffer *buffer)
{
	return container_of(buffer, struct hw_consumer_buffer, buffer);
}

static void iio_hw_buf_release(struct iio_buffer *buffer)
{
	struct hw_consumer_buffer *hw_buf =
		iio_buffer_to_hw_consumer_buffer(buffer);
	kfree(hw_buf);
}

static const struct iio_buffer_access_funcs iio_hw_buf_access = {
	.release = &iio_hw_buf_release,
	.modes = INDIO_BUFFER_HARDWARE,
};

static struct hw_consumer_buffer *iio_hw_consumer_get_buffer(
	struct iio_hw_consumer *hwc, struct iio_dev *indio_dev)
{
	size_t mask_size = BITS_TO_LONGS(indio_dev->masklength) * sizeof(long);
	struct hw_consumer_buffer *buf;

	list_for_each_entry(buf, &hwc->buffers, head) {
		if (buf->indio_dev == indio_dev)
			return buf;
	}

	buf = kzalloc(sizeof(*buf) + mask_size, GFP_KERNEL);
	if (!buf)
		return NULL;

	buf->buffer.access = &iio_hw_buf_access;
	buf->indio_dev = indio_dev;
	buf->buffer.scan_mask = buf->scan_mask;

	iio_buffer_init(&buf->buffer);
	list_add_tail(&buf->head, &hwc->buffers);

	return buf;
}

/**
 * iio_hw_consumer_alloc() - Allocate IIO hardware consumer
 * @dev: Pointer to consumer device.
 *
 * Returns a valid iio_hw_consumer on success or a ERR_PTR() on failure.
 */
struct iio_hw_consumer *iio_hw_consumer_alloc(struct device *dev)
{
	struct hw_consumer_buffer *buf;
	struct iio_hw_consumer *hwc;
	struct iio_channel *chan;
	int ret;

	hwc = kzalloc(sizeof(*hwc), GFP_KERNEL);
	if (!hwc)
		return ERR_PTR(-ENOMEM);

	INIT_LIST_HEAD(&hwc->buffers);

	hwc->channels = iio_channel_get_all(dev);
	if (IS_ERR(hwc->channels)) {
		ret = PTR_ERR(hwc->channels);
		goto err_free_hwc;
	}

	chan = &hwc->channels[0];
	while (chan->indio_dev) {
		buf = iio_hw_consumer_get_buffer(hwc, chan->indio_dev);
		if (!buf) {
			ret = -ENOMEM;
			goto err_put_buffers;
		}
		set_bit(chan->channel->scan_index, buf->buffer.scan_mask);
		chan++;
	}

	return hwc;

err_put_buffers:
	list_for_each_entry(buf, &hwc->buffers, head)
		iio_buffer_put(&buf->buffer);
	iio_channel_release_all(hwc->channels);
err_free_hwc:
	kfree(hwc);
	return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(iio_hw_consumer_alloc);

/**
 * iio_hw_consumer_free() - Free IIO hardware consumer
 * @hwc: hw consumer to free.
 */
void iio_hw_consumer_free(struct iio_hw_consumer *hwc)
{
	struct hw_consumer_buffer *buf, *n;

	iio_channel_release_all(hwc->channels);
	list_for_each_entry_safe(buf, n, &hwc->buffers, head)
		iio_buffer_put(&buf->buffer);
	kfree(hwc);
}
EXPORT_SYMBOL_GPL(iio_hw_consumer_free);

static void devm_iio_hw_consumer_release(struct device *dev, void *res)
{
	iio_hw_consumer_free(*(struct iio_hw_consumer **)res);
}

static int devm_iio_hw_consumer_match(struct device *dev, void *res, void *data)
{
	struct iio_hw_consumer **r = res;

	if (!r || !*r) {
		WARN_ON(!r || !*r);
		return 0;
	}
	return *r == data;
}

/**
 * devm_iio_hw_consumer_alloc - Resource-managed iio_hw_consumer_alloc()
 * @dev: Pointer to consumer device.
 *
 * Managed iio_hw_consumer_alloc. iio_hw_consumer allocated with this function
 * is automatically freed on driver detach.
 *
 * If an iio_hw_consumer allocated with this function needs to be freed
 * separately, devm_iio_hw_consumer_free() must be used.
 *
 * returns pointer to allocated iio_hw_consumer on success, NULL on failure.
 */
struct iio_hw_consumer *devm_iio_hw_consumer_alloc(struct device *dev)
{
	struct iio_hw_consumer **ptr, *iio_hwc;

	ptr = devres_alloc(devm_iio_hw_consumer_release, sizeof(*ptr),
			   GFP_KERNEL);
	if (!ptr)
		return NULL;

	iio_hwc = iio_hw_consumer_alloc(dev);
	if (IS_ERR(iio_hwc)) {
		devres_free(ptr);
	} else {
		*ptr = iio_hwc;
		devres_add(dev, ptr);
	}

	return iio_hwc;
}
EXPORT_SYMBOL_GPL(devm_iio_hw_consumer_alloc);

/**
 * devm_iio_hw_consumer_free - Resource-managed iio_hw_consumer_free()
 * @dev: Pointer to consumer device.
 * @hwc: iio_hw_consumer to free.
 *
 * Free iio_hw_consumer allocated with devm_iio_hw_consumer_alloc().
 */
void devm_iio_hw_consumer_free(struct device *dev, struct iio_hw_consumer *hwc)
{
	int rc;

	rc = devres_release(dev, devm_iio_hw_consumer_release,
			    devm_iio_hw_consumer_match, hwc);
	WARN_ON(rc);
}
EXPORT_SYMBOL_GPL(devm_iio_hw_consumer_free);

/**
 * iio_hw_consumer_enable() - Enable IIO hardware consumer
 * @hwc: iio_hw_consumer to enable.
 *
 * Returns 0 on success.
 */
int iio_hw_consumer_enable(struct iio_hw_consumer *hwc)
{
	struct hw_consumer_buffer *buf;
	int ret;

	list_for_each_entry(buf, &hwc->buffers, head) {
		ret = iio_update_buffers(buf->indio_dev, &buf->buffer, NULL);
		if (ret)
			goto err_disable_buffers;
	}

	return 0;

err_disable_buffers:
	list_for_each_entry_continue_reverse(buf, &hwc->buffers, head)
		iio_update_buffers(buf->indio_dev, NULL, &buf->buffer);
	return ret;
}
EXPORT_SYMBOL_GPL(iio_hw_consumer_enable);

/**
 * iio_hw_consumer_disable() - Disable IIO hardware consumer
 * @hwc: iio_hw_consumer to disable.
 */
void iio_hw_consumer_disable(struct iio_hw_consumer *hwc)
{
	struct hw_consumer_buffer *buf;

	list_for_each_entry(buf, &hwc->buffers, head)
		iio_update_buffers(buf->indio_dev, NULL, &buf->buffer);
}
EXPORT_SYMBOL_GPL(iio_hw_consumer_disable);

MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_DESCRIPTION("Hardware consumer buffer the IIO framework");
MODULE_LICENSE("GPL v2");