Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756783AbcJWWzj (ORCPT ); Sun, 23 Oct 2016 18:55:39 -0400 Received: from mail-eopbgr30131.outbound.protection.outlook.com ([40.107.3.131]:2963 "EHLO EUR03-AM5-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1754205AbcJWWzh (ORCPT ); Sun, 23 Oct 2016 18:55:37 -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 v3 8/8] iio: envelope-detector: ADC driver based on a DAC and a comparator Date: Mon, 24 Oct 2016 00:39:41 +0200 Message-ID: <1477262381-7800-9-git-send-email-peda@axentia.se> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1477262381-7800-1-git-send-email-peda@axentia.se> References: <1477262381-7800-1-git-send-email-peda@axentia.se> MIME-Version: 1.0 Content-Type: text/plain X-Originating-IP: [217.210.101.82] X-ClientProxiedBy: DB5PR08CA0016.eurprd08.prod.outlook.com (10.163.102.154) To DB6PR0201MB2312.eurprd02.prod.outlook.com (10.169.222.151) X-MS-Office365-Filtering-Correlation-Id: 61ab4397-390b-4871-00c7-08d3fb95b42f X-Microsoft-Exchange-Diagnostics: 1;DB6PR0201MB2312;2:6XNAaRa8CP7r+2QvHAQES2io77EhEvPGAncex7lo2uwKTv7MQnv05iqD7VZmrl83njlr/a9Y6tRv97QkPuQxGnl7hUDFTgJdxO7FDCiCWiR6QtcnADOD1vVMQ4PO/8fl63yXkhDSuasnBXxbnNQjLCArC/AM+ifletVF2QWDDFprPjk9PrxRNDWlo3rpeFUG1EaJysietjXRJCaG9VZtiA==;3:A9eOclbIngPtgdrsUZUfvL93ZaKog5Bjuy9XrsfYA7WJhDnz2hh0NHZQo6hyUiTykbk9s4co46LSs0BjrNxWvcpKcL9Z2KGMkIvOsMnx6dVrejyyUSCBdorw8b4no7iQ9sx4bVLwPsa6TXUhcPkoTA== X-Microsoft-Antispam: UriScan:;BCL:0;PCL:0;RULEID:;SRVR:DB6PR0201MB2312; X-Microsoft-Exchange-Diagnostics: 1;DB6PR0201MB2312;25:f8cpPajBtTm6+vUnVq8/4vF98f0ZvtbyBrcKnz4ZAXeIRj1LESp1+SBcpes6b1YGzGR4+5D9kKv+9qgOApwvsZAhzFXzoMYMCfXar2hTr7tHlJXo7t1d6v2DoymEcEi8gRXWTBt4tDyHAJI5B0OzebwG3bHUCej9zwfa7PQCx+kv7r/qTUXAqvxeX/iLglzozSflgH8Wu+jBFRKmt6lpPLXK7OlBFN5oodujonUImc7kV0yJf1xpvWQcgjfXfBbC3EFGw2hRf7b61PZYpDcOItosiVHaVW6JXTiv1/4sNH0+MsBto9ylCumZiokSm6Jk2HlHn6IODwc3MEannQ82lLLdG88UJzbxnB57rvlChefV8umsOgJu7PABkYlAlrvXtAIp20bFAIrPVlNkxaHDNhx/gLHCImZ+O6Lb28Eg3+cEIwKnO+KmXVmg9AGXPNiNhmgvA2tv2N6W9u+djXJcIXVland1DFcHT8tkhvXq7hLT5QJqkFGLIYwITXO10qGt+Kg6NDRsD+d/CXd7UXnHt5vtNCsfwN2Jp7yEZFkg0LkFb8BBEgKjE7WbEK73cpF+CAuEwnVpCb5opnSI4rsp60oaMsf0+bhLPcWbDqbUQWJKPofiXHel4cHh5s49VIHzf1N8ZzJzTUZLoqk1tD+TuUQcklxRnEUoRpGIlhiZwLw4CxlOswRaT8dcjr7EqVjlLj/ImsHHcc0ekOEJFJsC9pUq19tRZ/CwpHGk2ramNC1Hu7aolYpqA4cRMuf8KTU5 X-Microsoft-Exchange-Diagnostics: 1;DB6PR0201MB2312;31:BXb357oj1yZuDDSFHPXMuGpSp0qvCctUPqEgotTrq3wXSxes5FbdMlZiN/9xaZ5tItJM/be8BRGxYhp/Ke+ke3cmot4aQ3fJuv2UmsMe02kc9HXboAM1rbsuu9b8axeiJK2kyQ1xaHqYfrbSWqlJ3nSyOzvZVXTttlsIuSDxttzGX8+QFbzBR6r70EiRTPVDavhEU8YBdJML+MBfNG1RKMp0gQdYvOU8MMog3bfyKy4mW/wfrFgNZJ46gHrsd0W+;4:UeTiUg3mtOClHDQLwCDX/Y9SwYgeD03uuMrKfPRyHCiG50wWRaD6n1NY97SiL4qiTqyVMWiKXIj/TRWR2HplUtJjaIgALW/aksrvL3Q2K+sc/blcY1B0E76Pei1WCqmSilWAb7q8z/P8+eksdYwLZGwz/5+0iftF4vqYp+p308PJZkWqMVDBOFHTUyzHN5ZtkBfk5k7bCIuMchGRFVRWZVwzT+p7+v1BjoBov62YkoyVGBasp6iVHN3vOYMCb4VnqSX2vxQDackf+oO7zMEtJw3rJJepCcGmZUaosYZLiUyvPxlFdusy3wh6lespSx06uLoSoGx7Y7woD20zMoWu3xzHWet2T7UfcqgFsuPdRNMTKbZ2lX5OJutpM7P0L0Pg1ueSTgW3psZjmHlXVyOs+CjebMLWFkvyOfaZcuTbZZP9LWF2ilNO4QlLh2iBrixXj+vHoMpVD1sc+Q/rjLRB1T1UKr7VEBvyTvWUrYFpBlk= 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)(5005006)(8121501046)(10201501046)(3002001)(6043046)(6042046);SRVR:DB6PR0201MB2312;BCL:0;PCL:0;RULEID:;SRVR:DB6PR0201MB2312; X-Forefront-PRVS: 0104247462 X-Forefront-Antispam-Report: SFV:NSPM;SFS:(10019020)(4630300001)(6009001)(6069001)(7916002)(52314003)(189002)(199003)(50944005)(5660300001)(68736007)(4326007)(97736004)(50226002)(5003940100001)(2906002)(77096005)(81156014)(92566002)(81166006)(8676002)(101416001)(6116002)(3846002)(586003)(86362001)(575784001)(189998001)(19580405001)(19580395003)(66066001)(47776003)(6916009)(2950100002)(6666003)(7846002)(8666005)(7736002)(33646002)(76176999)(50986999)(110136003)(48376002)(305945005)(50466002)(36756003)(74482002)(229853001)(2351001)(42186005)(106356001)(105586002)(7059030)(2004002)(42262002);DIR:OUT;SFP:1102;SCL:1;SRVR:DB6PR0201MB2312;H:localhost.localdomain;FPR:;SPF:None;PTR:InfoNoRecords;MX:1;A:1;LANG:en; X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1;DB6PR0201MB2312;23:pQjcIpHYDkr9XHimAuAP+gfoFwo44XebjgDGhwf?= =?us-ascii?Q?4NUhBlbXu/xeEd+lJ1PkhTANHI781Zh2XHvXj4k3v3onnhMVPdgo7QXCKXkI?= =?us-ascii?Q?xxz+j+TC5AwHwtO4I3MMqcejxfE0Y860W0muGd2JlIzOouK93AAJtsKSv7ho?= =?us-ascii?Q?B2tA1wQhMVMn6Es6kQTUU+9Rhtf+rvMvl4k3BL+aTfEn3Y9L8Yi7aHy0Ttkn?= =?us-ascii?Q?3bC2oSDz4z0asKx25rukqjJuU1TV9zpaI6vQvr1S9AnhdcqIQSyFv8+vvjr6?= =?us-ascii?Q?vhvhnxFUQLK0cFFEeVMtOrDlF8m7eCGGgTKx6MQyc0p8CkXCcV+mfzC3BJOm?= =?us-ascii?Q?ciTL3DUcsybhQXICAiDa1HguO9/J+Pb+UsWj7FEdxz5odpmo/9qiohadZsGC?= =?us-ascii?Q?5RYduRlb/7f2Mg3sExpU0lCQ9Usr5PplNAG1tf4mDqSoO/e0G1mzpd4mw3iW?= =?us-ascii?Q?PyRacUtF+ye3w4fFesihTI8VpNEz+kPXUePrw050HcEaKiqkfBtn6qjq5i8x?= =?us-ascii?Q?W2hPuk5yYBY1Sk9rtVl+bgRP7/7XZfjex16rSdBN/5TEI6iyYfGPurj1GWdL?= =?us-ascii?Q?QcKujEocALE3OoN5ldwroxWZu4YrHMJ6AERI29M/DlHoc/GYKRd5p7STFPIw?= =?us-ascii?Q?MaXXQU+5T0aMrCYwmIFJO7M8EVz+jPjdnw2murMAFoGOWwXgmRjhAlalbtUn?= =?us-ascii?Q?Cs7dAKWtLvMjMAEVibSOaGmj7DAyH/Joh48ZPk822UllfGUGw2Fw65wiVEdM?= =?us-ascii?Q?eFpqpi4WfWaqdqhFftFzgl6Hp4o2cA+2dbn7iZZUx/e3MyHxWvrtDgmWTe0m?= =?us-ascii?Q?QOzcNp38iFsF0vO2PSD3rgSQaVHwV8U5cBIsYRYWNxxyeshWof+V5pC7dH/R?= =?us-ascii?Q?+JR9NT9xmbLIux+VwZtIsm29NDxvI0U6QIHfAUFnUnwrhFSMhLPUTtix4kCh?= =?us-ascii?Q?bPFu3uLdLo81Bj9SAMHfjDtox1sTs/WHq6pjl95F/UzM+P3JhQ5YJzWPZpo5?= =?us-ascii?Q?Lov7DTBcV8Xsr8tJJrLULLUxVUCbRED6pFJN7tDqxlUQfGadgwxQxttXp43i?= =?us-ascii?Q?skqCQUYI06QU+TkvsJT8zt97mH65stokxa4q7sj/0MKkCMDAsZNwglvj7Vou?= =?us-ascii?Q?uQsvMQnT3ppERatBSiJa2g6RB8rW+SEKJ/L9ovtRKMx/IpR36XhSWzh8uk43?= =?us-ascii?Q?Ykjg0gQVlwGNyjntksZPX+w7J+ksEjlJQbFb6oibGV+hVfFGwo3Jr6xQOyEd?= =?us-ascii?Q?klvN1bYX4Z7VfjNT3N0Yqgdx5MGAQipntqC4m9WYL?= X-Microsoft-Exchange-Diagnostics: 1;DB6PR0201MB2312;6:3vYp/jt6/sqn4YY1hbld7mbjqaKnxGmMYkjVaTuHk7BBqxcnQh30vJ1R4Z6ATTg433Sn5Rp5XJacLPN2c9X/q2ZSpuDFeYTyD0CPNDkel9xK1qTFwiK8S0zKAYockVy9EADYX3MYa3OxZz1CJ7rQdNSYW00YJ52l5zU9mBA/SryKQWfdTLEo9FT/27DTRGrWYHhO6CtUEQwJxN2Z2YiTkgdIjngRYcmrrMmccbzzTSdTPaHdzpKIkIYe0a8+Xt9PLOTPkhhZHKbsLqys0XO21zspoHr81ynzHZ88igFjNbVn6+MfJkO59rqiXo7zsZ2w3pp2Gb43bRZ3fSDa7LvImg==;5:9BIFE2H9OrnQ5FZF1i+524S3RRd460UH4zEnRBSkp2QlKgjs9x96Cc0Q6dX3LJi8JSO33gtD1srNwnjVFNcglHSbhQrF8eV6tDhfmgpVUa/DGuXeYCDYWMMpgoXoEngWq3ZxwjvOQwzZfG/hPY5z/Igj13XqxryfZSUntTWssyE=;24:sVIxTrsAcDxmBh4KY7HodQC4kMlbZQWX7vew5VfkkbfVS0qpLs3YkTT5C82JELLMB6GFz/x8iFezCysY3U5uNbsFwwErUJ7aoOtnWEmSsgE= SpamDiagnosticOutput: 1:99 SpamDiagnosticMetadata: NSPM X-Microsoft-Exchange-Diagnostics: 1;DB6PR0201MB2312;7:gusXqarXeWYQ03xWEexz/vOZoY9YzQsD/hhjEsmQpgiG86dSQNIPQSQi5dqARA0ANFg9KC71E6mSHz30a/v/A3KiSxGgpot+unu/985VqQwDfP/Nzj2Edwt6xoAGy8Z7SqB6TUqAY42tOwrxY5tcw2+pCjI3SYki6RIcAv9rzOdUNInxjSlQDlD6OZ8mV4KAC3iOp/Sl6iMcXo1O2xLKkboFWo+lqcp1PMkwZXyreBr3OuN7NAcZcDxKoYMMzParsgIZPlQ30qp74rbriLy5OCc3cphb5Ws42T5WFi0RP5trlIFiiguXgnEwAHjYvmuAUPxSiHslWwjVIx0lnWyIcjVfXgn/1Y59/fhmYMAKeoY= X-OriginatorOrg: axentia.se X-MS-Exchange-CrossTenant-OriginalArrivalTime: 23 Oct 2016 22:41:16.9660 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-Transport-CrossTenantHeadersStamped: DB6PR0201MB2312 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 16543 Lines: 547 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 | 422 +++++++++++++++++++++ 5 files changed, 471 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..2071f9bcfaa5 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector @@ -0,0 +1,36 @@ +What: /sys/bus/iio/devices/iio:deviceX/in_altvoltageY_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/in_altvoltageY_compare_interval +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 fca35d16037d..0cf3549e05e7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6123,7 +6123,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..fef15c0d7c9c --- /dev/null +++ b/drivers/iio/adc/envelope-detector.c @@ -0,0 +1,422 @@ +/* + * 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 ssize_t envelope_show_invert(struct iio_dev *indio_dev, + uintptr_t private, + struct iio_chan_spec const *ch, char *buf) +{ + struct envelope *env = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", env->invert); +} + +static ssize_t envelope_store_invert(struct iio_dev *indio_dev, + uintptr_t private, + struct iio_chan_spec const *ch, + const char *buf, size_t len) +{ + 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 ssize_t envelope_show_comp_interval(struct iio_dev *indio_dev, + uintptr_t private, + struct iio_chan_spec const *ch, + char *buf) +{ + struct envelope *env = iio_priv(indio_dev); + + return sprintf(buf, "%u\n", env->comp_interval); +} + +static ssize_t envelope_store_comp_interval(struct iio_dev *indio_dev, + uintptr_t private, + struct iio_chan_spec const *ch, + const char *buf, size_t len) +{ + 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 const struct iio_chan_spec_ext_info envelope_detector_ext_info[] = { + { .name = "invert", + .read = envelope_show_invert, + .write = envelope_store_invert, }, + { .name = "compare_interval", + .read = envelope_show_comp_interval, + .write = envelope_store_comp_interval, }, + { /* sentinel */ } +}; + +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), + .ext_info = envelope_detector_ext_info, + .indexed = 1, +}; + +static const struct iio_info envelope_detector_info = { + .read_raw = &envelope_detector_read_raw, + .driver_module = THIS_MODULE, +}; + +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 = iio_read_max_channel_raw(env->dac, &env->dac_max); + if (ret < 0) { + dev_err(dev, "dac does not indicate its raw maximum value\n"); + return 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