Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Driver for an envelope detector using a DAC and a comparator
0004  *
0005  * Copyright (C) 2016 Axentia Technologies AB
0006  *
0007  * Author: Peter Rosin <peda@axentia.se>
0008  */
0009 
0010 /*
0011  * The DAC is used to find the peak level of an alternating voltage input
0012  * signal by a binary search using the output of a comparator wired to
0013  * an interrupt pin. Like so:
0014  *                           _
0015  *                          | \
0016  *     input +------>-------|+ \
0017  *                          |   \
0018  *            .-------.     |    }---.
0019  *            |       |     |   /    |
0020  *            |    dac|-->--|- /     |
0021  *            |       |     |_/      |
0022  *            |       |              |
0023  *            |       |              |
0024  *            |    irq|------<-------'
0025  *            |       |
0026  *            '-------'
0027  */
0028 
0029 #include <linux/completion.h>
0030 #include <linux/device.h>
0031 #include <linux/err.h>
0032 #include <linux/kernel.h>
0033 #include <linux/module.h>
0034 #include <linux/mod_devicetable.h>
0035 #include <linux/mutex.h>
0036 #include <linux/iio/consumer.h>
0037 #include <linux/iio/iio.h>
0038 #include <linux/iio/sysfs.h>
0039 #include <linux/interrupt.h>
0040 #include <linux/irq.h>
0041 #include <linux/platform_device.h>
0042 #include <linux/spinlock.h>
0043 #include <linux/workqueue.h>
0044 
0045 struct envelope {
0046     spinlock_t comp_lock; /* protects comp */
0047     int comp;
0048 
0049     struct mutex read_lock; /* protects everything else */
0050 
0051     int comp_irq;
0052     u32 comp_irq_trigger;
0053     u32 comp_irq_trigger_inv;
0054 
0055     struct iio_channel *dac;
0056     struct delayed_work comp_timeout;
0057 
0058     unsigned int comp_interval;
0059     bool invert;
0060     u32 dac_max;
0061 
0062     int high;
0063     int level;
0064     int low;
0065 
0066     struct completion done;
0067 };
0068 
0069 /*
0070  * The envelope_detector_comp_latch function works together with the compare
0071  * interrupt service routine below (envelope_detector_comp_isr) as a latch
0072  * (one-bit memory) for if the interrupt has triggered since last calling
0073  * this function.
0074  * The ..._comp_isr function disables the interrupt so that the cpu does not
0075  * need to service a possible interrupt flood from the comparator when no-one
0076  * cares anyway, and this ..._comp_latch function reenables them again if
0077  * needed.
0078  */
0079 static int envelope_detector_comp_latch(struct envelope *env)
0080 {
0081     int comp;
0082 
0083     spin_lock_irq(&env->comp_lock);
0084     comp = env->comp;
0085     env->comp = 0;
0086     spin_unlock_irq(&env->comp_lock);
0087 
0088     if (!comp)
0089         return 0;
0090 
0091     /*
0092      * The irq was disabled, and is reenabled just now.
0093      * But there might have been a pending irq that
0094      * happened while the irq was disabled that fires
0095      * just as the irq is reenabled. That is not what
0096      * is desired.
0097      */
0098     enable_irq(env->comp_irq);
0099 
0100     /* So, synchronize this possibly pending irq... */
0101     synchronize_irq(env->comp_irq);
0102 
0103     /* ...and redo the whole dance. */
0104     spin_lock_irq(&env->comp_lock);
0105     comp = env->comp;
0106     env->comp = 0;
0107     spin_unlock_irq(&env->comp_lock);
0108 
0109     if (comp)
0110         enable_irq(env->comp_irq);
0111 
0112     return 1;
0113 }
0114 
0115 static irqreturn_t envelope_detector_comp_isr(int irq, void *ctx)
0116 {
0117     struct envelope *env = ctx;
0118 
0119     spin_lock(&env->comp_lock);
0120     env->comp = 1;
0121     disable_irq_nosync(env->comp_irq);
0122     spin_unlock(&env->comp_lock);
0123 
0124     return IRQ_HANDLED;
0125 }
0126 
0127 static void envelope_detector_setup_compare(struct envelope *env)
0128 {
0129     int ret;
0130 
0131     /*
0132      * Do a binary search for the peak input level, and stop
0133      * when that level is "trapped" between two adjacent DAC
0134      * values.
0135      * When invert is active, use the midpoint floor so that
0136      * env->level ends up as env->low when the termination
0137      * criteria below is fulfilled, and use the midpoint
0138      * ceiling when invert is not active so that env->level
0139      * ends up as env->high in that case.
0140      */
0141     env->level = (env->high + env->low + !env->invert) / 2;
0142 
0143     if (env->high == env->low + 1) {
0144         complete(&env->done);
0145         return;
0146     }
0147 
0148     /* Set a "safe" DAC level (if there is such a thing)... */
0149     ret = iio_write_channel_raw(env->dac, env->invert ? 0 : env->dac_max);
0150     if (ret < 0)
0151         goto err;
0152 
0153     /* ...clear the comparison result... */
0154     envelope_detector_comp_latch(env);
0155 
0156     /* ...set the real DAC level... */
0157     ret = iio_write_channel_raw(env->dac, env->level);
0158     if (ret < 0)
0159         goto err;
0160 
0161     /* ...and wait for a bit to see if the latch catches anything. */
0162     schedule_delayed_work(&env->comp_timeout,
0163                   msecs_to_jiffies(env->comp_interval));
0164     return;
0165 
0166 err:
0167     env->level = ret;
0168     complete(&env->done);
0169 }
0170 
0171 static void envelope_detector_timeout(struct work_struct *work)
0172 {
0173     struct envelope *env = container_of(work, struct envelope,
0174                         comp_timeout.work);
0175 
0176     /* Adjust low/high depending on the latch content... */
0177     if (!envelope_detector_comp_latch(env) ^ !env->invert)
0178         env->low = env->level;
0179     else
0180         env->high = env->level;
0181 
0182     /* ...and continue the search. */
0183     envelope_detector_setup_compare(env);
0184 }
0185 
0186 static int envelope_detector_read_raw(struct iio_dev *indio_dev,
0187                       struct iio_chan_spec const *chan,
0188                       int *val, int *val2, long mask)
0189 {
0190     struct envelope *env = iio_priv(indio_dev);
0191     int ret;
0192 
0193     switch (mask) {
0194     case IIO_CHAN_INFO_RAW:
0195         /*
0196          * When invert is active, start with high=max+1 and low=0
0197          * since we will end up with the low value when the
0198          * termination criteria is fulfilled (rounding down). And
0199          * start with high=max and low=-1 when invert is not active
0200          * since we will end up with the high value in that case.
0201          * This ensures that the returned value in both cases are
0202          * in the same range as the DAC and is a value that has not
0203          * triggered the comparator.
0204          */
0205         mutex_lock(&env->read_lock);
0206         env->high = env->dac_max + env->invert;
0207         env->low = -1 + env->invert;
0208         envelope_detector_setup_compare(env);
0209         wait_for_completion(&env->done);
0210         if (env->level < 0) {
0211             ret = env->level;
0212             goto err_unlock;
0213         }
0214         *val = env->invert ? env->dac_max - env->level : env->level;
0215         mutex_unlock(&env->read_lock);
0216 
0217         return IIO_VAL_INT;
0218 
0219     case IIO_CHAN_INFO_SCALE:
0220         return iio_read_channel_scale(env->dac, val, val2);
0221     }
0222 
0223     return -EINVAL;
0224 
0225 err_unlock:
0226     mutex_unlock(&env->read_lock);
0227     return ret;
0228 }
0229 
0230 static ssize_t envelope_show_invert(struct iio_dev *indio_dev,
0231                     uintptr_t private,
0232                     struct iio_chan_spec const *ch, char *buf)
0233 {
0234     struct envelope *env = iio_priv(indio_dev);
0235 
0236     return sprintf(buf, "%u\n", env->invert);
0237 }
0238 
0239 static ssize_t envelope_store_invert(struct iio_dev *indio_dev,
0240                      uintptr_t private,
0241                      struct iio_chan_spec const *ch,
0242                      const char *buf, size_t len)
0243 {
0244     struct envelope *env = iio_priv(indio_dev);
0245     unsigned long invert;
0246     int ret;
0247     u32 trigger;
0248 
0249     ret = kstrtoul(buf, 0, &invert);
0250     if (ret < 0)
0251         return ret;
0252     if (invert > 1)
0253         return -EINVAL;
0254 
0255     trigger = invert ? env->comp_irq_trigger_inv : env->comp_irq_trigger;
0256 
0257     mutex_lock(&env->read_lock);
0258     if (invert != env->invert)
0259         ret = irq_set_irq_type(env->comp_irq, trigger);
0260     if (!ret) {
0261         env->invert = invert;
0262         ret = len;
0263     }
0264     mutex_unlock(&env->read_lock);
0265 
0266     return ret;
0267 }
0268 
0269 static ssize_t envelope_show_comp_interval(struct iio_dev *indio_dev,
0270                        uintptr_t private,
0271                        struct iio_chan_spec const *ch,
0272                        char *buf)
0273 {
0274     struct envelope *env = iio_priv(indio_dev);
0275 
0276     return sprintf(buf, "%u\n", env->comp_interval);
0277 }
0278 
0279 static ssize_t envelope_store_comp_interval(struct iio_dev *indio_dev,
0280                         uintptr_t private,
0281                         struct iio_chan_spec const *ch,
0282                         const char *buf, size_t len)
0283 {
0284     struct envelope *env = iio_priv(indio_dev);
0285     unsigned long interval;
0286     int ret;
0287 
0288     ret = kstrtoul(buf, 0, &interval);
0289     if (ret < 0)
0290         return ret;
0291     if (interval > 1000)
0292         return -EINVAL;
0293 
0294     mutex_lock(&env->read_lock);
0295     env->comp_interval = interval;
0296     mutex_unlock(&env->read_lock);
0297 
0298     return len;
0299 }
0300 
0301 static const struct iio_chan_spec_ext_info envelope_detector_ext_info[] = {
0302     { .name = "invert",
0303       .read = envelope_show_invert,
0304       .write = envelope_store_invert, },
0305     { .name = "compare_interval",
0306       .read = envelope_show_comp_interval,
0307       .write = envelope_store_comp_interval, },
0308     { /* sentinel */ }
0309 };
0310 
0311 static const struct iio_chan_spec envelope_detector_iio_channel = {
0312     .type = IIO_ALTVOLTAGE,
0313     .info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
0314                 | BIT(IIO_CHAN_INFO_SCALE),
0315     .ext_info = envelope_detector_ext_info,
0316     .indexed = 1,
0317 };
0318 
0319 static const struct iio_info envelope_detector_info = {
0320     .read_raw = &envelope_detector_read_raw,
0321 };
0322 
0323 static int envelope_detector_probe(struct platform_device *pdev)
0324 {
0325     struct device *dev = &pdev->dev;
0326     struct iio_dev *indio_dev;
0327     struct envelope *env;
0328     enum iio_chan_type type;
0329     int ret;
0330 
0331     indio_dev = devm_iio_device_alloc(dev, sizeof(*env));
0332     if (!indio_dev)
0333         return -ENOMEM;
0334 
0335     platform_set_drvdata(pdev, indio_dev);
0336     env = iio_priv(indio_dev);
0337     env->comp_interval = 50; /* some sensible default? */
0338 
0339     spin_lock_init(&env->comp_lock);
0340     mutex_init(&env->read_lock);
0341     init_completion(&env->done);
0342     INIT_DELAYED_WORK(&env->comp_timeout, envelope_detector_timeout);
0343 
0344     indio_dev->name = dev_name(dev);
0345     indio_dev->info = &envelope_detector_info;
0346     indio_dev->channels = &envelope_detector_iio_channel;
0347     indio_dev->num_channels = 1;
0348 
0349     env->dac = devm_iio_channel_get(dev, "dac");
0350     if (IS_ERR(env->dac))
0351         return dev_err_probe(dev, PTR_ERR(env->dac),
0352                      "failed to get dac input channel\n");
0353 
0354     env->comp_irq = platform_get_irq_byname(pdev, "comp");
0355     if (env->comp_irq < 0)
0356         return env->comp_irq;
0357 
0358     ret = devm_request_irq(dev, env->comp_irq, envelope_detector_comp_isr,
0359                    0, "envelope-detector", env);
0360     if (ret)
0361         return dev_err_probe(dev, ret, "failed to request interrupt\n");
0362 
0363     env->comp_irq_trigger = irq_get_trigger_type(env->comp_irq);
0364     if (env->comp_irq_trigger & IRQF_TRIGGER_RISING)
0365         env->comp_irq_trigger_inv |= IRQF_TRIGGER_FALLING;
0366     if (env->comp_irq_trigger & IRQF_TRIGGER_FALLING)
0367         env->comp_irq_trigger_inv |= IRQF_TRIGGER_RISING;
0368     if (env->comp_irq_trigger & IRQF_TRIGGER_HIGH)
0369         env->comp_irq_trigger_inv |= IRQF_TRIGGER_LOW;
0370     if (env->comp_irq_trigger & IRQF_TRIGGER_LOW)
0371         env->comp_irq_trigger_inv |= IRQF_TRIGGER_HIGH;
0372 
0373     ret = iio_get_channel_type(env->dac, &type);
0374     if (ret < 0)
0375         return ret;
0376 
0377     if (type != IIO_VOLTAGE) {
0378         dev_err(dev, "dac is of the wrong type\n");
0379         return -EINVAL;
0380     }
0381 
0382     ret = iio_read_max_channel_raw(env->dac, &env->dac_max);
0383     if (ret < 0) {
0384         dev_err(dev, "dac does not indicate its raw maximum value\n");
0385         return ret;
0386     }
0387 
0388     return devm_iio_device_register(dev, indio_dev);
0389 }
0390 
0391 static const struct of_device_id envelope_detector_match[] = {
0392     { .compatible = "axentia,tse850-envelope-detector", },
0393     { /* sentinel */ }
0394 };
0395 MODULE_DEVICE_TABLE(of, envelope_detector_match);
0396 
0397 static struct platform_driver envelope_detector_driver = {
0398     .probe = envelope_detector_probe,
0399     .driver = {
0400         .name = "iio-envelope-detector",
0401         .of_match_table = envelope_detector_match,
0402     },
0403 };
0404 module_platform_driver(envelope_detector_driver);
0405 
0406 MODULE_DESCRIPTION("Envelope detector using a DAC and a comparator");
0407 MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
0408 MODULE_LICENSE("GPL v2");