Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S965147AbcJWDTf (ORCPT ); Sat, 22 Oct 2016 23:19:35 -0400 Received: from mail-db5eur01on0110.outbound.protection.outlook.com ([104.47.2.110]:46544 "EHLO EUR01-DB5-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S935604AbcJWDTc (ORCPT ); Sat, 22 Oct 2016 23:19:32 -0400 Authentication-Results: spf=none (sender IP is ) smtp.mailfrom=peda@axentia.se; From: Peter Rosin To: CC: Peter Rosin , Jonathan Cameron , Hartmut Knaack , Lars-Peter Clausen , "Peter Meerwald-Stadler" , Rob Herring , "Mark Rutland" , , Subject: [PATCH v2 7/7] iio: envelope-detector: ADC driver based on a DAC and a comparator Date: Sun, 23 Oct 2016 00:43:46 +0200 Message-ID: <1477176226-10566-8-git-send-email-peda@axentia.se> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1477176226-10566-1-git-send-email-peda@axentia.se> References: <1477176226-10566-1-git-send-email-peda@axentia.se> MIME-Version: 1.0 Content-Type: text/plain X-Originating-IP: [217.210.101.82] X-ClientProxiedBy: DBXPR04CA0046.eurprd04.prod.outlook.com (10.141.8.174) To VI1PR0201MB2317.eurprd02.prod.outlook.com (10.168.63.19) X-MS-Office365-Filtering-Correlation-Id: bd19b47a-3af8-4332-2eb8-08d3facd1c11 X-Microsoft-Exchange-Diagnostics: 1;VI1PR0201MB2317;2:erSW1MhUrfOb4nggcEoRcqoeKKEBgkEaob1qHB5+o58gSwY+zf7+BXCBibbPFwbUumfAosg9LkPj8p9CvwOALCKUF6mWOZ/9J0MuCBX5IotJyrxwaC7/H00VhuRqIsWdGJMX10BkCuLwN/xuDoOS1SKRz9Tw6NJEZ10ZyplLtF+I+SEax2CSC4rU0d/cDzoavNr1vvzcZetpGLgmpqCp7A==;3:ftnOn8zONFPYq0xxEkhVS3Ois163EL8rdXUjFil8gEagqw6hC7PGz7aNXsHenRH2dqrXzKanhFn+GuWzYuVAR0OKKmyBM7gJG5zmqZ1IshYlYj43vqHHMgKF95d22wyC49hY9A3Z+h2DSMaRMgGFLw==;25:huSj1Q+Yybrxr1YMS9lBdxW8Bf2/D2f2EzvkkW892zBB4UKpSR351OwqhZTs1UhOKNo5uIjzLg6lyAQZKGx6MDDSURTjUgldJ2BFkFW9fxg1Y/k/NmnJZnjp57eupRM79Q4pPvC556gbEsmP5uoAix5pM9WYOPcmExILg3pnFexaCu9gYcBzE1z9oyOVjyyo3JlrjcMGrTjn5rytxU2bw98Lkpy8cemQQ0hmEAQfuhy+Uo/EET+C3VpuckU+cVZxPVfLiqIpSI2I7eP7Q5u+Lu9sGKolKdg7ji4mPLUlCHiSkpuwbB3t9IiMhKLBxTUqte+2quiqZPWz/ypjtEGA8zSkCf822ARk0qN66cqSPpKGPAshcBlALWm0jVPwKBwj1sLhaDmL3o0M5QOyrTUAZAsgfOkEvz8T6Wnm7bvVFG92jWE3E/boifsoZ0brZzYc X-Microsoft-Antispam: UriScan:;BCL:0;PCL:0;RULEID:;SRVR:VI1PR0201MB2317; X-Microsoft-Exchange-Diagnostics: 1;VI1PR0201MB2317;31:orcb5lqukPDkYg9i3xBytDzMraC8yh9S/mF5Nc79EmJE2ySy0GSA/Xb+ZcepL75tMpIN09mwYohq7uBegLY6SpDFXnpClcRzikLLQIiVu3Mx2dLVZqnrfERPyfxcDaUm5dECIiMS9sSRSvNZ2bAknZs5L383X6ulVId3spB9JXgLhszq/A3Swul1fC55PWB0p5RGjuTqzfeX1NPCEzf+yLEuOS3Ytr112CPLnF7yd1ADZvPEpQln3oIiLHOCTLXn;4:uGldEMMSsXQyH9g7NquoN0SlDUGnHBR58ZW2zmg4Y4KRwEZAvvkgVh52jWW8GFWwJWzVmtMuomEzqVKE+JgnjyCRSnpNpLGctJsclMaRfy/ELEXWhe0dgbN1lHdWsQMjO3WkQcqxfOvgCc3q4dGZGslv7+SsqNvzqkkz5hLreLGIIsCa0A6DiOaTrHpi+SeKh1MLbVAEXunG70CGXxYHBIDScwAsUPp4x8bkbbKqXGhc0zx6S5lHdCh2k9LjVYzJ7x8+8ZgbX78mGj4kDve4wvYfNHvnU7RxUqSeVTsdd4CObkETT0MLKRyOHVGUMVvucyJGLu5WwfBvxDcF9Ua4hsQ7732eSpVW37Ot3H8yCmgiH9SuBgB/XqOvzcJEA+3p3oGsqYXnKQJNK81TiyhErYdZ3x2RRvqbUo9aYWj8COmFaukPqiADfL0bIr0cVTtb7I4F3MX0/rHd45FMxLTCz2/wHjmbW8tGjCrIwEtM5oU= X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:(9452136761055); X-Exchange-Antispam-Report-CFA-Test: BCL:0;PCL:0;RULEID:(6040176)(601004)(2401047)(8121501046)(5005006)(10201501046)(3002001)(6043046)(6042046);SRVR:VI1PR0201MB2317;BCL:0;PCL:0;RULEID:;SRVR:VI1PR0201MB2317; X-Forefront-PRVS: 01039C93E4 X-Forefront-Antispam-Report: SFV:NSPM;SFS:(10019020)(4630300001)(6009001)(6069001)(7916002)(189002)(52314003)(199003)(50944005)(33646002)(5660300001)(189998001)(74482002)(110136003)(97736004)(19580405001)(19580395003)(68736007)(81156014)(77096005)(8676002)(92566002)(81166006)(50226002)(48376002)(50466002)(50986999)(305945005)(6666003)(42186005)(2950100002)(6916009)(2906002)(36756003)(229853001)(8666005)(2351001)(7736002)(7846002)(105586002)(76176999)(101416001)(3846002)(5003940100001)(586003)(6116002)(106356001)(4326007)(47776003)(66066001)(86362001)(7059030)(2004002)(42262002);DIR:OUT;SFP:1102;SCL:1;SRVR:VI1PR0201MB2317;H:localhost.localdomain;FPR:;SPF:None;PTR:InfoNoRecords;MX:1;A:1;LANG:en; X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1;VI1PR0201MB2317;23:82GM2FZ9mL1lhSD0rX8/+nJMGU8av8BonIm0jwn?= =?us-ascii?Q?qQA4yM/Fvfs0lbRJkkAGugp1+HX3URzYAIyfQJjV2vDIKJJOrQjHDi1Zdd4H?= =?us-ascii?Q?criClkHqQrvr01W8S9dJ93RlpRvYVRKC6WH05h2Gpyod0qLs7oPQvt49fUmm?= =?us-ascii?Q?25wxBABq1GqZS7ly/b/GIRo3wqRHfrzoG7wJtT7+MSOWgcLRtMTNhOLGuhMb?= =?us-ascii?Q?7Yu96YxgwgpElhxKpJQm9/TYK+bROdEnWNa7Bdk0Y3J4RhK/sKgdMnqJf227?= =?us-ascii?Q?+pJyHwerl7PlnYM5tMUFwYy/hplm6KTaTZqA4aBJNad+IJUpFTsmRqPGLkmM?= =?us-ascii?Q?sdiAzskyoOEVvfkjJ1y+hJDvs3EoGznUira6WEn3iGaVlVkoy4QvlmmxHvb8?= =?us-ascii?Q?8QWz3H4pBINECj8aFMzrTrmGrBN12J5vOOwZo4vrw9PvZUKB4PlJwqTnZOCn?= =?us-ascii?Q?VEIhAM8QD8yhYXJvk3xEcn7+2Uk5zcESNoJW6z8nNG6XHy8JUeWG3on4KU6X?= =?us-ascii?Q?1zUvTMW/V9BGKfogD5kGYbqOr+j9u7HrYCiNLzHLPDNZFAFpdgeXnDSmTuA3?= =?us-ascii?Q?mqy1WoKZ0CGESSjieBurZp+bhgtS5O9M0/GZ4GOo2J0oUiMKVBJS0HqiofUG?= =?us-ascii?Q?3LxUVY7eO7n5vOx/zyMy0ftwq/S2s9xobX2jDsC/hiE+5bG3DgKFuIG6iYpi?= =?us-ascii?Q?PdDUxHOWsb5hz8fJnbEmloeoO+05+tuxZoDXpXQli24JLc4ljji9rQeadril?= =?us-ascii?Q?9funDrQlgY4ZHlrN0KlOx3PbYpHVK+XxrVrtOf3NR7OZ8TkgJQASG6KAJio4?= =?us-ascii?Q?gsWXPyLexElOry4YZ85HSLo2wzvocjbLmNKBy0QP7L6EE5ZAO5vihNrta5m7?= =?us-ascii?Q?Kiz6DcRQO3G19Zn/7O50MiOSZwfzGHTWKa1lvPy8AIPa0puYzT/BKPpdsUnz?= =?us-ascii?Q?4v9snSFYUjkiBTwj75qCa5C3wdefwirRUCBAv90a4aPnH+1kArxe0r52cuhu?= =?us-ascii?Q?or17A994zc9rMJEzk3L9pcjdZZJUmKjIjBvplEA9/bWVAnwyJZhE+qZwjWBa?= =?us-ascii?Q?N+H3nl9wJ/Xtz4CAx04588KnkKKE2iTiKyKI6LgwyEfxpYy5XtCRwOkHFH1Z?= =?us-ascii?Q?Lpw5K4YHM1JuOGYwRyczkgspG6U+PjRsI6IT8FtEUD59O9rMtdAQUwdljf+X?= =?us-ascii?Q?z0EGTASlKjfPpxxFtV/7/HP+lecWtbLWfl05ZcpndgEQZTdnX77McUc2p+1Q?= =?us-ascii?Q?5cygXeRR9qChzvwEcwyk=3D?= X-Microsoft-Exchange-Diagnostics: 1;VI1PR0201MB2317;6:A0OBLAcyk+A3BvkRHqKwVm6vIeEuWpmsYSR6Rs4MIUmE1qyKGIxJcT3uBJSfhDjxa4FBQhX9/MjgU/n98sua9zHUsAw43phOxWoRAPOwhljFdQEsczoOhYr8176U7bOKsifNDNgQ4fUKXhKD2OvZDNswd6cmLSjpdokACfHZ1wUGhYE1hVT4W3Izm+MeurtPRfVr68z836ONwCVDyifkAaQGrYaSz+Zs6YRclnUuTwzfbou9lt4dozQW35Qd65TmLMpwvWxX8xaJ+Fx9K0Q65XNGZKHdeJ7H6w7v6fPAS2Qz8GGTwFx/CcJAmUe6Zp9wYZi5fw/cIR7weqinDI5qfw==;5:Ae9mzk1mWZPh/yjHKv945UQpIFd1IzIgw6pGNa+FKPFZSRNwQBU7neNCbTZoawmb6XOqa92TTlaszEC/sNsyIjHnY55BZiJ5e2rbP3lixejrcYyxUTALsZ4cXMaAavT/Ve7c20oM2vlEAzjzRmgoKA==;24:4LV1Pg0PEjuXYCYs087QLWSzL2PJXjXF2yU20TXvb5aV+SRqmYnMfh5e4a/nb/JkuAyYz1xMQfesU1eYUBQoRlvNSZXvcGuy5Niu2Og5qIo= SpamDiagnosticOutput: 1:99 SpamDiagnosticMetadata: NSPM X-Microsoft-Exchange-Diagnostics: 1;VI1PR0201MB2317;7:4DeKIjDrQ4G1eAApDR26rqF1zitu1BLhol5JDS2XVYqzywa+DfWMexoX7LDn9FHwSFYEBpYJTG27irC3l9TnwunuZO2oOVuIYzZIrQAmH9x0f9A4+WTde1OfaLS7UN+Mer7h86UUIJUSXyygRHUTmTs5fjDhMJzvnr/jIL33VN6MgVVeVWeHm8XvrTbyRaosHnyDvvubHaGVV777Qi9X/SrbKus1YADTOYtF11LXESB2lKU4/RB+01k8pzfHj9bNDWmYPLC+PnNTiGD1IHdpwe2qhFM93iAQqlyMWz72IrR051oSvOa+Z2ozj62UoNy5+YewduMtNZhSDPL7QkV5rjaLNci2lg8mgMToZCgUkHc= X-OriginatorOrg: axentia.se X-MS-Exchange-CrossTenant-OriginalArrivalTime: 22 Oct 2016 22:45:21.9786 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-Transport-CrossTenantHeadersStamped: VI1PR0201MB2317 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 17403 Lines: 588 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 --- .../testing/sysfs-bus-iio-adc-envelope-detector | 36 ++ MAINTAINERS | 2 + drivers/iio/adc/Kconfig | 10 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/envelope-detector.c | 463 +++++++++++++++++++++ 5 files changed, 512 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector create mode 100644 drivers/iio/adc/envelope-detector.c diff --git a/Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector b/Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector new file mode 100644 index 000000000000..8d890028a649 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector @@ -0,0 +1,36 @@ +What: /sys/bus/iio/devices/iio:deviceX/invert +Date: October 2016 +KernelVersion: 4.9 +Contact: Peter Rosin +Description: + 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|------<-------' + | | + '-------' + The boolean invert attribute (0/1) should be set when the + input signal is centered around the maximum value of the + dac instead of zero. The envelope detector will search + from below in this case and will also invert the result. + The edge/level of the interrupt is also switched to its + opposite value. + +What: /sys/bus/iio/devices/iio:deviceX/compare_interval_ms +Date: October 2016 +KernelVersion: 4.9 +Contact: Peter Rosin +Description: + Number of milliseconds to wait for the comparator in each + step of the binary search for the input peak level. Needs + to relate to the frequency of the input signal. diff --git a/MAINTAINERS b/MAINTAINERS index 4b6f6ec1b703..0d20f3561700 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6122,7 +6122,9 @@ IIO ENVELOPE DETECTOR M: Peter Rosin L: linux-iio@vger.kernel.org S: Maintained +F: Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector F: Documentation/devicetree/bindings/iio/adc/envelope-detector.txt +F: drivers/iio/adc/envelope-detector.c IIO SUBSYSTEM AND DRIVERS M: Jonathan Cameron diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 7edcf3238620..d5c4a95855c2 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -195,6 +195,16 @@ config DA9150_GPADC To compile this driver as a module, choose M here: the module will be called berlin2-adc. +config ENVELOPE_DETECTOR + tristate "Envelope detector using a DAC and a comparator" + depends on OF + help + Say yes here to build support for an envelope detector using a DAC + and a comparator. + + To compile this driver as a module, choose M here: the module will be + called iio-envelope-detector. + config EXYNOS_ADC tristate "Exynos ADC driver support" depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || (OF && COMPILE_TEST) diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 7a40c04c311f..0d773c6a0578 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_BCM_IPROC_ADC) += bcm_iproc_adc.o obj-$(CONFIG_BERLIN2_ADC) += berlin2-adc.o obj-$(CONFIG_CC10001_ADC) += cc10001_adc.o obj-$(CONFIG_DA9150_GPADC) += da9150-gpadc.o +obj-$(CONFIG_ENVELOPE_DETECTOR) += envelope-detector.o obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o obj-$(CONFIG_FSL_MX25_ADC) += fsl-imx25-gcq.o obj-$(CONFIG_HI8435) += hi8435.o diff --git a/drivers/iio/adc/envelope-detector.c b/drivers/iio/adc/envelope-detector.c new file mode 100644 index 000000000000..3842d1a72023 --- /dev/null +++ b/drivers/iio/adc/envelope-detector.c @@ -0,0 +1,463 @@ +/* + * Driver for an envelope detector using a DAC and a comparator + * + * Copyright (C) 2016 Axentia Technologies AB + * + * Author: Peter Rosin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * 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|------<-------' + * | | + * '-------' + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct envelope { + spinlock_t comp_lock; /* protects comp */ + int comp; + + struct mutex read_lock; /* protects everything else */ + + int comp_irq; + u32 comp_irq_trigger; + u32 comp_irq_trigger_inv; + + struct iio_channel *dac; + struct delayed_work comp_timeout; + + unsigned int comp_interval; + bool invert; + u32 dac_max; + + int high; + int level; + int low; + + struct completion done; +}; + +/* + * The envelope_detector_comp_latch function works together with the compare + * interrupt service routine below (envelope_detector_comp_isr) as a latch + * (one-bit memory) for if the interrupt has triggered since last calling + * this function. + * The ..._comp_isr function disables the interrupt so that the cpu does not + * need to service a possible interrupt flood from the comparator when no-one + * cares anyway, and this ..._comp_latch function reenables them again if + * needed. + */ +static int envelope_detector_comp_latch(struct envelope *env) +{ + int comp; + + spin_lock_irq(&env->comp_lock); + comp = env->comp; + env->comp = 0; + spin_unlock_irq(&env->comp_lock); + + if (!comp) + return 0; + + /* + * The irq was disabled, and is reenabled just now. + * But there might have been a pending irq that + * happened while the irq was disabled that fires + * just as the irq is reenabled. That is not what + * is desired. + */ + enable_irq(env->comp_irq); + + /* So, synchronize this possibly pending irq... */ + synchronize_irq(env->comp_irq); + + /* ...and redo the whole dance. */ + spin_lock_irq(&env->comp_lock); + comp = env->comp; + env->comp = 0; + spin_unlock_irq(&env->comp_lock); + + if (comp) + enable_irq(env->comp_irq); + + return 1; +} + +static irqreturn_t envelope_detector_comp_isr(int irq, void *ctx) +{ + struct envelope *env = ctx; + + spin_lock(&env->comp_lock); + env->comp = 1; + disable_irq_nosync(env->comp_irq); + spin_unlock(&env->comp_lock); + + return IRQ_HANDLED; +} + +static void envelope_detector_setup_compare(struct envelope *env) +{ + int ret; + + /* + * Do a binary search for the peak input level, and stop + * when that level is "trapped" between two adjacent DAC + * values. + * When invert is active, use the midpoint floor so that + * env->level ends up as env->low when the termination + * criteria below is fulfilled, and use the midpoint + * ceiling when invert is not active so that env->level + * ends up as env->high in that case. + */ + env->level = (env->high + env->low + !env->invert) / 2; + + if (env->high == env->low + 1) { + complete(&env->done); + return; + } + + /* Set a "safe" DAC level (if there is such a thing)... */ + ret = iio_write_channel_raw(env->dac, env->invert ? 0 : env->dac_max); + if (ret < 0) + goto err; + + /* ...clear the comparison result... */ + envelope_detector_comp_latch(env); + + /* ...set the real DAC level... */ + ret = iio_write_channel_raw(env->dac, env->level); + if (ret < 0) + goto err; + + /* ...and wait for a bit to see if the latch catches anything. */ + schedule_delayed_work(&env->comp_timeout, + msecs_to_jiffies(env->comp_interval)); + return; + +err: + env->level = ret; + complete(&env->done); +} + +static void envelope_detector_timeout(struct work_struct *work) +{ + struct envelope *env = container_of(work, struct envelope, + comp_timeout.work); + + /* Adjust low/high depending on the latch content... */ + if (!envelope_detector_comp_latch(env) ^ !env->invert) + env->low = env->level; + else + env->high = env->level; + + /* ...and continue the search. */ + envelope_detector_setup_compare(env); +} + +static int envelope_detector_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct envelope *env = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * When invert is active, start with high=max+1 and low=0 + * since we will end up with the low value when the + * termination criteria is fulfilled (rounding down). And + * start with high=max and low=-1 when invert is not active + * since we will end up with the high value in that case. + * This ensures that the returned value in both cases are + * in the same range as the DAC and is a value that has not + * triggered the comparator. + */ + mutex_lock(&env->read_lock); + env->high = env->dac_max + env->invert; + env->low = -1 + env->invert; + envelope_detector_setup_compare(env); + wait_for_completion(&env->done); + if (env->level < 0) { + ret = env->level; + goto err_unlock; + } + *val = env->invert ? env->dac_max - env->level : env->level; + mutex_unlock(&env->read_lock); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + return iio_read_channel_scale(env->dac, val, val2); + } + + return -EINVAL; + +err_unlock: + mutex_unlock(&env->read_lock); + return ret; +} + +static int envelope_detector_channel_raw_max(struct iio_channel *ch) +{ + struct iio_dev *indio_dev = ch->indio_dev; + const int *vals; + int type; + int len; + int ret; + + if (!ch->indio_dev->info->read_avail) + return -EINVAL; + + ret = ch->indio_dev->info->read_avail(indio_dev, ch->channel, + &vals, &type, &len, + IIO_CHAN_INFO_RAW); + + if (ret < 0) + return ret; + if (type != IIO_VAL_INT) + return -EINVAL; + + switch (ret) { + case IIO_AVAIL_RANGE: + if (len != 3) + return -EINVAL; + return vals[2]; + } + return -EINVAL; +} + +static const struct iio_chan_spec envelope_detector_iio_channel = { + .type = IIO_ALTVOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) + | BIT(IIO_CHAN_INFO_SCALE), + .indexed = 1, +}; + +static ssize_t envelope_show_invert(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct envelope *env = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", env->invert); +} + +static ssize_t envelope_store_invert(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct envelope *env = iio_priv(indio_dev); + unsigned long invert; + int ret; + u32 trigger; + + ret = kstrtoul(buf, 0, &invert); + if (ret < 0) + return ret; + if (invert > 1) + return -EINVAL; + + trigger = invert ? env->comp_irq_trigger_inv : env->comp_irq_trigger; + + mutex_lock(&env->read_lock); + if (invert != env->invert) + ret = irq_set_irq_type(env->comp_irq, trigger); + if (!ret) { + env->invert = invert; + ret = len; + } + mutex_unlock(&env->read_lock); + + return ret; +} + +static IIO_DEVICE_ATTR(invert, 0644, + envelope_show_invert, + envelope_store_invert, 0); + +static ssize_t envelope_show_comp_interval(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct envelope *env = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", env->comp_interval); +} + +static ssize_t envelope_store_comp_interval(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct envelope *env = iio_priv(indio_dev); + unsigned long interval; + int ret; + + ret = kstrtoul(buf, 0, &interval); + if (ret < 0) + return ret; + if (interval > 1000) + return -EINVAL; + + mutex_lock(&env->read_lock); + env->comp_interval = interval; + mutex_unlock(&env->read_lock); + + return len; +} + +static IIO_DEVICE_ATTR(compare_interval_ms, 0644, + envelope_show_comp_interval, + envelope_store_comp_interval, 0); + +static struct attribute *envelope_detector_attributes[] = { + &iio_dev_attr_invert.dev_attr.attr, + &iio_dev_attr_compare_interval_ms.dev_attr.attr, + NULL, +}; + +static const struct attribute_group envelope_detector_attribute_group = { + .attrs = envelope_detector_attributes, +}; + +static const struct iio_info envelope_detector_info = { + .read_raw = &envelope_detector_read_raw, + .driver_module = THIS_MODULE, + .attrs = &envelope_detector_attribute_group, +}; + +static int envelope_detector_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct envelope *env; + enum iio_chan_type type; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*env)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + env = iio_priv(indio_dev); + env->comp_interval = 50; /* some sensible default? */ + + spin_lock_init(&env->comp_lock); + mutex_init(&env->read_lock); + init_completion(&env->done); + INIT_DELAYED_WORK(&env->comp_timeout, envelope_detector_timeout); + + indio_dev->name = dev_name(dev); + indio_dev->dev.parent = dev; + indio_dev->dev.of_node = dev->of_node; + indio_dev->info = &envelope_detector_info; + indio_dev->channels = &envelope_detector_iio_channel; + indio_dev->num_channels = 1; + + env->dac = devm_iio_channel_get(dev, "dac"); + if (IS_ERR(env->dac)) { + if (PTR_ERR(env->dac) != -EPROBE_DEFER) + dev_err(dev, "failed to get dac input channel\n"); + return PTR_ERR(env->dac); + } + + env->comp_irq = platform_get_irq_byname(pdev, "comp"); + if (env->comp_irq < 0) { + if (env->comp_irq != -EPROBE_DEFER) + dev_err(dev, "failed to get compare interrupt\n"); + return env->comp_irq; + } + + ret = devm_request_irq(dev, env->comp_irq, envelope_detector_comp_isr, + 0, "envelope-detector", env); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to request interrupt\n"); + return ret; + } + env->comp_irq_trigger = irq_get_trigger_type(env->comp_irq); + if (env->comp_irq_trigger & IRQF_TRIGGER_RISING) + env->comp_irq_trigger_inv |= IRQF_TRIGGER_FALLING; + if (env->comp_irq_trigger & IRQF_TRIGGER_FALLING) + env->comp_irq_trigger_inv |= IRQF_TRIGGER_RISING; + if (env->comp_irq_trigger & IRQF_TRIGGER_HIGH) + env->comp_irq_trigger_inv |= IRQF_TRIGGER_LOW; + if (env->comp_irq_trigger & IRQF_TRIGGER_LOW) + env->comp_irq_trigger_inv |= IRQF_TRIGGER_HIGH; + + ret = iio_get_channel_type(env->dac, &type); + if (ret < 0) + return ret; + + if (type != IIO_VOLTAGE) { + dev_err(dev, "dac is of the wrong type\n"); + return -EINVAL; + } + + ret = envelope_detector_channel_raw_max(env->dac); + if (ret < 0) { + dev_err(dev, "dac does not indicate its raw maximum value\n"); + return ret; + } + env->dac_max = ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id envelope_detector_match[] = { + { .compatible = "axentia,tse850-envelope-detector", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, envelope_detector_match); + +static struct platform_driver envelope_detector_driver = { + .probe = envelope_detector_probe, + .driver = { + .name = "iio-envelope-detector", + .of_match_table = envelope_detector_match, + }, +}; +module_platform_driver(envelope_detector_driver); + +MODULE_DESCRIPTION("Envelope detector using a DAC and a comparator"); +MODULE_AUTHOR("Peter Rosin "); +MODULE_LICENSE("GPL v2"); -- 2.1.4