Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

iio: envelope-detector: ADC driver based on a DAC and a comparator

The DAC is used to find the peak level of an alternating voltage input
signal by a binary search using the output of a comparator wired to
an interrupt pin. Like so:
_
| \
input +------>-------|+ \
| \
.-------. | }---.
| | | / |
| dac|-->--|- / |
| | |_/ |
| | |
| | |
| irq|------<-------'
| |
'-------'

Signed-off-by: Peter Rosin <peda@axentia.se>
Acked-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Jonathan Cameron <jic23@kernel.org>

authored by

Peter Rosin and committed by
Jonathan Cameron
b475f80b e778aa14

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