1 // SPDX-License-Identifier: GPL-2.0
3 * Driver for an envelope detector using a DAC and a comparator
5 * Copyright (C) 2016 Axentia Technologies AB
7 * Author: Peter Rosin <peda@axentia.se>
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:
16 * input +------>-------|+ \
24 * | irq|------<-------'
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/mutex.h>
35 #include <linux/iio/consumer.h>
36 #include <linux/iio/iio.h>
37 #include <linux/iio/sysfs.h>
38 #include <linux/interrupt.h>
39 #include <linux/irq.h>
41 #include <linux/of_device.h>
42 #include <linux/platform_device.h>
43 #include <linux/spinlock.h>
44 #include <linux/workqueue.h>
47 spinlock_t comp_lock
; /* protects comp */
50 struct mutex read_lock
; /* protects everything else */
54 u32 comp_irq_trigger_inv
;
56 struct iio_channel
*dac
;
57 struct delayed_work comp_timeout
;
59 unsigned int comp_interval
;
67 struct completion done
;
71 * The envelope_detector_comp_latch function works together with the compare
72 * interrupt service routine below (envelope_detector_comp_isr) as a latch
73 * (one-bit memory) for if the interrupt has triggered since last calling
75 * The ..._comp_isr function disables the interrupt so that the cpu does not
76 * need to service a possible interrupt flood from the comparator when no-one
77 * cares anyway, and this ..._comp_latch function reenables them again if
80 static int envelope_detector_comp_latch(struct envelope
*env
)
84 spin_lock_irq(&env
->comp_lock
);
87 spin_unlock_irq(&env
->comp_lock
);
93 * The irq was disabled, and is reenabled just now.
94 * But there might have been a pending irq that
95 * happened while the irq was disabled that fires
96 * just as the irq is reenabled. That is not what
99 enable_irq(env
->comp_irq
);
101 /* So, synchronize this possibly pending irq... */
102 synchronize_irq(env
->comp_irq
);
104 /* ...and redo the whole dance. */
105 spin_lock_irq(&env
->comp_lock
);
108 spin_unlock_irq(&env
->comp_lock
);
111 enable_irq(env
->comp_irq
);
116 static irqreturn_t
envelope_detector_comp_isr(int irq
, void *ctx
)
118 struct envelope
*env
= ctx
;
120 spin_lock(&env
->comp_lock
);
122 disable_irq_nosync(env
->comp_irq
);
123 spin_unlock(&env
->comp_lock
);
128 static void envelope_detector_setup_compare(struct envelope
*env
)
133 * Do a binary search for the peak input level, and stop
134 * when that level is "trapped" between two adjacent DAC
136 * When invert is active, use the midpoint floor so that
137 * env->level ends up as env->low when the termination
138 * criteria below is fulfilled, and use the midpoint
139 * ceiling when invert is not active so that env->level
140 * ends up as env->high in that case.
142 env
->level
= (env
->high
+ env
->low
+ !env
->invert
) / 2;
144 if (env
->high
== env
->low
+ 1) {
145 complete(&env
->done
);
149 /* Set a "safe" DAC level (if there is such a thing)... */
150 ret
= iio_write_channel_raw(env
->dac
, env
->invert
? 0 : env
->dac_max
);
154 /* ...clear the comparison result... */
155 envelope_detector_comp_latch(env
);
157 /* ...set the real DAC level... */
158 ret
= iio_write_channel_raw(env
->dac
, env
->level
);
162 /* ...and wait for a bit to see if the latch catches anything. */
163 schedule_delayed_work(&env
->comp_timeout
,
164 msecs_to_jiffies(env
->comp_interval
));
169 complete(&env
->done
);
172 static void envelope_detector_timeout(struct work_struct
*work
)
174 struct envelope
*env
= container_of(work
, struct envelope
,
177 /* Adjust low/high depending on the latch content... */
178 if (!envelope_detector_comp_latch(env
) ^ !env
->invert
)
179 env
->low
= env
->level
;
181 env
->high
= env
->level
;
183 /* ...and continue the search. */
184 envelope_detector_setup_compare(env
);
187 static int envelope_detector_read_raw(struct iio_dev
*indio_dev
,
188 struct iio_chan_spec
const *chan
,
189 int *val
, int *val2
, long mask
)
191 struct envelope
*env
= iio_priv(indio_dev
);
195 case IIO_CHAN_INFO_RAW
:
197 * When invert is active, start with high=max+1 and low=0
198 * since we will end up with the low value when the
199 * termination criteria is fulfilled (rounding down). And
200 * start with high=max and low=-1 when invert is not active
201 * since we will end up with the high value in that case.
202 * This ensures that the returned value in both cases are
203 * in the same range as the DAC and is a value that has not
204 * triggered the comparator.
206 mutex_lock(&env
->read_lock
);
207 env
->high
= env
->dac_max
+ env
->invert
;
208 env
->low
= -1 + env
->invert
;
209 envelope_detector_setup_compare(env
);
210 wait_for_completion(&env
->done
);
211 if (env
->level
< 0) {
215 *val
= env
->invert
? env
->dac_max
- env
->level
: env
->level
;
216 mutex_unlock(&env
->read_lock
);
220 case IIO_CHAN_INFO_SCALE
:
221 return iio_read_channel_scale(env
->dac
, val
, val2
);
227 mutex_unlock(&env
->read_lock
);
231 static ssize_t
envelope_show_invert(struct iio_dev
*indio_dev
,
233 struct iio_chan_spec
const *ch
, char *buf
)
235 struct envelope
*env
= iio_priv(indio_dev
);
237 return sprintf(buf
, "%u\n", env
->invert
);
240 static ssize_t
envelope_store_invert(struct iio_dev
*indio_dev
,
242 struct iio_chan_spec
const *ch
,
243 const char *buf
, size_t len
)
245 struct envelope
*env
= iio_priv(indio_dev
);
246 unsigned long invert
;
250 ret
= kstrtoul(buf
, 0, &invert
);
256 trigger
= invert
? env
->comp_irq_trigger_inv
: env
->comp_irq_trigger
;
258 mutex_lock(&env
->read_lock
);
259 if (invert
!= env
->invert
)
260 ret
= irq_set_irq_type(env
->comp_irq
, trigger
);
262 env
->invert
= invert
;
265 mutex_unlock(&env
->read_lock
);
270 static ssize_t
envelope_show_comp_interval(struct iio_dev
*indio_dev
,
272 struct iio_chan_spec
const *ch
,
275 struct envelope
*env
= iio_priv(indio_dev
);
277 return sprintf(buf
, "%u\n", env
->comp_interval
);
280 static ssize_t
envelope_store_comp_interval(struct iio_dev
*indio_dev
,
282 struct iio_chan_spec
const *ch
,
283 const char *buf
, size_t len
)
285 struct envelope
*env
= iio_priv(indio_dev
);
286 unsigned long interval
;
289 ret
= kstrtoul(buf
, 0, &interval
);
295 mutex_lock(&env
->read_lock
);
296 env
->comp_interval
= interval
;
297 mutex_unlock(&env
->read_lock
);
302 static const struct iio_chan_spec_ext_info envelope_detector_ext_info
[] = {
304 .read
= envelope_show_invert
,
305 .write
= envelope_store_invert
, },
306 { .name
= "compare_interval",
307 .read
= envelope_show_comp_interval
,
308 .write
= envelope_store_comp_interval
, },
312 static const struct iio_chan_spec envelope_detector_iio_channel
= {
313 .type
= IIO_ALTVOLTAGE
,
314 .info_mask_separate
= BIT(IIO_CHAN_INFO_RAW
)
315 | BIT(IIO_CHAN_INFO_SCALE
),
316 .ext_info
= envelope_detector_ext_info
,
320 static const struct iio_info envelope_detector_info
= {
321 .read_raw
= &envelope_detector_read_raw
,
324 static int envelope_detector_probe(struct platform_device
*pdev
)
326 struct device
*dev
= &pdev
->dev
;
327 struct iio_dev
*indio_dev
;
328 struct envelope
*env
;
329 enum iio_chan_type type
;
332 indio_dev
= devm_iio_device_alloc(dev
, sizeof(*env
));
336 platform_set_drvdata(pdev
, indio_dev
);
337 env
= iio_priv(indio_dev
);
338 env
->comp_interval
= 50; /* some sensible default? */
340 spin_lock_init(&env
->comp_lock
);
341 mutex_init(&env
->read_lock
);
342 init_completion(&env
->done
);
343 INIT_DELAYED_WORK(&env
->comp_timeout
, envelope_detector_timeout
);
345 indio_dev
->name
= dev_name(dev
);
346 indio_dev
->info
= &envelope_detector_info
;
347 indio_dev
->channels
= &envelope_detector_iio_channel
;
348 indio_dev
->num_channels
= 1;
350 env
->dac
= devm_iio_channel_get(dev
, "dac");
351 if (IS_ERR(env
->dac
))
352 return dev_err_probe(dev
, PTR_ERR(env
->dac
),
353 "failed to get dac input channel\n");
355 env
->comp_irq
= platform_get_irq_byname(pdev
, "comp");
356 if (env
->comp_irq
< 0)
357 return env
->comp_irq
;
359 ret
= devm_request_irq(dev
, env
->comp_irq
, envelope_detector_comp_isr
,
360 0, "envelope-detector", env
);
362 return dev_err_probe(dev
, ret
, "failed to request interrupt\n");
364 env
->comp_irq_trigger
= irq_get_trigger_type(env
->comp_irq
);
365 if (env
->comp_irq_trigger
& IRQF_TRIGGER_RISING
)
366 env
->comp_irq_trigger_inv
|= IRQF_TRIGGER_FALLING
;
367 if (env
->comp_irq_trigger
& IRQF_TRIGGER_FALLING
)
368 env
->comp_irq_trigger_inv
|= IRQF_TRIGGER_RISING
;
369 if (env
->comp_irq_trigger
& IRQF_TRIGGER_HIGH
)
370 env
->comp_irq_trigger_inv
|= IRQF_TRIGGER_LOW
;
371 if (env
->comp_irq_trigger
& IRQF_TRIGGER_LOW
)
372 env
->comp_irq_trigger_inv
|= IRQF_TRIGGER_HIGH
;
374 ret
= iio_get_channel_type(env
->dac
, &type
);
378 if (type
!= IIO_VOLTAGE
) {
379 dev_err(dev
, "dac is of the wrong type\n");
383 ret
= iio_read_max_channel_raw(env
->dac
, &env
->dac_max
);
385 dev_err(dev
, "dac does not indicate its raw maximum value\n");
389 return devm_iio_device_register(dev
, indio_dev
);
392 static const struct of_device_id envelope_detector_match
[] = {
393 { .compatible
= "axentia,tse850-envelope-detector", },
396 MODULE_DEVICE_TABLE(of
, envelope_detector_match
);
398 static struct platform_driver envelope_detector_driver
= {
399 .probe
= envelope_detector_probe
,
401 .name
= "iio-envelope-detector",
402 .of_match_table
= envelope_detector_match
,
405 module_platform_driver(envelope_detector_driver
);
407 MODULE_DESCRIPTION("Envelope detector using a DAC and a comparator");
408 MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
409 MODULE_LICENSE("GPL v2");