Commit | Line | Data |
---|---|---|
10d6e795 | 1 | // SPDX-License-Identifier: GPL-2.0 |
b475f80b PR |
2 | /* |
3 | * Driver for an envelope detector using a DAC and a comparator | |
4 | * | |
5 | * Copyright (C) 2016 Axentia Technologies AB | |
6 | * | |
7 | * Author: Peter Rosin <peda@axentia.se> | |
b475f80b PR |
8 | */ |
9 | ||
10 | /* | |
11 | * The DAC is used to find the peak level of an alternating voltage input | |
12 | * signal by a binary search using the output of a comparator wired to | |
13 | * an interrupt pin. Like so: | |
14 | * _ | |
15 | * | \ | |
16 | * input +------>-------|+ \ | |
17 | * | \ | |
18 | * .-------. | }---. | |
19 | * | | | / | | |
20 | * | dac|-->--|- / | | |
21 | * | | |_/ | | |
22 | * | | | | |
23 | * | | | | |
24 | * | irq|------<-------' | |
25 | * | | | |
26 | * '-------' | |
27 | */ | |
28 | ||
29 | #include <linux/completion.h> | |
30 | #include <linux/device.h> | |
31 | #include <linux/err.h> | |
32 | #include <linux/kernel.h> | |
33 | #include <linux/module.h> | |
f346c965 | 34 | #include <linux/mod_devicetable.h> |
b475f80b PR |
35 | #include <linux/mutex.h> |
36 | #include <linux/iio/consumer.h> | |
37 | #include <linux/iio/iio.h> | |
38 | #include <linux/iio/sysfs.h> | |
39 | #include <linux/interrupt.h> | |
40 | #include <linux/irq.h> | |
b475f80b PR |
41 | #include <linux/platform_device.h> |
42 | #include <linux/spinlock.h> | |
43 | #include <linux/workqueue.h> | |
44 | ||
45 | struct envelope { | |
46 | spinlock_t comp_lock; /* protects comp */ | |
47 | int comp; | |
48 | ||
49 | struct mutex read_lock; /* protects everything else */ | |
50 | ||
51 | int comp_irq; | |
52 | u32 comp_irq_trigger; | |
53 | u32 comp_irq_trigger_inv; | |
54 | ||
55 | struct iio_channel *dac; | |
56 | struct delayed_work comp_timeout; | |
57 | ||
58 | unsigned int comp_interval; | |
59 | bool invert; | |
60 | u32 dac_max; | |
61 | ||
62 | int high; | |
63 | int level; | |
64 | int low; | |
65 | ||
66 | struct completion done; | |
67 | }; | |
68 | ||
69 | /* | |
70 | * The envelope_detector_comp_latch function works together with the compare | |
71 | * interrupt service routine below (envelope_detector_comp_isr) as a latch | |
72 | * (one-bit memory) for if the interrupt has triggered since last calling | |
73 | * this function. | |
74 | * The ..._comp_isr function disables the interrupt so that the cpu does not | |
75 | * need to service a possible interrupt flood from the comparator when no-one | |
76 | * cares anyway, and this ..._comp_latch function reenables them again if | |
77 | * needed. | |
78 | */ | |
79 | static int envelope_detector_comp_latch(struct envelope *env) | |
80 | { | |
81 | int comp; | |
82 | ||
83 | spin_lock_irq(&env->comp_lock); | |
84 | comp = env->comp; | |
85 | env->comp = 0; | |
86 | spin_unlock_irq(&env->comp_lock); | |
87 | ||
88 | if (!comp) | |
89 | return 0; | |
90 | ||
91 | /* | |
92 | * The irq was disabled, and is reenabled just now. | |
93 | * But there might have been a pending irq that | |
94 | * happened while the irq was disabled that fires | |
95 | * just as the irq is reenabled. That is not what | |
96 | * is desired. | |
97 | */ | |
98 | enable_irq(env->comp_irq); | |
99 | ||
100 | /* So, synchronize this possibly pending irq... */ | |
101 | synchronize_irq(env->comp_irq); | |
102 | ||
103 | /* ...and redo the whole dance. */ | |
104 | spin_lock_irq(&env->comp_lock); | |
105 | comp = env->comp; | |
106 | env->comp = 0; | |
107 | spin_unlock_irq(&env->comp_lock); | |
108 | ||
109 | if (comp) | |
110 | enable_irq(env->comp_irq); | |
111 | ||
112 | return 1; | |
113 | } | |
114 | ||
115 | static irqreturn_t envelope_detector_comp_isr(int irq, void *ctx) | |
116 | { | |
117 | struct envelope *env = ctx; | |
118 | ||
119 | spin_lock(&env->comp_lock); | |
120 | env->comp = 1; | |
121 | disable_irq_nosync(env->comp_irq); | |
122 | spin_unlock(&env->comp_lock); | |
123 | ||
124 | return IRQ_HANDLED; | |
125 | } | |
126 | ||
127 | static void envelope_detector_setup_compare(struct envelope *env) | |
128 | { | |
129 | int ret; | |
130 | ||
131 | /* | |
132 | * Do a binary search for the peak input level, and stop | |
133 | * when that level is "trapped" between two adjacent DAC | |
134 | * values. | |
135 | * When invert is active, use the midpoint floor so that | |
136 | * env->level ends up as env->low when the termination | |
137 | * criteria below is fulfilled, and use the midpoint | |
138 | * ceiling when invert is not active so that env->level | |
139 | * ends up as env->high in that case. | |
140 | */ | |
141 | env->level = (env->high + env->low + !env->invert) / 2; | |
142 | ||
143 | if (env->high == env->low + 1) { | |
144 | complete(&env->done); | |
145 | return; | |
146 | } | |
147 | ||
148 | /* Set a "safe" DAC level (if there is such a thing)... */ | |
149 | ret = iio_write_channel_raw(env->dac, env->invert ? 0 : env->dac_max); | |
150 | if (ret < 0) | |
151 | goto err; | |
152 | ||
153 | /* ...clear the comparison result... */ | |
154 | envelope_detector_comp_latch(env); | |
155 | ||
156 | /* ...set the real DAC level... */ | |
157 | ret = iio_write_channel_raw(env->dac, env->level); | |
158 | if (ret < 0) | |
159 | goto err; | |
160 | ||
161 | /* ...and wait for a bit to see if the latch catches anything. */ | |
162 | schedule_delayed_work(&env->comp_timeout, | |
163 | msecs_to_jiffies(env->comp_interval)); | |
164 | return; | |
165 | ||
166 | err: | |
167 | env->level = ret; | |
168 | complete(&env->done); | |
169 | } | |
170 | ||
171 | static void envelope_detector_timeout(struct work_struct *work) | |
172 | { | |
173 | struct envelope *env = container_of(work, struct envelope, | |
174 | comp_timeout.work); | |
175 | ||
176 | /* Adjust low/high depending on the latch content... */ | |
177 | if (!envelope_detector_comp_latch(env) ^ !env->invert) | |
178 | env->low = env->level; | |
179 | else | |
180 | env->high = env->level; | |
181 | ||
182 | /* ...and continue the search. */ | |
183 | envelope_detector_setup_compare(env); | |
184 | } | |
185 | ||
186 | static int envelope_detector_read_raw(struct iio_dev *indio_dev, | |
187 | struct iio_chan_spec const *chan, | |
188 | int *val, int *val2, long mask) | |
189 | { | |
190 | struct envelope *env = iio_priv(indio_dev); | |
191 | int ret; | |
192 | ||
193 | switch (mask) { | |
194 | case IIO_CHAN_INFO_RAW: | |
195 | /* | |
196 | * When invert is active, start with high=max+1 and low=0 | |
197 | * since we will end up with the low value when the | |
198 | * termination criteria is fulfilled (rounding down). And | |
199 | * start with high=max and low=-1 when invert is not active | |
200 | * since we will end up with the high value in that case. | |
201 | * This ensures that the returned value in both cases are | |
202 | * in the same range as the DAC and is a value that has not | |
203 | * triggered the comparator. | |
204 | */ | |
205 | mutex_lock(&env->read_lock); | |
206 | env->high = env->dac_max + env->invert; | |
207 | env->low = -1 + env->invert; | |
208 | envelope_detector_setup_compare(env); | |
209 | wait_for_completion(&env->done); | |
210 | if (env->level < 0) { | |
211 | ret = env->level; | |
212 | goto err_unlock; | |
213 | } | |
214 | *val = env->invert ? env->dac_max - env->level : env->level; | |
215 | mutex_unlock(&env->read_lock); | |
216 | ||
217 | return IIO_VAL_INT; | |
218 | ||
219 | case IIO_CHAN_INFO_SCALE: | |
220 | return iio_read_channel_scale(env->dac, val, val2); | |
221 | } | |
222 | ||
223 | return -EINVAL; | |
224 | ||
225 | err_unlock: | |
226 | mutex_unlock(&env->read_lock); | |
227 | return ret; | |
228 | } | |
229 | ||
230 | static ssize_t envelope_show_invert(struct iio_dev *indio_dev, | |
231 | uintptr_t private, | |
232 | struct iio_chan_spec const *ch, char *buf) | |
233 | { | |
234 | struct envelope *env = iio_priv(indio_dev); | |
235 | ||
236 | return sprintf(buf, "%u\n", env->invert); | |
237 | } | |
238 | ||
239 | static ssize_t envelope_store_invert(struct iio_dev *indio_dev, | |
240 | uintptr_t private, | |
241 | struct iio_chan_spec const *ch, | |
242 | const char *buf, size_t len) | |
243 | { | |
244 | struct envelope *env = iio_priv(indio_dev); | |
245 | unsigned long invert; | |
246 | int ret; | |
247 | u32 trigger; | |
248 | ||
249 | ret = kstrtoul(buf, 0, &invert); | |
250 | if (ret < 0) | |
251 | return ret; | |
252 | if (invert > 1) | |
253 | return -EINVAL; | |
254 | ||
255 | trigger = invert ? env->comp_irq_trigger_inv : env->comp_irq_trigger; | |
256 | ||
257 | mutex_lock(&env->read_lock); | |
258 | if (invert != env->invert) | |
259 | ret = irq_set_irq_type(env->comp_irq, trigger); | |
260 | if (!ret) { | |
261 | env->invert = invert; | |
262 | ret = len; | |
263 | } | |
264 | mutex_unlock(&env->read_lock); | |
265 | ||
266 | return ret; | |
267 | } | |
268 | ||
269 | static ssize_t envelope_show_comp_interval(struct iio_dev *indio_dev, | |
270 | uintptr_t private, | |
271 | struct iio_chan_spec const *ch, | |
272 | char *buf) | |
273 | { | |
274 | struct envelope *env = iio_priv(indio_dev); | |
275 | ||
276 | return sprintf(buf, "%u\n", env->comp_interval); | |
277 | } | |
278 | ||
279 | static ssize_t envelope_store_comp_interval(struct iio_dev *indio_dev, | |
280 | uintptr_t private, | |
281 | struct iio_chan_spec const *ch, | |
282 | const char *buf, size_t len) | |
283 | { | |
284 | struct envelope *env = iio_priv(indio_dev); | |
285 | unsigned long interval; | |
286 | int ret; | |
287 | ||
288 | ret = kstrtoul(buf, 0, &interval); | |
289 | if (ret < 0) | |
290 | return ret; | |
291 | if (interval > 1000) | |
292 | return -EINVAL; | |
293 | ||
294 | mutex_lock(&env->read_lock); | |
295 | env->comp_interval = interval; | |
296 | mutex_unlock(&env->read_lock); | |
297 | ||
298 | return len; | |
299 | } | |
300 | ||
301 | static const struct iio_chan_spec_ext_info envelope_detector_ext_info[] = { | |
302 | { .name = "invert", | |
303 | .read = envelope_show_invert, | |
304 | .write = envelope_store_invert, }, | |
305 | { .name = "compare_interval", | |
306 | .read = envelope_show_comp_interval, | |
307 | .write = envelope_store_comp_interval, }, | |
308 | { /* sentinel */ } | |
309 | }; | |
310 | ||
311 | static const struct iio_chan_spec envelope_detector_iio_channel = { | |
312 | .type = IIO_ALTVOLTAGE, | |
313 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | |
314 | | BIT(IIO_CHAN_INFO_SCALE), | |
315 | .ext_info = envelope_detector_ext_info, | |
316 | .indexed = 1, | |
317 | }; | |
318 | ||
319 | static const struct iio_info envelope_detector_info = { | |
320 | .read_raw = &envelope_detector_read_raw, | |
b475f80b PR |
321 | }; |
322 | ||
323 | static int envelope_detector_probe(struct platform_device *pdev) | |
324 | { | |
325 | struct device *dev = &pdev->dev; | |
326 | struct iio_dev *indio_dev; | |
327 | struct envelope *env; | |
328 | enum iio_chan_type type; | |
329 | int ret; | |
330 | ||
331 | indio_dev = devm_iio_device_alloc(dev, sizeof(*env)); | |
332 | if (!indio_dev) | |
333 | return -ENOMEM; | |
334 | ||
335 | platform_set_drvdata(pdev, indio_dev); | |
336 | env = iio_priv(indio_dev); | |
337 | env->comp_interval = 50; /* some sensible default? */ | |
338 | ||
339 | spin_lock_init(&env->comp_lock); | |
340 | mutex_init(&env->read_lock); | |
341 | init_completion(&env->done); | |
342 | INIT_DELAYED_WORK(&env->comp_timeout, envelope_detector_timeout); | |
343 | ||
344 | indio_dev->name = dev_name(dev); | |
b475f80b PR |
345 | indio_dev->info = &envelope_detector_info; |
346 | indio_dev->channels = &envelope_detector_iio_channel; | |
347 | indio_dev->num_channels = 1; | |
348 | ||
349 | env->dac = devm_iio_channel_get(dev, "dac"); | |
55dc2952 KK |
350 | if (IS_ERR(env->dac)) |
351 | return dev_err_probe(dev, PTR_ERR(env->dac), | |
352 | "failed to get dac input channel\n"); | |
b475f80b PR |
353 | |
354 | env->comp_irq = platform_get_irq_byname(pdev, "comp"); | |
7c279229 | 355 | if (env->comp_irq < 0) |
b475f80b | 356 | return env->comp_irq; |
b475f80b PR |
357 | |
358 | ret = devm_request_irq(dev, env->comp_irq, envelope_detector_comp_isr, | |
359 | 0, "envelope-detector", env); | |
55dc2952 KK |
360 | if (ret) |
361 | return dev_err_probe(dev, ret, "failed to request interrupt\n"); | |
362 | ||
b475f80b PR |
363 | env->comp_irq_trigger = irq_get_trigger_type(env->comp_irq); |
364 | if (env->comp_irq_trigger & IRQF_TRIGGER_RISING) | |
365 | env->comp_irq_trigger_inv |= IRQF_TRIGGER_FALLING; | |
366 | if (env->comp_irq_trigger & IRQF_TRIGGER_FALLING) | |
367 | env->comp_irq_trigger_inv |= IRQF_TRIGGER_RISING; | |
368 | if (env->comp_irq_trigger & IRQF_TRIGGER_HIGH) | |
369 | env->comp_irq_trigger_inv |= IRQF_TRIGGER_LOW; | |
370 | if (env->comp_irq_trigger & IRQF_TRIGGER_LOW) | |
371 | env->comp_irq_trigger_inv |= IRQF_TRIGGER_HIGH; | |
372 | ||
373 | ret = iio_get_channel_type(env->dac, &type); | |
374 | if (ret < 0) | |
375 | return ret; | |
376 | ||
377 | if (type != IIO_VOLTAGE) { | |
378 | dev_err(dev, "dac is of the wrong type\n"); | |
379 | return -EINVAL; | |
380 | } | |
381 | ||
382 | ret = iio_read_max_channel_raw(env->dac, &env->dac_max); | |
383 | if (ret < 0) { | |
384 | dev_err(dev, "dac does not indicate its raw maximum value\n"); | |
385 | return ret; | |
386 | } | |
387 | ||
388 | return devm_iio_device_register(dev, indio_dev); | |
389 | } | |
390 | ||
391 | static const struct of_device_id envelope_detector_match[] = { | |
392 | { .compatible = "axentia,tse850-envelope-detector", }, | |
393 | { /* sentinel */ } | |
394 | }; | |
395 | MODULE_DEVICE_TABLE(of, envelope_detector_match); | |
396 | ||
397 | static struct platform_driver envelope_detector_driver = { | |
398 | .probe = envelope_detector_probe, | |
399 | .driver = { | |
400 | .name = "iio-envelope-detector", | |
401 | .of_match_table = envelope_detector_match, | |
402 | }, | |
403 | }; | |
404 | module_platform_driver(envelope_detector_driver); | |
405 | ||
406 | MODULE_DESCRIPTION("Envelope detector using a DAC and a comparator"); | |
407 | MODULE_AUTHOR("Peter Rosin <peda@axentia.se>"); | |
408 | MODULE_LICENSE("GPL v2"); |