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/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>
46 spinlock_t comp_lock
; /* protects comp */
49 struct mutex read_lock
; /* protects everything else */
53 u32 comp_irq_trigger_inv
;
55 struct iio_channel
*dac
;
56 struct delayed_work comp_timeout
;
58 unsigned int comp_interval
;
66 struct completion done
;
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
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
79 static int envelope_detector_comp_latch(struct envelope
*env
)
83 spin_lock_irq(&env
->comp_lock
);
86 spin_unlock_irq(&env
->comp_lock
);
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
98 enable_irq(env
->comp_irq
);
100 /* So, synchronize this possibly pending irq... */
101 synchronize_irq(env
->comp_irq
);
103 /* ...and redo the whole dance. */
104 spin_lock_irq(&env
->comp_lock
);
107 spin_unlock_irq(&env
->comp_lock
);
110 enable_irq(env
->comp_irq
);
115 static irqreturn_t
envelope_detector_comp_isr(int irq
, void *ctx
)
117 struct envelope
*env
= ctx
;
119 spin_lock(&env
->comp_lock
);
121 disable_irq_nosync(env
->comp_irq
);
122 spin_unlock(&env
->comp_lock
);
127 static void envelope_detector_setup_compare(struct envelope
*env
)
132 * Do a binary search for the peak input level, and stop
133 * when that level is "trapped" between two adjacent DAC
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.
141 env
->level
= (env
->high
+ env
->low
+ !env
->invert
) / 2;
143 if (env
->high
== env
->low
+ 1) {
144 complete(&env
->done
);
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
);
153 /* ...clear the comparison result... */
154 envelope_detector_comp_latch(env
);
156 /* ...set the real DAC level... */
157 ret
= iio_write_channel_raw(env
->dac
, env
->level
);
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
));
168 complete(&env
->done
);
171 static void envelope_detector_timeout(struct work_struct
*work
)
173 struct envelope
*env
= container_of(work
, struct envelope
,
176 /* Adjust low/high depending on the latch content... */
177 if (!envelope_detector_comp_latch(env
) ^ !env
->invert
)
178 env
->low
= env
->level
;
180 env
->high
= env
->level
;
182 /* ...and continue the search. */
183 envelope_detector_setup_compare(env
);
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
)
190 struct envelope
*env
= iio_priv(indio_dev
);
194 case IIO_CHAN_INFO_RAW
:
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.
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) {
214 *val
= env
->invert
? env
->dac_max
- env
->level
: env
->level
;
215 mutex_unlock(&env
->read_lock
);
219 case IIO_CHAN_INFO_SCALE
:
220 return iio_read_channel_scale(env
->dac
, val
, val2
);
226 mutex_unlock(&env
->read_lock
);
230 static ssize_t
envelope_show_invert(struct iio_dev
*indio_dev
,
232 struct iio_chan_spec
const *ch
, char *buf
)
234 struct envelope
*env
= iio_priv(indio_dev
);
236 return sprintf(buf
, "%u\n", env
->invert
);
239 static ssize_t
envelope_store_invert(struct iio_dev
*indio_dev
,
241 struct iio_chan_spec
const *ch
,
242 const char *buf
, size_t len
)
244 struct envelope
*env
= iio_priv(indio_dev
);
245 unsigned long invert
;
249 ret
= kstrtoul(buf
, 0, &invert
);
255 trigger
= invert
? env
->comp_irq_trigger_inv
: env
->comp_irq_trigger
;
257 mutex_lock(&env
->read_lock
);
258 if (invert
!= env
->invert
)
259 ret
= irq_set_irq_type(env
->comp_irq
, trigger
);
261 env
->invert
= invert
;
264 mutex_unlock(&env
->read_lock
);
269 static ssize_t
envelope_show_comp_interval(struct iio_dev
*indio_dev
,
271 struct iio_chan_spec
const *ch
,
274 struct envelope
*env
= iio_priv(indio_dev
);
276 return sprintf(buf
, "%u\n", env
->comp_interval
);
279 static ssize_t
envelope_store_comp_interval(struct iio_dev
*indio_dev
,
281 struct iio_chan_spec
const *ch
,
282 const char *buf
, size_t len
)
284 struct envelope
*env
= iio_priv(indio_dev
);
285 unsigned long interval
;
288 ret
= kstrtoul(buf
, 0, &interval
);
294 mutex_lock(&env
->read_lock
);
295 env
->comp_interval
= interval
;
296 mutex_unlock(&env
->read_lock
);
301 static const struct iio_chan_spec_ext_info envelope_detector_ext_info
[] = {
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
, },
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
,
319 static const struct iio_info envelope_detector_info
= {
320 .read_raw
= &envelope_detector_read_raw
,
323 static int envelope_detector_probe(struct platform_device
*pdev
)
325 struct device
*dev
= &pdev
->dev
;
326 struct iio_dev
*indio_dev
;
327 struct envelope
*env
;
328 enum iio_chan_type type
;
331 indio_dev
= devm_iio_device_alloc(dev
, sizeof(*env
));
335 platform_set_drvdata(pdev
, indio_dev
);
336 env
= iio_priv(indio_dev
);
337 env
->comp_interval
= 50; /* some sensible default? */
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
);
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;
349 env
->dac
= devm_iio_channel_get(dev
, "dac");
350 if (IS_ERR(env
->dac
))
351 return dev_err_probe(dev
, PTR_ERR(env
->dac
),
352 "failed to get dac input channel\n");
354 env
->comp_irq
= platform_get_irq_byname(pdev
, "comp");
355 if (env
->comp_irq
< 0)
356 return env
->comp_irq
;
358 ret
= devm_request_irq(dev
, env
->comp_irq
, envelope_detector_comp_isr
,
359 0, "envelope-detector", env
);
361 return dev_err_probe(dev
, ret
, "failed to request interrupt\n");
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
;
373 ret
= iio_get_channel_type(env
->dac
, &type
);
377 if (type
!= IIO_VOLTAGE
) {
378 dev_err(dev
, "dac is of the wrong type\n");
382 ret
= iio_read_max_channel_raw(env
->dac
, &env
->dac_max
);
384 dev_err(dev
, "dac does not indicate its raw maximum value\n");
388 return devm_iio_device_register(dev
, indio_dev
);
391 static const struct of_device_id envelope_detector_match
[] = {
392 { .compatible
= "axentia,tse850-envelope-detector", },
395 MODULE_DEVICE_TABLE(of
, envelope_detector_match
);
397 static struct platform_driver envelope_detector_driver
= {
398 .probe
= envelope_detector_probe
,
400 .name
= "iio-envelope-detector",
401 .of_match_table
= envelope_detector_match
,
404 module_platform_driver(envelope_detector_driver
);
406 MODULE_DESCRIPTION("Envelope detector using a DAC and a comparator");
407 MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
408 MODULE_LICENSE("GPL v2");