1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright 2011 Analog Devices Inc.
4 */
5
6#include <linux/kernel.h>
7#include <linux/module.h>
8#include <linux/platform_device.h>
9#include <linux/slab.h>
10#include <linux/list.h>
11#include <linux/irq_work.h>
12
13#include <linux/iio/iio.h>
14#include <linux/iio/trigger.h>
15
16struct iio_sysfs_trig {
17	struct iio_trigger *trig;
18	struct irq_work work;
19	int id;
20	struct list_head l;
21};
22
23static LIST_HEAD(iio_sysfs_trig_list);
24static DEFINE_MUTEX(iio_sysfs_trig_list_mut);
25
26static int iio_sysfs_trigger_probe(int id);
27static ssize_t iio_sysfs_trig_add(struct device *dev,
28				  struct device_attribute *attr,
29				  const char *buf,
30				  size_t len)
31{
32	int ret;
33	unsigned long input;
34
35	ret = kstrtoul(buf, 10, &input);
36	if (ret)
37		return ret;
38	ret = iio_sysfs_trigger_probe(input);
39	if (ret)
40		return ret;
41	return len;
42}
43static DEVICE_ATTR(add_trigger, S_IWUSR, NULL, &iio_sysfs_trig_add);
44
45static int iio_sysfs_trigger_remove(int id);
46static ssize_t iio_sysfs_trig_remove(struct device *dev,
47				     struct device_attribute *attr,
48				     const char *buf,
49				     size_t len)
50{
51	int ret;
52	unsigned long input;
53
54	ret = kstrtoul(buf, 10, &input);
55	if (ret)
56		return ret;
57	ret = iio_sysfs_trigger_remove(input);
58	if (ret)
59		return ret;
60	return len;
61}
62
63static DEVICE_ATTR(remove_trigger, S_IWUSR, NULL, &iio_sysfs_trig_remove);
64
65static struct attribute *iio_sysfs_trig_attrs[] = {
66	&dev_attr_add_trigger.attr,
67	&dev_attr_remove_trigger.attr,
68	NULL,
69};
70
71static const struct attribute_group iio_sysfs_trig_group = {
72	.attrs = iio_sysfs_trig_attrs,
73};
74
75static const struct attribute_group *iio_sysfs_trig_groups[] = {
76	&iio_sysfs_trig_group,
77	NULL
78};
79
80
81/* Nothing to actually do upon release */
82static void iio_trigger_sysfs_release(struct device *dev)
83{
84}
85
86static struct device iio_sysfs_trig_dev = {
87	.bus = &iio_bus_type,
88	.groups = iio_sysfs_trig_groups,
89	.release = &iio_trigger_sysfs_release,
90};
91
92static void iio_sysfs_trigger_work(struct irq_work *work)
93{
94	struct iio_sysfs_trig *trig = container_of(work, struct iio_sysfs_trig,
95							work);
96
97	iio_trigger_poll(trig->trig);
98}
99
100static ssize_t iio_sysfs_trigger_poll(struct device *dev,
101		struct device_attribute *attr, const char *buf, size_t count)
102{
103	struct iio_trigger *trig = to_iio_trigger(dev);
104	struct iio_sysfs_trig *sysfs_trig = iio_trigger_get_drvdata(trig);
105
106	irq_work_queue(&sysfs_trig->work);
107
108	return count;
109}
110
111static DEVICE_ATTR(trigger_now, S_IWUSR, NULL, iio_sysfs_trigger_poll);
112
113static struct attribute *iio_sysfs_trigger_attrs[] = {
114	&dev_attr_trigger_now.attr,
115	NULL,
116};
117
118static const struct attribute_group iio_sysfs_trigger_attr_group = {
119	.attrs = iio_sysfs_trigger_attrs,
120};
121
122static const struct attribute_group *iio_sysfs_trigger_attr_groups[] = {
123	&iio_sysfs_trigger_attr_group,
124	NULL
125};
126
127static const struct iio_trigger_ops iio_sysfs_trigger_ops = {
128};
129
130static int iio_sysfs_trigger_probe(int id)
131{
132	struct iio_sysfs_trig *t;
133	int ret;
134	bool foundit = false;
135
136	mutex_lock(&iio_sysfs_trig_list_mut);
137	list_for_each_entry(t, &iio_sysfs_trig_list, l)
138		if (id == t->id) {
139			foundit = true;
140			break;
141		}
142	if (foundit) {
143		ret = -EINVAL;
144		goto out1;
145	}
146	t = kmalloc(sizeof(*t), GFP_KERNEL);
147	if (t == NULL) {
148		ret = -ENOMEM;
149		goto out1;
150	}
151	t->id = id;
152	t->trig = iio_trigger_alloc("sysfstrig%d", id);
153	if (!t->trig) {
154		ret = -ENOMEM;
155		goto free_t;
156	}
157
158	t->trig->dev.groups = iio_sysfs_trigger_attr_groups;
159	t->trig->ops = &iio_sysfs_trigger_ops;
160	t->trig->dev.parent = &iio_sysfs_trig_dev;
161	iio_trigger_set_drvdata(t->trig, t);
162
163	init_irq_work(&t->work, iio_sysfs_trigger_work);
164
165	ret = iio_trigger_register(t->trig);
166	if (ret)
167		goto out2;
168	list_add(&t->l, &iio_sysfs_trig_list);
169	__module_get(THIS_MODULE);
170	mutex_unlock(&iio_sysfs_trig_list_mut);
171	return 0;
172
173out2:
174	iio_trigger_free(t->trig);
175free_t:
176	kfree(t);
177out1:
178	mutex_unlock(&iio_sysfs_trig_list_mut);
179	return ret;
180}
181
182static int iio_sysfs_trigger_remove(int id)
183{
184	bool foundit = false;
185	struct iio_sysfs_trig *t;
186
187	mutex_lock(&iio_sysfs_trig_list_mut);
188	list_for_each_entry(t, &iio_sysfs_trig_list, l)
189		if (id == t->id) {
190			foundit = true;
191			break;
192		}
193	if (!foundit) {
194		mutex_unlock(&iio_sysfs_trig_list_mut);
195		return -EINVAL;
196	}
197
198	iio_trigger_unregister(t->trig);
199	irq_work_sync(&t->work);
200	iio_trigger_free(t->trig);
201
202	list_del(&t->l);
203	kfree(t);
204	module_put(THIS_MODULE);
205	mutex_unlock(&iio_sysfs_trig_list_mut);
206	return 0;
207}
208
209
210static int __init iio_sysfs_trig_init(void)
211{
212	int ret;
213	device_initialize(&iio_sysfs_trig_dev);
214	dev_set_name(&iio_sysfs_trig_dev, "iio_sysfs_trigger");
215	ret = device_add(&iio_sysfs_trig_dev);
216	if (ret)
217		put_device(&iio_sysfs_trig_dev);
218	return ret;
219}
220module_init(iio_sysfs_trig_init);
221
222static void __exit iio_sysfs_trig_exit(void)
223{
224	device_unregister(&iio_sysfs_trig_dev);
225}
226module_exit(iio_sysfs_trig_exit);
227
228MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>");
229MODULE_DESCRIPTION("Sysfs based trigger for the iio subsystem");
230MODULE_LICENSE("GPL v2");
231MODULE_ALIAS("platform:iio-trig-sysfs");
232