diff mbox

[v3,5/7] iio: multiplexer: new iio category and iio-mux driver

Message ID 1479734235-18837-6-git-send-email-peda@axentia.se
State Superseded
Headers show

Commit Message

Peter Rosin Nov. 21, 2016, 1:17 p.m. UTC
When a multiplexer changes how an iio device behaves (for example
by feeding different signals to an ADC), this driver can be used
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.

Signed-off-by: Peter Rosin <peda@axentia.se>
---
 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 | 450 ++++++++++++++++++++++++++++++++++++++
 6 files changed, 477 insertions(+)
 create mode 100644 drivers/iio/multiplexer/Kconfig
 create mode 100644 drivers/iio/multiplexer/Makefile
 create mode 100644 drivers/iio/multiplexer/iio-mux.c

Comments

kernel test robot Nov. 21, 2016, 2:33 p.m. UTC | #1
Hi Peter,

[auto build test ERROR on linus/master]
[also build test ERROR on v4.9-rc6]
[cannot apply to next-20161117]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]

url:    https://github.com/0day-ci/linux/commits/Peter-Rosin/mux-controller-abstraction-and-iio-i2c-muxes/20161121-215311
config: x86_64-allmodconfig (attached as .config)
compiler: gcc-6 (Debian 6.2.0-3) 6.2.0 20160901
reproduce:
        # save the attached .config to linux build tree
        make ARCH=x86_64 

All errors (new ones prefixed by >>):

   drivers/iio/multiplexer/iio-mux.c: In function 'mux_read_avail':
>> drivers/iio/multiplexer/iio-mux.c:134:9: error: implicit declaration of function 'iio_read_avail_channel_raw' [-Werror=implicit-function-declaration]
      ret = iio_read_avail_channel_raw(mux->parent, vals, length);
            ^~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/iio/multiplexer/iio-mux.c: At top level:
>> drivers/iio/multiplexer/iio-mux.c:174:2: error: unknown field 'read_avail' specified in initializer
     .read_avail = mux_read_avail,
     ^
>> drivers/iio/multiplexer/iio-mux.c:174:16: error: initialization from incompatible pointer type [-Werror=incompatible-pointer-types]
     .read_avail = mux_read_avail,
                   ^~~~~~~~~~~~~~
   drivers/iio/multiplexer/iio-mux.c:174:16: note: (near initialization for 'mux_info.read_raw_multi')
   drivers/iio/multiplexer/iio-mux.c: In function 'mux_configure_channel':
>> drivers/iio/multiplexer/iio-mux.c:267:6: error: implicit declaration of function 'iio_channel_has_available' [-Werror=implicit-function-declaration]
     if (iio_channel_has_available(pchan, IIO_CHAN_INFO_RAW))
         ^~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/iio/multiplexer/iio-mux.c:268:7: error: 'struct iio_chan_spec' has no member named 'info_mask_separate_available'; did you mean 'info_mask_separate'?
      chan->info_mask_separate_available |= BIT(IIO_CHAN_INFO_RAW);
          ^~
   cc1: some warnings being treated as errors

