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 | |
16 | struct iio_sysfs_trig { |
17 | struct iio_trigger *trig; |
18 | struct irq_work work; |
19 | int id; |
20 | struct list_head l; |
21 | }; |
22 | |
23 | static LIST_HEAD(iio_sysfs_trig_list); |
24 | static DEFINE_MUTEX(iio_sysfs_trig_list_mut); |
25 | |
26 | static int iio_sysfs_trigger_probe(int id); |
27 | static 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(s: buf, base: 10, res: &input); |
36 | if (ret) |
37 | return ret; |
38 | ret = iio_sysfs_trigger_probe(id: input); |
39 | if (ret) |
40 | return ret; |
41 | return len; |
42 | } |
43 | static DEVICE_ATTR(add_trigger, S_IWUSR, NULL, &iio_sysfs_trig_add); |
44 | |
45 | static int iio_sysfs_trigger_remove(int id); |
46 | static 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(s: buf, base: 10, res: &input); |
55 | if (ret) |
56 | return ret; |
57 | ret = iio_sysfs_trigger_remove(id: input); |
58 | if (ret) |
59 | return ret; |
60 | return len; |
61 | } |
62 | |
63 | static DEVICE_ATTR(remove_trigger, S_IWUSR, NULL, &iio_sysfs_trig_remove); |
64 | |
65 | static struct attribute *iio_sysfs_trig_attrs[] = { |
66 | &dev_attr_add_trigger.attr, |
67 | &dev_attr_remove_trigger.attr, |
68 | NULL, |
69 | }; |
70 | |
71 | static const struct attribute_group iio_sysfs_trig_group = { |
72 | .attrs = iio_sysfs_trig_attrs, |
73 | }; |
74 | |
75 | static 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 */ |
82 | static void iio_trigger_sysfs_release(struct device *dev) |
83 | { |
84 | } |
85 | |
86 | static 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 | |
92 | static 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->trig); |
98 | } |
99 | |
100 | static 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(d: dev); |
104 | struct iio_sysfs_trig *sysfs_trig = iio_trigger_get_drvdata(trig); |
105 | |
106 | irq_work_queue(work: &sysfs_trig->work); |
107 | |
108 | return count; |
109 | } |
110 | |
111 | static DEVICE_ATTR(trigger_now, S_IWUSR, NULL, iio_sysfs_trigger_poll); |
112 | |
113 | static struct attribute *iio_sysfs_trigger_attrs[] = { |
114 | &dev_attr_trigger_now.attr, |
115 | NULL, |
116 | }; |
117 | |
118 | static const struct attribute_group iio_sysfs_trigger_attr_group = { |
119 | .attrs = iio_sysfs_trigger_attrs, |
120 | }; |
121 | |
122 | static const struct attribute_group *iio_sysfs_trigger_attr_groups[] = { |
123 | &iio_sysfs_trigger_attr_group, |
124 | NULL |
125 | }; |
126 | |
127 | static int iio_sysfs_trigger_probe(int id) |
128 | { |
129 | struct iio_sysfs_trig *t; |
130 | int ret; |
131 | bool foundit = false; |
132 | |
133 | mutex_lock(&iio_sysfs_trig_list_mut); |
134 | list_for_each_entry(t, &iio_sysfs_trig_list, l) |
135 | if (id == t->id) { |
136 | foundit = true; |
137 | break; |
138 | } |
139 | if (foundit) { |
140 | ret = -EINVAL; |
141 | goto err_unlock; |
142 | } |
143 | t = kmalloc(size: sizeof(*t), GFP_KERNEL); |
144 | if (t == NULL) { |
145 | ret = -ENOMEM; |
146 | goto err_unlock; |
147 | } |
148 | t->id = id; |
149 | t->trig = iio_trigger_alloc(&iio_sysfs_trig_dev, "sysfstrig%d" , id); |
150 | if (!t->trig) { |
151 | ret = -ENOMEM; |
152 | goto err_free_sys_trig; |
153 | } |
154 | |
155 | t->trig->dev.groups = iio_sysfs_trigger_attr_groups; |
156 | iio_trigger_set_drvdata(trig: t->trig, data: t); |
157 | |
158 | t->work = IRQ_WORK_INIT_HARD(iio_sysfs_trigger_work); |
159 | |
160 | ret = iio_trigger_register(trig_info: t->trig); |
161 | if (ret) |
162 | goto err_free_trig; |
163 | list_add(new: &t->l, head: &iio_sysfs_trig_list); |
164 | __module_get(THIS_MODULE); |
165 | mutex_unlock(lock: &iio_sysfs_trig_list_mut); |
166 | return 0; |
167 | |
168 | err_free_trig: |
169 | iio_trigger_free(trig: t->trig); |
170 | err_free_sys_trig: |
171 | kfree(objp: t); |
172 | err_unlock: |
173 | mutex_unlock(lock: &iio_sysfs_trig_list_mut); |
174 | return ret; |
175 | } |
176 | |
177 | static int iio_sysfs_trigger_remove(int id) |
178 | { |
179 | struct iio_sysfs_trig *t = NULL, *iter; |
180 | |
181 | mutex_lock(&iio_sysfs_trig_list_mut); |
182 | list_for_each_entry(iter, &iio_sysfs_trig_list, l) |
183 | if (id == iter->id) { |
184 | t = iter; |
185 | break; |
186 | } |
187 | if (!t) { |
188 | mutex_unlock(lock: &iio_sysfs_trig_list_mut); |
189 | return -EINVAL; |
190 | } |
191 | |
192 | iio_trigger_unregister(trig_info: t->trig); |
193 | irq_work_sync(work: &t->work); |
194 | iio_trigger_free(trig: t->trig); |
195 | |
196 | list_del(entry: &t->l); |
197 | kfree(objp: t); |
198 | module_put(THIS_MODULE); |
199 | mutex_unlock(lock: &iio_sysfs_trig_list_mut); |
200 | return 0; |
201 | } |
202 | |
203 | |
204 | static int __init iio_sysfs_trig_init(void) |
205 | { |
206 | int ret; |
207 | device_initialize(dev: &iio_sysfs_trig_dev); |
208 | dev_set_name(dev: &iio_sysfs_trig_dev, name: "iio_sysfs_trigger" ); |
209 | ret = device_add(dev: &iio_sysfs_trig_dev); |
210 | if (ret) |
211 | put_device(dev: &iio_sysfs_trig_dev); |
212 | return ret; |
213 | } |
214 | module_init(iio_sysfs_trig_init); |
215 | |
216 | static void __exit iio_sysfs_trig_exit(void) |
217 | { |
218 | device_unregister(dev: &iio_sysfs_trig_dev); |
219 | } |
220 | module_exit(iio_sysfs_trig_exit); |
221 | |
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 | |