From patchwork Fri Mar 10 11:10:00 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Peter Rosin X-Patchwork-Id: 737359 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3vfl450Nfwz9s7k for ; Fri, 10 Mar 2017 22:13:09 +1100 (AEDT) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=axentiatech.onmicrosoft.com header.i=@axentiatech.onmicrosoft.com header.b="IoIHk7tf"; dkim-atps=neutral Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S934636AbdCJLLl (ORCPT ); Fri, 10 Mar 2017 06:11:41 -0500 Received: from mail-db5eur01on0093.outbound.protection.outlook.com ([104.47.2.93]:48197 "EHLO EUR01-DB5-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S934530AbdCJLLb (ORCPT ); Fri, 10 Mar 2017 06:11:31 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=axentiatech.onmicrosoft.com; s=selector1-axentia-se; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version; bh=Sh6K2v2Uhlz6NoJLWWN5x9jrLr62tlmCnTqEsMjE/d4=; b=IoIHk7tfnP1+MChy2c8aYlC1PrgLB936A0imHo9J4Viqe3VdnHW1bDjS/q6yulYyyVOKZz1fw8lSDr4Bda8jD9c7MqvYzMNNMSs7DxGOswRmTS/oVx79HDGjkGt+iB2ILkSxfeHinlVCIUAsO4/LOMgLucuKDko7jSqvoL0gs/s= Authentication-Results: vger.kernel.org; dkim=none (message not signed) header.d=none; vger.kernel.org; dmarc=none action=none header.from=axentia.se; Received: from localhost.localdomain (81.224.171.159) by VI1PR0202MB2560.eurprd02.prod.outlook.com (10.173.79.139) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P384) id 15.1.947.12; Fri, 10 Mar 2017 11:10:23 +0000 From: Peter Rosin To: CC: Peter Rosin , Wolfram Sang , Rob Herring , Mark Rutland , Jonathan Cameron , Hartmut Knaack , Lars-Peter Clausen , Peter Meerwald-Stadler , Jonathan Corbet , , , , , Andrew Morton , Colin Ian King , Paul Gortmaker Subject: [PATCH v10 06/11] iio: multiplexer: new iio category and iio-mux driver Date: Fri, 10 Mar 2017 12:10:00 +0100 Message-ID: <1489144205-17427-7-git-send-email-peda@axentia.se> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1489144205-17427-1-git-send-email-peda@axentia.se> References: <1489144205-17427-1-git-send-email-peda@axentia.se> MIME-Version: 1.0 X-Originating-IP: [81.224.171.159] X-ClientProxiedBy: HE1PR02CA0050.eurprd02.prod.outlook.com (10.163.170.18) To VI1PR0202MB2560.eurprd02.prod.outlook.com (10.173.79.139) X-MS-Office365-Filtering-Correlation-Id: f7bed552-371e-494b-4e0f-08d467a60dd3 X-Microsoft-Antispam: UriScan:; BCL:0; PCL:0; RULEID:(22001); SRVR:VI1PR0202MB2560; X-Microsoft-Exchange-Diagnostics: 1; VI1PR0202MB2560; 3:O5sv0ydxpphhEgEKHJQ0yShX3y5tc8OPCSYgr6e6qVwOD6OzBf/jN8Tv34K8CaKBKIJZ73z4WdeCu8IfcPAG49PqSQXmsUhqDvnVEva7IBoKoVgI0UOAMTVFRvx9lbG7gga9QxEa9kdJxfJWcFIVcRWD9wS+8wV6oNS6Lvakb5swfsNf4Lt/hHJBO4dbtMLo3cudGM1g94xIK3pkZBRSDtDSqLeqmXisTt+YYruGIifXrlnD1csD7Je7K+LwNKppTyFOVbr+1bUGIGBXzvvTRA==; 25:jeupQ3KphUXbEKY7os9AOzrRiCw3c8xyyx5JgCx6NQztXloEZBR3CBv869n9NXloOJJiWENlT74aw3t2SL+9gs/mhL4GLohJ/qsmbJd+NgpYec9ef3FiCEJnii5geZ4Bxva8sjKxNgvQOlJxAvhoJQ8/e5ZPbGlkO9DNSudKbyIb67AalNXe77ogobwdbF8H+pUp5zxfC6Gf2z+SOwn844hq2FU/JsCzxVjtrznwL2PduLP/TKX+kCaY+AulyWC/BTGmm0LKqEDPdUuARQusrCNnJNi647DKVIg5ksjmxtu3/pLUu06xOX79t7qvzaJy+przAP7ffQBDdsX2C0wyo/FoLd9L5KIQwrhFEDJL1LcZTjmPGjnWaQMoLfIswtWrqus8DTniVxwsfCjq1/AYBYUfeYZWoZWnunY8yVJ20RcwL6M/ptFEJG3b/A/9yE3kXwq1CpaKVC5I5zENPheyHA== X-Microsoft-Exchange-Diagnostics: 1; VI1PR0202MB2560; 31:o4wwkzQTqPAgNMxmv0znz12ElRK+sU6QWVH4s79doCVOk7AyyAF/cCQWQbIkhf1X8H9heWucK4Q9KN1jzYpC5/nuhIPwUAXZ37gu5KANQE5Sr+LiuCTCkoBh0kWc++vYCbuq5wQVIXttlB9WPA8gWSLKuJj014mX6cg51ZNWzVk9lEM1ux1Qh8pFt31oSsBe/zSyC8WwpEwBVA4alIUGiyszrc8dlWSZBIx19ikXqCFHMsAp6oNwfoyyz+CFMjjK X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:(9452136761055); X-Exchange-Antispam-Report-CFA-Test: BCL:0; PCL:0; RULEID:(6040375)(601004)(2401047)(5005006)(8121501046)(3002001)(10201501046)(6041248)(2016111802025)(20161123560025)(20161123558025)(20161123555025)(20161123564025)(20161123562025)(6043046)(6072148); SRVR:VI1PR0202MB2560; BCL:0; PCL:0; RULEID:; SRVR:VI1PR0202MB2560; X-Microsoft-Exchange-Diagnostics: 1; VI1PR0202MB2560; 4:k4kzofJN6LspjpBeEPnI7+3uDr+KHcPUV3Km8Xs2qmtI/ZtTpxutHDvQiwFmq2lLfKdEXjS/yQrulADDz5LjxGCWXCsyHAgoW5e8ZQMPjeHv+cWVKO2ojl0ScsUcE5yiwr1FWXnuaCnYFpEqGngjk14GyCL0Slaj8AaTFTCQMxkHqFIzhafo2G0FBpRyd8RJabTT9wheymb/IdgMSZj99j9//1iM51ex2DQEB/1L5dETZY8Uc08o/Rs8/cqgUI2H+JGVvGEgDmZGNSIQhbAxhiw0s4hBB4Q4aztanhysNZgmshHbdYw30s56z85Tm3goVmDhaYkw+4KimkDF+eFLavwCCa5mM2ygkQ4YWBdh3VmceTrCS+sDBBiV5y+fs0YSpu2DbSGJKuI07d71gBmEcN6hhH/cCQEzmf2r2izfziv/49bifjt14rXbV/LaK+Q/eORGecddJO1V5BFYMwh8BAU7K0Dhvnzwstg16wV3UkARBT2BSReeTH2ptLvXhb+rX7X2qmGbNrjkPfmKb+pTIrTxVdVjpT3YMK0ELm+wMLG10Xszzspj8KAcKAR3JNUjdEwQ23lvDTP+4ls3XwIg5fT50Jy/G8nf/26GDFNb8taCIydHs5oJE8DCl9jKHIrAIyCFNNpXvlz+T1R6sim471UWsaeYd8PekZPfORmMEMaqitGLwZ0nZgcgkNenAujyUTjxTIvtw3TvnapDRx2EWQ== X-Forefront-PRVS: 02426D11FE X-Forefront-Antispam-Report: SFV:NSPM; SFS:(10019020)(4630300001)(6009001)(39830400002)(39450400003)(39410400002)(5660300001)(6506006)(8666007)(6486002)(25786008)(42186005)(2351001)(2950100002)(6512007)(2906002)(6666003)(38730400002)(6916009)(7736002)(50466002)(110136004)(54906002)(50226002)(4326008)(575784001)(86362001)(48376002)(53936002)(66066001)(47776003)(74482002)(81166006)(50986999)(76176999)(5003940100001)(189998001)(305945005)(8676002)(3846002)(36756003)(6116002)(33646002)(42262002); DIR:OUT; SFP:1102; SCL:1; SRVR:VI1PR0202MB2560; H:localhost.localdomain; FPR:; SPF:None; MLV:sfv; LANG:en; X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1; VI1PR0202MB2560; 23:yzC4V1GXiu8a3+4yXr+122gnPEBYYCI5PPdDMbw?= =?us-ascii?Q?nD2wPv5jEkmqHdz7a23gwzapMxpwlv9Ym10LHT3G73X3AEjuYhgP83MF5+61?= =?us-ascii?Q?ZA9SVQFOoIo4BroLB7hrUJUmw/0PxFSfNo9XKK4OLFYSm/Jy5HUd8SeOYpqK?= =?us-ascii?Q?L8Cu63Ali8Em7pmJhi9lRMFFKwNZgqMw2MFPH7ignQ3tWlogF4EHv7i+TNM9?= =?us-ascii?Q?db0w30mgFxM104LyczGMQORN+HCkF9/DZjtMJD0wZTAxCMkYtsNfixR8ctAr?= =?us-ascii?Q?7N+WgjtnXUptQX0qMsi9LZVl8SrJ32mc+wIwkDgQiy903hE04MEm0Miisymf?= =?us-ascii?Q?hNI6+Of7xIu7HOCd1iLbDzrtNaE6NAnZgZI7aK4E0DPEaN04H8IO2satbN+8?= =?us-ascii?Q?0lpGmlh3Rr96zfL2YKoGm/ZD8Yv83+Q72R7SHNEQdRjsYN1GOQknQ8X27Vgz?= =?us-ascii?Q?U8uGaVxQgX0nzXBsPnmRrSR39J2ZoRruQsODp6wrbiH4gCv11QqC9i8y8wJl?= =?us-ascii?Q?5EjFUDsIl3kif5nva8AJLb0V1BY4mTvLiEf/pTTg6uRlb0p0+G+MIQZCcV/S?= =?us-ascii?Q?3/K141g+i6zoi6j9ve4zBVcX8UuDPhJOZ9jbnetXkCwbfUgnKJn1XF1xb8g2?= =?us-ascii?Q?/BlHo4U7oAeMYRlXrOUQOvI8ImSEd2bzEAqYLjbwVyvI9fPu3IJNaDf+7edb?= =?us-ascii?Q?PryXhXBpnnICwNuYE0xVBaDW9I6WTHOJ8e54+BhO5CwpRdfIEW+VJn8P1FIz?= =?us-ascii?Q?gHVmbLoqObCE/T+e6h2tuHoD/kuL3V2fy2UGlhfZL5u1Fb40q14SGd9xYKF7?= =?us-ascii?Q?r76LRmpsIHXhsdPPSsjHsBVSGC7YNwq87rU0F1c7wlgWpRljkDkS8Bw9fKy5?= =?us-ascii?Q?5lAbvqOA3/xCKGeexFFxqmJNBS66gd5M6GQUFnWU0VyvtJS77HQiaSOkgIed?= =?us-ascii?Q?FmUCUJ12QCUd+lYCfWtSAZpJ3GVySph8x1wtxzQhaMzI8YhR86PkM7j+bofl?= =?us-ascii?Q?QU7Sntof3qG+q3HVkys2awc9pK9LlDFyGCPRUUjnXP0u2D7MR+Iw0r9i0pzw?= =?us-ascii?Q?+RTEp8hGud1j4EUvhz0wx3f1GJdnT?= X-Microsoft-Exchange-Diagnostics: 1; VI1PR0202MB2560; 6:OeegOZkFW9umN3tCzSCyYhm678YWQ7/M4aRhn0vnq7j3yEFAqE70vPLAqzVL0Qf0WN7rjn8nJgIlctKNiPYrFjct54NUEOrqfDpUCYW4vwGLBBOjESr+a2AfwxFnjRsGBpjoMAFynnsTbQgZSNY1CeqKC+wXyUKIK8JC0M1Re7S7BbVGkkGp6JIOtis7tzU6ZJtFfv9x98yHOVO9hOzWx+v4DM4zGQwE0HgfpLbpW5a4qJdoZRIyqIwuJjvPcLwUVSVF7ONIH/smqm3qdy27O1UOXmbrrvFhNo+3czGUITrx58bVlB7gC7Ua7ILu7tXVedQoH9GrENRoF7WQzF4AsnbxRp2x6EteYrnfnevmNBr+P/6ne494VUAyMrXeeZ04MIZQ+RBaGAUeJLgzMq8bGw==; 5:4zR6lh5sfFV6WoBMUhmw7mOs6jJIegNmbffLjomt8jyLx2Zgj1ZHa8RjBHJ1U+yhndJvM5QxqlxkW/DTb2kKNA4VjwvXhoRK9ms+Q4H+Uiix+YaDOQT/3JC2dR3lukmOGp33HbwceiSlR3o9ZXJ4RSS12bGIyIoAFnkF61EWHAg=; 24:TC5IcEUb2LkDweqR3Kv668hjQq6z7y4z59owcjgq3fiPbHhN/Gnjp5zsNQmmH7lNlPrBGOhW6OuAS0FSENpVRGtuVzK5OPexyR+N5IQu62s= SpamDiagnosticOutput: 1:99 SpamDiagnosticMetadata: NSPM X-Microsoft-Exchange-Diagnostics: 1; VI1PR0202MB2560; 7:qwVXK5z10TVPWfqnDvpAXlUx8XKCyLxSjBgKHUtVymubolE9NdjWUxmqKeRZx7+8sY0n2k/AYW9dy1G2FRtDUA2oe8BLGnlxl8gt6ItXYWYOvD5Wy/DRtlsr7yggk69tTOHqMIRlSJsNCS2S1jLg7DQ9WQiG2OIRokHu8voBtYf+/tt5MXM1Qytca+R5z4QhndNx6kUnyfiJCrt9zy3OBdJlU7JUcD1etVqh4T6fgK0bGUQ9T0KXZojbLEhRbdiHKReXdfpwhdcAJKKIvuNKJguMRfO9QRG+LPdEBuZUp9hUa2W1RifEB2K1oYYCM5ruOgYO45JxgiZ1vt4GewHx7w== X-OriginatorOrg: axentia.se X-MS-Exchange-CrossTenant-OriginalArrivalTime: 10 Mar 2017 11:10:23.9641 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-Transport-CrossTenantHeadersStamped: VI1PR0202MB2560 Sender: linux-i2c-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-i2c@vger.kernel.org When a multiplexer changes how an iio device behaves (for example by feeding different signals to an ADC), this driver can be used to create one virtual iio channel for each multiplexer state. Depends on the generic multiplexer subsystem. Cache any ext_info values from the parent iio channel, creating a private copy of the ext_info attributes for each multiplexer state/channel. Reviewed-by: Jonathan Cameron Signed-off-by: Peter Rosin --- MAINTAINERS | 1 + drivers/iio/Kconfig | 1 + drivers/iio/Makefile | 1 + drivers/iio/multiplexer/Kconfig | 18 ++ drivers/iio/multiplexer/Makefile | 6 + drivers/iio/multiplexer/iio-mux.c | 456 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 483 insertions(+) create mode 100644 drivers/iio/multiplexer/Kconfig create mode 100644 drivers/iio/multiplexer/Makefile create mode 100644 drivers/iio/multiplexer/iio-mux.c diff --git a/MAINTAINERS b/MAINTAINERS index 4af912305d2c..23cfd5bc2158 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6365,6 +6365,7 @@ M: Peter Rosin L: linux-iio@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt +F: drivers/iio/multiplexer/iio-mux.c IIO SUBSYSTEM AND DRIVERS M: Jonathan Cameron diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index a918270d6f54..b3c8c6ef0dff 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -83,6 +83,7 @@ source "drivers/iio/humidity/Kconfig" source "drivers/iio/imu/Kconfig" source "drivers/iio/light/Kconfig" source "drivers/iio/magnetometer/Kconfig" +source "drivers/iio/multiplexer/Kconfig" source "drivers/iio/orientation/Kconfig" if IIO_TRIGGER source "drivers/iio/trigger/Kconfig" diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index 33fa4026f92c..93c769cd99bf 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -28,6 +28,7 @@ obj-y += humidity/ obj-y += imu/ obj-y += light/ obj-y += magnetometer/ +obj-y += multiplexer/ obj-y += orientation/ obj-y += potentiometer/ obj-y += potentiostat/ diff --git a/drivers/iio/multiplexer/Kconfig b/drivers/iio/multiplexer/Kconfig new file mode 100644 index 000000000000..70a044510686 --- /dev/null +++ b/drivers/iio/multiplexer/Kconfig @@ -0,0 +1,18 @@ +# +# Multiplexer drivers +# +# When adding new entries keep the list in alphabetical order + +menu "Multiplexers" + +config IIO_MUX + tristate "IIO multiplexer driver" + select MULTIPLEXER + depends on OF + help + Say yes here to build support for the IIO multiplexer. + + To compile this driver as a module, choose M here: the + module will be called iio-mux. + +endmenu diff --git a/drivers/iio/multiplexer/Makefile b/drivers/iio/multiplexer/Makefile new file mode 100644 index 000000000000..68be3c4abd07 --- /dev/null +++ b/drivers/iio/multiplexer/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for industrial I/O multiplexer drivers +# + +# When adding new entries keep the list in alphabetical order +obj-$(CONFIG_IIO_MUX) += iio-mux.o diff --git a/drivers/iio/multiplexer/iio-mux.c b/drivers/iio/multiplexer/iio-mux.c new file mode 100644 index 000000000000..94d40f9bef4c --- /dev/null +++ b/drivers/iio/multiplexer/iio-mux.c @@ -0,0 +1,456 @@ +/* + * IIO multiplexer driver + * + * Copyright (C) 2017 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct mux_ext_info_cache { + char *data; + size_t size; +}; + +struct mux_child { + struct mux_ext_info_cache *ext_info_cache; +}; + +struct mux { + int cached_state; + struct mux_control *control; + struct iio_channel *parent; + struct iio_dev *indio_dev; + struct iio_chan_spec *chan; + struct iio_chan_spec_ext_info *ext_info; + struct mux_child *child; +}; + +static int iio_mux_select(struct mux *mux, int idx) +{ + struct mux_child *child = &mux->child[idx]; + struct iio_chan_spec const *chan = &mux->chan[idx]; + int ret; + int i; + + ret = mux_control_select(mux->control, chan->channel); + if (ret < 0) { + mux->cached_state = -1; + return ret; + } + + if (mux->cached_state == chan->channel) + return 0; + + if (chan->ext_info) { + for (i = 0; chan->ext_info[i].name; ++i) { + const char *attr = chan->ext_info[i].name; + struct mux_ext_info_cache *cache; + + cache = &child->ext_info_cache[i]; + + if (cache->size < 0) + continue; + + ret = iio_write_channel_ext_info(mux->parent, attr, + cache->data, + cache->size); + + if (ret < 0) { + mux_control_deselect(mux->control); + mux->cached_state = -1; + return ret; + } + } + } + mux->cached_state = chan->channel; + + return 0; +} + +static void iio_mux_deselect(struct mux *mux) +{ + mux_control_deselect(mux->control); +} + +static int mux_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mux *mux = iio_priv(indio_dev); + int idx = chan - mux->chan; + int ret; + + ret = iio_mux_select(mux, idx); + if (ret < 0) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iio_read_channel_raw(mux->parent, val); + break; + + case IIO_CHAN_INFO_SCALE: + ret = iio_read_channel_scale(mux->parent, val, val2); + break; + + default: + ret = -EINVAL; + } + + iio_mux_deselect(mux); + + return ret; +} + +static int mux_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + struct mux *mux = iio_priv(indio_dev); + int idx = chan - mux->chan; + int ret; + + ret = iio_mux_select(mux, idx); + if (ret < 0) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *type = IIO_VAL_INT; + ret = iio_read_avail_channel_raw(mux->parent, vals, length); + break; + + default: + ret = -EINVAL; + } + + iio_mux_deselect(mux); + + return ret; +} + +static int mux_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mux *mux = iio_priv(indio_dev); + int idx = chan - mux->chan; + int ret; + + ret = iio_mux_select(mux, idx); + if (ret < 0) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = iio_write_channel_raw(mux->parent, val); + break; + + default: + ret = -EINVAL; + } + + iio_mux_deselect(mux); + + return ret; +} + +static const struct iio_info mux_info = { + .read_raw = mux_read_raw, + .read_avail = mux_read_avail, + .write_raw = mux_write_raw, + .driver_module = THIS_MODULE, +}; + +static ssize_t mux_read_ext_info(struct iio_dev *indio_dev, uintptr_t private, + struct iio_chan_spec const *chan, char *buf) +{ + struct mux *mux = iio_priv(indio_dev); + int idx = chan - mux->chan; + ssize_t ret; + + ret = iio_mux_select(mux, idx); + if (ret < 0) + return ret; + + ret = iio_read_channel_ext_info(mux->parent, + mux->ext_info[private].name, + buf); + + iio_mux_deselect(mux); + + return ret; +} + +static ssize_t mux_write_ext_info(struct iio_dev *indio_dev, uintptr_t private, + struct iio_chan_spec const *chan, + const char *buf, size_t len) +{ + struct device *dev = indio_dev->dev.parent; + struct mux *mux = iio_priv(indio_dev); + int idx = chan - mux->chan; + char *new; + ssize_t ret; + + ret = iio_mux_select(mux, idx); + if (ret < 0) + return ret; + + new = devm_kmemdup(dev, buf, len + 1, GFP_KERNEL); + if (!new) { + iio_mux_deselect(mux); + return -ENOMEM; + } + + new[len] = 0; + + ret = iio_write_channel_ext_info(mux->parent, + mux->ext_info[private].name, + buf, len); + if (ret < 0) { + iio_mux_deselect(mux); + devm_kfree(dev, new); + return ret; + } + + devm_kfree(dev, mux->child[idx].ext_info_cache[private].data); + mux->child[idx].ext_info_cache[private].data = new; + mux->child[idx].ext_info_cache[private].size = len; + + iio_mux_deselect(mux); + + return ret; +} + +static int mux_configure_channel(struct device *dev, struct mux *mux, + u32 state, const char *label, int idx) +{ + struct mux_child *child = &mux->child[idx]; + struct iio_chan_spec *chan = &mux->chan[idx]; + struct iio_chan_spec const *pchan = mux->parent->channel; + char *page = NULL; + int num_ext_info; + int i; + int ret; + + chan->indexed = 1; + chan->output = pchan->output; + chan->datasheet_name = label; + chan->ext_info = mux->ext_info; + + ret = iio_get_channel_type(mux->parent, &chan->type); + if (ret < 0) { + dev_err(dev, "failed to get parent channel type\n"); + return ret; + } + + if (iio_channel_has_info(pchan, IIO_CHAN_INFO_RAW)) + chan->info_mask_separate |= BIT(IIO_CHAN_INFO_RAW); + if (iio_channel_has_info(pchan, IIO_CHAN_INFO_SCALE)) + chan->info_mask_separate |= BIT(IIO_CHAN_INFO_SCALE); + + if (iio_channel_has_available(pchan, IIO_CHAN_INFO_RAW)) + chan->info_mask_separate_available |= BIT(IIO_CHAN_INFO_RAW); + + if (state >= mux->control->states) { + dev_err(dev, "too many channels\n"); + return -EINVAL; + } + + chan->channel = state; + + num_ext_info = iio_get_channel_ext_info_count(mux->parent); + if (num_ext_info) { + page = devm_kzalloc(dev, PAGE_SIZE, GFP_KERNEL); + if (!page) + return -ENOMEM; + } + child->ext_info_cache = devm_kzalloc(dev, + sizeof(*child->ext_info_cache) * + num_ext_info, GFP_KERNEL); + for (i = 0; i < num_ext_info; ++i) { + child->ext_info_cache[i].size = -1; + + if (!pchan->ext_info[i].write) + continue; + if (!pchan->ext_info[i].read) + continue; + + ret = iio_read_channel_ext_info(mux->parent, + mux->ext_info[i].name, + page); + if (ret < 0) { + dev_err(dev, "failed to get ext_info '%s'\n", + pchan->ext_info[i].name); + return ret; + } + if (ret >= PAGE_SIZE) { + dev_err(dev, "too large ext_info '%s'\n", + pchan->ext_info[i].name); + return -EINVAL; + } + + child->ext_info_cache[i].data = devm_kmemdup(dev, page, ret + 1, + GFP_KERNEL); + child->ext_info_cache[i].data[ret] = 0; + child->ext_info_cache[i].size = ret; + } + + if (page) + devm_kfree(dev, page); + + return 0; +} + +/* + * Same as of_property_for_each_string(), but also keeps track of the + * index of each string. + */ +#define of_property_for_each_string_index(np, propname, prop, s, i) \ + for (prop = of_find_property(np, propname, NULL), \ + s = of_prop_next_string(prop, NULL), \ + i = 0; \ + s; \ + s = of_prop_next_string(prop, s), \ + i++) + +static int mux_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; + struct iio_dev *indio_dev; + struct iio_channel *parent; + struct mux *mux; + struct property *prop; + const char *label; + u32 state; + int sizeof_ext_info; + int children; + int sizeof_priv; + int i; + int ret; + + if (!np) + return -ENODEV; + + parent = devm_iio_channel_get(dev, "parent"); + if (IS_ERR(parent)) { + if (PTR_ERR(parent) != -EPROBE_DEFER) + dev_err(dev, "failed to get parent channel\n"); + return PTR_ERR(parent); + } + + sizeof_ext_info = iio_get_channel_ext_info_count(parent); + if (sizeof_ext_info) { + sizeof_ext_info += 1; /* one extra entry for the sentinel */ + sizeof_ext_info *= sizeof(*mux->ext_info); + } + + children = 0; + of_property_for_each_string(np, "channels", prop, label) { + if (*label) + children++; + } + if (children <= 0) { + dev_err(dev, "not even a single child\n"); + return -EINVAL; + } + + sizeof_priv = sizeof(*mux); + sizeof_priv += sizeof(*mux->child) * children; + sizeof_priv += sizeof(*mux->chan) * children; + sizeof_priv += sizeof_ext_info; + + indio_dev = devm_iio_device_alloc(dev, sizeof_priv); + if (!indio_dev) + return -ENOMEM; + + mux = iio_priv(indio_dev); + mux->child = (struct mux_child *)(mux + 1); + mux->chan = (struct iio_chan_spec *)(mux->child + children); + + platform_set_drvdata(pdev, indio_dev); + + mux->parent = parent; + mux->cached_state = -1; + + indio_dev->name = dev_name(dev); + indio_dev->dev.parent = dev; + indio_dev->info = &mux_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mux->chan; + indio_dev->num_channels = children; + if (sizeof_ext_info) { + mux->ext_info = devm_kmemdup(dev, + parent->channel->ext_info, + sizeof_ext_info, GFP_KERNEL); + if (!mux->ext_info) + return -ENOMEM; + + for (i = 0; mux->ext_info[i].name; ++i) { + if (parent->channel->ext_info[i].read) + mux->ext_info[i].read = mux_read_ext_info; + if (parent->channel->ext_info[i].write) + mux->ext_info[i].write = mux_write_ext_info; + mux->ext_info[i].private = i; + } + } + + mux->control = devm_mux_control_get(dev, NULL); + if (IS_ERR(mux->control)) { + if (PTR_ERR(mux->control) != -EPROBE_DEFER) + dev_err(dev, "failed to get control-mux\n"); + return PTR_ERR(mux->control); + } + + i = 0; + of_property_for_each_string_index(np, "channels", prop, label, state) { + if (!*label) + continue; + + ret = mux_configure_channel(dev, mux, state, label, i++); + if (ret < 0) + return ret; + } + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) { + dev_err(dev, "failed to register iio device\n"); + return ret; + } + + return 0; +} + +static const struct of_device_id mux_match[] = { + { .compatible = "io-channel-mux" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mux_match); + +static struct platform_driver mux_driver = { + .probe = mux_probe, + .driver = { + .name = "iio-mux", + .of_match_table = mux_match, + }, +}; +module_platform_driver(mux_driver); + +MODULE_DESCRIPTION("IIO multiplexer driver"); +MODULE_AUTHOR("Peter Rosin "); +MODULE_LICENSE("GPL v2");