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