1 | // SPDX-License-Identifier: GPL-2.0 |
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> |
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> |
34 | #include <linux/mod_devicetable.h> |
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> |
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(lock: &env->comp_lock); |
84 | comp = env->comp; |
85 | env->comp = 0; |
86 | spin_unlock_irq(lock: &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(irq: env->comp_irq); |
99 | |
100 | /* So, synchronize this possibly pending irq... */ |
101 | synchronize_irq(irq: env->comp_irq); |
102 | |
103 | /* ...and redo the whole dance. */ |
104 | spin_lock_irq(lock: &env->comp_lock); |
105 | comp = env->comp; |
106 | env->comp = 0; |
107 | spin_unlock_irq(lock: &env->comp_lock); |
108 | |
109 | if (comp) |
110 | enable_irq(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(lock: &env->comp_lock); |
120 | env->comp = 1; |
121 | disable_irq_nosync(irq: env->comp_irq); |
122 | spin_unlock(lock: &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(chan: env->dac, val: 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(chan: env->dac, val: 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(dwork: &env->comp_timeout, |
163 | delay: msecs_to_jiffies(m: 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(lock: &env->read_lock); |
216 | |
217 | return IIO_VAL_INT; |
218 | |
219 | case IIO_CHAN_INFO_SCALE: |
220 | return iio_read_channel_scale(chan: env->dac, val, val2); |
221 | } |
222 | |
223 | return -EINVAL; |
224 | |
225 | err_unlock: |
226 | mutex_unlock(lock: &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, fmt: "%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(s: buf, base: 0, res: &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(irq: env->comp_irq, type: trigger); |
260 | if (!ret) { |
261 | env->invert = invert; |
262 | ret = len; |
263 | } |
264 | mutex_unlock(lock: &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, fmt: "%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(s: buf, base: 0, res: &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(lock: &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, |
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(parent: dev, sizeof_priv: sizeof(*env)); |
332 | if (!indio_dev) |
333 | return -ENOMEM; |
334 | |
335 | platform_set_drvdata(pdev, data: 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(x: &env->done); |
342 | INIT_DELAYED_WORK(&env->comp_timeout, envelope_detector_timeout); |
343 | |
344 | indio_dev->name = dev_name(dev); |
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, consumer_channel: "dac" ); |
350 | if (IS_ERR(ptr: env->dac)) |
351 | return dev_err_probe(dev, err: PTR_ERR(ptr: env->dac), |
352 | fmt: "failed to get dac input channel\n" ); |
353 | |
354 | env->comp_irq = platform_get_irq_byname(pdev, "comp" ); |
355 | if (env->comp_irq < 0) |
356 | return env->comp_irq; |
357 | |
358 | ret = devm_request_irq(dev, irq: env->comp_irq, handler: envelope_detector_comp_isr, |
359 | irqflags: 0, devname: "envelope-detector" , dev_id: env); |
360 | if (ret) |
361 | return dev_err_probe(dev, err: ret, fmt: "failed to request interrupt\n" ); |
362 | |
363 | env->comp_irq_trigger = irq_get_trigger_type(irq: 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(channel: env->dac, type: &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(chan: env->dac, val: &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" ); |
409 | |