vim +/iio_read_avail_channel_raw +134 drivers/iio/multiplexer/iio-mux.c

   128		if (ret < 0)
   129			return ret;
   130	
   131		switch (mask) {
   132		case IIO_CHAN_INFO_RAW:
   133			*type = IIO_VAL_INT;
 > 134			ret = iio_read_avail_channel_raw(mux->parent, vals, length);
   135			break;
   136	
   137		default:
   138			ret = -EINVAL;
   139		}
   140	
   141		iio_mux_deselect(mux);
   142	
   143		return ret;
   144	}
   145	
   146	static int mux_write_raw(struct iio_dev *indio_dev,
   147				 struct iio_chan_spec const *chan,
   148				 int val, int val2, long mask)
   149	{
   150		struct mux *mux = iio_priv(indio_dev);
   151		int idx = chan - mux->chan;
   152		int ret;
   153	
   154		ret = iio_mux_select(mux, idx);
   155		if (ret < 0)
   156			return ret;
   157	
   158		switch (mask) {
   159		case IIO_CHAN_INFO_RAW:
   160			ret = iio_write_channel_raw(mux->parent, val);
   161			break;
   162	
   163		default:
   164			ret = -EINVAL;
   165		}
   166	
   167		iio_mux_deselect(mux);
   168	
   169		return ret;
   170	}
   171	
   172	static const struct iio_info mux_info = {
   173		.read_raw = mux_read_raw,
 > 174		.read_avail = mux_read_avail,
   175		.write_raw = mux_write_raw,
   176		.driver_module = THIS_MODULE,
   177	};
   178	
   179	static ssize_t mux_read_ext_info(struct iio_dev *indio_dev, uintptr_t private,
   180					 struct iio_chan_spec const *chan, char *buf)
   181	{
   182		struct mux *mux = iio_priv(indio_dev);
   183		int idx = chan - mux->chan;
   184		ssize_t ret;
   185	
   186		ret = iio_mux_select(mux, idx);
   187		if (ret < 0)
   188			return ret;
   189	
   190		ret = iio_read_channel_ext_info(mux->parent,
   191						mux->ext_info[private].name,
   192						buf);
   193	
   194		iio_mux_deselect(mux);
   195	
   196		return ret;
   197	}
   198	
   199	static ssize_t mux_write_ext_info(struct iio_dev *indio_dev, uintptr_t private,
   200					  struct iio_chan_spec const *chan,
   201					  const char *buf, size_t len)
   202	{
   203		struct device *dev = indio_dev->dev.parent;
   204		struct mux *mux = iio_priv(indio_dev);
   205		int idx = chan - mux->chan;
   206		char *new;
   207		ssize_t ret;
   208	
   209		ret = iio_mux_select(mux, idx);
   210		if (ret < 0)
   211			return ret;
   212	
   213		new = devm_kmemdup(dev, buf, len + 1, GFP_KERNEL);
   214		if (!new) {
   215			iio_mux_deselect(mux);
   216			return -ENOMEM;
   217		}
   218	
   219		new[len] = 0;
   220	
   221		ret = iio_write_channel_ext_info(mux->parent,
   222						 mux->ext_info[private].name,
   223						 buf, len);
   224		if (ret < 0) {
   225			iio_mux_deselect(mux);
   226			devm_kfree(dev, new);
   227			return ret;
   228		}
   229	
   230		devm_kfree(dev, mux->child[idx].ext_info_cache[private].data);
   231		mux->child[idx].ext_info_cache[private].data = new;
   232		mux->child[idx].ext_info_cache[private].size = len;
   233	
   234		iio_mux_deselect(mux);
   235	
   236		return ret;
   237	}
   238	
   239	static int mux_configure_channel(struct device *dev, struct mux *mux,
   240					 struct device_node *child_np, int idx)
   241	{
   242		struct mux_child *child = &mux->child[idx];
   243		struct iio_chan_spec *chan = &mux->chan[idx];
   244		struct iio_chan_spec const *pchan = mux->parent->channel;
   245		u32 state;
   246		char *page = NULL;
   247		int num_ext_info;
   248		int i;
   249		int ret;
   250	
   251		chan->indexed = 1;
   252		chan->output = pchan->output;
   253		chan->datasheet_name = child_np->name;
   254		chan->ext_info = mux->ext_info;
   255	
   256		ret = iio_get_channel_type(mux->parent, &chan->type);
   257		if (ret < 0) {
   258			dev_err(dev, "failed to get parent channel type\n");
   259			return ret;
   260		}
   261	
   262		if (iio_channel_has_info(pchan, IIO_CHAN_INFO_RAW))
   263			chan->info_mask_separate |= BIT(IIO_CHAN_INFO_RAW);
   264		if (iio_channel_has_info(pchan, IIO_CHAN_INFO_SCALE))
   265			chan->info_mask_separate |= BIT(IIO_CHAN_INFO_SCALE);
   266	
 > 267		if (iio_channel_has_available(pchan, IIO_CHAN_INFO_RAW))
 > 268			chan->info_mask_separate_available |= BIT(IIO_CHAN_INFO_RAW);
   269	
   270		ret = of_property_read_u32(child_np, "reg", &state);
   271		if (ret < 0) {

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
diff mbox

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 3fc0dbbc04ea..02d9072aae16 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6129,6 +6129,7 @@  M:	Peter Rosin <peda@axentia.se>
 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 <jic23@kernel.org>
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index 6743b18194fb..dcb541d0d70e 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -82,6 +82,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 87e4c4369e2f..f9879c29cf6f 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -27,6 +27,7 @@  obj-y += humidity/
 obj-y += imu/
 obj-y += light/
 obj-y += magnetometer/
+obj-y += multiplexer/
 obj-y += orientation/
 obj-y += potentiometer/
 obj-y += pressure/
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..0b9d04914241
--- /dev/null
+++ b/drivers/iio/multiplexer/iio-mux.c
@@ -0,0 +1,450 @@ 
+/*
+ * IIO multiplexer driver
+ *
+ * Copyright (C) 2016 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda@axentia.se>
+ *
+ * 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 <linux/err.h>
+#include <linux/iio/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mux.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+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,
+				 struct device_node *child_np, 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;
+	u32 state;
+	char *page = NULL;
+	int num_ext_info;
+	int i;
+	int ret;
+
+	chan->indexed = 1;
+	chan->output = pchan->output;
+	chan->datasheet_name = child_np->name;
+	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);
+
+	ret = of_property_read_u32(child_np, "reg", &state);
+	if (ret < 0) {
+		dev_err(dev, "no reg property for node '%s'\n", child_np->name);
+		return ret;
+	}
+
+	if (state >= mux->control->states) {
+		dev_err(dev, "invalid reg %u\n", state);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < idx; ++i) {
+		if (mux->chan[i].channel == state) {
+			dev_err(dev, "double use of reg %u\n", state);
+			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;
+}
+
+static int mux_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *child_np;
+	struct iio_dev *indio_dev;
+	struct iio_channel *parent;
+	struct mux *mux;
+	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 = of_get_child_count(np);
+	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);
+	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;
+	for_each_child_of_node(np, child_np) {
+		ret = mux_configure_channel(dev, mux, child_np, i);
+		if (ret < 0)
+			return ret;
+		i++;
+	}
+
+	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 = "iio-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 <peda@axentia.se>");
+MODULE_LICENSE("GPL v2");