envelope-detector.c (10808B)
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 45struct 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 */ 79static 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 115static 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 127static 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 166err: 167 env->level = ret; 168 complete(&env->done); 169} 170 171static 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 186static 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 225err_unlock: 226 mutex_unlock(&env->read_lock); 227 return ret; 228} 229 230static 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 239static 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 269static 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 279static 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 301static 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 311static 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 319static const struct iio_info envelope_detector_info = { 320 .read_raw = &envelope_detector_read_raw, 321}; 322 323static 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); 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"); 350 if (IS_ERR(env->dac)) 351 return dev_err_probe(dev, PTR_ERR(env->dac), 352 "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, env->comp_irq, envelope_detector_comp_isr, 359 0, "envelope-detector", env); 360 if (ret) 361 return dev_err_probe(dev, ret, "failed to request interrupt\n"); 362 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 391static const struct of_device_id envelope_detector_match[] = { 392 { .compatible = "axentia,tse850-envelope-detector", }, 393 { /* sentinel */ } 394}; 395MODULE_DEVICE_TABLE(of, envelope_detector_match); 396 397static 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}; 404module_platform_driver(envelope_detector_driver); 405 406MODULE_DESCRIPTION("Envelope detector using a DAC and a comparator"); 407MODULE_AUTHOR("Peter Rosin <peda@axentia.se>"); 408MODULE_LICENSE("GPL v2");