* [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller
[not found] ` <1492101794-13444-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
@ 2017-04-13 16:43 ` Peter Rosin
[not found] ` <1492101794-13444-4-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
` (3 more replies)
2017-04-13 16:43 ` [PATCH v13 05/10] dt-bindings: iio: io-channel-mux: document io-channel-mux bindings Peter Rosin
` (3 subsequent siblings)
4 siblings, 4 replies; 38+ messages in thread
From: Peter Rosin @ 2017-04-13 16:43 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA, Greg Kroah-Hartman
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel-bIcnvbaLZ9MEGnE8C9+IrQ
Add a new minimalistic subsystem that handles multiplexer controllers.
When multiplexers are used in various places in the kernel, and the
same multiplexer controller can be used for several independent things,
there should be one place to implement support for said multiplexer
controller.
A single multiplexer controller can also be used to control several
parallel multiplexers, that are in turn used by different subsystems
in the kernel, leading to a need to coordinate multiplexer accesses.
The multiplexer subsystem handles this coordination.
This new mux controller subsystem initially comes with a single backend
driver that controls gpio based multiplexers. Even though not needed by
this initial driver, the mux controller subsystem is prepared to handle
chips with multiple (independent) mux controllers.
Reviewed-by: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Signed-off-by: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
Documentation/driver-model/devres.txt | 8 +
MAINTAINERS | 2 +
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/mux/Kconfig | 34 +++
drivers/mux/Makefile | 6 +
drivers/mux/mux-core.c | 422 ++++++++++++++++++++++++++++++++++
drivers/mux/mux-gpio.c | 114 +++++++++
include/linux/mux.h | 252 ++++++++++++++++++++
9 files changed, 841 insertions(+)
create mode 100644 drivers/mux/Kconfig
create mode 100644 drivers/mux/Makefile
create mode 100644 drivers/mux/mux-core.c
create mode 100644 drivers/mux/mux-gpio.c
create mode 100644 include/linux/mux.h
diff --git a/Documentation/driver-model/devres.txt b/Documentation/driver-model/devres.txt
index efb8200819d6..e2343d9cbec7 100644
--- a/Documentation/driver-model/devres.txt
+++ b/Documentation/driver-model/devres.txt
@@ -337,6 +337,14 @@ MEM
MFD
devm_mfd_add_devices()
+MUX
+ devm_mux_chip_alloc()
+ devm_mux_chip_free()
+ devm_mux_chip_register()
+ devm_mux_chip_unregister()
+ devm_mux_control_get()
+ devm_mux_control_put()
+
PER-CPU MEM
devm_alloc_percpu()
devm_free_percpu()
diff --git a/MAINTAINERS b/MAINTAINERS
index 7fc06739c8ad..591eba737678 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8563,6 +8563,8 @@ M: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
S: Maintained
F: Documentation/devicetree/bindings/mux/
F: include/linux/dt-bindings/mux/
+F: include/linux/mux.h
+F: drivers/mux/
MULTISOUND SOUND DRIVER
M: Andrew Veliath <andrewtv-Jdbf3xiKgS8@public.gmane.org>
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 117ca14ccf85..a7ea13e1b869 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -204,4 +204,6 @@ source "drivers/fpga/Kconfig"
source "drivers/fsi/Kconfig"
+source "drivers/mux/Kconfig"
+
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 2eced9afba53..c0436f6dd5a9 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -177,3 +177,4 @@ obj-$(CONFIG_ANDROID) += android/
obj-$(CONFIG_NVMEM) += nvmem/
obj-$(CONFIG_FPGA) += fpga/
obj-$(CONFIG_FSI) += fsi/
+obj-$(CONFIG_MULTIPLEXER) += mux/
diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig
new file mode 100644
index 000000000000..41dfe08ead84
--- /dev/null
+++ b/drivers/mux/Kconfig
@@ -0,0 +1,34 @@
+#
+# Multiplexer devices
+#
+
+menuconfig MULTIPLEXER
+ tristate "Multiplexer subsystem"
+ help
+ Multiplexer controller subsystem. Multiplexers are used in a
+ variety of settings, and this subsystem abstracts their use
+ so that the rest of the kernel sees a common interface. When
+ multiple parallel multiplexers are controlled by one single
+ multiplexer controller, this subsystem also coordinates the
+ multiplexer accesses.
+
+ To compile the subsystem as a module, choose M here: the module will
+ be called mux-core.
+
+if MULTIPLEXER
+
+config MUX_GPIO
+ tristate "GPIO-controlled Multiplexer"
+ depends on OF && GPIOLIB
+ help
+ GPIO-controlled Multiplexer controller.
+
+ The driver builds a single multiplexer controller using a number
+ of gpio pins. For N pins, there will be 2^N possible multiplexer
+ states. The GPIO pins can be connected (by the hardware) to several
+ multiplexers, which in that case will be operated in parallel.
+
+ To compile the driver as a module, choose M here: the module will
+ be called mux-gpio.
+
+endif
diff --git a/drivers/mux/Makefile b/drivers/mux/Makefile
new file mode 100644
index 000000000000..bb16953f6290
--- /dev/null
+++ b/drivers/mux/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for multiplexer devices.
+#
+
+obj-$(CONFIG_MULTIPLEXER) += mux-core.o
+obj-$(CONFIG_MUX_GPIO) += mux-gpio.o
diff --git a/drivers/mux/mux-core.c b/drivers/mux/mux-core.c
new file mode 100644
index 000000000000..66a8bccfc3d7
--- /dev/null
+++ b/drivers/mux/mux-core.c
@@ -0,0 +1,422 @@
+/*
+ * Multiplexer subsystem
+ *
+ * Copyright (C) 2017 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "mux-core: " fmt
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/idr.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+
+/*
+ * The idle-as-is "state" is not an actual state that may be selected, it
+ * only implies that the state should not be changed. So, use that state
+ * as indication that the cached state of the multiplexer is unknown.
+ */
+#define MUX_CACHE_UNKNOWN MUX_IDLE_AS_IS
+
+static struct class mux_class = {
+ .name = "mux",
+ .owner = THIS_MODULE,
+};
+
+static int __init mux_init(void)
+{
+ return class_register(&mux_class);
+}
+
+static DEFINE_IDA(mux_ida);
+
+static void mux_chip_release(struct device *dev)
+{
+ struct mux_chip *mux_chip = to_mux_chip(dev);
+
+ ida_simple_remove(&mux_ida, mux_chip->id);
+ kfree(mux_chip);
+}
+
+static struct device_type mux_type = {
+ .name = "mux-chip",
+ .release = mux_chip_release,
+};
+
+struct mux_chip *mux_chip_alloc(struct device *dev,
+ unsigned int controllers, size_t sizeof_priv)
+{
+ struct mux_chip *mux_chip;
+ int i;
+
+ if (WARN_ON(!dev || !controllers))
+ return NULL;
+
+ mux_chip = kzalloc(sizeof(*mux_chip) +
+ controllers * sizeof(*mux_chip->mux) +
+ sizeof_priv, GFP_KERNEL);
+ if (!mux_chip)
+ return NULL;
+
+ mux_chip->mux = (struct mux_control *)(mux_chip + 1);
+ mux_chip->dev.class = &mux_class;
+ mux_chip->dev.type = &mux_type;
+ mux_chip->dev.parent = dev;
+ mux_chip->dev.of_node = dev->of_node;
+ dev_set_drvdata(&mux_chip->dev, mux_chip);
+
+ mux_chip->id = ida_simple_get(&mux_ida, 0, 0, GFP_KERNEL);
+ if (mux_chip->id < 0) {
+ pr_err("muxchipX failed to get a device id\n");
+ kfree(mux_chip);
+ return NULL;
+ }
+ dev_set_name(&mux_chip->dev, "muxchip%d", mux_chip->id);
+
+ mux_chip->controllers = controllers;
+ for (i = 0; i < controllers; ++i) {
+ struct mux_control *mux = &mux_chip->mux[i];
+
+ mux->chip = mux_chip;
+ init_rwsem(&mux->lock);
+ mux->cached_state = MUX_CACHE_UNKNOWN;
+ mux->idle_state = MUX_IDLE_AS_IS;
+ }
+
+ device_initialize(&mux_chip->dev);
+
+ return mux_chip;
+}
+EXPORT_SYMBOL_GPL(mux_chip_alloc);
+
+static int mux_control_set(struct mux_control *mux, int state)
+{
+ int ret = mux->chip->ops->set(mux, state);
+
+ mux->cached_state = ret < 0 ? MUX_CACHE_UNKNOWN : state;
+
+ return ret;
+}
+
+int mux_chip_register(struct mux_chip *mux_chip)
+{
+ int i;
+ int ret;
+
+ for (i = 0; i < mux_chip->controllers; ++i) {
+ struct mux_control *mux = &mux_chip->mux[i];
+
+ if (mux->idle_state == mux->cached_state)
+ continue;
+
+ ret = mux_control_set(mux, mux->idle_state);
+ if (ret < 0) {
+ dev_err(&mux_chip->dev, "unable to set idle state\n");
+ return ret;
+ }
+ }
+
+ ret = device_add(&mux_chip->dev);
+ if (ret < 0)
+ dev_err(&mux_chip->dev,
+ "device_add failed in mux_chip_register: %d\n", ret);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mux_chip_register);
+
+void mux_chip_unregister(struct mux_chip *mux_chip)
+{
+ device_del(&mux_chip->dev);
+}
+EXPORT_SYMBOL_GPL(mux_chip_unregister);
+
+void mux_chip_free(struct mux_chip *mux_chip)
+{
+ if (!mux_chip)
+ return;
+
+ put_device(&mux_chip->dev);
+}
+EXPORT_SYMBOL_GPL(mux_chip_free);
+
+static void devm_mux_chip_release(struct device *dev, void *res)
+{
+ struct mux_chip *mux_chip = *(struct mux_chip **)res;
+
+ mux_chip_free(mux_chip);
+}
+
+struct mux_chip *devm_mux_chip_alloc(struct device *dev,
+ unsigned int controllers,
+ size_t sizeof_priv)
+{
+ struct mux_chip **ptr, *mux_chip;
+
+ ptr = devres_alloc(devm_mux_chip_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return NULL;
+
+ mux_chip = mux_chip_alloc(dev, controllers, sizeof_priv);
+ if (!mux_chip) {
+ devres_free(ptr);
+ return NULL;
+ }
+
+ *ptr = mux_chip;
+ devres_add(dev, ptr);
+
+ return mux_chip;
+}
+EXPORT_SYMBOL_GPL(devm_mux_chip_alloc);
+
+static int devm_mux_chip_match(struct device *dev, void *res, void *data)
+{
+ struct mux_chip **r = res;
+
+ if (WARN_ON(!r || !*r))
+ return 0;
+
+ return *r == data;
+}
+
+void devm_mux_chip_free(struct device *dev, struct mux_chip *mux_chip)
+{
+ WARN_ON(devres_release(dev, devm_mux_chip_release,
+ devm_mux_chip_match, mux_chip));
+}
+EXPORT_SYMBOL_GPL(devm_mux_chip_free);
+
+static void devm_mux_chip_reg_release(struct device *dev, void *res)
+{
+ struct mux_chip *mux_chip = *(struct mux_chip **)res;
+
+ mux_chip_unregister(mux_chip);
+}
+
+int devm_mux_chip_register(struct device *dev,
+ struct mux_chip *mux_chip)
+{
+ struct mux_chip **ptr;
+ int res;
+
+ ptr = devres_alloc(devm_mux_chip_reg_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return -ENOMEM;
+
+ res = mux_chip_register(mux_chip);
+ if (res) {
+ devres_free(ptr);
+ return res;
+ }
+
+ *ptr = mux_chip;
+ devres_add(dev, ptr);
+
+ return res;
+}
+EXPORT_SYMBOL_GPL(devm_mux_chip_register);
+
+void devm_mux_chip_unregister(struct device *dev, struct mux_chip *mux_chip)
+{
+ WARN_ON(devres_release(dev, devm_mux_chip_reg_release,
+ devm_mux_chip_match, mux_chip));
+}
+EXPORT_SYMBOL_GPL(devm_mux_chip_unregister);
+
+int mux_control_select(struct mux_control *mux, int state)
+{
+ int ret;
+
+ if (down_read_trylock(&mux->lock)) {
+ if (mux->cached_state == state)
+ return 0;
+
+ /* Sigh, the mux needs updating... */
+ up_read(&mux->lock);
+ }
+
+ /* ...or it's just contended. */
+ down_write(&mux->lock);
+
+ if (mux->cached_state == state) {
+ /*
+ * Hmmm, someone else changed the mux to my liking.
+ * That makes me wonder how long I waited for nothing?
+ */
+ downgrade_write(&mux->lock);
+ return 0;
+ }
+
+ ret = mux_control_set(mux, state);
+ if (ret < 0) {
+ if (mux->idle_state != MUX_IDLE_AS_IS)
+ mux_control_set(mux, mux->idle_state);
+
+ up_write(&mux->lock);
+ return ret;
+ }
+
+ downgrade_write(&mux->lock);
+
+ return 1;
+}
+EXPORT_SYMBOL_GPL(mux_control_select);
+
+int mux_control_deselect(struct mux_control *mux)
+{
+ int ret = 0;
+
+ if (mux->idle_state != MUX_IDLE_AS_IS &&
+ mux->idle_state != mux->cached_state)
+ ret = mux_control_set(mux, mux->idle_state);
+
+ up_read(&mux->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mux_control_deselect);
+
+static int of_dev_node_match(struct device *dev, const void *data)
+{
+ return dev->of_node == data;
+}
+
+static struct mux_chip *of_find_mux_chip_by_node(struct device_node *np)
+{
+ struct device *dev;
+
+ dev = class_find_device(&mux_class, NULL, np, of_dev_node_match);
+
+ return dev ? to_mux_chip(dev) : NULL;
+}
+
+struct mux_control *mux_control_get(struct device *dev, const char *mux_name)
+{
+ struct device_node *np = dev->of_node;
+ struct of_phandle_args args;
+ struct mux_chip *mux_chip;
+ unsigned int controller;
+ int index = 0;
+ int ret;
+
+ if (mux_name) {
+ index = of_property_match_string(np, "mux-control-names",
+ mux_name);
+ if (index < 0) {
+ dev_err(dev, "mux controller '%s' not found\n",
+ mux_name);
+ return ERR_PTR(index);
+ }
+ }
+
+ ret = of_parse_phandle_with_args(np,
+ "mux-controls", "#mux-control-cells",
+ index, &args);
+ if (ret) {
+ dev_err(dev, "%s: failed to get mux-control %s(%i)\n",
+ np->full_name, mux_name ?: "", index);
+ return ERR_PTR(ret);
+ }
+
+ mux_chip = of_find_mux_chip_by_node(args.np);
+ of_node_put(args.np);
+ if (!mux_chip)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ if (args.args_count > 1 ||
+ (!args.args_count && (mux_chip->controllers > 1))) {
+ dev_err(dev, "%s: wrong #mux-control-cells for %s\n",
+ np->full_name, args.np->full_name);
+ return ERR_PTR(-EINVAL);
+ }
+
+ controller = 0;
+ if (args.args_count)
+ controller = args.args[0];
+
+ if (controller >= mux_chip->controllers) {
+ dev_err(dev, "%s: bad mux controller %u specified in %s\n",
+ np->full_name, controller, args.np->full_name);
+ return ERR_PTR(-EINVAL);
+ }
+
+ get_device(&mux_chip->dev);
+ return &mux_chip->mux[controller];
+}
+EXPORT_SYMBOL_GPL(mux_control_get);
+
+void mux_control_put(struct mux_control *mux)
+{
+ put_device(&mux->chip->dev);
+}
+EXPORT_SYMBOL_GPL(mux_control_put);
+
+static void devm_mux_control_release(struct device *dev, void *res)
+{
+ struct mux_control *mux = *(struct mux_control **)res;
+
+ mux_control_put(mux);
+}
+
+struct mux_control *devm_mux_control_get(struct device *dev,
+ const char *mux_name)
+{
+ struct mux_control **ptr, *mux;
+
+ ptr = devres_alloc(devm_mux_control_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ mux = mux_control_get(dev, mux_name);
+ if (IS_ERR(mux)) {
+ devres_free(ptr);
+ return mux;
+ }
+
+ *ptr = mux;
+ devres_add(dev, ptr);
+
+ return mux;
+}
+EXPORT_SYMBOL_GPL(devm_mux_control_get);
+
+static int devm_mux_control_match(struct device *dev, void *res, void *data)
+{
+ struct mux_control **r = res;
+
+ if (WARN_ON(!r || !*r))
+ return 0;
+
+ return *r == data;
+}
+
+void devm_mux_control_put(struct device *dev, struct mux_control *mux)
+{
+ WARN_ON(devres_release(dev, devm_mux_control_release,
+ devm_mux_control_match, mux));
+}
+EXPORT_SYMBOL_GPL(devm_mux_control_put);
+
+/*
+ * Using subsys_initcall instead of module_init here to ensure - for the
+ * non-modular case - that the subsystem is initialized when mux consumers
+ * and mux controllers start to use it /without/ relying on link order.
+ * For the modular case, the ordering is ensured with module dependencies.
+ */
+subsys_initcall(mux_init);
+
+MODULE_DESCRIPTION("Multiplexer subsystem");
+MODULE_AUTHOR("Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mux/mux-gpio.c b/drivers/mux/mux-gpio.c
new file mode 100644
index 000000000000..227d3572e6db
--- /dev/null
+++ b/drivers/mux/mux-gpio.c
@@ -0,0 +1,114 @@
+/*
+ * GPIO-controlled multiplexer driver
+ *
+ * Copyright (C) 2017 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
+ *
+ * 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/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+struct mux_gpio {
+ struct gpio_descs *gpios;
+ int *val;
+};
+
+static int mux_gpio_set(struct mux_control *mux, int state)
+{
+ struct mux_gpio *mux_gpio = mux_chip_priv(mux->chip);
+ int i;
+
+ for (i = 0; i < mux_gpio->gpios->ndescs; i++)
+ mux_gpio->val[i] = (state >> i) & 1;
+
+ gpiod_set_array_value_cansleep(mux_gpio->gpios->ndescs,
+ mux_gpio->gpios->desc,
+ mux_gpio->val);
+
+ return 0;
+}
+
+static const struct mux_control_ops mux_gpio_ops = {
+ .set = mux_gpio_set,
+};
+
+static const struct of_device_id mux_gpio_dt_ids[] = {
+ { .compatible = "gpio-mux", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mux_gpio_dt_ids);
+
+static int mux_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mux_chip *mux_chip;
+ struct mux_gpio *mux_gpio;
+ int pins;
+ s32 idle_state;
+ int ret;
+
+ pins = gpiod_count(dev, "mux");
+ if (pins < 0)
+ return pins;
+
+ mux_chip = devm_mux_chip_alloc(dev, 1, sizeof(*mux_gpio) +
+ pins * sizeof(*mux_gpio->val));
+ if (!mux_chip)
+ return -ENOMEM;
+
+ mux_gpio = mux_chip_priv(mux_chip);
+ mux_gpio->val = (int *)(mux_gpio + 1);
+ mux_chip->ops = &mux_gpio_ops;
+
+ mux_gpio->gpios = devm_gpiod_get_array(dev, "mux", GPIOD_OUT_LOW);
+ if (IS_ERR(mux_gpio->gpios)) {
+ ret = PTR_ERR(mux_gpio->gpios);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to get gpios\n");
+ return ret;
+ }
+ WARN_ON(pins != mux_gpio->gpios->ndescs);
+ mux_chip->mux->states = 1 << pins;
+
+ ret = device_property_read_u32(dev, "idle-state", (u32 *)&idle_state);
+ if (ret >= 0 && idle_state != MUX_IDLE_AS_IS) {
+ if (idle_state < 0 || idle_state >= mux_chip->mux->states) {
+ dev_err(dev, "invalid idle-state %u\n", idle_state);
+ return -EINVAL;
+ }
+
+ mux_chip->mux->idle_state = idle_state;
+ }
+
+ ret = devm_mux_chip_register(dev, mux_chip);
+ if (ret < 0)
+ return ret;
+
+ dev_info(dev, "%u-way mux-controller registered\n",
+ mux_chip->mux->states);
+
+ return 0;
+}
+
+static struct platform_driver mux_gpio_driver = {
+ .driver = {
+ .name = "gpio-mux",
+ .of_match_table = of_match_ptr(mux_gpio_dt_ids),
+ },
+ .probe = mux_gpio_probe,
+};
+module_platform_driver(mux_gpio_driver);
+
+MODULE_DESCRIPTION("GPIO-controlled multiplexer driver");
+MODULE_AUTHOR("Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mux.h b/include/linux/mux.h
new file mode 100644
index 000000000000..febdde4246df
--- /dev/null
+++ b/include/linux/mux.h
@@ -0,0 +1,252 @@
+/*
+ * mux.h - definitions for the multiplexer interface
+ *
+ * Copyright (C) 2017 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
+ *
+ * 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.
+ */
+
+#ifndef _LINUX_MUX_H
+#define _LINUX_MUX_H
+
+#include <linux/device.h>
+#include <linux/rwsem.h>
+
+struct mux_chip;
+struct mux_control;
+struct platform_device;
+
+/**
+ * struct mux_control_ops - Mux controller operations for a mux chip.
+ * @set: Set the state of the given mux controller.
+ */
+struct mux_control_ops {
+ int (*set)(struct mux_control *mux, int state);
+};
+
+/* These defines match the constants from the dt-bindings. On purpose. */
+#define MUX_IDLE_AS_IS (-1)
+#define MUX_IDLE_DISCONNECT (-2)
+
+/**
+ * struct mux_control - Represents a mux controller.
+ * @lock: Protects the mux controller state.
+ * @chip: The mux chip that is handling this mux controller.
+ * @states: The number of mux controller states.
+ * @cached_state: The current mux controller state, or -1 if none.
+ * @idle_state: The mux controller state to use when inactive, or one
+ * of MUX_IDLE_AS_IS and MUX_IDLE_DISCONNECT.
+ */
+struct mux_control {
+ struct rw_semaphore lock; /* protects the state of the mux */
+
+ struct mux_chip *chip;
+
+ unsigned int states;
+ int cached_state;
+ int idle_state;
+};
+
+/**
+ * struct mux_chip - Represents a chip holding mux controllers.
+ * @controllers: Number of mux controllers handled by the chip.
+ * @mux: Array of mux controllers that are handled.
+ * @dev: Device structure.
+ * @id: Used to identify the device internally.
+ * @ops: Mux controller operations.
+ */
+struct mux_chip {
+ unsigned int controllers;
+ struct mux_control *mux;
+ struct device dev;
+ int id;
+
+ const struct mux_control_ops *ops;
+};
+
+#define to_mux_chip(x) container_of((x), struct mux_chip, dev)
+
+/**
+ * mux_chip_priv() - Get the extra memory reserved by mux_chip_alloc().
+ * @mux_chip: The mux-chip to get the private memory from.
+ *
+ * Return: Pointer to the private memory reserved by the allocator.
+ */
+static inline void *mux_chip_priv(struct mux_chip *mux_chip)
+{
+ return &mux_chip->mux[mux_chip->controllers];
+}
+
+/**
+ * mux_chip_alloc() - Allocate a mux-chip.
+ * @dev: The parent device implementing the mux interface.
+ * @controllers: The number of mux controllers to allocate for this chip.
+ * @sizeof_priv: Size of extra memory area for private use by the caller.
+ *
+ * Return: A pointer to the new mux-chip, NULL on failure.
+ */
+struct mux_chip *mux_chip_alloc(struct device *dev,
+ unsigned int controllers, size_t sizeof_priv);
+
+/**
+ * mux_chip_register() - Register a mux-chip, thus readying the controllers
+ * for use.
+ * @mux_chip: The mux-chip to register.
+ *
+ * Do not retry registration of the same mux-chip on failure. You should
+ * instead put it away with mux_chip_free() and allocate a new one, if you
+ * for some reason would like to retry registration.
+ *
+ * Return: Zero on success or a negative errno on error.
+ */
+int mux_chip_register(struct mux_chip *mux_chip);
+
+/**
+ * mux_chip_unregister() - Take the mux-chip off-line.
+ * @mux_chip: The mux-chip to unregister.
+ *
+ * mux_chip_unregister() reverses the effects of mux_chip_register().
+ * But not completely, you should not try to call mux_chip_register()
+ * on a mux-chip that has been registered before.
+ */
+void mux_chip_unregister(struct mux_chip *mux_chip);
+
+/**
+ * mux_chip_free() - Free the mux-chip for good.
+ * @mux_chip: The mux-chip to free.
+ *
+ * mux_chip_free() reverses the effects of mux_chip_alloc().
+ */
+void mux_chip_free(struct mux_chip *mux_chip);
+
+/**
+ * devm_mux_chip_alloc() - Resource-managed version of mux_chip_alloc().
+ * @dev: The parent device implementing the mux interface.
+ * @controllers: The number of mux controllers to allocate for this chip.
+ * @sizeof_priv: Size of extra memory area for private use by the caller.
+ *
+ * See mux_chip_alloc() for more details.
+ *
+ * Return: A pointer to the new mux-chip, NULL on failure.
+ */
+struct mux_chip *devm_mux_chip_alloc(struct device *dev,
+ unsigned int controllers,
+ size_t sizeof_priv);
+
+/**
+ * devm_mux_chip_register() - Resource-managed version mux_chip_register().
+ * @dev: The parent device implementing the mux interface.
+ * @mux_chip: The mux-chip to register.
+ *
+ * See mux_chip_register() for more details.
+ *
+ * Return: Zero on success or a negative errno on error.
+ */
+int devm_mux_chip_register(struct device *dev, struct mux_chip *mux_chip);
+
+/**
+ * devm_mux_chip_unregister() - Resource-managed version mux_chip_unregister().
+ * @dev: The device that originally registered the mux-chip.
+ * @mux_chip: The mux-chip to unregister.
+ *
+ * See mux_chip_unregister() for more details.
+ *
+ * Note that you do not normally need to call this function.
+ */
+void devm_mux_chip_unregister(struct device *dev, struct mux_chip *mux_chip);
+
+/**
+ * devm_mux_chip_free() - Resource-managed version mux_chip_free().
+ * @dev: The device that originally got the mux-chip.
+ * @mux_chip: The mux-chip to free.
+ *
+ * See mux_chip_free() for more details.
+ *
+ * Note that you do not normally need to call this function.
+ */
+void devm_mux_chip_free(struct device *dev, struct mux_chip *mux_chip);
+
+/**
+ * mux_control_select() - Select the given multiplexer state.
+ * @mux: The mux-control to request a change of state from.
+ * @state: The new requested state.
+ *
+ * Make sure to call mux_control_deselect() when the operation is complete and
+ * the mux-control is free for others to use, but do not call
+ * mux_control_deselect() if mux_control_select() fails.
+ *
+ * Return: 0 if the requested state was already active, or 1 it the
+ * mux-control state was changed to the requested state. Or a negative
+ * errno on error.
+ *
+ * Note that the difference in return value of zero or one is of
+ * questionable value; especially if the mux-control has several independent
+ * consumers, which is something the consumers should perhaps not be making
+ * assumptions about.
+ */
+int mux_control_select(struct mux_control *mux, int state);
+
+/**
+ * mux_control_deselect() - Deselect the previously selected multiplexer state.
+ * @mux: The mux-control to deselect.
+ *
+ * Return: 0 on success and a negative errno on error. An error can only
+ * occur if the mux has an idle state. Note that even if an error occurs, the
+ * mux-control is unlocked and is thus free for the next access.
+ */
+int mux_control_deselect(struct mux_control *mux);
+
+/**
+ * mux_control_get_index() - Get the index of the given mux controller
+ * @mux: The mux-control to get the index for.
+ *
+ * Return: The index of the mux controller within the mux chip the mux
+ * controller is a part of.
+ */
+static inline unsigned int mux_control_get_index(struct mux_control *mux)
+{
+ return mux - mux->chip->mux;
+}
+
+/**
+ * mux_control_get() - Get the mux-control for a device.
+ * @dev: The device that needs a mux-control.
+ * @mux_name: The name identifying the mux-control.
+ *
+ * Return: A pointer to the mux-control, or an ERR_PTR with a negative errno.
+ */
+struct mux_control *mux_control_get(struct device *dev, const char *mux_name);
+
+/**
+ * mux_control_put() - Put away the mux-control for good.
+ * @mux: The mux-control to put away.
+ *
+ * mux_control_put() reverses the effects of mux_control_get().
+ */
+void mux_control_put(struct mux_control *mux);
+
+/**
+ * devm_mux_control_get() - Get the mux-control for a device, with resource
+ * management.
+ * @dev: The device that needs a mux-control.
+ * @mux_name: The name identifying the mux-control.
+ *
+ * Return: Pointer to the mux-control, or an ERR_PTR with a negative errno.
+ */
+struct mux_control *devm_mux_control_get(struct device *dev,
+ const char *mux_name);
+
+/**
+ * devm_mux_control_put() - Resource-managed version mux_control_put().
+ * @dev: The device that originally got the mux-control.
+ * @mux: The mux-control to put away.
+ *
+ * Note that you do not normally need to call this function.
+ */
+void devm_mux_control_put(struct device *dev, struct mux_control *mux);
+
+#endif /* _LINUX_MUX_H */
--
2.1.4
^ permalink raw reply related [flat|nested] 38+ messages in thread
[parent not found: <1492101794-13444-4-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>]
* Re: [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller
[not found] ` <1492101794-13444-4-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
@ 2017-04-18 8:34 ` Philipp Zabel
2017-04-18 8:51 ` Greg Kroah-Hartman
1 sibling, 0 replies; 38+ messages in thread
From: Philipp Zabel @ 2017-04-18 8:34 UTC (permalink / raw)
To: Peter Rosin
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA, Greg Kroah-Hartman,
Wolfram Sang, Rob Herring, Mark Rutland, Jonathan Cameron,
Hartmut Knaack, Lars-Peter Clausen, Peter Meerwald-Stadler,
Jonathan Corbet, linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA, Andrew Morton, Colin Ian King,
Paul Gortmaker, kernel-bIcnvbaLZ9MEGnE8C9+IrQ
Hi Peter,
On Thu, 2017-04-13 at 18:43 +0200, Peter Rosin wrote:
[...]
> diff --git a/include/linux/mux.h b/include/linux/mux.h
> new file mode 100644
> index 000000000000..febdde4246df
> --- /dev/null
> +++ b/include/linux/mux.h
[...]
Consider separating mux.h into a consumer header and a driver header.
Right now there is no separation between the consumer API and the
framework internals.
regards
Philipp
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller
[not found] ` <1492101794-13444-4-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
2017-04-18 8:34 ` Philipp Zabel
@ 2017-04-18 8:51 ` Greg Kroah-Hartman
2017-04-18 10:59 ` Peter Rosin
1 sibling, 1 reply; 38+ messages in thread
From: Greg Kroah-Hartman @ 2017-04-18 8:51 UTC (permalink / raw)
To: Peter Rosin
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA, Wolfram Sang, Rob Herring,
Mark Rutland, Jonathan Cameron, Hartmut Knaack,
Lars-Peter Clausen, Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel-bIcnvbaLZ9MEGnE8C9+IrQ
On Thu, Apr 13, 2017 at 06:43:07PM +0200, Peter Rosin wrote:
> +config MUX_GPIO
> + tristate "GPIO-controlled Multiplexer"
> + depends on OF && GPIOLIB
Why have the gpio and mux core in the same patch?
And why does this depend on OF?
> + help
> + GPIO-controlled Multiplexer controller.
> +
> + The driver builds a single multiplexer controller using a number
> + of gpio pins. For N pins, there will be 2^N possible multiplexer
> + states. The GPIO pins can be connected (by the hardware) to several
> + multiplexers, which in that case will be operated in parallel.
> +
> + To compile the driver as a module, choose M here: the module will
> + be called mux-gpio.
> +
> +endif
> diff --git a/drivers/mux/Makefile b/drivers/mux/Makefile
> new file mode 100644
> index 000000000000..bb16953f6290
> --- /dev/null
> +++ b/drivers/mux/Makefile
> @@ -0,0 +1,6 @@
> +#
> +# Makefile for multiplexer devices.
> +#
> +
> +obj-$(CONFIG_MULTIPLEXER) += mux-core.o
> +obj-$(CONFIG_MUX_GPIO) += mux-gpio.o
> diff --git a/drivers/mux/mux-core.c b/drivers/mux/mux-core.c
> new file mode 100644
> index 000000000000..66a8bccfc3d7
> --- /dev/null
> +++ b/drivers/mux/mux-core.c
> @@ -0,0 +1,422 @@
> +/*
> + * Multiplexer subsystem
> + *
> + * Copyright (C) 2017 Axentia Technologies AB
> + *
> + * Author: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
> + *
> + * 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.
> + */
> +
> +#define pr_fmt(fmt) "mux-core: " fmt
> +
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/export.h>
> +#include <linux/idr.h>
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/mux.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/slab.h>
> +
> +/*
> + * The idle-as-is "state" is not an actual state that may be selected, it
> + * only implies that the state should not be changed. So, use that state
> + * as indication that the cached state of the multiplexer is unknown.
> + */
> +#define MUX_CACHE_UNKNOWN MUX_IDLE_AS_IS
> +
> +static struct class mux_class = {
> + .name = "mux",
> + .owner = THIS_MODULE,
> +};
No Documentation/ABI/ update for your sysfs files? Please do so.
> +
> +static int __init mux_init(void)
> +{
> + return class_register(&mux_class);
> +}
> +
> +static DEFINE_IDA(mux_ida);
When your module is unloaded, you forgot to clean this structure up with
what was done with it.
> +
> +static void mux_chip_release(struct device *dev)
> +{
> + struct mux_chip *mux_chip = to_mux_chip(dev);
> +
> + ida_simple_remove(&mux_ida, mux_chip->id);
> + kfree(mux_chip);
> +}
> +
> +static struct device_type mux_type = {
> + .name = "mux-chip",
> + .release = mux_chip_release,
> +};
> +
> +struct mux_chip *mux_chip_alloc(struct device *dev,
> + unsigned int controllers, size_t sizeof_priv)
> +{
> + struct mux_chip *mux_chip;
> + int i;
> +
> + if (WARN_ON(!dev || !controllers))
> + return NULL;
> +
> + mux_chip = kzalloc(sizeof(*mux_chip) +
> + controllers * sizeof(*mux_chip->mux) +
> + sizeof_priv, GFP_KERNEL);
> + if (!mux_chip)
> + return NULL;
You don't return PTR_ERR(-ENOMEM)? Ok, why not? (I'm not arguing for
it, just curious...)
> +
> + mux_chip->mux = (struct mux_control *)(mux_chip + 1);
> + mux_chip->dev.class = &mux_class;
> + mux_chip->dev.type = &mux_type;
> + mux_chip->dev.parent = dev;
> + mux_chip->dev.of_node = dev->of_node;
> + dev_set_drvdata(&mux_chip->dev, mux_chip);
> +
> + mux_chip->id = ida_simple_get(&mux_ida, 0, 0, GFP_KERNEL);
> + if (mux_chip->id < 0) {
> + pr_err("muxchipX failed to get a device id\n");
> + kfree(mux_chip);
> + return NULL;
> + }
> + dev_set_name(&mux_chip->dev, "muxchip%d", mux_chip->id);
> +
> + mux_chip->controllers = controllers;
> + for (i = 0; i < controllers; ++i) {
> + struct mux_control *mux = &mux_chip->mux[i];
> +
> + mux->chip = mux_chip;
> + init_rwsem(&mux->lock);
> + mux->cached_state = MUX_CACHE_UNKNOWN;
> + mux->idle_state = MUX_IDLE_AS_IS;
> + }
> +
> + device_initialize(&mux_chip->dev);
Why are you not registering the device here as well? Why have this be a
two step process?
> +
> + return mux_chip;
> +}
> +EXPORT_SYMBOL_GPL(mux_chip_alloc);
> +
> +static int mux_control_set(struct mux_control *mux, int state)
> +{
> + int ret = mux->chip->ops->set(mux, state);
> +
> + mux->cached_state = ret < 0 ? MUX_CACHE_UNKNOWN : state;
> +
> + return ret;
> +}
> +
> +int mux_chip_register(struct mux_chip *mux_chip)
> +{
> + int i;
> + int ret;
> +
> + for (i = 0; i < mux_chip->controllers; ++i) {
> + struct mux_control *mux = &mux_chip->mux[i];
> +
> + if (mux->idle_state == mux->cached_state)
> + continue;
> +
> + ret = mux_control_set(mux, mux->idle_state);
> + if (ret < 0) {
> + dev_err(&mux_chip->dev, "unable to set idle state\n");
> + return ret;
> + }
> + }
> +
> + ret = device_add(&mux_chip->dev);
> + if (ret < 0)
> + dev_err(&mux_chip->dev,
> + "device_add failed in mux_chip_register: %d\n", ret);
Did you run checkpatch.pl in strict mode on this new file? Please do so :)
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(mux_chip_register);
> +
> +void mux_chip_unregister(struct mux_chip *mux_chip)
> +{
> + device_del(&mux_chip->dev);
> +}
> +EXPORT_SYMBOL_GPL(mux_chip_unregister);
> +
> +void mux_chip_free(struct mux_chip *mux_chip)
> +{
> + if (!mux_chip)
> + return;
> +
> + put_device(&mux_chip->dev);
> +}
> +EXPORT_SYMBOL_GPL(mux_chip_free);
> +
> +static void devm_mux_chip_release(struct device *dev, void *res)
> +{
> + struct mux_chip *mux_chip = *(struct mux_chip **)res;
> +
> + mux_chip_free(mux_chip);
> +}
> +
> +struct mux_chip *devm_mux_chip_alloc(struct device *dev,
> + unsigned int controllers,
> + size_t sizeof_priv)
> +{
> + struct mux_chip **ptr, *mux_chip;
> +
> + ptr = devres_alloc(devm_mux_chip_release, sizeof(*ptr), GFP_KERNEL);
> + if (!ptr)
> + return NULL;
> +
> + mux_chip = mux_chip_alloc(dev, controllers, sizeof_priv);
> + if (!mux_chip) {
> + devres_free(ptr);
> + return NULL;
> + }
> +
> + *ptr = mux_chip;
> + devres_add(dev, ptr);
> +
> + return mux_chip;
> +}
> +EXPORT_SYMBOL_GPL(devm_mux_chip_alloc);
Having devm functions that create/destroy other struct devices worries
me, do we have other examples of this happening today? Are you sure you
got the reference counting all correct?
> +
> +static int devm_mux_chip_match(struct device *dev, void *res, void *data)
> +{
> + struct mux_chip **r = res;
> +
> + if (WARN_ON(!r || !*r))
How can this happen?
> + return 0;
> +
> + return *r == data;
> +}
> +
> +void devm_mux_chip_free(struct device *dev, struct mux_chip *mux_chip)
> +{
> + WARN_ON(devres_release(dev, devm_mux_chip_release,
> + devm_mux_chip_match, mux_chip));
What can someone do with these WARN_ON() splats in the kernel log?
> +}
> +EXPORT_SYMBOL_GPL(devm_mux_chip_free);
> +
> +static void devm_mux_chip_reg_release(struct device *dev, void *res)
> +{
> + struct mux_chip *mux_chip = *(struct mux_chip **)res;
> +
> + mux_chip_unregister(mux_chip);
> +}
> +
> +int devm_mux_chip_register(struct device *dev,
> + struct mux_chip *mux_chip)
> +{
> + struct mux_chip **ptr;
> + int res;
> +
> + ptr = devres_alloc(devm_mux_chip_reg_release, sizeof(*ptr), GFP_KERNEL);
> + if (!ptr)
> + return -ENOMEM;
> +
> + res = mux_chip_register(mux_chip);
> + if (res) {
> + devres_free(ptr);
> + return res;
> + }
> +
> + *ptr = mux_chip;
> + devres_add(dev, ptr);
> +
> + return res;
> +}
> +EXPORT_SYMBOL_GPL(devm_mux_chip_register);
> +
> +void devm_mux_chip_unregister(struct device *dev, struct mux_chip *mux_chip)
> +{
> + WARN_ON(devres_release(dev, devm_mux_chip_reg_release,
> + devm_mux_chip_match, mux_chip));
> +}
> +EXPORT_SYMBOL_GPL(devm_mux_chip_unregister);
> +
> +int mux_control_select(struct mux_control *mux, int state)
> +{
> + int ret;
> +
> + if (down_read_trylock(&mux->lock)) {
> + if (mux->cached_state == state)
> + return 0;
> +
> + /* Sigh, the mux needs updating... */
> + up_read(&mux->lock);
> + }
> +
> + /* ...or it's just contended. */
> + down_write(&mux->lock);
Why use a read/write lock at all? Have you tested this to verify it
really is faster and needed?
> +
> + if (mux->cached_state == state) {
> + /*
> + * Hmmm, someone else changed the mux to my liking.
> + * That makes me wonder how long I waited for nothing?
> + */
> + downgrade_write(&mux->lock);
Oh that always scares me... Are you _sure_ this is correct? And
needed?
> + return 0;
> + }
> +
> + ret = mux_control_set(mux, state);
> + if (ret < 0) {
> + if (mux->idle_state != MUX_IDLE_AS_IS)
> + mux_control_set(mux, mux->idle_state);
> +
> + up_write(&mux->lock);
> + return ret;
> + }
> +
> + downgrade_write(&mux->lock);
> +
> + return 1;
> +}
> +EXPORT_SYMBOL_GPL(mux_control_select);
> +
> +int mux_control_deselect(struct mux_control *mux)
> +{
> + int ret = 0;
> +
> + if (mux->idle_state != MUX_IDLE_AS_IS &&
> + mux->idle_state != mux->cached_state)
> + ret = mux_control_set(mux, mux->idle_state);
> +
> + up_read(&mux->lock);
You require a lock to be held for a "global" function? Without
documentation? Or even a sparse marking? That's asking for trouble...
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(mux_control_deselect);
> +
> +static int of_dev_node_match(struct device *dev, const void *data)
> +{
> + return dev->of_node == data;
> +}
> +
> +static struct mux_chip *of_find_mux_chip_by_node(struct device_node *np)
> +{
> + struct device *dev;
> +
> + dev = class_find_device(&mux_class, NULL, np, of_dev_node_match);
> +
> + return dev ? to_mux_chip(dev) : NULL;
> +}
> +
> +struct mux_control *mux_control_get(struct device *dev, const char *mux_name)
> +{
> + struct device_node *np = dev->of_node;
> + struct of_phandle_args args;
> + struct mux_chip *mux_chip;
> + unsigned int controller;
> + int index = 0;
> + int ret;
> +
> + if (mux_name) {
> + index = of_property_match_string(np, "mux-control-names",
> + mux_name);
> + if (index < 0) {
> + dev_err(dev, "mux controller '%s' not found\n",
> + mux_name);
> + return ERR_PTR(index);
> + }
> + }
> +
> + ret = of_parse_phandle_with_args(np,
> + "mux-controls", "#mux-control-cells",
> + index, &args);
> + if (ret) {
> + dev_err(dev, "%s: failed to get mux-control %s(%i)\n",
> + np->full_name, mux_name ?: "", index);
> + return ERR_PTR(ret);
> + }
> +
> + mux_chip = of_find_mux_chip_by_node(args.np);
> + of_node_put(args.np);
> + if (!mux_chip)
> + return ERR_PTR(-EPROBE_DEFER);
> +
> + if (args.args_count > 1 ||
> + (!args.args_count && (mux_chip->controllers > 1))) {
> + dev_err(dev, "%s: wrong #mux-control-cells for %s\n",
> + np->full_name, args.np->full_name);
> + return ERR_PTR(-EINVAL);
> + }
> +
> + controller = 0;
> + if (args.args_count)
> + controller = args.args[0];
> +
> + if (controller >= mux_chip->controllers) {
> + dev_err(dev, "%s: bad mux controller %u specified in %s\n",
> + np->full_name, controller, args.np->full_name);
> + return ERR_PTR(-EINVAL);
> + }
> +
> + get_device(&mux_chip->dev);
> + return &mux_chip->mux[controller];
> +}
> +EXPORT_SYMBOL_GPL(mux_control_get);
> +
> +void mux_control_put(struct mux_control *mux)
> +{
> + put_device(&mux->chip->dev);
> +}
> +EXPORT_SYMBOL_GPL(mux_control_put);
> +
> +static void devm_mux_control_release(struct device *dev, void *res)
> +{
> + struct mux_control *mux = *(struct mux_control **)res;
> +
> + mux_control_put(mux);
> +}
> +
> +struct mux_control *devm_mux_control_get(struct device *dev,
> + const char *mux_name)
> +{
> + struct mux_control **ptr, *mux;
> +
> + ptr = devres_alloc(devm_mux_control_release, sizeof(*ptr), GFP_KERNEL);
> + if (!ptr)
> + return ERR_PTR(-ENOMEM);
> +
> + mux = mux_control_get(dev, mux_name);
> + if (IS_ERR(mux)) {
> + devres_free(ptr);
> + return mux;
> + }
> +
> + *ptr = mux;
> + devres_add(dev, ptr);
> +
> + return mux;
> +}
> +EXPORT_SYMBOL_GPL(devm_mux_control_get);
> +
> +static int devm_mux_control_match(struct device *dev, void *res, void *data)
> +{
> + struct mux_control **r = res;
> +
> + if (WARN_ON(!r || !*r))
> + return 0;
Same here, how can this happen?
> +
> + return *r == data;
> +}
> +
> +void devm_mux_control_put(struct device *dev, struct mux_control *mux)
> +{
> + WARN_ON(devres_release(dev, devm_mux_control_release,
> + devm_mux_control_match, mux));
> +}
> +EXPORT_SYMBOL_GPL(devm_mux_control_put);
> +
> +/*
> + * Using subsys_initcall instead of module_init here to ensure - for the
> + * non-modular case - that the subsystem is initialized when mux consumers
> + * and mux controllers start to use it /without/ relying on link order.
> + * For the modular case, the ordering is ensured with module dependencies.
> + */
> +subsys_initcall(mux_init);
Even with subsys_initcall you are relying on link order, you do realize
that? What about other subsystems that rely on this? :)
> +
> +MODULE_DESCRIPTION("Multiplexer subsystem");
> +MODULE_AUTHOR("Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/mux/mux-gpio.c b/drivers/mux/mux-gpio.c
> new file mode 100644
> index 000000000000..227d3572e6db
> --- /dev/null
> +++ b/drivers/mux/mux-gpio.c
> @@ -0,0 +1,114 @@
> +/*
> + * GPIO-controlled multiplexer driver
> + *
> + * Copyright (C) 2017 Axentia Technologies AB
> + *
> + * Author: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
> + *
> + * 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/gpio/consumer.h>
> +#include <linux/module.h>
> +#include <linux/mux.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +
> +struct mux_gpio {
> + struct gpio_descs *gpios;
> + int *val;
> +};
> +
> +static int mux_gpio_set(struct mux_control *mux, int state)
> +{
> + struct mux_gpio *mux_gpio = mux_chip_priv(mux->chip);
> + int i;
> +
> + for (i = 0; i < mux_gpio->gpios->ndescs; i++)
> + mux_gpio->val[i] = (state >> i) & 1;
> +
> + gpiod_set_array_value_cansleep(mux_gpio->gpios->ndescs,
> + mux_gpio->gpios->desc,
> + mux_gpio->val);
> +
> + return 0;
> +}
> +
> +static const struct mux_control_ops mux_gpio_ops = {
> + .set = mux_gpio_set,
> +};
> +
> +static const struct of_device_id mux_gpio_dt_ids[] = {
> + { .compatible = "gpio-mux", },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, mux_gpio_dt_ids);
> +
> +static int mux_gpio_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct mux_chip *mux_chip;
> + struct mux_gpio *mux_gpio;
> + int pins;
> + s32 idle_state;
> + int ret;
> +
> + pins = gpiod_count(dev, "mux");
> + if (pins < 0)
> + return pins;
> +
> + mux_chip = devm_mux_chip_alloc(dev, 1, sizeof(*mux_gpio) +
> + pins * sizeof(*mux_gpio->val));
> + if (!mux_chip)
> + return -ENOMEM;
> +
> + mux_gpio = mux_chip_priv(mux_chip);
> + mux_gpio->val = (int *)(mux_gpio + 1);
> + mux_chip->ops = &mux_gpio_ops;
> +
> + mux_gpio->gpios = devm_gpiod_get_array(dev, "mux", GPIOD_OUT_LOW);
> + if (IS_ERR(mux_gpio->gpios)) {
> + ret = PTR_ERR(mux_gpio->gpios);
> + if (ret != -EPROBE_DEFER)
> + dev_err(dev, "failed to get gpios\n");
> + return ret;
> + }
> + WARN_ON(pins != mux_gpio->gpios->ndescs);
> + mux_chip->mux->states = 1 << pins;
> +
> + ret = device_property_read_u32(dev, "idle-state", (u32 *)&idle_state);
> + if (ret >= 0 && idle_state != MUX_IDLE_AS_IS) {
> + if (idle_state < 0 || idle_state >= mux_chip->mux->states) {
> + dev_err(dev, "invalid idle-state %u\n", idle_state);
> + return -EINVAL;
> + }
> +
> + mux_chip->mux->idle_state = idle_state;
> + }
> +
> + ret = devm_mux_chip_register(dev, mux_chip);
> + if (ret < 0)
> + return ret;
> +
> + dev_info(dev, "%u-way mux-controller registered\n",
> + mux_chip->mux->states);
> +
> + return 0;
> +}
> +
> +static struct platform_driver mux_gpio_driver = {
> + .driver = {
> + .name = "gpio-mux",
> + .of_match_table = of_match_ptr(mux_gpio_dt_ids),
> + },
> + .probe = mux_gpio_probe,
> +};
> +module_platform_driver(mux_gpio_driver);
> +
> +MODULE_DESCRIPTION("GPIO-controlled multiplexer driver");
> +MODULE_AUTHOR("Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/mux.h b/include/linux/mux.h
> new file mode 100644
> index 000000000000..febdde4246df
> --- /dev/null
> +++ b/include/linux/mux.h
> @@ -0,0 +1,252 @@
> +/*
> + * mux.h - definitions for the multiplexer interface
> + *
> + * Copyright (C) 2017 Axentia Technologies AB
> + *
> + * Author: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
> + *
> + * 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.
> + */
> +
> +#ifndef _LINUX_MUX_H
> +#define _LINUX_MUX_H
> +
> +#include <linux/device.h>
> +#include <linux/rwsem.h>
> +
> +struct mux_chip;
> +struct mux_control;
> +struct platform_device;
> +
> +/**
> + * struct mux_control_ops - Mux controller operations for a mux chip.
> + * @set: Set the state of the given mux controller.
> + */
> +struct mux_control_ops {
> + int (*set)(struct mux_control *mux, int state);
> +};
> +
> +/* These defines match the constants from the dt-bindings. On purpose. */
Why on purpose?
> +#define MUX_IDLE_AS_IS (-1)
> +#define MUX_IDLE_DISCONNECT (-2)
> +
> +/**
> + * struct mux_control - Represents a mux controller.
> + * @lock: Protects the mux controller state.
> + * @chip: The mux chip that is handling this mux controller.
> + * @states: The number of mux controller states.
> + * @cached_state: The current mux controller state, or -1 if none.
> + * @idle_state: The mux controller state to use when inactive, or one
> + * of MUX_IDLE_AS_IS and MUX_IDLE_DISCONNECT.
> + */
> +struct mux_control {
> + struct rw_semaphore lock; /* protects the state of the mux */
> +
> + struct mux_chip *chip;
> +
> + unsigned int states;
> + int cached_state;
> + int idle_state;
> +};
> +
> +/**
> + * struct mux_chip - Represents a chip holding mux controllers.
> + * @controllers: Number of mux controllers handled by the chip.
> + * @mux: Array of mux controllers that are handled.
> + * @dev: Device structure.
> + * @id: Used to identify the device internally.
> + * @ops: Mux controller operations.
> + */
> +struct mux_chip {
> + unsigned int controllers;
> + struct mux_control *mux;
> + struct device dev;
> + int id;
> +
> + const struct mux_control_ops *ops;
> +};
> +
> +#define to_mux_chip(x) container_of((x), struct mux_chip, dev)
> +
> +/**
> + * mux_chip_priv() - Get the extra memory reserved by mux_chip_alloc().
> + * @mux_chip: The mux-chip to get the private memory from.
> + *
> + * Return: Pointer to the private memory reserved by the allocator.
> + */
> +static inline void *mux_chip_priv(struct mux_chip *mux_chip)
> +{
> + return &mux_chip->mux[mux_chip->controllers];
> +}
> +
> +/**
> + * mux_chip_alloc() - Allocate a mux-chip.
> + * @dev: The parent device implementing the mux interface.
> + * @controllers: The number of mux controllers to allocate for this chip.
> + * @sizeof_priv: Size of extra memory area for private use by the caller.
> + *
> + * Return: A pointer to the new mux-chip, NULL on failure.
> + */
> +struct mux_chip *mux_chip_alloc(struct device *dev,
> + unsigned int controllers, size_t sizeof_priv);
> +
Don't put kernel doc comments in a .h file, they normally go into the .c
file, next to the code itself. That makes it easier to fix up and
realise when they need to be changed when the code changes. The .h file
rarely changes.
> +/**
> + * mux_chip_register() - Register a mux-chip, thus readying the controllers
> + * for use.
> + * @mux_chip: The mux-chip to register.
> + *
> + * Do not retry registration of the same mux-chip on failure. You should
> + * instead put it away with mux_chip_free() and allocate a new one, if you
> + * for some reason would like to retry registration.
> + *
> + * Return: Zero on success or a negative errno on error.
> + */
> +int mux_chip_register(struct mux_chip *mux_chip);
> +
> +/**
> + * mux_chip_unregister() - Take the mux-chip off-line.
> + * @mux_chip: The mux-chip to unregister.
> + *
> + * mux_chip_unregister() reverses the effects of mux_chip_register().
> + * But not completely, you should not try to call mux_chip_register()
> + * on a mux-chip that has been registered before.
> + */
> +void mux_chip_unregister(struct mux_chip *mux_chip);
> +
> +/**
> + * mux_chip_free() - Free the mux-chip for good.
> + * @mux_chip: The mux-chip to free.
> + *
> + * mux_chip_free() reverses the effects of mux_chip_alloc().
> + */
> +void mux_chip_free(struct mux_chip *mux_chip);
> +
> +/**
> + * devm_mux_chip_alloc() - Resource-managed version of mux_chip_alloc().
> + * @dev: The parent device implementing the mux interface.
> + * @controllers: The number of mux controllers to allocate for this chip.
> + * @sizeof_priv: Size of extra memory area for private use by the caller.
> + *
> + * See mux_chip_alloc() for more details.
> + *
> + * Return: A pointer to the new mux-chip, NULL on failure.
> + */
> +struct mux_chip *devm_mux_chip_alloc(struct device *dev,
> + unsigned int controllers,
> + size_t sizeof_priv);
> +
> +/**
> + * devm_mux_chip_register() - Resource-managed version mux_chip_register().
> + * @dev: The parent device implementing the mux interface.
> + * @mux_chip: The mux-chip to register.
> + *
> + * See mux_chip_register() for more details.
> + *
> + * Return: Zero on success or a negative errno on error.
> + */
> +int devm_mux_chip_register(struct device *dev, struct mux_chip *mux_chip);
> +
> +/**
> + * devm_mux_chip_unregister() - Resource-managed version mux_chip_unregister().
> + * @dev: The device that originally registered the mux-chip.
> + * @mux_chip: The mux-chip to unregister.
> + *
> + * See mux_chip_unregister() for more details.
> + *
> + * Note that you do not normally need to call this function.
Odd, then why is it exported???
> + */
> +void devm_mux_chip_unregister(struct device *dev, struct mux_chip *mux_chip);
> +
> +/**
> + * devm_mux_chip_free() - Resource-managed version mux_chip_free().
> + * @dev: The device that originally got the mux-chip.
> + * @mux_chip: The mux-chip to free.
> + *
> + * See mux_chip_free() for more details.
> + *
> + * Note that you do not normally need to call this function.
> + */
> +void devm_mux_chip_free(struct device *dev, struct mux_chip *mux_chip);
> +
> +/**
> + * mux_control_select() - Select the given multiplexer state.
> + * @mux: The mux-control to request a change of state from.
> + * @state: The new requested state.
> + *
> + * Make sure to call mux_control_deselect() when the operation is complete and
> + * the mux-control is free for others to use, but do not call
> + * mux_control_deselect() if mux_control_select() fails.
> + *
> + * Return: 0 if the requested state was already active, or 1 it the
> + * mux-control state was changed to the requested state. Or a negative
> + * errno on error.
> + *
> + * Note that the difference in return value of zero or one is of
> + * questionable value; especially if the mux-control has several independent
> + * consumers, which is something the consumers should perhaps not be making
> + * assumptions about.
I don't understand this note, what is a user of this api supposed to do
differently between 1 and 0? Why make the difference at all?
And I agree with the comment to split this up into 2 different .h files,
if possible.
thanks,
greg k-h
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller
2017-04-18 8:51 ` Greg Kroah-Hartman
@ 2017-04-18 10:59 ` Peter Rosin
[not found] ` <bdeecccf-02d6-226b-8516-1d41e3602a7a-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
2017-05-05 13:19 ` Peter Rosin
0 siblings, 2 replies; 38+ messages in thread
From: Peter Rosin @ 2017-04-18 10:59 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: linux-kernel, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, linux-i2c, devicetree,
linux-iio, linux-doc, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel
On 2017-04-18 10:51, Greg Kroah-Hartman wrote:
> On Thu, Apr 13, 2017 at 06:43:07PM +0200, Peter Rosin wrote:
>> +config MUX_GPIO
>> + tristate "GPIO-controlled Multiplexer"
>> + depends on OF && GPIOLIB
>
> Why have the gpio and mux core in the same patch?
One is not usable w/o the other. I can split them if you want to?
> And why does this depend on OF?
That's historical, I was originally using of_property_read_u32.
I'll remove the dep...
>> + help
>> + GPIO-controlled Multiplexer controller.
>> +
>> + The driver builds a single multiplexer controller using a number
>> + of gpio pins. For N pins, there will be 2^N possible multiplexer
>> + states. The GPIO pins can be connected (by the hardware) to several
>> + multiplexers, which in that case will be operated in parallel.
>> +
>> + To compile the driver as a module, choose M here: the module will
>> + be called mux-gpio.
>> +
>> +endif
>> diff --git a/drivers/mux/Makefile b/drivers/mux/Makefile
>> new file mode 100644
>> index 000000000000..bb16953f6290
>> --- /dev/null
>> +++ b/drivers/mux/Makefile
>> @@ -0,0 +1,6 @@
>> +#
>> +# Makefile for multiplexer devices.
>> +#
>> +
>> +obj-$(CONFIG_MULTIPLEXER) += mux-core.o
>> +obj-$(CONFIG_MUX_GPIO) += mux-gpio.o
>> diff --git a/drivers/mux/mux-core.c b/drivers/mux/mux-core.c
>> new file mode 100644
>> index 000000000000..66a8bccfc3d7
>> --- /dev/null
>> +++ b/drivers/mux/mux-core.c
>> @@ -0,0 +1,422 @@
>> +/*
>> + * Multiplexer subsystem
>> + *
>> + * Copyright (C) 2017 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.
>> + */
>> +
>> +#define pr_fmt(fmt) "mux-core: " fmt
>> +
>> +#include <linux/device.h>
>> +#include <linux/err.h>
>> +#include <linux/export.h>
>> +#include <linux/idr.h>
>> +#include <linux/init.h>
>> +#include <linux/module.h>
>> +#include <linux/mux.h>
>> +#include <linux/of.h>
>> +#include <linux/of_platform.h>
>> +#include <linux/slab.h>
>> +
>> +/*
>> + * The idle-as-is "state" is not an actual state that may be selected, it
>> + * only implies that the state should not be changed. So, use that state
>> + * as indication that the cached state of the multiplexer is unknown.
>> + */
>> +#define MUX_CACHE_UNKNOWN MUX_IDLE_AS_IS
>> +
>> +static struct class mux_class = {
>> + .name = "mux",
>> + .owner = THIS_MODULE,
>> +};
>
> No Documentation/ABI/ update for your sysfs files? Please do so.
Ok I'll look into it. Wasn't even aware that I added any. But there's the
new class of course...
>> +
>> +static int __init mux_init(void)
>> +{
>> + return class_register(&mux_class);
>> +}
>> +
>> +static DEFINE_IDA(mux_ida);
>
> When your module is unloaded, you forgot to clean this structure up with
> what was done with it.
I was under the impression that not providing an exit function for modules
made the module infrastructure prevent unloading (by bumping some reference
counter). Maybe that is a misconception?
>> +
>> +static void mux_chip_release(struct device *dev)
>> +{
>> + struct mux_chip *mux_chip = to_mux_chip(dev);
>> +
>> + ida_simple_remove(&mux_ida, mux_chip->id);
>> + kfree(mux_chip);
>> +}
>> +
>> +static struct device_type mux_type = {
>> + .name = "mux-chip",
>> + .release = mux_chip_release,
>> +};
>> +
>> +struct mux_chip *mux_chip_alloc(struct device *dev,
>> + unsigned int controllers, size_t sizeof_priv)
>> +{
>> + struct mux_chip *mux_chip;
>> + int i;
>> +
>> + if (WARN_ON(!dev || !controllers))
>> + return NULL;
>> +
>> + mux_chip = kzalloc(sizeof(*mux_chip) +
>> + controllers * sizeof(*mux_chip->mux) +
>> + sizeof_priv, GFP_KERNEL);
>> + if (!mux_chip)
>> + return NULL;
>
> You don't return PTR_ERR(-ENOMEM)? Ok, why not? (I'm not arguing for
> it, just curious...)
There's no particular reason. Do you think I should change it?
>> +
>> + mux_chip->mux = (struct mux_control *)(mux_chip + 1);
>> + mux_chip->dev.class = &mux_class;
>> + mux_chip->dev.type = &mux_type;
>> + mux_chip->dev.parent = dev;
>> + mux_chip->dev.of_node = dev->of_node;
>> + dev_set_drvdata(&mux_chip->dev, mux_chip);
>> +
>> + mux_chip->id = ida_simple_get(&mux_ida, 0, 0, GFP_KERNEL);
>> + if (mux_chip->id < 0) {
>> + pr_err("muxchipX failed to get a device id\n");
>> + kfree(mux_chip);
>> + return NULL;
>> + }
>> + dev_set_name(&mux_chip->dev, "muxchip%d", mux_chip->id);
>> +
>> + mux_chip->controllers = controllers;
>> + for (i = 0; i < controllers; ++i) {
>> + struct mux_control *mux = &mux_chip->mux[i];
>> +
>> + mux->chip = mux_chip;
>> + init_rwsem(&mux->lock);
>> + mux->cached_state = MUX_CACHE_UNKNOWN;
>> + mux->idle_state = MUX_IDLE_AS_IS;
>> + }
>> +
>> + device_initialize(&mux_chip->dev);
>
> Why are you not registering the device here as well? Why have this be a
> two step process?
Because of idle state handling. The drivers are expected to fill in
the desired idle state(s) after allocating the mux controller(s).
Then, when registering, the desired idle state is activated (if the
idle state is not idle-as-is, of course) and as a last step the mux
is "advertised".
>> +
>> + return mux_chip;
>> +}
>> +EXPORT_SYMBOL_GPL(mux_chip_alloc);
>> +
>> +static int mux_control_set(struct mux_control *mux, int state)
>> +{
>> + int ret = mux->chip->ops->set(mux, state);
>> +
>> + mux->cached_state = ret < 0 ? MUX_CACHE_UNKNOWN : state;
>> +
>> + return ret;
>> +}
>> +
>> +int mux_chip_register(struct mux_chip *mux_chip)
>> +{
>> + int i;
>> + int ret;
>> +
>> + for (i = 0; i < mux_chip->controllers; ++i) {
>> + struct mux_control *mux = &mux_chip->mux[i];
>> +
>> + if (mux->idle_state == mux->cached_state)
>> + continue;
>> +
>> + ret = mux_control_set(mux, mux->idle_state);
>> + if (ret < 0) {
>> + dev_err(&mux_chip->dev, "unable to set idle state\n");
>> + return ret;
>> + }
>> + }
>> +
>> + ret = device_add(&mux_chip->dev);
>> + if (ret < 0)
>> + dev_err(&mux_chip->dev,
>> + "device_add failed in mux_chip_register: %d\n", ret);
>
> Did you run checkpatch.pl in strict mode on this new file? Please do so :)
I did, and did it again just to be sure, and I do not get any complaints.
So, what's wrong?
$ scripts/checkpatch.pl --strict mux-13/0003-mux-minimal-mux-subsystem-and-gpio-based-mux-control.patch
total: 0 errors, 0 warnings, 0 checks, 860 lines checked
mux-13/0003-mux-minimal-mux-subsystem-and-gpio-based-mux-control.patch has no obvious style problems and is ready for submission.
The chackpatch warnings I am aware of are three instances of (for the
'prop', 's' and 'i' arguments):
mux-13/0006-iio-multiplexer-new-iio-category-and-iio-mux-driver.patch
---------------------------------------------------------------------
CHECK: Macro argument reuse 'prop' - possible side-effects?
#433: FILE: drivers/iio/multiplexer/iio-mux.c:326:
+#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++)
But those kinds of warnings are also present in the code I plagiarized,
so I don't feel too bad...
And then there are a couple of false positives about files added w/o
adding an entry to MAINTAINERS (the files are covers by wildcards).
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(mux_chip_register);
>> +
>> +void mux_chip_unregister(struct mux_chip *mux_chip)
>> +{
>> + device_del(&mux_chip->dev);
>> +}
>> +EXPORT_SYMBOL_GPL(mux_chip_unregister);
>> +
>> +void mux_chip_free(struct mux_chip *mux_chip)
>> +{
>> + if (!mux_chip)
>> + return;
>> +
>> + put_device(&mux_chip->dev);
>> +}
>> +EXPORT_SYMBOL_GPL(mux_chip_free);
>> +
>> +static void devm_mux_chip_release(struct device *dev, void *res)
>> +{
>> + struct mux_chip *mux_chip = *(struct mux_chip **)res;
>> +
>> + mux_chip_free(mux_chip);
>> +}
>> +
>> +struct mux_chip *devm_mux_chip_alloc(struct device *dev,
>> + unsigned int controllers,
>> + size_t sizeof_priv)
>> +{
>> + struct mux_chip **ptr, *mux_chip;
>> +
>> + ptr = devres_alloc(devm_mux_chip_release, sizeof(*ptr), GFP_KERNEL);
>> + if (!ptr)
>> + return NULL;
>> +
>> + mux_chip = mux_chip_alloc(dev, controllers, sizeof_priv);
>> + if (!mux_chip) {
>> + devres_free(ptr);
>> + return NULL;
>> + }
>> +
>> + *ptr = mux_chip;
>> + devres_add(dev, ptr);
>> +
>> + return mux_chip;
>> +}
>> +EXPORT_SYMBOL_GPL(devm_mux_chip_alloc);
>
>
> Having devm functions that create/destroy other struct devices worries
> me, do we have other examples of this happening today? Are you sure you
> got the reference counting all correct?
drivers/iio/industrialio_core.c:devm_iio_device_alloc
Or is the iio case different in some subtle way that I'm missing?
>> +
>> +static int devm_mux_chip_match(struct device *dev, void *res, void *data)
>> +{
>> + struct mux_chip **r = res;
>> +
>> + if (WARN_ON(!r || !*r))
>
> How can this happen?
It shouldn't. I copied the pattern from the iio subsystem.
>> + return 0;
>> +
>> + return *r == data;
>> +}
>> +
>> +void devm_mux_chip_free(struct device *dev, struct mux_chip *mux_chip)
>> +{
>> + WARN_ON(devres_release(dev, devm_mux_chip_release,
>> + devm_mux_chip_match, mux_chip));
>
> What can someone do with these WARN_ON() splats in the kernel log?
Don't know. Again, I copied the pattern from the iio subsystem.
>
>> +}
>> +EXPORT_SYMBOL_GPL(devm_mux_chip_free);
>> +
>> +static void devm_mux_chip_reg_release(struct device *dev, void *res)
>> +{
>> + struct mux_chip *mux_chip = *(struct mux_chip **)res;
>> +
>> + mux_chip_unregister(mux_chip);
>> +}
>> +
>> +int devm_mux_chip_register(struct device *dev,
>> + struct mux_chip *mux_chip)
>> +{
>> + struct mux_chip **ptr;
>> + int res;
>> +
>> + ptr = devres_alloc(devm_mux_chip_reg_release, sizeof(*ptr), GFP_KERNEL);
>> + if (!ptr)
>> + return -ENOMEM;
>> +
>> + res = mux_chip_register(mux_chip);
>> + if (res) {
>> + devres_free(ptr);
>> + return res;
>> + }
>> +
>> + *ptr = mux_chip;
>> + devres_add(dev, ptr);
>> +
>> + return res;
>> +}
>> +EXPORT_SYMBOL_GPL(devm_mux_chip_register);
>> +
>> +void devm_mux_chip_unregister(struct device *dev, struct mux_chip *mux_chip)
>> +{
>> + WARN_ON(devres_release(dev, devm_mux_chip_reg_release,
>> + devm_mux_chip_match, mux_chip));
>> +}
>> +EXPORT_SYMBOL_GPL(devm_mux_chip_unregister);
>> +
>> +int mux_control_select(struct mux_control *mux, int state)
>> +{
>> + int ret;
>> +
>> + if (down_read_trylock(&mux->lock)) {
>> + if (mux->cached_state == state)
>> + return 0;
>> +
>> + /* Sigh, the mux needs updating... */
>> + up_read(&mux->lock);
>> + }
>> +
>> + /* ...or it's just contended. */
>> + down_write(&mux->lock);
>
> Why use a read/write lock at all? Have you tested this to verify it
> really is faster and needed?
For one of the HW configuration that drove the development, the same mux
controller is used to mux both an I2C channel and a couple of ADC lines.
If there is no kind of reader/writer locking going on, there is no way to
do ADC readings concurrently with an I2C transfer even when the consumers
want the mux in the same position. With an ordinary mutex controlling the
mux position, the consumers will unconditionally get serialized, which
seems like a waste to me. Or maybe I'm missing something?
>> +
>> + if (mux->cached_state == state) {
>> + /*
>> + * Hmmm, someone else changed the mux to my liking.
>> + * That makes me wonder how long I waited for nothing?
>> + */
>> + downgrade_write(&mux->lock);
>
> Oh that always scares me... Are you _sure_ this is correct? And
> needed?
It might not be needed, and it would probably work ok to just fall
through and call mux_control_set unconditionally. What is it that
always scares you exactly? Relying on cached state to be correct?
Downgrading writer locks?
>> + return 0;
>> + }
>> +
>> + ret = mux_control_set(mux, state);
>> + if (ret < 0) {
>> + if (mux->idle_state != MUX_IDLE_AS_IS)
>> + mux_control_set(mux, mux->idle_state);
>> +
>> + up_write(&mux->lock);
>> + return ret;
>> + }
>> +
>> + downgrade_write(&mux->lock);
>> +
>> + return 1;
>> +}
>> +EXPORT_SYMBOL_GPL(mux_control_select);
>> +
>> +int mux_control_deselect(struct mux_control *mux)
>> +{
>> + int ret = 0;
>> +
>> + if (mux->idle_state != MUX_IDLE_AS_IS &&
>> + mux->idle_state != mux->cached_state)
>> + ret = mux_control_set(mux, mux->idle_state);
>> +
>> + up_read(&mux->lock);
>
> You require a lock to be held for a "global" function? Without
> documentation? Or even a sparse marking? That's asking for trouble...
Documentation I can handle, but where should I look to understand how I
should add sparse markings?
The mux needs to be locked somehow. But as I stated in the cover letter
the rwsem isn't a perfect fit.
I'm using an rwsem to lock a mux, but that isn't really a
perfect fit. Is there a better locking primitive that I don't
know about that fits better? I had a mutex at one point, but
that didn't allow any concurrent accesses at all. At least
the rwsem allows concurrent access as long as all users
agree on the mux state, but I suspect that the rwsem will
degrade to the mutex situation pretty quickly if there is
any contention.
Also, the lock doesn't add anything if there is only one consumer of
a mux controller. Maybe there should be some mechanism for shortcutting
the locking for the (more common?) single-consumer case?
But again, I need the locking for my multi-consumer use case.
>> +
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(mux_control_deselect);
>> +
>> +static int of_dev_node_match(struct device *dev, const void *data)
>> +{
>> + return dev->of_node == data;
>> +}
>> +
>> +static struct mux_chip *of_find_mux_chip_by_node(struct device_node *np)
>> +{
>> + struct device *dev;
>> +
>> + dev = class_find_device(&mux_class, NULL, np, of_dev_node_match);
>> +
>> + return dev ? to_mux_chip(dev) : NULL;
>> +}
>> +
>> +struct mux_control *mux_control_get(struct device *dev, const char *mux_name)
>> +{
>> + struct device_node *np = dev->of_node;
>> + struct of_phandle_args args;
>> + struct mux_chip *mux_chip;
>> + unsigned int controller;
>> + int index = 0;
>> + int ret;
>> +
>> + if (mux_name) {
>> + index = of_property_match_string(np, "mux-control-names",
>> + mux_name);
>> + if (index < 0) {
>> + dev_err(dev, "mux controller '%s' not found\n",
>> + mux_name);
>> + return ERR_PTR(index);
>> + }
>> + }
>> +
>> + ret = of_parse_phandle_with_args(np,
>> + "mux-controls", "#mux-control-cells",
>> + index, &args);
>> + if (ret) {
>> + dev_err(dev, "%s: failed to get mux-control %s(%i)\n",
>> + np->full_name, mux_name ?: "", index);
>> + return ERR_PTR(ret);
>> + }
>> +
>> + mux_chip = of_find_mux_chip_by_node(args.np);
>> + of_node_put(args.np);
>> + if (!mux_chip)
>> + return ERR_PTR(-EPROBE_DEFER);
>> +
>> + if (args.args_count > 1 ||
>> + (!args.args_count && (mux_chip->controllers > 1))) {
>> + dev_err(dev, "%s: wrong #mux-control-cells for %s\n",
>> + np->full_name, args.np->full_name);
>> + return ERR_PTR(-EINVAL);
>> + }
>> +
>> + controller = 0;
>> + if (args.args_count)
>> + controller = args.args[0];
>> +
>> + if (controller >= mux_chip->controllers) {
>> + dev_err(dev, "%s: bad mux controller %u specified in %s\n",
>> + np->full_name, controller, args.np->full_name);
>> + return ERR_PTR(-EINVAL);
>> + }
>> +
>> + get_device(&mux_chip->dev);
>> + return &mux_chip->mux[controller];
>> +}
>> +EXPORT_SYMBOL_GPL(mux_control_get);
>> +
>> +void mux_control_put(struct mux_control *mux)
>> +{
>> + put_device(&mux->chip->dev);
>> +}
>> +EXPORT_SYMBOL_GPL(mux_control_put);
>> +
>> +static void devm_mux_control_release(struct device *dev, void *res)
>> +{
>> + struct mux_control *mux = *(struct mux_control **)res;
>> +
>> + mux_control_put(mux);
>> +}
>> +
>> +struct mux_control *devm_mux_control_get(struct device *dev,
>> + const char *mux_name)
>> +{
>> + struct mux_control **ptr, *mux;
>> +
>> + ptr = devres_alloc(devm_mux_control_release, sizeof(*ptr), GFP_KERNEL);
>> + if (!ptr)
>> + return ERR_PTR(-ENOMEM);
>> +
>> + mux = mux_control_get(dev, mux_name);
>> + if (IS_ERR(mux)) {
>> + devres_free(ptr);
>> + return mux;
>> + }
>> +
>> + *ptr = mux;
>> + devres_add(dev, ptr);
>> +
>> + return mux;
>> +}
>> +EXPORT_SYMBOL_GPL(devm_mux_control_get);
>> +
>> +static int devm_mux_control_match(struct device *dev, void *res, void *data)
>> +{
>> + struct mux_control **r = res;
>> +
>> + if (WARN_ON(!r || !*r))
>> + return 0;
>
> Same here, how can this happen?
Same response as above.
>> +
>> + return *r == data;
>> +}
>> +
>> +void devm_mux_control_put(struct device *dev, struct mux_control *mux)
>> +{
>> + WARN_ON(devres_release(dev, devm_mux_control_release,
>> + devm_mux_control_match, mux));
>> +}
>> +EXPORT_SYMBOL_GPL(devm_mux_control_put);
>> +
>> +/*
>> + * Using subsys_initcall instead of module_init here to ensure - for the
>> + * non-modular case - that the subsystem is initialized when mux consumers
>> + * and mux controllers start to use it /without/ relying on link order.
>> + * For the modular case, the ordering is ensured with module dependencies.
>> + */
>> +subsys_initcall(mux_init);
>
> Even with subsys_initcall you are relying on link order, you do realize
> that? What about other subsystems that rely on this? :)
Yes, that is true, but if others start relying on this, that's their problem,
right? :-)
>
>> +
>> +MODULE_DESCRIPTION("Multiplexer subsystem");
>> +MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/drivers/mux/mux-gpio.c b/drivers/mux/mux-gpio.c
>> new file mode 100644
>> index 000000000000..227d3572e6db
>> --- /dev/null
>> +++ b/drivers/mux/mux-gpio.c
>> @@ -0,0 +1,114 @@
>> +/*
>> + * GPIO-controlled multiplexer driver
>> + *
>> + * Copyright (C) 2017 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/gpio/consumer.h>
>> +#include <linux/module.h>
>> +#include <linux/mux.h>
>> +#include <linux/of_platform.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/property.h>
>> +
>> +struct mux_gpio {
>> + struct gpio_descs *gpios;
>> + int *val;
>> +};
>> +
>> +static int mux_gpio_set(struct mux_control *mux, int state)
>> +{
>> + struct mux_gpio *mux_gpio = mux_chip_priv(mux->chip);
>> + int i;
>> +
>> + for (i = 0; i < mux_gpio->gpios->ndescs; i++)
>> + mux_gpio->val[i] = (state >> i) & 1;
>> +
>> + gpiod_set_array_value_cansleep(mux_gpio->gpios->ndescs,
>> + mux_gpio->gpios->desc,
>> + mux_gpio->val);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct mux_control_ops mux_gpio_ops = {
>> + .set = mux_gpio_set,
>> +};
>> +
>> +static const struct of_device_id mux_gpio_dt_ids[] = {
>> + { .compatible = "gpio-mux", },
>> + { /* sentinel */ }
>> +};
>> +MODULE_DEVICE_TABLE(of, mux_gpio_dt_ids);
>> +
>> +static int mux_gpio_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct mux_chip *mux_chip;
>> + struct mux_gpio *mux_gpio;
>> + int pins;
>> + s32 idle_state;
>> + int ret;
>> +
>> + pins = gpiod_count(dev, "mux");
>> + if (pins < 0)
>> + return pins;
>> +
>> + mux_chip = devm_mux_chip_alloc(dev, 1, sizeof(*mux_gpio) +
>> + pins * sizeof(*mux_gpio->val));
>> + if (!mux_chip)
>> + return -ENOMEM;
>> +
>> + mux_gpio = mux_chip_priv(mux_chip);
>> + mux_gpio->val = (int *)(mux_gpio + 1);
>> + mux_chip->ops = &mux_gpio_ops;
>> +
>> + mux_gpio->gpios = devm_gpiod_get_array(dev, "mux", GPIOD_OUT_LOW);
>> + if (IS_ERR(mux_gpio->gpios)) {
>> + ret = PTR_ERR(mux_gpio->gpios);
>> + if (ret != -EPROBE_DEFER)
>> + dev_err(dev, "failed to get gpios\n");
>> + return ret;
>> + }
>> + WARN_ON(pins != mux_gpio->gpios->ndescs);
>> + mux_chip->mux->states = 1 << pins;
>> +
>> + ret = device_property_read_u32(dev, "idle-state", (u32 *)&idle_state);
>> + if (ret >= 0 && idle_state != MUX_IDLE_AS_IS) {
>> + if (idle_state < 0 || idle_state >= mux_chip->mux->states) {
>> + dev_err(dev, "invalid idle-state %u\n", idle_state);
>> + return -EINVAL;
>> + }
>> +
>> + mux_chip->mux->idle_state = idle_state;
>> + }
>> +
>> + ret = devm_mux_chip_register(dev, mux_chip);
>> + if (ret < 0)
>> + return ret;
>> +
>> + dev_info(dev, "%u-way mux-controller registered\n",
>> + mux_chip->mux->states);
>> +
>> + return 0;
>> +}
>> +
>> +static struct platform_driver mux_gpio_driver = {
>> + .driver = {
>> + .name = "gpio-mux",
>> + .of_match_table = of_match_ptr(mux_gpio_dt_ids),
>> + },
>> + .probe = mux_gpio_probe,
>> +};
>> +module_platform_driver(mux_gpio_driver);
>> +
>> +MODULE_DESCRIPTION("GPIO-controlled multiplexer driver");
>> +MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/include/linux/mux.h b/include/linux/mux.h
>> new file mode 100644
>> index 000000000000..febdde4246df
>> --- /dev/null
>> +++ b/include/linux/mux.h
>> @@ -0,0 +1,252 @@
>> +/*
>> + * mux.h - definitions for the multiplexer interface
>> + *
>> + * Copyright (C) 2017 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.
>> + */
>> +
>> +#ifndef _LINUX_MUX_H
>> +#define _LINUX_MUX_H
>> +
>> +#include <linux/device.h>
>> +#include <linux/rwsem.h>
>> +
>> +struct mux_chip;
>> +struct mux_control;
>> +struct platform_device;
>> +
>> +/**
>> + * struct mux_control_ops - Mux controller operations for a mux chip.
>> + * @set: Set the state of the given mux controller.
>> + */
>> +struct mux_control_ops {
>> + int (*set)(struct mux_control *mux, int state);
>> +};
>> +
>> +/* These defines match the constants from the dt-bindings. On purpose. */
>
> Why on purpose?
I sure wasn't an accident? :-)
Want me to remove it?
>> +#define MUX_IDLE_AS_IS (-1)
>> +#define MUX_IDLE_DISCONNECT (-2)
>> +
>> +/**
>> + * struct mux_control - Represents a mux controller.
>> + * @lock: Protects the mux controller state.
>> + * @chip: The mux chip that is handling this mux controller.
>> + * @states: The number of mux controller states.
>> + * @cached_state: The current mux controller state, or -1 if none.
>> + * @idle_state: The mux controller state to use when inactive, or one
>> + * of MUX_IDLE_AS_IS and MUX_IDLE_DISCONNECT.
>> + */
>> +struct mux_control {
>> + struct rw_semaphore lock; /* protects the state of the mux */
>> +
>> + struct mux_chip *chip;
>> +
>> + unsigned int states;
>> + int cached_state;
>> + int idle_state;
>> +};
>> +
>> +/**
>> + * struct mux_chip - Represents a chip holding mux controllers.
>> + * @controllers: Number of mux controllers handled by the chip.
>> + * @mux: Array of mux controllers that are handled.
>> + * @dev: Device structure.
>> + * @id: Used to identify the device internally.
>> + * @ops: Mux controller operations.
>> + */
>> +struct mux_chip {
>> + unsigned int controllers;
>> + struct mux_control *mux;
>> + struct device dev;
>> + int id;
>> +
>> + const struct mux_control_ops *ops;
>> +};
>> +
>> +#define to_mux_chip(x) container_of((x), struct mux_chip, dev)
>> +
>> +/**
>> + * mux_chip_priv() - Get the extra memory reserved by mux_chip_alloc().
>> + * @mux_chip: The mux-chip to get the private memory from.
>> + *
>> + * Return: Pointer to the private memory reserved by the allocator.
>> + */
>> +static inline void *mux_chip_priv(struct mux_chip *mux_chip)
>> +{
>> + return &mux_chip->mux[mux_chip->controllers];
>> +}
>> +
>> +/**
>> + * mux_chip_alloc() - Allocate a mux-chip.
>> + * @dev: The parent device implementing the mux interface.
>> + * @controllers: The number of mux controllers to allocate for this chip.
>> + * @sizeof_priv: Size of extra memory area for private use by the caller.
>> + *
>> + * Return: A pointer to the new mux-chip, NULL on failure.
>> + */
>> +struct mux_chip *mux_chip_alloc(struct device *dev,
>> + unsigned int controllers, size_t sizeof_priv);
>> +
>
> Don't put kernel doc comments in a .h file, they normally go into the .c
> file, next to the code itself. That makes it easier to fix up and
> realise when they need to be changed when the code changes. The .h file
> rarely changes.
I'll move the lot over.
>> +/**
>> + * mux_chip_register() - Register a mux-chip, thus readying the controllers
>> + * for use.
>> + * @mux_chip: The mux-chip to register.
>> + *
>> + * Do not retry registration of the same mux-chip on failure. You should
>> + * instead put it away with mux_chip_free() and allocate a new one, if you
>> + * for some reason would like to retry registration.
>> + *
>> + * Return: Zero on success or a negative errno on error.
>> + */
>> +int mux_chip_register(struct mux_chip *mux_chip);
>> +
>> +/**
>> + * mux_chip_unregister() - Take the mux-chip off-line.
>> + * @mux_chip: The mux-chip to unregister.
>> + *
>> + * mux_chip_unregister() reverses the effects of mux_chip_register().
>> + * But not completely, you should not try to call mux_chip_register()
>> + * on a mux-chip that has been registered before.
>> + */
>> +void mux_chip_unregister(struct mux_chip *mux_chip);
>> +
>> +/**
>> + * mux_chip_free() - Free the mux-chip for good.
>> + * @mux_chip: The mux-chip to free.
>> + *
>> + * mux_chip_free() reverses the effects of mux_chip_alloc().
>> + */
>> +void mux_chip_free(struct mux_chip *mux_chip);
>> +
>> +/**
>> + * devm_mux_chip_alloc() - Resource-managed version of mux_chip_alloc().
>> + * @dev: The parent device implementing the mux interface.
>> + * @controllers: The number of mux controllers to allocate for this chip.
>> + * @sizeof_priv: Size of extra memory area for private use by the caller.
>> + *
>> + * See mux_chip_alloc() for more details.
>> + *
>> + * Return: A pointer to the new mux-chip, NULL on failure.
>> + */
>> +struct mux_chip *devm_mux_chip_alloc(struct device *dev,
>> + unsigned int controllers,
>> + size_t sizeof_priv);
>> +
>> +/**
>> + * devm_mux_chip_register() - Resource-managed version mux_chip_register().
>> + * @dev: The parent device implementing the mux interface.
>> + * @mux_chip: The mux-chip to register.
>> + *
>> + * See mux_chip_register() for more details.
>> + *
>> + * Return: Zero on success or a negative errno on error.
>> + */
>> +int devm_mux_chip_register(struct device *dev, struct mux_chip *mux_chip);
>> +
>> +/**
>> + * devm_mux_chip_unregister() - Resource-managed version mux_chip_unregister().
>> + * @dev: The device that originally registered the mux-chip.
>> + * @mux_chip: The mux-chip to unregister.
>> + *
>> + * See mux_chip_unregister() for more details.
>> + *
>> + * Note that you do not normally need to call this function.
>
> Odd, then why is it exported???
You normally don't call the devm_foo_{free,release,unregister,etc} functions.
The intention is of course that the resourse cleans up automatically. But there
are no cases where the manual clean up is not available, at least not that I can
find?
>> + */
>> +void devm_mux_chip_unregister(struct device *dev, struct mux_chip *mux_chip);
>> +
>> +/**
>> + * devm_mux_chip_free() - Resource-managed version mux_chip_free().
>> + * @dev: The device that originally got the mux-chip.
>> + * @mux_chip: The mux-chip to free.
>> + *
>> + * See mux_chip_free() for more details.
>> + *
>> + * Note that you do not normally need to call this function.
>> + */
>> +void devm_mux_chip_free(struct device *dev, struct mux_chip *mux_chip);
>> +
>> +/**
>> + * mux_control_select() - Select the given multiplexer state.
>> + * @mux: The mux-control to request a change of state from.
>> + * @state: The new requested state.
>> + *
>> + * Make sure to call mux_control_deselect() when the operation is complete and
>> + * the mux-control is free for others to use, but do not call
>> + * mux_control_deselect() if mux_control_select() fails.
>> + *
>> + * Return: 0 if the requested state was already active, or 1 it the
>> + * mux-control state was changed to the requested state. Or a negative
>> + * errno on error.
>> + *
>> + * Note that the difference in return value of zero or one is of
>> + * questionable value; especially if the mux-control has several independent
>> + * consumers, which is something the consumers should perhaps not be making
>> + * assumptions about.
>
> I don't understand this note, what is a user of this api supposed to do
> differently between 1 and 0? Why make the difference at all?
If the consumer somehow *knows* that it is the only user of a mux controller,
it can use the return value to shortcut (perhaps costly) actions only needed
when the mux changes. The 1/0 difference was also a "free" extra given the
current implementation of mux_control_select. But it's cheep for the consumer
to keep track of this by itself if it needs it.
It's when there are several (independent) consumers of a mux controller that
the information in the return value is questionable and can't be used to
shortcut actions only needed on mux changes.
Now that you point the finger at it and I have been made to spell out the
problem, I think it's probably wise to remove the distiction so that users
do not start to use the return value when they shouldn't. It's generally
better to keep track of the expected mux state in the consumer and use that
local information to shortcut those (perhaps costly) actions instead.
> And I agree with the comment to split this up into 2 different .h files,
> if possible.
Will split.
> thanks,
>
> greg k-h
>
I'll wait for further feedback before posting a v14.
Cheers and thanks,
peda
^ permalink raw reply [flat|nested] 38+ messages in thread
[parent not found: <bdeecccf-02d6-226b-8516-1d41e3602a7a-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>]
* Re: [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller
[not found] ` <bdeecccf-02d6-226b-8516-1d41e3602a7a-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
@ 2017-04-18 11:44 ` Greg Kroah-Hartman
2017-04-18 21:53 ` Peter Rosin
0 siblings, 1 reply; 38+ messages in thread
From: Greg Kroah-Hartman @ 2017-04-18 11:44 UTC (permalink / raw)
To: Peter Rosin
Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA, Wolfram Sang, Rob Herring,
Mark Rutland, Jonathan Cameron, Hartmut Knaack,
Lars-Peter Clausen, Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel-bIcnvbaLZ9MEGnE8C9+IrQ
On Tue, Apr 18, 2017 at 12:59:50PM +0200, Peter Rosin wrote:
> On 2017-04-18 10:51, Greg Kroah-Hartman wrote:
> > On Thu, Apr 13, 2017 at 06:43:07PM +0200, Peter Rosin wrote:
> >> +config MUX_GPIO
> >> + tristate "GPIO-controlled Multiplexer"
> >> + depends on OF && GPIOLIB
> >
> > Why have the gpio and mux core in the same patch?
>
> One is not usable w/o the other. I can split them if you want to?
Then why are they two different config options? Add the core code in
one patch, and then add the gpio controled mutiplexer in a separate
patch.
> >> +static struct class mux_class = {
> >> + .name = "mux",
> >> + .owner = THIS_MODULE,
> >> +};
> >
> > No Documentation/ABI/ update for your sysfs files? Please do so.
>
> Ok I'll look into it. Wasn't even aware that I added any. But there's the
> new class of course...
Hint, you have files, the devices that belong to the class :)
> >> +static int __init mux_init(void)
> >> +{
> >> + return class_register(&mux_class);
> >> +}
> >> +
> >> +static DEFINE_IDA(mux_ida);
> >
> > When your module is unloaded, you forgot to clean this structure up with
> > what was done with it.
>
> I was under the impression that not providing an exit function for modules
> made the module infrastructure prevent unloading (by bumping some reference
> counter). Maybe that is a misconception?
Ah, messy, don't do that. Make it so you can unload your module please,
why wouldn't you want that to happen?
> >> + mux_chip = kzalloc(sizeof(*mux_chip) +
> >> + controllers * sizeof(*mux_chip->mux) +
> >> + sizeof_priv, GFP_KERNEL);
> >> + if (!mux_chip)
> >> + return NULL;
> >
> > You don't return PTR_ERR(-ENOMEM)? Ok, why not? (I'm not arguing for
> > it, just curious...)
>
> There's no particular reason. Do you think I should change it?
What does the caller do with an error? Pass it up to where? Who gets
it? Don't you want the caller to know you are out of memory?
> >> +
> >> + device_initialize(&mux_chip->dev);
> >
> > Why are you not registering the device here as well? Why have this be a
> > two step process?
>
> Because of idle state handling. The drivers are expected to fill in
> the desired idle state(s) after allocating the mux controller(s).
> Then, when registering, the desired idle state is activated (if the
> idle state is not idle-as-is, of course) and as a last step the mux
> is "advertised".
Ok, is that documented in the functions somewhere?
> >> + ret = device_add(&mux_chip->dev);
> >> + if (ret < 0)
> >> + dev_err(&mux_chip->dev,
> >> + "device_add failed in mux_chip_register: %d\n", ret);
> >
> > Did you run checkpatch.pl in strict mode on this new file? Please do so :)
>
> I did, and did it again just to be sure, and I do not get any complaints.
> So, what's wrong?
You list the function name in the printk string, it should complain
that __func__ should be used. Oh well, it's just a perl script, it
doesn't always catch everything.
isn't always correct :)
> >> +EXPORT_SYMBOL_GPL(devm_mux_chip_alloc);
> >
> >
> > Having devm functions that create/destroy other struct devices worries
> > me, do we have other examples of this happening today? Are you sure you
> > got the reference counting all correct?
>
> drivers/iio/industrialio_core.c:devm_iio_device_alloc
>
> Or is the iio case different in some subtle way that I'm missing?
I don't know, hopefully you got it all correct, I haven't audited that
code path in a long time :)
> >> +
> >> +static int devm_mux_chip_match(struct device *dev, void *res, void *data)
> >> +{
> >> + struct mux_chip **r = res;
> >> +
> >> + if (WARN_ON(!r || !*r))
> >
> > How can this happen?
>
> It shouldn't. I copied the pattern from the iio subsystem.
Then it should be removed there too...
> >> +void devm_mux_chip_free(struct device *dev, struct mux_chip *mux_chip)
> >> +{
> >> + WARN_ON(devres_release(dev, devm_mux_chip_release,
> >> + devm_mux_chip_match, mux_chip));
> >
> > What can someone do with these WARN_ON() splats in the kernel log?
>
> Don't know. Again, I copied the pattern from the iio subsystem.
If you don't know what it should be used for, don't copy it!
Cargo-cult coding is horrible, please no.
> >> + /* ...or it's just contended. */
> >> + down_write(&mux->lock);
> >
> > Why use a read/write lock at all? Have you tested this to verify it
> > really is faster and needed?
>
> For one of the HW configuration that drove the development, the same mux
> controller is used to mux both an I2C channel and a couple of ADC lines.
>
> If there is no kind of reader/writer locking going on, there is no way to
> do ADC readings concurrently with an I2C transfer even when the consumers
> want the mux in the same position. With an ordinary mutex controlling the
> mux position, the consumers will unconditionally get serialized, which
> seems like a waste to me. Or maybe I'm missing something?
Why is serializing things a "waste"? Again, a rw semaphore is slower,
takes more logic to get correct, and is very complex at times. If you
are not SURE you need it, and that it matters, don't use it. And if you
do use it, document the heck out of it how you need it and why.
> >> +
> >> + if (mux->cached_state == state) {
> >> + /*
> >> + * Hmmm, someone else changed the mux to my liking.
> >> + * That makes me wonder how long I waited for nothing?
> >> + */
> >> + downgrade_write(&mux->lock);
> >
> > Oh that always scares me... Are you _sure_ this is correct? And
> > needed?
>
> It might not be needed, and it would probably work ok to just fall
> through and call mux_control_set unconditionally. What is it that
> always scares you exactly? Relying on cached state to be correct?
> Downgrading writer locks?
downgrading a writer lock scares me, especially for something as
"simple" as this type of interface. Again, don't use it unless you
_have_ to. Simple is good, start with that always.
> >> + if (mux->idle_state != MUX_IDLE_AS_IS &&
> >> + mux->idle_state != mux->cached_state)
> >> + ret = mux_control_set(mux, mux->idle_state);
> >> +
> >> + up_read(&mux->lock);
> >
> > You require a lock to be held for a "global" function? Without
> > documentation? Or even a sparse marking? That's asking for trouble...
>
> Documentation I can handle, but where should I look to understand how I
> should add sparse markings?
Run sparse on the code and see what it says :)
> The mux needs to be locked somehow. But as I stated in the cover letter
> the rwsem isn't a perfect fit.
>
> I'm using an rwsem to lock a mux, but that isn't really a
> perfect fit. Is there a better locking primitive that I don't
> know about that fits better? I had a mutex at one point, but
> that didn't allow any concurrent accesses at all. At least
> the rwsem allows concurrent access as long as all users
> agree on the mux state, but I suspect that the rwsem will
> degrade to the mutex situation pretty quickly if there is
> any contention.
>
> Also, the lock doesn't add anything if there is only one consumer of
> a mux controller. Maybe there should be some mechanism for shortcutting
> the locking for the (more common?) single-consumer case?
>
> But again, I need the locking for my multi-consumer use case.
Go back to a mutex, and having a function that requires it to be held
is, icky.
> >> +/*
> >> + * Using subsys_initcall instead of module_init here to ensure - for the
> >> + * non-modular case - that the subsystem is initialized when mux consumers
> >> + * and mux controllers start to use it /without/ relying on link order.
> >> + * For the modular case, the ordering is ensured with module dependencies.
> >> + */
> >> +subsys_initcall(mux_init);
> >
> > Even with subsys_initcall you are relying on link order, you do realize
> > that? What about other subsystems that rely on this? :)
>
> Yes, that is true, but if others start relying on this, that's their problem,
> right? :-)
Yes, but no need to document something that isn't true. You are relying
on link order here.
> >> +struct mux_control_ops {
> >> + int (*set)(struct mux_control *mux, int state);
> >> +};
> >> +
> >> +/* These defines match the constants from the dt-bindings. On purpose. */
> >
> > Why on purpose?
>
> I sure wasn't an accident? :-)
>
> Want me to remove it?
At least explain _why_ you are doing this, that would help, right?
thanks,
greg k-h
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller
2017-04-18 11:44 ` Greg Kroah-Hartman
@ 2017-04-18 21:53 ` Peter Rosin
[not found] ` <aacdc359-e46d-7d1a-d959-63d4ecb99fde-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
0 siblings, 1 reply; 38+ messages in thread
From: Peter Rosin @ 2017-04-18 21:53 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: linux-kernel, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, linux-i2c, devicetree,
linux-iio, linux-doc, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel
On 2017-04-18 13:44, Greg Kroah-Hartman wrote:
> On Tue, Apr 18, 2017 at 12:59:50PM +0200, Peter Rosin wrote:
>> On 2017-04-18 10:51, Greg Kroah-Hartman wrote:
>>> On Thu, Apr 13, 2017 at 06:43:07PM +0200, Peter Rosin wrote:
>>>> +config MUX_GPIO
>>>> + tristate "GPIO-controlled Multiplexer"
>>>> + depends on OF && GPIOLIB
>>>
>>> Why have the gpio and mux core in the same patch?
>>
>> One is not usable w/o the other. I can split them if you want to?
>
> Then why are they two different config options? Add the core code in
> one patch, and then add the gpio controled mutiplexer in a separate
> patch.
Ah, I meant when there are not yet any other mux drivers. I'll just
split the patch.
>>>> +static struct class mux_class = {
>>>> + .name = "mux",
>>>> + .owner = THIS_MODULE,
>>>> +};
>>>
>>> No Documentation/ABI/ update for your sysfs files? Please do so.
>>
>> Ok I'll look into it. Wasn't even aware that I added any. But there's the
>> new class of course...
>
> Hint, you have files, the devices that belong to the class :)
Right.
>>>> +static int __init mux_init(void)
>>>> +{
>>>> + return class_register(&mux_class);
>>>> +}
>>>> +
>>>> +static DEFINE_IDA(mux_ida);
>>>
>>> When your module is unloaded, you forgot to clean this structure up with
>>> what was done with it.
>>
>> I was under the impression that not providing an exit function for modules
>> made the module infrastructure prevent unloading (by bumping some reference
>> counter). Maybe that is a misconception?
>
> Ah, messy, don't do that. Make it so you can unload your module please,
> why wouldn't you want that to happen?
What made me do it was the worry that mux drivers might be left dangling
w/o the core. But that can probably only happen if someone is very
deliberately trying to break stuff by forcing unloads??
>>>> + mux_chip = kzalloc(sizeof(*mux_chip) +
>>>> + controllers * sizeof(*mux_chip->mux) +
>>>> + sizeof_priv, GFP_KERNEL);
>>>> + if (!mux_chip)
>>>> + return NULL;
>>>
>>> You don't return PTR_ERR(-ENOMEM)? Ok, why not? (I'm not arguing for
>>> it, just curious...)
>>
>> There's no particular reason. Do you think I should change it?
>
> What does the caller do with an error? Pass it up to where? Who gets
> it? Don't you want the caller to know you are out of memory?
The current callers return -ENOMEM when NULL is returned here. Looks
like I'm going to be doing some fairly major changes anyway so I'll
just change this too while at it...
>>>> +
>>>> + device_initialize(&mux_chip->dev);
>>>
>>> Why are you not registering the device here as well? Why have this be a
>>> two step process?
>>
>> Because of idle state handling. The drivers are expected to fill in
>> the desired idle state(s) after allocating the mux controller(s).
>> Then, when registering, the desired idle state is activated (if the
>> idle state is not idle-as-is, of course) and as a last step the mux
>> is "advertised".
>
> Ok, is that documented in the functions somewhere?
I'll make sure to add it if it's missing.
>>>> + ret = device_add(&mux_chip->dev);
>>>> + if (ret < 0)
>>>> + dev_err(&mux_chip->dev,
>>>> + "device_add failed in mux_chip_register: %d\n", ret);
>>>
>>> Did you run checkpatch.pl in strict mode on this new file? Please do so :)
>>
>> I did, and did it again just to be sure, and I do not get any complaints.
>> So, what's wrong?
>
> You list the function name in the printk string, it should complain
> that __func__ should be used. Oh well, it's just a perl script, it
> doesn't always catch everything.
> isn't always correct :)
Ah, ok.
>>>> +EXPORT_SYMBOL_GPL(devm_mux_chip_alloc);
>>>
>>>
>>> Having devm functions that create/destroy other struct devices worries
>>> me, do we have other examples of this happening today? Are you sure you
>>> got the reference counting all correct?
>>
>> drivers/iio/industrialio_core.c:devm_iio_device_alloc
>>
>> Or is the iio case different in some subtle way that I'm missing?
>
> I don't know, hopefully you got it all correct, I haven't audited that
> code path in a long time :)
Looks as ok to me as it did before. Moving on... :-)
>>>> +
>>>> +static int devm_mux_chip_match(struct device *dev, void *res, void *data)
>>>> +{
>>>> + struct mux_chip **r = res;
>>>> +
>>>> + if (WARN_ON(!r || !*r))
>>>
>>> How can this happen?
>>
>> It shouldn't. I copied the pattern from the iio subsystem.
>
> Then it should be removed there too...
Ok, I'll see if I can find time to suggest some patch(es) to Jonathan.
>>>> +void devm_mux_chip_free(struct device *dev, struct mux_chip *mux_chip)
>>>> +{
>>>> + WARN_ON(devres_release(dev, devm_mux_chip_release,
>>>> + devm_mux_chip_match, mux_chip));
>>>
>>> What can someone do with these WARN_ON() splats in the kernel log?
>>
>> Don't know. Again, I copied the pattern from the iio subsystem.
>
> If you don't know what it should be used for, don't copy it!
>
> Cargo-cult coding is horrible, please no.
Ok, I'll just drop those WARNs...
>>>> + /* ...or it's just contended. */
>>>> + down_write(&mux->lock);
>>>
>>> Why use a read/write lock at all? Have you tested this to verify it
>>> really is faster and needed?
>>
>> For one of the HW configuration that drove the development, the same mux
>> controller is used to mux both an I2C channel and a couple of ADC lines.
>>
>> If there is no kind of reader/writer locking going on, there is no way to
>> do ADC readings concurrently with an I2C transfer even when the consumers
>> want the mux in the same position. With an ordinary mutex controlling the
>> mux position, the consumers will unconditionally get serialized, which
>> seems like a waste to me. Or maybe I'm missing something?
>
> Why is serializing things a "waste"? Again, a rw semaphore is slower,
> takes more logic to get correct, and is very complex at times. If you
> are not SURE you need it, and that it matters, don't use it. And if you
> do use it, document the heck out of it how you need it and why.
It's a waste of time because two independent mux consumers of the same
mux controller can't do things concurrently even if they want the same
thing from the mux. Let's say that one mux consumer is an iio-mux and
one is an i2c-mux. Also, let's say that you for some reason need to get
a lot of samples at a determined rate through the iio-mux. With a mutex,
that can't happen if there is an access through the i2c-mux taking
"forever" in the eyes of the ADC/iio-mux, even if they both want the
shared mux controller to be in the same position. The ADC/iio-mux and
the i2c-mux would be waiting for each other for no purpose at all.
>>>> +
>>>> + if (mux->cached_state == state) {
>>>> + /*
>>>> + * Hmmm, someone else changed the mux to my liking.
>>>> + * That makes me wonder how long I waited for nothing?
>>>> + */
>>>> + downgrade_write(&mux->lock);
>>>
>>> Oh that always scares me... Are you _sure_ this is correct? And
>>> needed?
>>
>> It might not be needed, and it would probably work ok to just fall
>> through and call mux_control_set unconditionally. What is it that
>> always scares you exactly? Relying on cached state to be correct?
>> Downgrading writer locks?
>
> downgrading a writer lock scares me, especially for something as
> "simple" as this type of interface. Again, don't use it unless you
> _have_ to. Simple is good, start with that always.
Some kind of lock needs to be grabbed in mux_control_select and
released in mux_control_deselect that fixes the mux state while the
mux consumer goes about its business. For the reasons stated above I
went with a reader/writer lock instead of the mutex I had originally.
Agreed, the code in mux_control_select is a few more lines than I
like, but I suspected the big issue to be holding *any* lock over
a pair of "global" functions. Changing from holding a rw-lock as
reader to instead holding a mutex changes very little in my eyes.
mux_control_select is simply not *that* complicated...
>>>> + if (mux->idle_state != MUX_IDLE_AS_IS &&
>>>> + mux->idle_state != mux->cached_state)
>>>> + ret = mux_control_set(mux, mux->idle_state);
>>>> +
>>>> + up_read(&mux->lock);
>>>
>>> You require a lock to be held for a "global" function? Without
>>> documentation? Or even a sparse marking? That's asking for trouble...
>>
>> Documentation I can handle, but where should I look to understand how I
>> should add sparse markings?
>
> Run sparse on the code and see what it says :)
Will do.
>> The mux needs to be locked somehow. But as I stated in the cover letter
>> the rwsem isn't a perfect fit.
>>
>> I'm using an rwsem to lock a mux, but that isn't really a
>> perfect fit. Is there a better locking primitive that I don't
>> know about that fits better? I had a mutex at one point, but
>> that didn't allow any concurrent accesses at all. At least
>> the rwsem allows concurrent access as long as all users
>> agree on the mux state, but I suspect that the rwsem will
>> degrade to the mutex situation pretty quickly if there is
>> any contention.
>>
>> Also, the lock doesn't add anything if there is only one consumer of
>> a mux controller. Maybe there should be some mechanism for shortcutting
>> the locking for the (more common?) single-consumer case?
>>
>> But again, I need the locking for my multi-consumer use case.
>
> Go back to a mutex, and having a function that requires it to be held
> is, icky.
But how do you propose that the ickyness is avoided? It's a requirement
that any waiters are released when the mux is available...
*thinking/coding for a bit*
I'm going to experiment with the below (untested) code which AFAICT
has the issues that starvation is possible, that it isn't first-come
first-serve, and that once there is contention the waiters may wait
for a longer time than needed...
On the positive side there are no actual locks held from select over
to deselect and there's no rwsem. But a missing (or an extra) deselect
still messes things up pretty fatally, since the below is really just
some kind of open coded locking thingy that I thought was one of the
things I should stay away from. But maybe you like it better?
(In the below, mux->wait_sem is assumed to be initialized to zero.)
int mux_control_select(struct mux_control *mux, int state)
{
int ret = 0;
again:
mutex_lock(&mux->lock);
if (mux->cached_state == state) {
/* The mux is already correct, just bump the user count. */
++mux->users;
goto done;
}
if (mux->users) {
/* The mux needs updating but is in use, wait... */
++mux->waiters;
mutex_unlock(&mux->lock);
down(&mux->wait_sem);
goto again;
}
/* The mux needs updating and is unused. */
ret = mux_control_set(mux, state);
if (ret >= 0) {
++mux->users;
goto done;
}
/* The mux update failed, try to revert if appropriate... */
if (mux->idle_state != MUX_IDLE_AS_IS)
mux_control_set(mux, mux->idle_state);
/* ...and release a waiter if there is one. */
if (mux->waiters) {
--mux->waiters;
up(&mux->wait_sem);
}
done:
mutex_unlock(&mux->lock);
return ret;
}
int mux_control_deselect(struct mux_control *mux)
{
int ret = 0;
mutex_lock(&mux->lock);
if (--mux->users)
goto done;
/* This was the last user, idle the mux if appropriate... */
if (mux->idle_state != MUX_IDLE_AS_IS &&
mux->idle_state != mux->cached_state)
ret = mux_control_set(mux, mux->idle_state);
/* ...and release a waiter if there is one. */
if (mux->waiters) {
--mux->waiters;
up(&mux->wait_sem);
}
done:
mutex_unlock(&mux->lock);
return ret;
}
>>>> +/*
>>>> + * Using subsys_initcall instead of module_init here to ensure - for the
>>>> + * non-modular case - that the subsystem is initialized when mux consumers
>>>> + * and mux controllers start to use it /without/ relying on link order.
>>>> + * For the modular case, the ordering is ensured with module dependencies.
>>>> + */
>>>> +subsys_initcall(mux_init);
>>>
>>> Even with subsys_initcall you are relying on link order, you do realize
>>> that? What about other subsystems that rely on this? :)
>>
>> Yes, that is true, but if others start relying on this, that's their problem,
>> right? :-)
>
> Yes, but no need to document something that isn't true. You are relying
> on link order here.
Well, am I? If I change it to module_init I do get runtime errors for a
non-modular build (when mux consumers get initialized before the mux core).
With subsys_init the mux core get initialized before all (current)
consumers. I don't really see how the link order *currently* matters?
So, just making sure that we are on the same page, the thing that relies on
link order are any mux consumers/drivers that in the future may be added as
part of some other subsys_init call (or earlier). Right?
Hmmm, or are you perhaps referring to the fact that the mux core depends on
other subsystems being initialized first?
>>>> +struct mux_control_ops {
>>>> + int (*set)(struct mux_control *mux, int state);
>>>> +};
>>>> +
>>>> +/* These defines match the constants from the dt-bindings. On purpose. */
>>>
>>> Why on purpose?
>>
>> I sure wasn't an accident? :-)
>>
>> Want me to remove it?
>
> At least explain _why_ you are doing this, that would help, right?
Should I perhaps just #include <linux/dt-bindings/mux/mux.h> instead?
Is that an OK thing to do? I didn't because I feared it might come back
to haunt me at some point if the bindings header needed stuff in the
future that made it incompatible...
It's also not terribly common to include bindings from an include...
Cheers,
peda
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller
2017-04-18 10:59 ` Peter Rosin
[not found] ` <bdeecccf-02d6-226b-8516-1d41e3602a7a-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
@ 2017-05-05 13:19 ` Peter Rosin
1 sibling, 0 replies; 38+ messages in thread
From: Peter Rosin @ 2017-05-05 13:19 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: linux-kernel, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, linux-i2c, devicetree,
linux-iio, linux-doc, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel
On 2017-04-18 12:59, Peter Rosin wrote:
> On 2017-04-18 10:51, Greg Kroah-Hartman wrote:
>> On Thu, Apr 13, 2017 at 06:43:07PM +0200, Peter Rosin wrote:
>>> +/**
>>> + * devm_mux_chip_unregister() - Resource-managed version mux_chip_unregister().
>>> + * @dev: The device that originally registered the mux-chip.
>>> + * @mux_chip: The mux-chip to unregister.
>>> + *
>>> + * See mux_chip_unregister() for more details.
>>> + *
>>> + * Note that you do not normally need to call this function.
>>
>> Odd, then why is it exported???
>
> You normally don't call the devm_foo_{free,release,unregister,etc} functions.
> The intention is of course that the resourse cleans up automatically. But there
> are no cases where the manual clean up is not available, at least not that I can
> find?
I had a second look and there are of course plenty of examples of missing clean up
versions for devm function. I simply hadn't looked very hard at all.
So, for v15 I intend to remove all of devm_mux_chip_unregister, devm_mux_chip_free
and devm_mux_control_put. They are all just sitting there with no callers. And the
mux-mmio/video-mux drivers by Philipp Zabel that build on top of this series don't
need them either. Besides, easy to resurrect if needed...
I will do v15 with the above, the change from mutex to semaphore for locking the
mux controller state [1] and a few small documentation improvements. That will be
rebased onto v4.12-rc1 and sent in 10 days or so, or whenever v4.12-rc1 is out.
Meanwhile, what I currently intend for v15 but based on v4.11 is available from
https://gitlab.com/peda-linux/mux.git in the "mux" branch.
Cheers,
peda
[1] https://lkml.org/lkml/2017/4/25/411
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller
2017-04-13 16:43 ` [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller Peter Rosin
[not found] ` <1492101794-13444-4-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
@ 2017-04-19 9:06 ` Philipp Zabel
[not found] ` <1492592817.2970.14.camel-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
2017-04-21 14:18 ` Philipp Zabel
2017-04-21 14:23 ` Philipp Zabel
3 siblings, 1 reply; 38+ messages in thread
From: Philipp Zabel @ 2017-04-19 9:06 UTC (permalink / raw)
To: Peter Rosin
Cc: linux-kernel, Greg Kroah-Hartman, Wolfram Sang, Rob Herring,
Mark Rutland, Jonathan Cameron, Hartmut Knaack,
Lars-Peter Clausen, Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c, devicetree, linux-iio, linux-doc, Andrew Morton,
Colin Ian King, Paul Gortmaker, kernel
On Thu, 2017-04-13 at 18:43 +0200, Peter Rosin wrote:
> Add a new minimalistic subsystem that handles multiplexer controllers.
> When multiplexers are used in various places in the kernel, and the
> same multiplexer controller can be used for several independent things,
> there should be one place to implement support for said multiplexer
> controller.
>
> A single multiplexer controller can also be used to control several
> parallel multiplexers, that are in turn used by different subsystems
> in the kernel, leading to a need to coordinate multiplexer accesses.
> The multiplexer subsystem handles this coordination.
>
> This new mux controller subsystem initially comes with a single backend
> driver that controls gpio based multiplexers. Even though not needed by
> this initial driver, the mux controller subsystem is prepared to handle
> chips with multiple (independent) mux controllers.
>
> Reviewed-by: Jonathan Cameron <jic23@kernel.org>
> Signed-off-by: Peter Rosin <peda@axentia.se>
> ---
> Documentation/driver-model/devres.txt | 8 +
> MAINTAINERS | 2 +
> drivers/Kconfig | 2 +
> drivers/Makefile | 1 +
> drivers/mux/Kconfig | 34 +++
> drivers/mux/Makefile | 6 +
> drivers/mux/mux-core.c | 422 ++++++++++++++++++++++++++++++++++
> drivers/mux/mux-gpio.c | 114 +++++++++
> include/linux/mux.h | 252 ++++++++++++++++++++
> 9 files changed, 841 insertions(+)
> create mode 100644 drivers/mux/Kconfig
> create mode 100644 drivers/mux/Makefile
> create mode 100644 drivers/mux/mux-core.c
> create mode 100644 drivers/mux/mux-gpio.c
> create mode 100644 include/linux/mux.h
>
> diff --git a/Documentation/driver-model/devres.txt b/Documentation/driver-model/devres.txt
> index efb8200819d6..e2343d9cbec7 100644
> --- a/Documentation/driver-model/devres.txt
> +++ b/Documentation/driver-model/devres.txt
> @@ -337,6 +337,14 @@ MEM
> MFD
> devm_mfd_add_devices()
>
> +MUX
> + devm_mux_chip_alloc()
> + devm_mux_chip_free()
> + devm_mux_chip_register()
> + devm_mux_chip_unregister()
> + devm_mux_control_get()
> + devm_mux_control_put()
> +
> PER-CPU MEM
> devm_alloc_percpu()
> devm_free_percpu()
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 7fc06739c8ad..591eba737678 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -8563,6 +8563,8 @@ M: Peter Rosin <peda@axentia.se>
> S: Maintained
> F: Documentation/devicetree/bindings/mux/
> F: include/linux/dt-bindings/mux/
> +F: include/linux/mux.h
> +F: drivers/mux/
>
> MULTISOUND SOUND DRIVER
> M: Andrew Veliath <andrewtv@usa.net>
> diff --git a/drivers/Kconfig b/drivers/Kconfig
> index 117ca14ccf85..a7ea13e1b869 100644
> --- a/drivers/Kconfig
> +++ b/drivers/Kconfig
> @@ -204,4 +204,6 @@ source "drivers/fpga/Kconfig"
>
> source "drivers/fsi/Kconfig"
>
> +source "drivers/mux/Kconfig"
> +
> endmenu
> diff --git a/drivers/Makefile b/drivers/Makefile
> index 2eced9afba53..c0436f6dd5a9 100644
> --- a/drivers/Makefile
> +++ b/drivers/Makefile
> @@ -177,3 +177,4 @@ obj-$(CONFIG_ANDROID) += android/
> obj-$(CONFIG_NVMEM) += nvmem/
> obj-$(CONFIG_FPGA) += fpga/
> obj-$(CONFIG_FSI) += fsi/
> +obj-$(CONFIG_MULTIPLEXER) += mux/
> diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig
> new file mode 100644
> index 000000000000..41dfe08ead84
> --- /dev/null
> +++ b/drivers/mux/Kconfig
> @@ -0,0 +1,34 @@
> +#
> +# Multiplexer devices
> +#
> +
> +menuconfig MULTIPLEXER
> + tristate "Multiplexer subsystem"
> + help
> + Multiplexer controller subsystem. Multiplexers are used in a
> + variety of settings, and this subsystem abstracts their use
> + so that the rest of the kernel sees a common interface. When
> + multiple parallel multiplexers are controlled by one single
> + multiplexer controller, this subsystem also coordinates the
> + multiplexer accesses.
> +
> + To compile the subsystem as a module, choose M here: the module will
> + be called mux-core.
> +
> +if MULTIPLEXER
> +
> +config MUX_GPIO
> + tristate "GPIO-controlled Multiplexer"
> + depends on OF && GPIOLIB
> + help
> + GPIO-controlled Multiplexer controller.
> +
> + The driver builds a single multiplexer controller using a number
> + of gpio pins. For N pins, there will be 2^N possible multiplexer
> + states. The GPIO pins can be connected (by the hardware) to several
> + multiplexers, which in that case will be operated in parallel.
> +
> + To compile the driver as a module, choose M here: the module will
> + be called mux-gpio.
> +
> +endif
> diff --git a/drivers/mux/Makefile b/drivers/mux/Makefile
> new file mode 100644
> index 000000000000..bb16953f6290
> --- /dev/null
> +++ b/drivers/mux/Makefile
> @@ -0,0 +1,6 @@
> +#
> +# Makefile for multiplexer devices.
> +#
> +
> +obj-$(CONFIG_MULTIPLEXER) += mux-core.o
> +obj-$(CONFIG_MUX_GPIO) += mux-gpio.o
> diff --git a/drivers/mux/mux-core.c b/drivers/mux/mux-core.c
> new file mode 100644
> index 000000000000..66a8bccfc3d7
> --- /dev/null
> +++ b/drivers/mux/mux-core.c
> @@ -0,0 +1,422 @@
> +/*
> + * Multiplexer subsystem
> + *
> + * Copyright (C) 2017 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.
> + */
> +
> +#define pr_fmt(fmt) "mux-core: " fmt
> +
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/export.h>
> +#include <linux/idr.h>
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/mux.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/slab.h>
> +
> +/*
> + * The idle-as-is "state" is not an actual state that may be selected, it
> + * only implies that the state should not be changed. So, use that state
> + * as indication that the cached state of the multiplexer is unknown.
> + */
> +#define MUX_CACHE_UNKNOWN MUX_IDLE_AS_IS
> +
> +static struct class mux_class = {
> + .name = "mux",
> + .owner = THIS_MODULE,
> +};
> +
> +static int __init mux_init(void)
> +{
> + return class_register(&mux_class);
> +}
> +
> +static DEFINE_IDA(mux_ida);
> +
> +static void mux_chip_release(struct device *dev)
> +{
> + struct mux_chip *mux_chip = to_mux_chip(dev);
> +
> + ida_simple_remove(&mux_ida, mux_chip->id);
> + kfree(mux_chip);
> +}
> +
> +static struct device_type mux_type = {
> + .name = "mux-chip",
> + .release = mux_chip_release,
> +};
> +
> +struct mux_chip *mux_chip_alloc(struct device *dev,
> + unsigned int controllers, size_t sizeof_priv)
> +{
> + struct mux_chip *mux_chip;
> + int i;
> +
> + if (WARN_ON(!dev || !controllers))
> + return NULL;
> +
> + mux_chip = kzalloc(sizeof(*mux_chip) +
> + controllers * sizeof(*mux_chip->mux) +
> + sizeof_priv, GFP_KERNEL);
> + if (!mux_chip)
> + return NULL;
> +
> + mux_chip->mux = (struct mux_control *)(mux_chip + 1);
> + mux_chip->dev.class = &mux_class;
> + mux_chip->dev.type = &mux_type;
> + mux_chip->dev.parent = dev;
> + mux_chip->dev.of_node = dev->of_node;
> + dev_set_drvdata(&mux_chip->dev, mux_chip);
> +
> + mux_chip->id = ida_simple_get(&mux_ida, 0, 0, GFP_KERNEL);
> + if (mux_chip->id < 0) {
> + pr_err("muxchipX failed to get a device id\n");
> + kfree(mux_chip);
> + return NULL;
> + }
> + dev_set_name(&mux_chip->dev, "muxchip%d", mux_chip->id);
> +
> + mux_chip->controllers = controllers;
> + for (i = 0; i < controllers; ++i) {
> + struct mux_control *mux = &mux_chip->mux[i];
> +
> + mux->chip = mux_chip;
> + init_rwsem(&mux->lock);
> + mux->cached_state = MUX_CACHE_UNKNOWN;
> + mux->idle_state = MUX_IDLE_AS_IS;
> + }
> +
> + device_initialize(&mux_chip->dev);
> +
> + return mux_chip;
> +}
> +EXPORT_SYMBOL_GPL(mux_chip_alloc);
> +
> +static int mux_control_set(struct mux_control *mux, int state)
> +{
> + int ret = mux->chip->ops->set(mux, state);
> +
> + mux->cached_state = ret < 0 ? MUX_CACHE_UNKNOWN : state;
> +
> + return ret;
> +}
> +
> +int mux_chip_register(struct mux_chip *mux_chip)
> +{
> + int i;
> + int ret;
> +
> + for (i = 0; i < mux_chip->controllers; ++i) {
> + struct mux_control *mux = &mux_chip->mux[i];
> +
> + if (mux->idle_state == mux->cached_state)
> + continue;
> +
> + ret = mux_control_set(mux, mux->idle_state);
> + if (ret < 0) {
> + dev_err(&mux_chip->dev, "unable to set idle state\n");
> + return ret;
> + }
> + }
> +
> + ret = device_add(&mux_chip->dev);
> + if (ret < 0)
> + dev_err(&mux_chip->dev,
> + "device_add failed in mux_chip_register: %d\n", ret);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(mux_chip_register);
> +
> +void mux_chip_unregister(struct mux_chip *mux_chip)
> +{
> + device_del(&mux_chip->dev);
> +}
> +EXPORT_SYMBOL_GPL(mux_chip_unregister);
> +
> +void mux_chip_free(struct mux_chip *mux_chip)
> +{
> + if (!mux_chip)
> + return;
> +
> + put_device(&mux_chip->dev);
> +}
> +EXPORT_SYMBOL_GPL(mux_chip_free);
> +
> +static void devm_mux_chip_release(struct device *dev, void *res)
> +{
> + struct mux_chip *mux_chip = *(struct mux_chip **)res;
> +
> + mux_chip_free(mux_chip);
> +}
> +
> +struct mux_chip *devm_mux_chip_alloc(struct device *dev,
> + unsigned int controllers,
> + size_t sizeof_priv)
> +{
> + struct mux_chip **ptr, *mux_chip;
> +
> + ptr = devres_alloc(devm_mux_chip_release, sizeof(*ptr), GFP_KERNEL);
> + if (!ptr)
> + return NULL;
> +
> + mux_chip = mux_chip_alloc(dev, controllers, sizeof_priv);
> + if (!mux_chip) {
> + devres_free(ptr);
> + return NULL;
> + }
> +
> + *ptr = mux_chip;
> + devres_add(dev, ptr);
> +
> + return mux_chip;
> +}
> +EXPORT_SYMBOL_GPL(devm_mux_chip_alloc);
> +
> +static int devm_mux_chip_match(struct device *dev, void *res, void *data)
> +{
> + struct mux_chip **r = res;
> +
> + if (WARN_ON(!r || !*r))
> + return 0;
> +
> + return *r == data;
> +}
> +
> +void devm_mux_chip_free(struct device *dev, struct mux_chip *mux_chip)
> +{
> + WARN_ON(devres_release(dev, devm_mux_chip_release,
> + devm_mux_chip_match, mux_chip));
> +}
> +EXPORT_SYMBOL_GPL(devm_mux_chip_free);
> +
> +static void devm_mux_chip_reg_release(struct device *dev, void *res)
> +{
> + struct mux_chip *mux_chip = *(struct mux_chip **)res;
> +
> + mux_chip_unregister(mux_chip);
> +}
> +
> +int devm_mux_chip_register(struct device *dev,
> + struct mux_chip *mux_chip)
> +{
> + struct mux_chip **ptr;
> + int res;
> +
> + ptr = devres_alloc(devm_mux_chip_reg_release, sizeof(*ptr), GFP_KERNEL);
> + if (!ptr)
> + return -ENOMEM;
> +
> + res = mux_chip_register(mux_chip);
> + if (res) {
> + devres_free(ptr);
> + return res;
> + }
> +
> + *ptr = mux_chip;
> + devres_add(dev, ptr);
> +
> + return res;
> +}
> +EXPORT_SYMBOL_GPL(devm_mux_chip_register);
> +
> +void devm_mux_chip_unregister(struct device *dev, struct mux_chip *mux_chip)
> +{
> + WARN_ON(devres_release(dev, devm_mux_chip_reg_release,
> + devm_mux_chip_match, mux_chip));
> +}
> +EXPORT_SYMBOL_GPL(devm_mux_chip_unregister);
> +
> +int mux_control_select(struct mux_control *mux, int state)
If we let two of these race, ...
> +{
> + int ret;
> +
> + if (down_read_trylock(&mux->lock)) {
> + if (mux->cached_state == state)
> + return 0;
> + /* Sigh, the mux needs updating... */
> + up_read(&mux->lock);
... and both decide the mux needs updating ...
> + }
> +
> + /* ...or it's just contended. */
> + down_write(&mux->lock);
... then the last to get to down_write will just wait here forever (or
until the first consumer calls mux_control_deselect, which may never
happen)?
> +
> + if (mux->cached_state == state) {
> + /*
> + * Hmmm, someone else changed the mux to my liking.
> + * That makes me wonder how long I waited for nothing?
> + */
> + downgrade_write(&mux->lock);
> + return 0;
> + }
> +
> + ret = mux_control_set(mux, state);
> + if (ret < 0) {
> + if (mux->idle_state != MUX_IDLE_AS_IS)
> + mux_control_set(mux, mux->idle_state);
> +
> + up_write(&mux->lock);
> + return ret;
> + }
> +
> + downgrade_write(&mux->lock);
> +
> + return 1;
> +}
> +EXPORT_SYMBOL_GPL(mux_control_select);
I wonder if these should be called mux_control_lock/unlock instead,
which would allow for try_lock and lock_timeout variants.
regards
Philipp
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller
2017-04-13 16:43 ` [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller Peter Rosin
[not found] ` <1492101794-13444-4-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
2017-04-19 9:06 ` Philipp Zabel
@ 2017-04-21 14:18 ` Philipp Zabel
2017-04-21 15:08 ` Peter Rosin
2017-04-21 14:23 ` Philipp Zabel
3 siblings, 1 reply; 38+ messages in thread
From: Philipp Zabel @ 2017-04-21 14:18 UTC (permalink / raw)
To: Peter Rosin
Cc: linux-kernel, Greg Kroah-Hartman, Wolfram Sang, Rob Herring,
Mark Rutland, Jonathan Cameron, Hartmut Knaack,
Lars-Peter Clausen, Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c, devicetree, linux-iio, linux-doc, Andrew Morton,
Colin Ian King, Paul Gortmaker, kernel
Hi Peter,
On Thu, 2017-04-13 at 18:43 +0200, Peter Rosin wrote:
[...]
> +int mux_control_select(struct mux_control *mux, int state)
state could be unsigned int for the consumer facing API.
> +{
> + int ret;
And mux_control_select should check that (0 <= state < mux->states).
regards
Philipp
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller
2017-04-21 14:18 ` Philipp Zabel
@ 2017-04-21 15:08 ` Peter Rosin
0 siblings, 0 replies; 38+ messages in thread
From: Peter Rosin @ 2017-04-21 15:08 UTC (permalink / raw)
To: Philipp Zabel
Cc: linux-kernel, Greg Kroah-Hartman, Wolfram Sang, Rob Herring,
Mark Rutland, Jonathan Cameron, Hartmut Knaack,
Lars-Peter Clausen, Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c, devicetree, linux-iio, linux-doc, Andrew Morton,
Colin Ian King, Paul Gortmaker, kernel
On 2017-04-21 16:18, Philipp Zabel wrote:
> Hi Peter,
>
> On Thu, 2017-04-13 at 18:43 +0200, Peter Rosin wrote:
> [...]
>> +int mux_control_select(struct mux_control *mux, int state)
>
> state could be unsigned int for the consumer facing API.
>
>> +{
>> + int ret;
>
> And mux_control_select should check that (0 <= state < mux->states).
Yes, that makes sense. I worried that we might end up with
signed/unsigned comparisons since the internal state still needs
to be signed, but that didn't happen when I tried...
I'll include this change in v14.
Cheers,
peda
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller
2017-04-13 16:43 ` [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller Peter Rosin
` (2 preceding siblings ...)
2017-04-21 14:18 ` Philipp Zabel
@ 2017-04-21 14:23 ` Philipp Zabel
2017-04-21 14:32 ` Peter Rosin
3 siblings, 1 reply; 38+ messages in thread
From: Philipp Zabel @ 2017-04-21 14:23 UTC (permalink / raw)
To: Peter Rosin
Cc: linux-kernel, Greg Kroah-Hartman, Wolfram Sang, Rob Herring,
Mark Rutland, Jonathan Cameron, Hartmut Knaack,
Lars-Peter Clausen, Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c, devicetree, linux-iio, linux-doc, Andrew Morton,
Colin Ian King, Paul Gortmaker, kernel
On Thu, 2017-04-13 at 18:43 +0200, Peter Rosin wrote:
[...]
> +int mux_chip_register(struct mux_chip *mux_chip)
> +{
> + int i;
> + int ret;
> +
> + for (i = 0; i < mux_chip->controllers; ++i) {
> + struct mux_control *mux = &mux_chip->mux[i];
> +
> + if (mux->idle_state == mux->cached_state)
> + continue;
I think this should be changed to
- if (mux->idle_state == mux->cached_state)
+ if (mux->idle_state == mux->cached_state ||
+ mux->idle_state == MUX_IDLE_AS_IS)
continue;
or the following mux_control_set will be called with state ==
MUX_IDLE_AS_IS. Alternatively, mux_control_set should return when passed
this value.
> + ret = mux_control_set(mux, mux->idle_state);
> + if (ret < 0) {
> + dev_err(&mux_chip->dev, "unable to set idle state\n");
> + return ret;
> + }
> + }
> +
> + ret = device_add(&mux_chip->dev);
> + if (ret < 0)
> + dev_err(&mux_chip->dev,
> + "device_add failed in mux_chip_register: %d\n", ret);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(mux_chip_register);
regards
Philipp
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller
2017-04-21 14:23 ` Philipp Zabel
@ 2017-04-21 14:32 ` Peter Rosin
[not found] ` <9e3d48c4-0dbc-3e80-c653-b0357abf1d6f-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
0 siblings, 1 reply; 38+ messages in thread
From: Peter Rosin @ 2017-04-21 14:32 UTC (permalink / raw)
To: Philipp Zabel
Cc: linux-kernel, Greg Kroah-Hartman, Wolfram Sang, Rob Herring,
Mark Rutland, Jonathan Cameron, Hartmut Knaack,
Lars-Peter Clausen, Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c, devicetree, linux-iio, linux-doc, Andrew Morton,
Colin Ian King, Paul Gortmaker, kernel
On 2017-04-21 16:23, Philipp Zabel wrote:
> On Thu, 2017-04-13 at 18:43 +0200, Peter Rosin wrote:
> [...]
>> +int mux_chip_register(struct mux_chip *mux_chip)
>> +{
>> + int i;
>> + int ret;
>> +
>> + for (i = 0; i < mux_chip->controllers; ++i) {
>> + struct mux_control *mux = &mux_chip->mux[i];
>> +
>> + if (mux->idle_state == mux->cached_state)
>> + continue;
>
> I think this should be changed to
>
> - if (mux->idle_state == mux->cached_state)
> + if (mux->idle_state == mux->cached_state ||
> + mux->idle_state == MUX_IDLE_AS_IS)
> continue;
>
> or the following mux_control_set will be called with state ==
> MUX_IDLE_AS_IS. Alternatively, mux_control_set should return when passed
> this value.
That cannot happen because ->cached_state is initialized to -1
in mux_chip_alloc, so should always be == MUX_IDLE_AS_IS when
registering. And drivers are not supposed to touch ->cached_state.
I.e., ->cached_state is "owned" by the core.
Cheers,
peda
>> + ret = mux_control_set(mux, mux->idle_state);
>> + if (ret < 0) {
>> + dev_err(&mux_chip->dev, "unable to set idle state\n");
>> + return ret;
>> + }
>> + }
>> +
>> + ret = device_add(&mux_chip->dev);
>> + if (ret < 0)
>> + dev_err(&mux_chip->dev,
>> + "device_add failed in mux_chip_register: %d\n", ret);
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(mux_chip_register);
>
> regards
> Philipp
>
^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH v13 05/10] dt-bindings: iio: io-channel-mux: document io-channel-mux bindings
[not found] ` <1492101794-13444-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
2017-04-13 16:43 ` [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller Peter Rosin
@ 2017-04-13 16:43 ` Peter Rosin
2017-04-13 16:43 ` [PATCH v13 06/10] iio: multiplexer: new iio category and iio-mux driver Peter Rosin
` (2 subsequent siblings)
4 siblings, 0 replies; 38+ messages in thread
From: Peter Rosin @ 2017-04-13 16:43 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA, Greg Kroah-Hartman
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel-bIcnvbaLZ9MEGnE8C9+IrQ
Describe how a multiplexer can be used to select which signal is fed to
an io-channel.
Acked-by: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Signed-off-by: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
.../bindings/iio/multiplexer/io-channel-mux.txt | 39 ++++++++++++++++++++++
MAINTAINERS | 6 ++++
2 files changed, 45 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/multiplexer/io-channel-mux.txt
diff --git a/Documentation/devicetree/bindings/iio/multiplexer/io-channel-mux.txt b/Documentation/devicetree/bindings/iio/multiplexer/io-channel-mux.txt
new file mode 100644
index 000000000000..c82794002595
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/multiplexer/io-channel-mux.txt
@@ -0,0 +1,39 @@
+I/O channel multiplexer bindings
+
+If a multiplexer is used to select which hardware signal is fed to
+e.g. an ADC channel, these bindings describe that situation.
+
+Required properties:
+- compatible : "io-channel-mux"
+- io-channels : Channel node of the parent channel that has multiplexed
+ input.
+- io-channel-names : Should be "parent".
+- #address-cells = <1>;
+- #size-cells = <0>;
+- mux-controls : Mux controller node to use for operating the mux
+- channels : List of strings, labeling the mux controller states.
+
+For each non-empty string in the channels property, an io-channel will
+be created. The number of this io-channel is the same as the index into
+the list of strings in the channels property, and also matches the mux
+controller state. The mux controller state is described in
+../mux/mux-controller.txt
+
+Example:
+ mux: mux-controller {
+ compatible = "mux-gpio";
+ #mux-control-cells = <0>;
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
+ <&pioA 1 GPIO_ACTIVE_HIGH>;
+ };
+
+ adc-mux {
+ compatible = "io-channel-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ mux-controls = <&mux>;
+
+ channels = "sync", "in", "system-regulator";
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 591eba737678..4af912305d2c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6360,6 +6360,12 @@ 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 MULTIPLEXER
+M: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
+L: linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+S: Maintained
+F: Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt
+
IIO SUBSYSTEM AND DRIVERS
M: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
R: Hartmut Knaack <knaack.h-Mmb7MZpHnFY@public.gmane.org>
--
2.1.4
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v13 06/10] iio: multiplexer: new iio category and iio-mux driver
[not found] ` <1492101794-13444-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
2017-04-13 16:43 ` [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller Peter Rosin
2017-04-13 16:43 ` [PATCH v13 05/10] dt-bindings: iio: io-channel-mux: document io-channel-mux bindings Peter Rosin
@ 2017-04-13 16:43 ` Peter Rosin
2017-04-13 16:43 ` [PATCH v13 09/10] dt-bindings: mux-adg792a: document devicetree bindings for ADG792A/G mux Peter Rosin
2017-04-13 16:43 ` [PATCH v13 10/10] mux: adg792a: add mux controller driver for ADG792A/G Peter Rosin
4 siblings, 0 replies; 38+ messages in thread
From: Peter Rosin @ 2017-04-13 16:43 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA, Greg Kroah-Hartman
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel-bIcnvbaLZ9MEGnE8C9+IrQ
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 <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Signed-off-by: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
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 | 459 ++++++++++++++++++++++++++++++++++++++
6 files changed, 486 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 <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
L: linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.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-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
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..bab9e6902090
--- /dev/null
+++ b/drivers/iio/multiplexer/iio-mux.c
@@ -0,0 +1,459 @@
+/*
+ * IIO multiplexer driver
+ *
+ * Copyright (C) 2017 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
+ *
+ * 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;
+ ssize_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;
+
+ if (len >= PAGE_SIZE)
+ return -EINVAL;
+
+ 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 <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
--
2.1.4
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v13 09/10] dt-bindings: mux-adg792a: document devicetree bindings for ADG792A/G mux
[not found] ` <1492101794-13444-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
` (2 preceding siblings ...)
2017-04-13 16:43 ` [PATCH v13 06/10] iio: multiplexer: new iio category and iio-mux driver Peter Rosin
@ 2017-04-13 16:43 ` Peter Rosin
2017-04-13 16:43 ` [PATCH v13 10/10] mux: adg792a: add mux controller driver for ADG792A/G Peter Rosin
4 siblings, 0 replies; 38+ messages in thread
From: Peter Rosin @ 2017-04-13 16:43 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA, Greg Kroah-Hartman
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel-bIcnvbaLZ9MEGnE8C9+IrQ
Analog Devices ADG792A/G is a triple 4:1 mux.
Acked-by: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Reviewed-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Signed-off-by: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
.../devicetree/bindings/mux/adi,adg792a.txt | 75 ++++++++++++++++++++++
1 file changed, 75 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mux/adi,adg792a.txt
diff --git a/Documentation/devicetree/bindings/mux/adi,adg792a.txt b/Documentation/devicetree/bindings/mux/adi,adg792a.txt
new file mode 100644
index 000000000000..96b787a69f50
--- /dev/null
+++ b/Documentation/devicetree/bindings/mux/adi,adg792a.txt
@@ -0,0 +1,75 @@
+Bindings for Analog Devices ADG792A/G Triple 4:1 Multiplexers
+
+Required properties:
+- compatible : "adi,adg792a" or "adi,adg792g"
+- #mux-control-cells : <0> if parallel (the three muxes are bound together
+ with a single mux controller controlling all three muxes), or <1> if
+ not (one mux controller for each mux).
+* Standard mux-controller bindings as described in mux-controller.txt
+
+Optional properties for ADG792G:
+- gpio-controller : if present, #gpio-cells below is required.
+- #gpio-cells : should be <2>
+ - First cell is the GPO line number, i.e. 0 or 1
+ - Second cell is used to specify active high (0)
+ or active low (1)
+
+Optional properties:
+- idle-state : if present, array of states that the mux controllers will have
+ when idle. The special state MUX_IDLE_AS_IS is the default and
+ MUX_IDLE_DISCONNECT is also supported.
+
+States 0 through 3 correspond to signals A through D in the datasheet.
+
+Example:
+
+ /*
+ * Three independent mux controllers (of which one is used).
+ * Mux 0 is disconnected when idle, mux 1 idles in the previously
+ * selected state and mux 2 idles with signal B.
+ */
+ &i2c0 {
+ mux: mux-controller@50 {
+ compatible = "adi,adg792a";
+ reg = <0x50>;
+ #mux-control-cells = <1>;
+
+ idle-state = <MUX_IDLE_DISCONNECT MUX_IDLE_AS_IS 1>;
+ };
+ };
+
+ adc-mux {
+ compatible = "io-channel-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ mux-controls = <&mux 2>;
+
+ channels = "sync-1", "", "out";
+ };
+
+
+ /*
+ * Three parallel muxes with one mux controller, useful e.g. if
+ * the adc is differential, thus needing two signals to be muxed
+ * simultaneously for correct operation.
+ */
+ &i2c0 {
+ pmux: mux-controller@50 {
+ compatible = "adi,adg792a";
+ reg = <0x50>;
+ #mux-control-cells = <0>;
+
+ idle-state = <1>;
+ };
+ };
+
+ diff-adc-mux {
+ compatible = "io-channel-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ mux-controls = <&pmux>;
+
+ channels = "sync-1", "", "out";
+ };
--
2.1.4
^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH v13 10/10] mux: adg792a: add mux controller driver for ADG792A/G
[not found] ` <1492101794-13444-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
` (3 preceding siblings ...)
2017-04-13 16:43 ` [PATCH v13 09/10] dt-bindings: mux-adg792a: document devicetree bindings for ADG792A/G mux Peter Rosin
@ 2017-04-13 16:43 ` Peter Rosin
4 siblings, 0 replies; 38+ messages in thread
From: Peter Rosin @ 2017-04-13 16:43 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA, Greg Kroah-Hartman
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel-bIcnvbaLZ9MEGnE8C9+IrQ
Analog Devices ADG792A/G is a triple 4:1 mux.
Reviewed-by: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Signed-off-by: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
drivers/mux/Kconfig | 12 ++++
drivers/mux/Makefile | 1 +
drivers/mux/mux-adg792a.c | 141 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 154 insertions(+)
create mode 100644 drivers/mux/mux-adg792a.c
diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig
index 41dfe08ead84..86668b4d2fc5 100644
--- a/drivers/mux/Kconfig
+++ b/drivers/mux/Kconfig
@@ -17,6 +17,18 @@ menuconfig MULTIPLEXER
if MULTIPLEXER
+config MUX_ADG792A
+ tristate "Analog Devices ADG792A/ADG792G Multiplexers"
+ depends on I2C
+ help
+ ADG792A and ADG792G Wide Bandwidth Triple 4:1 Multiplexers
+
+ The driver supports both operating the three multiplexers in
+ parallel and operating them independently.
+
+ To compile the driver as a module, choose M here: the module will
+ be called mux-adg792a.
+
config MUX_GPIO
tristate "GPIO-controlled Multiplexer"
depends on OF && GPIOLIB
diff --git a/drivers/mux/Makefile b/drivers/mux/Makefile
index bb16953f6290..b00a7d37d2fb 100644
--- a/drivers/mux/Makefile
+++ b/drivers/mux/Makefile
@@ -3,4 +3,5 @@
#
obj-$(CONFIG_MULTIPLEXER) += mux-core.o
+obj-$(CONFIG_MUX_ADG792A) += mux-adg792a.o
obj-$(CONFIG_MUX_GPIO) += mux-gpio.o
diff --git a/drivers/mux/mux-adg792a.c b/drivers/mux/mux-adg792a.c
new file mode 100644
index 000000000000..58c0ecf49a4a
--- /dev/null
+++ b/drivers/mux/mux-adg792a.c
@@ -0,0 +1,141 @@
+/*
+ * Multiplexer driver for Analog Devices ADG792A/G Triple 4:1 mux
+ *
+ * Copyright (C) 2017 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
+ *
+ * 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/i2c.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+
+#define ADG792A_LDSW BIT(0)
+#define ADG792A_RESETB BIT(1) /* active low, reset when zero */
+#define ADG792A_DISABLE(mux) (0x50 | (mux))
+#define ADG792A_DISABLE_ALL (0x5f)
+#define ADG792A_MUX(mux, state) (0xc0 | (((mux) + 1) << 2) | (state))
+#define ADG792A_MUX_ALL(state) (0xc0 | (state))
+
+static int adg792a_set(struct mux_control *mux, int state)
+{
+ struct i2c_client *i2c = to_i2c_client(mux->chip->dev.parent);
+ u8 cmd;
+
+ if (mux->chip->controllers == 1) {
+ /* parallel mux controller operation */
+ if (state == MUX_IDLE_DISCONNECT)
+ cmd = ADG792A_DISABLE_ALL;
+ else
+ cmd = ADG792A_MUX_ALL(state);
+ } else {
+ unsigned int controller = mux_control_get_index(mux);
+
+ if (state == MUX_IDLE_DISCONNECT)
+ cmd = ADG792A_DISABLE(controller);
+ else
+ cmd = ADG792A_MUX(controller, state);
+ }
+
+ return i2c_smbus_write_byte_data(i2c, cmd,
+ ADG792A_RESETB | ADG792A_LDSW);
+}
+
+static const struct mux_control_ops adg792a_ops = {
+ .set = adg792a_set,
+};
+
+static int adg792a_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &i2c->dev;
+ struct mux_chip *mux_chip;
+ u32 cells;
+ int ret;
+ int i;
+
+ ret = of_property_read_u32(dev->of_node, "#mux-control-cells", &cells);
+ if (ret < 0)
+ return ret;
+ if (cells >= 2)
+ return -EINVAL;
+
+ mux_chip = devm_mux_chip_alloc(dev, cells ? 3 : 1, 0);
+ if (!mux_chip)
+ return -ENOMEM;
+
+ mux_chip->ops = &adg792a_ops;
+
+ ret = i2c_smbus_write_byte_data(i2c, ADG792A_DISABLE_ALL,
+ ADG792A_LDSW);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < mux_chip->controllers; ++i) {
+ struct mux_control *mux = &mux_chip->mux[i];
+ s32 idle_state;
+
+ mux->states = 4;
+
+ ret = of_property_read_u32_index(dev->of_node, "idle-state", i,
+ (s32 *)&idle_state);
+ if (ret < 0)
+ continue;
+
+ switch (idle_state) {
+ case 0 ... 4:
+ case MUX_IDLE_DISCONNECT:
+ mux_chip->mux[i].idle_state = idle_state;
+ break;
+ case MUX_IDLE_AS_IS:
+ break;
+ default:
+ dev_err(dev, "invalid idle-state %d\n", idle_state);
+ return -EINVAL;
+ }
+ }
+
+ ret = devm_mux_chip_register(dev, mux_chip);
+ if (ret < 0)
+ return ret;
+
+ if (cells)
+ dev_info(dev, "3x single pole quadruple throw muxes registered\n");
+ else
+ dev_info(dev, "triple pole quadruple throw mux registered\n");
+
+ return 0;
+}
+
+static const struct i2c_device_id adg792a_id[] = {
+ { .name = "adg792a", },
+ { .name = "adg792g", },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, adg792a_id);
+
+static const struct of_device_id adg792a_of_match[] = {
+ { .compatible = "adi,adg792a", },
+ { .compatible = "adi,adg792g", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adg792a_of_match);
+
+static struct i2c_driver adg792a_driver = {
+ .driver = {
+ .name = "adg792a",
+ .of_match_table = of_match_ptr(adg792a_of_match),
+ },
+ .probe = adg792a_probe,
+ .id_table = adg792a_id,
+};
+module_i2c_driver(adg792a_driver);
+
+MODULE_DESCRIPTION("Analog Devices ADG792A/G Triple 4:1 mux driver");
+MODULE_AUTHOR("Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
--
2.1.4
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 38+ messages in thread