linux/drivers/iio/adc/envelope-detector.c
<<
>>
Prefs
   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
  49struct 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 */
  83static 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
 119static 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
 131static 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
 170err:
 171        env->level = ret;
 172        complete(&env->done);
 173}
 174
 175static 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
 190static 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
 229err_unlock:
 230        mutex_unlock(&env->read_lock);
 231        return ret;
 232}
 233
 234static 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
 243static 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
 273static 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
 283static 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
 305static 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
 315static 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
 323static const struct iio_info envelope_detector_info = {
 324        .read_raw = &envelope_detector_read_raw,
 325};
 326
 327static int envelope_detector_probe(struct platform_device *pdev)
 328{
 329        struct device *dev = &pdev->dev;
 330        struct iio_dev *indio_dev;
 331        struct envelope *env;
 332        enum iio_chan_type type;
 333        int ret;
 334
 335        indio_dev = devm_iio_device_alloc(dev, sizeof(*env));
 336        if (!indio_dev)
 337                return -ENOMEM;
 338
 339        platform_set_drvdata(pdev, indio_dev);
 340        env = iio_priv(indio_dev);
 341        env->comp_interval = 50; /* some sensible default? */
 342
 343        spin_lock_init(&env->comp_lock);
 344        mutex_init(&env->read_lock);
 345        init_completion(&env->done);
 346        INIT_DELAYED_WORK(&env->comp_timeout, envelope_detector_timeout);
 347
 348        indio_dev->name = dev_name(dev);
 349        indio_dev->dev.parent = dev;
 350        indio_dev->dev.of_node = dev->of_node;
 351        indio_dev->info = &envelope_detector_info;
 352        indio_dev->channels = &envelope_detector_iio_channel;
 353        indio_dev->num_channels = 1;
 354
 355        env->dac = devm_iio_channel_get(dev, "dac");
 356        if (IS_ERR(env->dac)) {
 357                if (PTR_ERR(env->dac) != -EPROBE_DEFER)
 358                        dev_err(dev, "failed to get dac input channel\n");
 359                return PTR_ERR(env->dac);
 360        }
 361
 362        env->comp_irq = platform_get_irq_byname(pdev, "comp");
 363        if (env->comp_irq < 0) {
 364                if (env->comp_irq != -EPROBE_DEFER)
 365                        dev_err(dev, "failed to get compare interrupt\n");
 366                return env->comp_irq;
 367        }
 368
 369        ret = devm_request_irq(dev, env->comp_irq, envelope_detector_comp_isr,
 370                               0, "envelope-detector", env);
 371        if (ret) {
 372                if (ret != -EPROBE_DEFER)
 373                        dev_err(dev, "failed to request interrupt\n");
 374                return ret;
 375        }
 376        env->comp_irq_trigger = irq_get_trigger_type(env->comp_irq);
 377        if (env->comp_irq_trigger & IRQF_TRIGGER_RISING)
 378                env->comp_irq_trigger_inv |= IRQF_TRIGGER_FALLING;
 379        if (env->comp_irq_trigger & IRQF_TRIGGER_FALLING)
 380                env->comp_irq_trigger_inv |= IRQF_TRIGGER_RISING;
 381        if (env->comp_irq_trigger & IRQF_TRIGGER_HIGH)
 382                env->comp_irq_trigger_inv |= IRQF_TRIGGER_LOW;
 383        if (env->comp_irq_trigger & IRQF_TRIGGER_LOW)
 384                env->comp_irq_trigger_inv |= IRQF_TRIGGER_HIGH;
 385
 386        ret = iio_get_channel_type(env->dac, &type);
 387        if (ret < 0)
 388                return ret;
 389
 390        if (type != IIO_VOLTAGE) {
 391                dev_err(dev, "dac is of the wrong type\n");
 392                return -EINVAL;
 393        }
 394
 395        ret = iio_read_max_channel_raw(env->dac, &env->dac_max);
 396        if (ret < 0) {
 397                dev_err(dev, "dac does not indicate its raw maximum value\n");
 398                return ret;
 399        }
 400
 401        return devm_iio_device_register(dev, indio_dev);
 402}
 403
 404static const struct of_device_id envelope_detector_match[] = {
 405        { .compatible = "axentia,tse850-envelope-detector", },
 406        { /* sentinel */ }
 407};
 408MODULE_DEVICE_TABLE(of, envelope_detector_match);
 409
 410static struct platform_driver envelope_detector_driver = {
 411        .probe = envelope_detector_probe,
 412        .driver = {
 413                .name = "iio-envelope-detector",
 414                .of_match_table = envelope_detector_match,
 415        },
 416};
 417module_platform_driver(envelope_detector_driver);
 418
 419MODULE_DESCRIPTION("Envelope detector using a DAC and a comparator");
 420MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
 421MODULE_LICENSE("GPL v2");
 422