* [PATCH v8 06/12] iio: multiplexer: new iio category and iio-mux driver
From: Peter Rosin @ 2017-01-18 15:57 UTC (permalink / raw)
To: linux-kernel
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, Andrew Morton, linux-i2c,
devicetree, linux-iio, linux-doc
In-Reply-To: <1484755035-25927-1-git-send-email-peda@axentia.se>
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@kernel.org>
Signed-off-by: Peter Rosin <peda@axentia.se>
---
MAINTAINERS | 1 +
drivers/iio/Kconfig | 1 +
drivers/iio/Makefile | 1 +
drivers/iio/multiplexer/Kconfig | 18 ++
drivers/iio/multiplexer/Makefile | 6 +
drivers/iio/multiplexer/iio-mux.c | 456 ++++++++++++++++++++++++++++++++++++++
6 files changed, 483 insertions(+)
create mode 100644 drivers/iio/multiplexer/Kconfig
create mode 100644 drivers/iio/multiplexer/Makefile
create mode 100644 drivers/iio/multiplexer/iio-mux.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 35325a3d9ca4..bfbc1a9dd3fe 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6294,6 +6294,7 @@ M: Peter Rosin <peda@axentia.se>
L: linux-iio@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt
+F: drivers/iio/multiplexer/iio-mux.c
IIO SUBSYSTEM AND DRIVERS
M: Jonathan Cameron <jic23@kernel.org>
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index a918270d6f54..b3c8c6ef0dff 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -83,6 +83,7 @@ source "drivers/iio/humidity/Kconfig"
source "drivers/iio/imu/Kconfig"
source "drivers/iio/light/Kconfig"
source "drivers/iio/magnetometer/Kconfig"
+source "drivers/iio/multiplexer/Kconfig"
source "drivers/iio/orientation/Kconfig"
if IIO_TRIGGER
source "drivers/iio/trigger/Kconfig"
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index 33fa4026f92c..93c769cd99bf 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -28,6 +28,7 @@ obj-y += humidity/
obj-y += imu/
obj-y += light/
obj-y += magnetometer/
+obj-y += multiplexer/
obj-y += orientation/
obj-y += potentiometer/
obj-y += potentiostat/
diff --git a/drivers/iio/multiplexer/Kconfig b/drivers/iio/multiplexer/Kconfig
new file mode 100644
index 000000000000..70a044510686
--- /dev/null
+++ b/drivers/iio/multiplexer/Kconfig
@@ -0,0 +1,18 @@
+#
+# Multiplexer drivers
+#
+# When adding new entries keep the list in alphabetical order
+
+menu "Multiplexers"
+
+config IIO_MUX
+ tristate "IIO multiplexer driver"
+ select MULTIPLEXER
+ depends on OF
+ help
+ Say yes here to build support for the IIO multiplexer.
+
+ To compile this driver as a module, choose M here: the
+ module will be called iio-mux.
+
+endmenu
diff --git a/drivers/iio/multiplexer/Makefile b/drivers/iio/multiplexer/Makefile
new file mode 100644
index 000000000000..68be3c4abd07
--- /dev/null
+++ b/drivers/iio/multiplexer/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for industrial I/O multiplexer drivers
+#
+
+# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_IIO_MUX) += iio-mux.o
diff --git a/drivers/iio/multiplexer/iio-mux.c b/drivers/iio/multiplexer/iio-mux.c
new file mode 100644
index 000000000000..94d40f9bef4c
--- /dev/null
+++ b/drivers/iio/multiplexer/iio-mux.c
@@ -0,0 +1,456 @@
+/*
+ * IIO multiplexer driver
+ *
+ * Copyright (C) 2017 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda@axentia.se>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/err.h>
+#include <linux/iio/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mux.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+struct mux_ext_info_cache {
+ char *data;
+ size_t size;
+};
+
+struct mux_child {
+ struct mux_ext_info_cache *ext_info_cache;
+};
+
+struct mux {
+ int cached_state;
+ struct mux_control *control;
+ struct iio_channel *parent;
+ struct iio_dev *indio_dev;
+ struct iio_chan_spec *chan;
+ struct iio_chan_spec_ext_info *ext_info;
+ struct mux_child *child;
+};
+
+static int iio_mux_select(struct mux *mux, int idx)
+{
+ struct mux_child *child = &mux->child[idx];
+ struct iio_chan_spec const *chan = &mux->chan[idx];
+ int ret;
+ int i;
+
+ ret = mux_control_select(mux->control, chan->channel);
+ if (ret < 0) {
+ mux->cached_state = -1;
+ return ret;
+ }
+
+ if (mux->cached_state == chan->channel)
+ return 0;
+
+ if (chan->ext_info) {
+ for (i = 0; chan->ext_info[i].name; ++i) {
+ const char *attr = chan->ext_info[i].name;
+ struct mux_ext_info_cache *cache;
+
+ cache = &child->ext_info_cache[i];
+
+ if (cache->size < 0)
+ continue;
+
+ ret = iio_write_channel_ext_info(mux->parent, attr,
+ cache->data,
+ cache->size);
+
+ if (ret < 0) {
+ mux_control_deselect(mux->control);
+ mux->cached_state = -1;
+ return ret;
+ }
+ }
+ }
+ mux->cached_state = chan->channel;
+
+ return 0;
+}
+
+static void iio_mux_deselect(struct mux *mux)
+{
+ mux_control_deselect(mux->control);
+}
+
+static int mux_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct mux *mux = iio_priv(indio_dev);
+ int idx = chan - mux->chan;
+ int ret;
+
+ ret = iio_mux_select(mux, idx);
+ if (ret < 0)
+ return ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = iio_read_channel_raw(mux->parent, val);
+ break;
+
+ case IIO_CHAN_INFO_SCALE:
+ ret = iio_read_channel_scale(mux->parent, val, val2);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ iio_mux_deselect(mux);
+
+ return ret;
+}
+
+static int mux_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct mux *mux = iio_priv(indio_dev);
+ int idx = chan - mux->chan;
+ int ret;
+
+ ret = iio_mux_select(mux, idx);
+ if (ret < 0)
+ return ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ *type = IIO_VAL_INT;
+ ret = iio_read_avail_channel_raw(mux->parent, vals, length);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ iio_mux_deselect(mux);
+
+ return ret;
+}
+
+static int mux_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct mux *mux = iio_priv(indio_dev);
+ int idx = chan - mux->chan;
+ int ret;
+
+ ret = iio_mux_select(mux, idx);
+ if (ret < 0)
+ return ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = iio_write_channel_raw(mux->parent, val);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ iio_mux_deselect(mux);
+
+ return ret;
+}
+
+static const struct iio_info mux_info = {
+ .read_raw = mux_read_raw,
+ .read_avail = mux_read_avail,
+ .write_raw = mux_write_raw,
+ .driver_module = THIS_MODULE,
+};
+
+static ssize_t mux_read_ext_info(struct iio_dev *indio_dev, uintptr_t private,
+ struct iio_chan_spec const *chan, char *buf)
+{
+ struct mux *mux = iio_priv(indio_dev);
+ int idx = chan - mux->chan;
+ ssize_t ret;
+
+ ret = iio_mux_select(mux, idx);
+ if (ret < 0)
+ return ret;
+
+ ret = iio_read_channel_ext_info(mux->parent,
+ mux->ext_info[private].name,
+ buf);
+
+ iio_mux_deselect(mux);
+
+ return ret;
+}
+
+static ssize_t mux_write_ext_info(struct iio_dev *indio_dev, uintptr_t private,
+ struct iio_chan_spec const *chan,
+ const char *buf, size_t len)
+{
+ struct device *dev = indio_dev->dev.parent;
+ struct mux *mux = iio_priv(indio_dev);
+ int idx = chan - mux->chan;
+ char *new;
+ ssize_t ret;
+
+ ret = iio_mux_select(mux, idx);
+ if (ret < 0)
+ return ret;
+
+ new = devm_kmemdup(dev, buf, len + 1, GFP_KERNEL);
+ if (!new) {
+ iio_mux_deselect(mux);
+ return -ENOMEM;
+ }
+
+ new[len] = 0;
+
+ ret = iio_write_channel_ext_info(mux->parent,
+ mux->ext_info[private].name,
+ buf, len);
+ if (ret < 0) {
+ iio_mux_deselect(mux);
+ devm_kfree(dev, new);
+ return ret;
+ }
+
+ devm_kfree(dev, mux->child[idx].ext_info_cache[private].data);
+ mux->child[idx].ext_info_cache[private].data = new;
+ mux->child[idx].ext_info_cache[private].size = len;
+
+ iio_mux_deselect(mux);
+
+ return ret;
+}
+
+static int mux_configure_channel(struct device *dev, struct mux *mux,
+ 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@axentia.se>");
+MODULE_LICENSE("GPL v2");
--
2.1.4
^ permalink raw reply related
* [PATCH v8 07/12] dt-bindings: i2c: i2c-mux-simple: document i2c-mux-simple bindings
From: Peter Rosin @ 2017-01-18 15:57 UTC (permalink / raw)
To: linux-kernel
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, Andrew Morton, linux-i2c,
devicetree, linux-iio, linux-doc
In-Reply-To: <1484755035-25927-1-git-send-email-peda@axentia.se>
Describe how a generic multiplexer controller is used to mux an i2c bus.
Acked-by: Jonathan Cameron <jic23@kernel.org>
Signed-off-by: Peter Rosin <peda@axentia.se>
---
.../devicetree/bindings/i2c/i2c-mux-simple.txt | 81 ++++++++++++++++++++++
1 file changed, 81 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-mux-simple.txt
diff --git a/Documentation/devicetree/bindings/i2c/i2c-mux-simple.txt b/Documentation/devicetree/bindings/i2c/i2c-mux-simple.txt
new file mode 100644
index 000000000000..253d5027843b
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-mux-simple.txt
@@ -0,0 +1,81 @@
+Simple I2C Bus Mux
+
+This binding describes an I2C bus multiplexer that uses a mux controller
+from the mux subsystem to route the I2C signals.
+
+ .-----. .-----.
+ | dev | | dev |
+ .------------. '-----' '-----'
+ | SoC | | |
+ | | .--------+--------'
+ | .------. | .------+ child bus A, on MUX value set to 0
+ | | I2C |-|--| Mux |
+ | '------' | '--+---+ child bus B, on MUX value set to 1
+ | .------. | | '----------+--------+--------.
+ | | MUX- | | | | | |
+ | | Ctrl |-|-----+ .-----. .-----. .-----.
+ | '------' | | dev | | dev | | dev |
+ '------------' '-----' '-----' '-----'
+
+Required properties:
+- compatible: i2c-mux-simple,mux-locked or i2c-mux-simple,parent-locked
+- i2c-parent: The phandle of the I2C bus that this multiplexer's master-side
+ port is connected to.
+- mux-controls: The phandle of the mux controller to use for operating the
+ mux.
+* Standard I2C mux properties. See i2c-mux.txt in this directory.
+* I2C child bus nodes. See i2c-mux.txt in this directory. The sub-bus number
+ is also the mux-controller state described in ../mux/mux-controller.txt
+
+For each i2c child node, an I2C child bus will be created. They will
+be numbered based on their order in the device tree.
+
+Whenever an access is made to a device on a child bus, the value set
+in the relevant node's reg property will be set as the state in the
+mux controller.
+
+Example:
+ mux: mux-controller {
+ compatible = "mux-gpio";
+ #mux-control-cells = <0>;
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
+ <&pioA 1 GPIO_ACTIVE_HIGH>;
+ };
+
+ i2c-mux {
+ compatible = "i2c-mux-simple,mux-locked";
+ i2c-parent = <&i2c1>;
+
+ mux-controls = <&mux>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ i2c@1 {
+ reg = <1>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ssd1307: oled@3c {
+ compatible = "solomon,ssd1307fb-i2c";
+ reg = <0x3c>;
+ pwms = <&pwm 4 3000>;
+ reset-gpios = <&gpio2 7 1>;
+ reset-active-low;
+ };
+ };
+
+ i2c@3 {
+ reg = <3>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pca9555: pca9555@20 {
+ compatible = "nxp,pca9555";
+ gpio-controller;
+ #gpio-cells = <2>;
+ reg = <0x20>;
+ };
+ };
+ };
--
2.1.4
^ permalink raw reply related
* [PATCH v8 08/12] i2c: i2c-mux-simple: new driver
From: Peter Rosin @ 2017-01-18 15:57 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, Andrew Morton,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1484755035-25927-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
This is a generic simple i2c mux that uses the generic multiplexer
subsystem to do the muxing.
The user can select if the mux is to be mux-locked and parent-locked
as described in Documentation/i2c/i2c-topology.
Acked-by: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Acked-by: Wolfram Sang <wsa-z923LK4zBo2bacvFa/9K2g@public.gmane.org>
Signed-off-by: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
drivers/i2c/muxes/Kconfig | 13 +++
drivers/i2c/muxes/Makefile | 1 +
drivers/i2c/muxes/i2c-mux-simple.c | 182 +++++++++++++++++++++++++++++++++++++
3 files changed, 196 insertions(+)
create mode 100644 drivers/i2c/muxes/i2c-mux-simple.c
diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig
index 10b3d17ae3ea..565921e09a96 100644
--- a/drivers/i2c/muxes/Kconfig
+++ b/drivers/i2c/muxes/Kconfig
@@ -73,6 +73,19 @@ config I2C_MUX_REG
This driver can also be built as a module. If so, the module
will be called i2c-mux-reg.
+config I2C_MUX_SIMPLE
+ tristate "Simple I2C multiplexer"
+ select MULTIPLEXER
+ depends on OF
+ help
+ If you say yes to this option, support will be included for a
+ simple generic I2C multiplexer. This driver provides access to
+ I2C busses connected through a MUX, which is controlled
+ by a generic MUX controller.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-mux-simple.
+
config I2C_DEMUX_PINCTRL
tristate "pinctrl-based I2C demultiplexer"
depends on PINCTRL && OF
diff --git a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile
index 9948fa45037f..6821d95c92a3 100644
--- a/drivers/i2c/muxes/Makefile
+++ b/drivers/i2c/muxes/Makefile
@@ -11,5 +11,6 @@ obj-$(CONFIG_I2C_MUX_PCA9541) += i2c-mux-pca9541.o
obj-$(CONFIG_I2C_MUX_PCA954x) += i2c-mux-pca954x.o
obj-$(CONFIG_I2C_MUX_PINCTRL) += i2c-mux-pinctrl.o
obj-$(CONFIG_I2C_MUX_REG) += i2c-mux-reg.o
+obj-$(CONFIG_I2C_MUX_SIMPLE) += i2c-mux-simple.o
ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG
diff --git a/drivers/i2c/muxes/i2c-mux-simple.c b/drivers/i2c/muxes/i2c-mux-simple.c
new file mode 100644
index 000000000000..6a7de6e91ae3
--- /dev/null
+++ b/drivers/i2c/muxes/i2c-mux-simple.c
@@ -0,0 +1,182 @@
+/*
+ * Generic simple I2C multiplexer
+ *
+ * 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/i2c.h>
+#include <linux/i2c-mux.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#define SIMPLE_PARENT_LOCKED (0)
+#define SIMPLE_MUX_LOCKED (1)
+
+struct mux {
+ struct mux_control *control;
+
+ bool do_not_deselect;
+};
+
+static int i2c_mux_select(struct i2c_mux_core *muxc, u32 chan)
+{
+ struct mux *mux = i2c_mux_priv(muxc);
+ int ret;
+
+ ret = mux_control_select(mux->control, chan);
+ mux->do_not_deselect = ret < 0;
+
+ return ret;
+}
+
+static int i2c_mux_deselect(struct i2c_mux_core *muxc, u32 chan)
+{
+ struct mux *mux = i2c_mux_priv(muxc);
+
+ if (mux->do_not_deselect)
+ return 0;
+
+ return mux_control_deselect(mux->control);
+}
+
+static struct i2c_adapter *mux_parent_adapter(struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ struct device_node *parent_np;
+ struct i2c_adapter *parent;
+
+ parent_np = of_parse_phandle(np, "i2c-parent", 0);
+ if (!parent_np) {
+ dev_err(dev, "Cannot parse i2c-parent\n");
+ return ERR_PTR(-ENODEV);
+ }
+ parent = of_find_i2c_adapter_by_node(parent_np);
+ of_node_put(parent_np);
+ if (!parent)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ return parent;
+}
+
+static const struct of_device_id i2c_mux_of_match[] = {
+ { .compatible = "i2c-mux-simple,parent-locked",
+ .data = (void *)SIMPLE_PARENT_LOCKED, },
+ { .compatible = "i2c-mux-simple,mux-locked",
+ .data = (void *)SIMPLE_MUX_LOCKED, },
+ {},
+};
+MODULE_DEVICE_TABLE(of, i2c_mux_of_match);
+
+static int i2c_mux_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *child;
+ const struct of_device_id *match;
+ struct i2c_mux_core *muxc;
+ struct mux *mux;
+ struct i2c_adapter *parent;
+ int children;
+ int ret;
+
+ if (!np)
+ return -ENODEV;
+
+ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ return -ENOMEM;
+
+ 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);
+ }
+
+ parent = mux_parent_adapter(dev);
+ if (IS_ERR(parent)) {
+ if (PTR_ERR(parent) != -EPROBE_DEFER)
+ dev_err(dev, "failed to get i2c-parent adapter\n");
+ return PTR_ERR(parent);
+ }
+
+ children = of_get_child_count(np);
+
+ muxc = i2c_mux_alloc(parent, dev, children, 0, 0,
+ i2c_mux_select, i2c_mux_deselect);
+ if (!muxc) {
+ ret = -ENOMEM;
+ goto err_parent;
+ }
+ muxc->priv = mux;
+
+ platform_set_drvdata(pdev, muxc);
+
+ match = of_match_device(of_match_ptr(i2c_mux_of_match), dev);
+ if (match)
+ muxc->mux_locked = !!of_device_get_match_data(dev);
+
+ for_each_child_of_node(np, child) {
+ u32 chan;
+
+ ret = of_property_read_u32(child, "reg", &chan);
+ if (ret < 0) {
+ dev_err(dev, "no reg property for node '%s'\n",
+ child->name);
+ goto err_children;
+ }
+
+ if (chan >= mux->control->states) {
+ dev_err(dev, "invalid reg %u\n", chan);
+ ret = -EINVAL;
+ goto err_children;
+ }
+
+ ret = i2c_mux_add_adapter(muxc, 0, chan, 0);
+ if (ret)
+ goto err_children;
+ }
+
+ dev_info(dev, "%d-port mux on %s adapter\n", children, parent->name);
+
+ return 0;
+
+err_children:
+ i2c_mux_del_adapters(muxc);
+err_parent:
+ i2c_put_adapter(parent);
+
+ return ret;
+}
+
+static int i2c_mux_remove(struct platform_device *pdev)
+{
+ struct i2c_mux_core *muxc = platform_get_drvdata(pdev);
+
+ i2c_mux_del_adapters(muxc);
+ i2c_put_adapter(muxc->parent);
+
+ return 0;
+}
+
+static struct platform_driver i2c_mux_driver = {
+ .probe = i2c_mux_probe,
+ .remove = i2c_mux_remove,
+ .driver = {
+ .name = "i2c-mux-simple",
+ .of_match_table = i2c_mux_of_match,
+ },
+};
+module_platform_driver(i2c_mux_driver);
+
+MODULE_DESCRIPTION("Simple I2C multiplexer driver");
+MODULE_AUTHOR("Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
--
2.1.4
^ permalink raw reply related
* [PATCH v8 12/12] mux: support simplified bindings for single-user gpio mux
From: Peter Rosin @ 2017-01-18 15:57 UTC (permalink / raw)
To: linux-kernel
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, Andrew Morton, linux-i2c,
devicetree, linux-iio, linux-doc
In-Reply-To: <1484755035-25927-1-git-send-email-peda@axentia.se>
Allow bindings for a GPIO controlled mux to be specified in the
mux consumer node.
Signed-off-by: Peter Rosin <peda@axentia.se>
---
drivers/mux/Kconfig | 5 +----
drivers/mux/mux-core.c | 23 +++++++++++++++++++++--
drivers/mux/mux-gpio.c | 28 +++++++++++++++++++++-------
drivers/mux/mux-gpio.h | 13 +++++++++++++
4 files changed, 56 insertions(+), 13 deletions(-)
create mode 100644 drivers/mux/mux-gpio.h
diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig
index d13fcf98958e..cd161c228537 100644
--- a/drivers/mux/Kconfig
+++ b/drivers/mux/Kconfig
@@ -29,7 +29,7 @@ config MUX_ADG792A
be called mux-adg792a.
config MUX_GPIO
- tristate "GPIO-controlled Multiplexer"
+ bool "GPIO-controlled Multiplexer"
depends on OF && GPIOLIB
help
GPIO-controlled Multiplexer controller.
@@ -39,7 +39,4 @@ config MUX_GPIO
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/mux-core.c b/drivers/mux/mux-core.c
index 16a61253d164..0caafd6f5a77 100644
--- a/drivers/mux/mux-core.c
+++ b/drivers/mux/mux-core.c
@@ -21,6 +21,8 @@
#include <linux/of_platform.h>
#include <linux/slab.h>
+#include "mux-gpio.h"
+
static struct class mux_class = {
.name = "mux",
.owner = THIS_MODULE,
@@ -314,9 +316,26 @@ struct mux_control *mux_control_get(struct device *dev, const char *mux_name)
ret = of_parse_phandle_with_args(np,
"mux-controls", "#mux-control-cells",
index, &args);
+
+#ifdef CONFIG_MUX_GPIO
+ if (ret == -ENOENT && !mux_name) {
+ mux_chip = mux_gpio_alloc(dev);
+ if (!IS_ERR(mux_chip)) {
+ ret = devm_mux_chip_register(dev, mux_chip);
+ if (ret < 0)
+ return ERR_PTR(ret);
+ get_device(&mux_chip->dev);
+ return mux_chip->mux;
+ }
+
+ ret = PTR_ERR(mux_chip);
+ }
+#endif
+
if (ret) {
- dev_err(dev, "%s: failed to get mux-control %s(%i)\n",
- np->full_name, mux_name ?: "", index);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "%s: failed to get mux-control %s(%i)\n",
+ np->full_name, mux_name ?: "", index);
return ERR_PTR(ret);
}
diff --git a/drivers/mux/mux-gpio.c b/drivers/mux/mux-gpio.c
index 48ca4d6b4fc7..2ab8735e3415 100644
--- a/drivers/mux/mux-gpio.c
+++ b/drivers/mux/mux-gpio.c
@@ -18,6 +18,8 @@
#include <linux/platform_device.h>
#include <linux/property.h>
+#include "mux-gpio.h"
+
struct mux_gpio {
struct gpio_descs *gpios;
int *val;
@@ -48,24 +50,21 @@ static const struct of_device_id mux_gpio_dt_ids[] = {
};
MODULE_DEVICE_TABLE(of, mux_gpio_dt_ids);
-static int mux_gpio_probe(struct platform_device *pdev)
+struct mux_chip *mux_gpio_alloc(struct device *dev)
{
- struct device *dev = &pdev->dev;
- struct device_node *np = dev->of_node;
struct mux_chip *mux_chip;
struct mux_gpio *mux_gpio;
int pins;
- u32 idle_state;
int ret;
pins = gpiod_count(dev, "mux");
if (pins < 0)
- return pins;
+ return ERR_PTR(pins);
mux_chip = devm_mux_chip_alloc(dev, 1, sizeof(*mux_gpio) +
pins * sizeof(*mux_gpio->val));
if (!mux_chip)
- return -ENOMEM;
+ return ERR_PTR(-ENOMEM);
mux_gpio = mux_chip_priv(mux_chip);
mux_gpio->val = (int *)(mux_gpio + 1);
@@ -76,11 +75,26 @@ static int mux_gpio_probe(struct platform_device *pdev)
ret = PTR_ERR(mux_gpio->gpios);
if (ret != -EPROBE_DEFER)
dev_err(dev, "failed to get gpios\n");
- return ret;
+ return ERR_PTR(ret);
}
WARN_ON(pins != mux_gpio->gpios->ndescs);
mux_chip->mux->states = 1 << pins;
+ return mux_chip;
+}
+EXPORT_SYMBOL_GPL(mux_gpio_alloc);
+
+static int mux_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mux_chip *mux_chip;
+ u32 idle_state;
+ int ret;
+
+ mux_chip = mux_gpio_alloc(dev);
+ if (IS_ERR(mux_chip))
+ return PTR_ERR(mux_chip);
+
ret = device_property_read_u32(dev, "idle-state", &idle_state);
if (ret >= 0) {
if (idle_state >= mux_chip->mux->states) {
diff --git a/drivers/mux/mux-gpio.h b/drivers/mux/mux-gpio.h
new file mode 100644
index 000000000000..fe3e8d0173aa
--- /dev/null
+++ b/drivers/mux/mux-gpio.h
@@ -0,0 +1,13 @@
+/*
+ * GPIO-controlled multiplexer driver 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.
+ */
+
+struct mux_chip *mux_gpio_alloc(struct device *dev);
--
2.1.4
^ permalink raw reply related
* [PATCH v8 10/12] mux: adg792a: add mux controller driver for ADG792A/G
From: Peter Rosin @ 2017-01-18 15:57 UTC (permalink / raw)
To: linux-kernel
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, Andrew Morton, linux-i2c,
devicetree, linux-iio, linux-doc
In-Reply-To: <1484755035-25927-1-git-send-email-peda@axentia.se>
Analog Devices ADG792A/G is a triple 4:1 mux.
Reviewed-by: Jonathan Cameron <jic23@kernel.org>
Signed-off-by: Peter Rosin <peda@axentia.se>
---
drivers/mux/Kconfig | 12 ++++
drivers/mux/Makefile | 1 +
drivers/mux/mux-adg792a.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 180 insertions(+)
create mode 100644 drivers/mux/mux-adg792a.c
diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig
index 57e6f4e5fda9..d13fcf98958e 100644
--- a/drivers/mux/Kconfig
+++ b/drivers/mux/Kconfig
@@ -16,6 +16,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 facc43da3648..fddbb073fc77 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..6a4c4c9f585b
--- /dev/null
+++ b/drivers/mux/mux-adg792a.c
@@ -0,0 +1,167 @@
+/*
+ * Multiplexer driver for Analog Devices ADG792A/G Triple 4:1 mux
+ *
+ * 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/i2c.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+
+#define ADG792A_LDSW BIT(0)
+#define ADG792A_RESET BIT(1)
+#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))
+
+#define ADG792A_DISABLE_STATE (4)
+
+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 == ADG792A_DISABLE_STATE)
+ cmd = ADG792A_DISABLE_ALL;
+ else
+ cmd = ADG792A_MUX_ALL(state);
+ } else {
+ unsigned int controller = mux_control_get_index(mux);
+
+ if (state == ADG792A_DISABLE_STATE)
+ cmd = ADG792A_DISABLE(controller);
+ else
+ cmd = ADG792A_MUX(controller, state);
+ }
+
+ return i2c_smbus_write_byte_data(i2c, cmd, 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;
+ bool parallel;
+ int count;
+ int ret;
+ int i;
+
+ parallel = of_property_read_bool(i2c->dev.of_node, "adi,parallel");
+
+ mux_chip = devm_mux_chip_alloc(dev, parallel ? 1 : 3, 0);
+ if (!mux_chip)
+ return -ENOMEM;
+
+ mux_chip->ops = &adg792a_ops;
+
+ ret = i2c_smbus_write_byte_data(i2c, ADG792A_DISABLE_ALL,
+ ADG792A_RESET | ADG792A_LDSW);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < mux_chip->controllers; ++i) {
+ struct mux_control *mux = &mux_chip->mux[i];
+
+ mux->states = 4;
+ }
+
+ count = of_property_count_u32_elems(dev->of_node, "adi,idle-state");
+ for (i = 0; i < count; i += 2) {
+ u32 index;
+ u32 idle_state;
+
+ ret = of_property_read_u32_index(dev->of_node,
+ "adi,idle-state", i,
+ &index);
+ if (ret < 0)
+ return ret;
+ if (index >= mux_chip->controllers) {
+ dev_err(dev, "invalid mux %u\n", index);
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32_index(dev->of_node,
+ "adi,idle-state", i + 1,
+ &idle_state);
+ if (ret < 0)
+ return ret;
+ if (idle_state >= ADG792A_DISABLE_STATE) {
+ dev_err(dev, "invalid idle-state %u for mux %u\n",
+ idle_state, index);
+ return -EINVAL;
+ }
+ mux_chip->mux[index].idle_state = idle_state;
+ }
+
+ count = of_property_count_u32_elems(dev->of_node,
+ "adi,idle-high-impedance");
+ for (i = 0; i < count; ++i) {
+ u32 index;
+
+ ret = of_property_read_u32_index(dev->of_node,
+ "adi,idle-high-impedance", i,
+ &index);
+ if (ret < 0)
+ return ret;
+ if (index >= mux_chip->controllers) {
+ dev_err(dev, "invalid mux %u\n", index);
+ return -EINVAL;
+ }
+
+ mux_chip->mux[index].idle_state = ADG792A_DISABLE_STATE;
+ }
+
+ ret = devm_mux_chip_register(dev, mux_chip);
+ if (ret < 0)
+ return ret;
+
+ if (parallel)
+ dev_info(dev, "triple pole quadruple throw mux registered\n");
+ else
+ dev_info(dev, "3x single pole quadruple throw muxes 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@axentia.se");
+MODULE_LICENSE("GPL v2");
--
2.1.4
^ permalink raw reply related
* [PATCH v8 11/12] dt-bindings: simplified bindings for single-user gpio mux
From: Peter Rosin @ 2017-01-18 15:57 UTC (permalink / raw)
To: linux-kernel
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, Andrew Morton, linux-i2c,
devicetree, linux-iio, linux-doc
In-Reply-To: <1484755035-25927-1-git-send-email-peda@axentia.se>
Allow bindings for a GPIO controlled mux to be specified in the
mux consumer node.
Acked-by: Jonathan Cameron <jic23@kernel.org>
Signed-off-by: Peter Rosin <peda@axentia.se>
---
.../devicetree/bindings/mux/mux-controller.txt | 26 ++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/Documentation/devicetree/bindings/mux/mux-controller.txt b/Documentation/devicetree/bindings/mux/mux-controller.txt
index 42b2177e5ae1..4e89df8b2392 100644
--- a/Documentation/devicetree/bindings/mux/mux-controller.txt
+++ b/Documentation/devicetree/bindings/mux/mux-controller.txt
@@ -125,3 +125,29 @@ An example mux controller might look like this:
reg = <0x50>;
#mux-control-cells = <1>;
};
+
+
+Combinded controller and consumer of a GPIO mux
+-----------------------------------------------
+
+For the common case of a single consumer of a GPIO controlled mux, there is
+a simplified binding which will instantiate an implicit mux controller. Just
+specify a mux-gpios property with the same interpretation as in mux-gpio.txt.
+Note that other properties described in mux-gpio.txt are not available in
+this simplified form and that the mux controller is unnamed. If you need
+more than one mux controller, a shared mux controller or if you need a
+specific idle-state, use the more flexible binding with the mux controller
+in its own node.
+
+Example:
+
+ adc-mux {
+ compatible = "io-channel-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
+ <&pioA 1 GPIO_ACTIVE_HIGH>;
+
+ channels = "sync-1", "in", "out", "sync-2";
+ };
--
2.1.4
^ permalink raw reply related
* [PATCH v8 09/12] dt-bindings: mux-adg792a: document devicetree bindings for ADG792A/G mux
From: Peter Rosin @ 2017-01-18 15:57 UTC (permalink / raw)
To: linux-kernel
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, Andrew Morton, linux-i2c,
devicetree, linux-iio, linux-doc
In-Reply-To: <1484755035-25927-1-git-send-email-peda@axentia.se>
Analog Devices ADG792A/G is a triple 4:1 mux.
Acked-by: Jonathan Cameron <jic23@kernel.org>
Signed-off-by: Peter Rosin <peda@axentia.se>
---
.../devicetree/bindings/mux/mux-adg792a.txt | 79 ++++++++++++++++++++++
1 file changed, 79 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mux/mux-adg792a.txt
diff --git a/Documentation/devicetree/bindings/mux/mux-adg792a.txt b/Documentation/devicetree/bindings/mux/mux-adg792a.txt
new file mode 100644
index 000000000000..0b26dd11f070
--- /dev/null
+++ b/Documentation/devicetree/bindings/mux/mux-adg792a.txt
@@ -0,0 +1,79 @@
+Bindings for Analog Devices ADG792A/G Triple 4:1 Multiplexers
+
+Required properties:
+- compatible : "adi,adg792a" or "adi,adg792g"
+- #mux-control-cells : <0> if parallel, or <1> if not.
+* Standard mux-controller bindings as decribed 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:
+- adi,parallel : if present, the three muxes are bound together with a single
+ mux controller, controlling all three muxes in parallel.
+- adi,idle-state : if present, array of 2-tuples with mux controller number
+ and state that mux controllers will have when idle. States 0 through 3
+ correspond to signals A through D in the datasheet.
+- adi,idle-high-impedance : if present, array of mux controller numbers that
+ should be in the disconnected high-impedance state when idle.
+
+Mux controller states 0 through 3 correspond to signals A through D in the
+datasheet. If a mux controller is mentioned in neither adi,idle-state nor
+adi,idle-high-impedance it is left in its previously selected state when idle.
+
+Example:
+
+ /*
+ * Three independent mux controllers (of which one is used).
+ * Mux 0 is disconnected when idle, mux 1 idles with signal C
+ * and mux 2 idles with signal A.
+ */
+ &i2c0 {
+ mux: adg792a@50 {
+ compatible = "adi,adg792a";
+ reg = <0x50>;
+ #mux-control-cells = <1>;
+
+ adi,idle-high-impedance = <0>;
+ adi,idle-state = <1 2>, <2 0>;
+ };
+ };
+
+ adc-mux {
+ compatible = "io-channel-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ mux-controls = <&mux 1>;
+
+ 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: adg792a@50 {
+ compatible = "adi,adg792a";
+ reg = <0x50>;
+ #mux-control-cells = <0>;
+ adi,parallel;
+ };
+ };
+
+ 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
* [PATCH v8 03/12] mux: minimal mux subsystem and gpio-based mux controller
From: Peter Rosin @ 2017-01-18 15:57 UTC (permalink / raw)
To: linux-kernel
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, Andrew Morton, linux-i2c,
devicetree, linux-iio, linux-doc
In-Reply-To: <1484755035-25927-1-git-send-email-peda@axentia.se>
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 | 33 +++
drivers/mux/Makefile | 6 +
drivers/mux/mux-core.c | 406 ++++++++++++++++++++++++++++++++++
drivers/mux/mux-gpio.c | 115 ++++++++++
include/linux/mux.h | 244 ++++++++++++++++++++
9 files changed, 817 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 dc51fb024190..1e9ae701a587 100644
--- a/Documentation/driver-model/devres.txt
+++ b/Documentation/driver-model/devres.txt
@@ -332,6 +332,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 be57f5b8f2c2..e4fac17e0fa4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8473,6 +8473,8 @@ MULTIPLEXER SUBSYSTEM
M: Peter Rosin <peda@axentia.se>
S: Maintained
F: Documentation/devicetree/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 e1e2066cecdb..ea231018089e 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -202,4 +202,6 @@ source "drivers/hwtracing/intel_th/Kconfig"
source "drivers/fpga/Kconfig"
+source "drivers/mux/Kconfig"
+
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 060026a02f59..8ff0460bb2f8 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -173,3 +173,4 @@ obj-$(CONFIG_STM) += hwtracing/stm/
obj-$(CONFIG_ANDROID) += android/
obj-$(CONFIG_NVMEM) += nvmem/
obj-$(CONFIG_FPGA) += fpga/
+obj-$(CONFIG_MULTIPLEXER) += mux/
diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig
new file mode 100644
index 000000000000..57e6f4e5fda9
--- /dev/null
+++ b/drivers/mux/Kconfig
@@ -0,0 +1,33 @@
+#
+# Multiplexer devices
+#
+
+menuconfig MULTIPLEXER
+ bool "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.
+
+ If unsure, say no.
+
+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..facc43da3648
--- /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..16a61253d164
--- /dev/null
+++ b/drivers/mux/mux-core.c
@@ -0,0 +1,406 @@
+/*
+ * 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/idr.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+
+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 = -1;
+ mux->idle_state = -1;
+ }
+
+ 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 ? -1 : 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 ERR_PTR(-ENOMEM);
+
+ mux_chip = mux_chip_alloc(dev, controllers, sizeof_priv);
+ if (IS_ERR(mux_chip)) {
+ devres_free(ptr);
+ return mux_chip;
+ }
+
+ *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 != -1)
+ 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 != -1 && mux->cached_state != mux->idle_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);
+
+subsys_initcall(mux_init);
+
+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..48ca4d6b4fc7
--- /dev/null
+++ b/drivers/mux/mux-gpio.c
@@ -0,0 +1,115 @@
+/*
+ * 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 = "mux-gpio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mux_gpio_dt_ids);
+
+static int mux_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct mux_chip *mux_chip;
+ struct mux_gpio *mux_gpio;
+ int pins;
+ u32 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", &idle_state);
+ if (ret >= 0) {
+ if (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 = "mux-gpio",
+ .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..ec9e605d8acf
--- /dev/null
+++ b/include/linux/mux.h
@@ -0,0 +1,244 @@
+/*
+ * 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 {
+ int (*set)(struct mux_control *mux, int state);
+};
+
+/**
+ * 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 -1
+ * for none.
+ */
+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 is 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 negavive
+ * 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 for others to 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 the 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
* [PATCH v8 00/12] mux controller abstraction and iio/i2c muxes
From: Peter Rosin @ 2017-01-18 15:57 UTC (permalink / raw)
To: linux-kernel
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, Andrew Morton, linux-i2c,
devicetree, linux-iio, linux-doc
Hi!
v7 -> v8 changes
- cleanup the implementation of the simplified gpio bindings, but still...
- ...reorder patches so that they appear last in the series (patches 11
and 12 were patches 4 and 5 in v7) since Jonathan convinced me that
they were perhaps not such a good idea after all. But I still wanted
to show the last version I had and I'm still a bit undecided...
- added some words to the remaining otherwise empty commit messages
- added various acks/reviews from Jonathan and Wolfram.
- move mux last in drivers/Kconfig and drivers/Makefile
- bump copyright years
- centralize error reporting of common operatinons to the mux core
- add WARN_ON for faulty usage of mux_chip_register
- simplify code for other WARN_ON call sites
v6 -> v7 changes
- move from drivers/misc/mux-* to drivers/mux/
[I will remove Cc:s to Arnd and Greg for v8, when/if that happens]
- add managed versions of mux_chip_alloc and mux_chip_register
- use the above in mux-gpio.c and mux-adg792a.c
- also use them to support a simplified binding of a gpio mux for the common
case where there is a single consumer (and no specific idle requirements)
- new binding for describing idle behaviour of mux-adg792a
- add bindings for the gpo-pins on the mux-adg792g
- use device_property_read_u32 instead of of_property_read_u32 in mux-gpio.c
- rename iio mux compatible to io-channel-mux (was iio-mux)
- remove linuxism in the bindings (it was mentioning a function name)
- add missing quote in the example in the io-channel-mux binding
- factor out preparatory cleanup of devres docs to its own patch
- add blank line in mux_chip_free
- use SIMPLE_{PARENT,MUX}_LOCKED instead of magic numbers {0,1} in
i2c-mux-simple.c
- add some acks and a reviewed-by from Jonathan
v5 -> v6 changes
- fix stupidity in mux_chip_priv, mux_gpio_remove and adg792a_remove.
- change the devicetree bindings for the iio-mux to use a list of strings
(channels property) instead of a list children.
v4 -> v5 changes
- remove support for fancier dt layouts and go back to the phandle
approach from v2 and before, killing the horrible non-working
refcounting crap from v4 and avoiding a bunch of life-time issues
in v3.
- introduce the concept of a mux-chip, that can hold one or more
mux-controllers (inspired by the pwm subsystem).
- add dt #mux-control-cells property needed to get to the desired
mux controller if a mux chip provides more than one.
- take away the option to build the mux-core as a module.
- if the mux controller has an idle state, make sure the mux controller
is set up in the idle state initially (when it should be idle).
- do not use a variable length array on the stack in mux_gpio_set to
temporarily store the gpio state, preallocate space instead.
- fix resource leak on one failure path in mux_gpio_probe.
- driver for Analog Devices ADG792A/G, literally the first mux chip
I found on the Internet with an i2c interface (that was not a
dedicated i2c multiplexer like PCA9547) which I used to verify
that the abstractions in the mux core are up to the task. Untested,
just proof of concept that at least looks pretty and compiles...
- various touch-ups.
v3 -> v4 changes
- rebased onto next-20161122 (depends on recent _available iio changes).
- added support for having the mux-controller in a child node of a
mux-consumer if it is a sole consumer, to hopefully even further satisfy
the complaint from Rob (and later Lars-Peter) about dt complexity.
- the above came at the cost of some rather horrible refcounting code,
please review and suggest how it should be done...
- changed to register a device class instead of a bus.
- pass in the parent device into mux_control_alloc and require less
work from mux-control drivers.
- changed device names from mux:control%d to mux%d
- move kernel-doc from mux-core.c to mux.h (and add some bits).
- give the gpio driver a chance to update all mux pins at once.
- factor out iio ext_info lookup into new helper function. /Lars-Peter
- use an unsigned type for the iio ext_info count. /Lars-Peter
- unified "brag strings" in the file headers.
v2 -> v3 changes
- have the mux-controller in the parent node of any mux-controller consumer,
to hopefully satisfy complaint from Rob about dt complexity.
- improve commit message of the mux subsystem commit, making it more
general, as requested by Jonathan.
- remove priv member from struct mux_control and calculate it on the
fly. /Jonathan
- make the function comments in mux-core.c kernel doc. /Jonathan
- add devm_mux_control_* to Documentation/driver.model/devres.txt. /Jonathan
- add common dt bindings for mux-controllers, refer to them from the
mux-gpio bindings. /Rob
- clarify how the gpio pins map to the mux state. /Rob
- separate CONFIG_ variables for the mux core and the mux gpio driver.
- improve Kconfig help texts.
- make CONFIG_MUX_GPIO depend on CONFIG_GPIOLIB.
- keep track of the number of mux states in the mux core.
- since the iio channel number is used as mux state, it was possible
to drop the state member from the mux_child struct.
- cleanup dt bindings for i2c-mux-simple, it had some of copy-paste
problems from ots origin (i2c-mux-gpio).
- select the mux control subsystem in config for the i2c-mux-simple driver.
- add entries to MAINTAINERS and my sign-off, I'm now satisfied and know
nothing in this to be ashamed of.
v1 -> v2 changes
- fixup export of mux_control_put reported by kbuild
- drop devicetree iio-ext-info property as noted by Lars-Peter,
and replace the functionality by exposing all ext_info
attributes of the parent channel for each of the muxed
channels. A cache on top of that and each muxed channel
gets its own view of the ext_info of the parent channel.
- implement idle-state for muxes
- clear out the cache on failure in order to force a mux
update on the following use
- cleanup the probe of i2c-mux-simple driver
- fix a bug in the i2c-mux-simple driver, where failure in
the selection of the mux caused a deadlock when the mux
was later unconditionally deselected.
I have a piece of hardware that is using the same 3 GPIO pins
to control four 8-way muxes. Three of them control ADC lines
to an ADS1015 chip with an iio driver, and the last one
controls the SDA line of an i2c bus. We have some deployed
code to handle this, but you do not want to see it or ever
hear about it. I'm not sure why I even mention it. Anyway,
the situation has nagged me to no end for quite some time.
So, after first getting more intimate with the i2c muxing code
and later discovering the drivers/iio/inkern.c file and
writing a couple of drivers making use of it, I came up with
what I think is an acceptable solution; add a generic mux
controller driver (and subsystem) that is shared between all
instances, and combine that with an iio mux driver and a new
generic i2c mux driver. The new i2c mux I called "simple"
since it is only hooking the i2c muxing and the new mux
controller (much like the alsa simple card driver does for ASoC).
One thing that I would like to do, but don't see a solution
for, is to move the mux control code that is present in
various drivers in drivers/i2c/muxes to this new minimalistic
muxing subsystem, thus converting all present i2c muxes (but
perhaps not gates and arbitrators) to be i2c-mux-simple muxes.
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 "mux" name feels a bit ambitious, there are many muxes
in the world, and this tiny bit of code is probably not good
enough to be a nice fit for all...
Cheers,
peda
Peter Rosin (12):
devres: trivial whitespace fix
dt-bindings: document devicetree bindings for mux-controllers and
mux-gpio
mux: minimal mux subsystem and gpio-based mux controller
iio: inkern: api for manipulating ext_info of iio channels
dt-bindings: iio: io-channel-mux: document io-channel-mux bindings
iio: multiplexer: new iio category and iio-mux driver
dt-bindings: i2c: i2c-mux-simple: document i2c-mux-simple bindings
i2c: i2c-mux-simple: new driver
dt-bindings: mux-adg792a: document devicetree bindings for ADG792A/G
mux
mux: adg792a: add mux controller driver for ADG792A/G
dt-bindings: simplified bindings for single-user gpio mux
mux: support simplified bindings for single-user gpio mux
.../devicetree/bindings/i2c/i2c-mux-simple.txt | 81 ++++
.../bindings/iio/multiplexer/io-channel-mux.txt | 39 ++
.../devicetree/bindings/mux/mux-adg792a.txt | 79 ++++
.../devicetree/bindings/mux/mux-controller.txt | 153 +++++++
Documentation/devicetree/bindings/mux/mux-gpio.txt | 68 +++
Documentation/driver-model/devres.txt | 10 +-
MAINTAINERS | 14 +
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/i2c/muxes/Kconfig | 13 +
drivers/i2c/muxes/Makefile | 1 +
drivers/i2c/muxes/i2c-mux-simple.c | 182 ++++++++
drivers/iio/Kconfig | 1 +
drivers/iio/Makefile | 1 +
drivers/iio/inkern.c | 60 +++
drivers/iio/multiplexer/Kconfig | 18 +
drivers/iio/multiplexer/Makefile | 6 +
drivers/iio/multiplexer/iio-mux.c | 456 +++++++++++++++++++++
drivers/mux/Kconfig | 42 ++
drivers/mux/Makefile | 7 +
drivers/mux/mux-adg792a.c | 167 ++++++++
drivers/mux/mux-core.c | 425 +++++++++++++++++++
drivers/mux/mux-gpio.c | 129 ++++++
drivers/mux/mux-gpio.h | 13 +
include/linux/iio/consumer.h | 37 ++
include/linux/mux.h | 244 +++++++++++
26 files changed, 2248 insertions(+), 1 deletion(-)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-mux-simple.txt
create mode 100644 Documentation/devicetree/bindings/iio/multiplexer/io-channel-mux.txt
create mode 100644 Documentation/devicetree/bindings/mux/mux-adg792a.txt
create mode 100644 Documentation/devicetree/bindings/mux/mux-controller.txt
create mode 100644 Documentation/devicetree/bindings/mux/mux-gpio.txt
create mode 100644 drivers/i2c/muxes/i2c-mux-simple.c
create mode 100644 drivers/iio/multiplexer/Kconfig
create mode 100644 drivers/iio/multiplexer/Makefile
create mode 100644 drivers/iio/multiplexer/iio-mux.c
create mode 100644 drivers/mux/Kconfig
create mode 100644 drivers/mux/Makefile
create mode 100644 drivers/mux/mux-adg792a.c
create mode 100644 drivers/mux/mux-core.c
create mode 100644 drivers/mux/mux-gpio.c
create mode 100644 drivers/mux/mux-gpio.h
create mode 100644 include/linux/mux.h
--
2.1.4
^ permalink raw reply
* Re: [PATCH v9 2/5] i2c: Add STM32F4 I2C driver
From: Uwe Kleine-König @ 2017-01-18 18:42 UTC (permalink / raw)
To: M'boumba Cedric Madianga
Cc: devicetree, Alexandre Torgue, Wolfram Sang, linux-kernel,
Linus Walleij, Patrice Chotard, Russell King, Rob Herring,
linux-i2c, Maxime Coquelin, linux-arm-kernel
In-Reply-To: <CAOAejn0ea2j_oUijOdHgiL3JU4TM0HZoL+-ggzjpPL2eA7tDCw@mail.gmail.com>
Hello Cedric,
On Wed, Jan 18, 2017 at 04:21:17PM +0100, M'boumba Cedric Madianga wrote:
> >> + * In standard mode, the maximum allowed SCL rise time is 1000 ns.
> >> + * If, in the I2C_CR2 register, the value of FREQ[5:0] bits is equal to
> >> + * 0x08 so period = 125 ns therefore the TRISE[5:0] bits must be
> >> + * programmed with 09h.(1000 ns / 125 ns = 8 + 1)
> >
> > * programmed with 0x9.
> > (1000 ns / 125 ns = 8)
> >
> >> + * So, for I2C standard mode TRISE = FREQ[5:0] + 1
> >> + *
> >> + * In fast mode, the maximum allowed SCL rise time is 300 ns.
> >> + * If, in the I2C_CR2 register, the value of FREQ[5:0] bits is equal to
> >> + * 0x08 so period = 125 ns therefore the TRISE[5:0] bits must be
> >> + * programmed with 03h.(300 ns / 125 ns = 2 + 1)
> >
> > as above s/03h/0x3/;
>
> ok
>
> > s/.(/. (/;
> ok
>
> > s/+ 1//;
> This formula is use to understand how we find the result 0x3
> So, 0x3 => 300 ns / 125ns = 2 + 1
Yeah, I understood that, but writing 300 ns / 125ns = 2 + 1 is
irritating at best.
> >> + * So, for I2C fast mode TRISE = FREQ[5:0] * 300 / 1000 + 1
> >> + */
> >> + if (i2c_dev->speed == STM32F4_I2C_SPEED_STANDARD)
> >> + trise = freq + 1;
> >> + else
> >> + trise = freq * 300 / 1000 + 1;
> >
> > I'd use
> >
> > * 3 / 10
> >
> > without downside and lesser chance to overflow.
>
> There is no chance of overflow as the max freq value allowed is 46
ok
> >> + /*
> >> + * In fast mode, we compute CCR with duty = 0 as with low
> >> + * frequencies we are not able to reach 400 kHz.
> >> + * In that case:
> >> + * t_scl_high = CCR * I2C parent clk period
> >> + * t_scl_low = 2 * CCR * I2C parent clk period
> >> + * So, CCR = I2C parent rate / (400 kHz * 3)
> >> + *
> >> + * For example with parent rate = 6 MHz:
> >> + * CCR = 6000000 / (400000 * 3) = 5
> >> + * t_scl_high = 5 * (1 / 6000000) = 833 ns > 600 ns
> >> + * t_scl_low = 2 * 5 * (1 / 6000000) = 1667 ns > 1300 ns
> >> + * t_scl_high + t_scl_low = 2500 ns so 400 kHz is reached
> >> + */
> >
> > Huh, that's surprising. So you don't use DUTY any more. I found two
> > hints in the manual that contradict here:
>
> Yes with the above formula we could use duty = 0 by default
>
> >
> > f_{PCLK1} must be at least 2 MHz to achieve Sm mode I2C frequencies
>
> STM32F4_I2C_MIN_STANDARD_FREQ = 2
>
> > It must be at least 4 MHz to achieve Fm mode I2C frequencies.
>
> STM32F4_I2C_MIN_FAST_FREQ = 6
>
> > It must be a multiple of 10MHz to reach the 400 kHz maximum I2C Fm mode clock.
>
> If we use this rule only 3 values are allowed 10 Mhz, 20 Mhz, 30 Mhz and 40 Mhz.
> It is very restrictive.
> So I don't take it into account in order to have more frequencies even
> if 400 Khz is not reached.
> Indeed, in many cases we are very close to 400 Khz.
> For example, the default I2C parent clock in my board is 45 Mhz
> I reach 395 kHz in theory and 390 kHz by testing.
> I am in Fast mode but not with the max freq but very close.
fine
> > and
> >
> > [...]
> > If DUTY = 1: (to reach 400 kHz)
> >
> > Strange.
> >
> >> + val = DIV_ROUND_UP(i2c_dev->parent_rate, 400000 * 3);
> >
> > the manual reads:
> >
> > The minimum allowed value is 0x04, except in FAST DUTY mode
> > where the minimum allowed value is 0x01
> >
> > You don't check for that, right?
>
> As the minimum freq value is 6 Mhz in fast mode the minimum CCR is 5
> as described in the comment.
> So I don't need to check that again as it is already done by checking
> parent frequency.
That would then go into a comment.
> > CCR is 11 bits wide. A comment confirming that this cannot overflow
> > would be nice.
>
> Again there is no chance of overflow thanks to parent frequency check
Right, this time I saw this myself, so I requested a comment stating
this fact.
Best regards
Uwe
--
Pengutronix e.K. | Uwe Kleine-König |
Industrial Linux Solutions | http://www.pengutronix.de/ |
^ permalink raw reply
* Re: [PATCH v9 2/5] i2c: Add STM32F4 I2C driver
From: M'boumba Cedric Madianga @ 2017-01-18 20:55 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: devicetree, Alexandre Torgue, Wolfram Sang, linux-kernel,
Linus Walleij, Patrice Chotard, Russell King, Rob Herring,
linux-i2c, Maxime Coquelin, linux-arm-kernel
In-Reply-To: <20170118184237.tvhlsksdcw2ckwan@pengutronix.de>
2017-01-18 19:42 GMT+01:00 Uwe Kleine-König <u.kleine-koenig@pengutronix.de>:
> Hello Cedric,
>
> On Wed, Jan 18, 2017 at 04:21:17PM +0100, M'boumba Cedric Madianga wrote:
>> >> + * In standard mode, the maximum allowed SCL rise time is 1000 ns.
>> >> + * If, in the I2C_CR2 register, the value of FREQ[5:0] bits is equal to
>> >> + * 0x08 so period = 125 ns therefore the TRISE[5:0] bits must be
>> >> + * programmed with 09h.(1000 ns / 125 ns = 8 + 1)
>> >
>> > * programmed with 0x9.
>> > (1000 ns / 125 ns = 8)
>> >
>> >> + * So, for I2C standard mode TRISE = FREQ[5:0] + 1
>> >> + *
>> >> + * In fast mode, the maximum allowed SCL rise time is 300 ns.
>> >> + * If, in the I2C_CR2 register, the value of FREQ[5:0] bits is equal to
>> >> + * 0x08 so period = 125 ns therefore the TRISE[5:0] bits must be
>> >> + * programmed with 03h.(300 ns / 125 ns = 2 + 1)
>> >
>> > as above s/03h/0x3/;
>>
>> ok
>>
>> > s/.(/. (/;
>> ok
>>
>> > s/+ 1//;
>> This formula is use to understand how we find the result 0x3
>> So, 0x3 => 300 ns / 125ns = 2 + 1
>
> Yeah, I understood that, but writing 300 ns / 125ns = 2 + 1 is
> irritating at best.
Ok. I will write 0x3 (300 ns / 125 ns + 1) and 0x9 (1000 ns / 125 ns + 1)
>> > [...]
>> > If DUTY = 1: (to reach 400 kHz)
>> >
>> > Strange.
>> >
>> >> + val = DIV_ROUND_UP(i2c_dev->parent_rate, 400000 * 3);
>> >
>> > the manual reads:
>> >
>> > The minimum allowed value is 0x04, except in FAST DUTY mode
>> > where the minimum allowed value is 0x01
>> >
>> > You don't check for that, right?
>>
>> As the minimum freq value is 6 Mhz in fast mode the minimum CCR is 5
>> as described in the comment.
>> So I don't need to check that again as it is already done by checking
>> parent frequency.
>
> That would then go into a comment.
Is it really needed ?
Adding some comments to explain implementation choices or hardware
way of working is clearly useful.
But for this kind of thing, I am really surprised...
>
>> > CCR is 11 bits wide. A comment confirming that this cannot overflow
>> > would be nice.
>>
>> Again there is no chance of overflow thanks to parent frequency check
>
> Right, this time I saw this myself, so I requested a comment stating
> this fact.
ditto
Best regards,
Cedric
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
^ permalink raw reply
* Re: [PATCH v5 5/5] i2c: mux: pca954x: Add irq-mask-enable to delay enabling irqs
From: Phil Reid @ 2017-01-19 7:48 UTC (permalink / raw)
To: Peter Rosin, wsa, robh+dt, mark.rutland, linux-i2c, devicetree
In-Reply-To: <312e32e9-a828-d692-895b-4042729e5417@axentia.se>
On 18/01/2017 20:19, Peter Rosin wrote:
> On 2017-01-17 09:00, Phil Reid wrote:
>> Unfortunately some hardware device will assert their irq line immediately
>> on power on and provide no mechanism to mask the irq. As the i2c muxes
>> provide no method to mask irq line this provides a work around by keeping
>> the parent irq masked until enough device drivers have loaded to service
>> all pending interrupts.
>>
>> For example the the ltc1760 assert its SMBALERT irq immediately on power
>> on. With two ltc1760 attached to bus 0 & 1 on a pca954x mux when the first
>> device is registered irq are enabled and fire continuously as the second
>> device driver has not yet loaded. Setting this parameter to <1 1> will
>> delay the irq being enabled until both devices are ready.
>>
>> Signed-off-by: Phil Reid <preid@electromag.com.au>
>> ---
>> drivers/i2c/muxes/i2c-mux-pca954x.c | 33 ++++++++++++++++++++++++++++++---
>> 1 file changed, 30 insertions(+), 3 deletions(-)
>>
>> diff --git a/drivers/i2c/muxes/i2c-mux-pca954x.c b/drivers/i2c/muxes/i2c-mux-pca954x.c
>> index f55da88..012b2ef 100644
>> --- a/drivers/i2c/muxes/i2c-mux-pca954x.c
>> +++ b/drivers/i2c/muxes/i2c-mux-pca954x.c
>> @@ -76,6 +76,19 @@ struct chip_desc {
>> } muxtype;
>> };
>>
>> +/*
>> + * irq_mask_enable: Provides a mechanism to work around hardware that asserts
>> + * their irq immediately on power on. It allows the enabling of the irq to be
>> + * delayed until the corresponding bits in the the irq_mask are set thru
>> + * irq_unmask.
>> + * For example the ltc1760 assert its SMBALERT irq immediately on power on.
>> + * With two ltc1760 attached to bus 0 & 1 on a pca954x mux when the first
>> + * device is registered irq are enabled and fire continuously as the second
>> + * device driver has not yet loaded. Setting this parameter to 0x3 while
>> + * delay the irq being enabled until both devices are ready.
>> + * This workaround will not work if two devices share an interrupt on the
>> + * same bus segment.
>
> It will also not work if something shares the interrupt with the pca954x mux,
> on the parent side of the mux, so to speak. Then that other driver may
> potentially enable the irq "behind the back" of the pca954x driver.
>
>> + */
>> struct pca954x {
>> const struct chip_desc *chip;
>>
>> @@ -84,7 +97,9 @@ struct pca954x {
>> struct i2c_client *client;
>>
>> struct irq_domain *irq;
>> + unsigned int irq_mask_enable;
>> unsigned int irq_mask;
>> + bool irq_enabled;
>> spinlock_t lock;
>> };
>>
>> @@ -266,8 +281,10 @@ static void pca954x_irq_mask(struct irq_data *idata)
>> spin_lock_irqsave(&data->lock, flags);
>>
>> data->irq_mask &= ~BIT(pos);
>> - if (!data->irq_mask)
>> + if (data->irq_enabled && !data->irq_mask) {
>> disable_irq(data->client->irq);
>> + data->irq_enabled = false;
>> + }
>
> When irq_mask_enable is non-zero, I think the parent irq should be masked
> when the first irq from the set in irq_mask_enable is masked. For symmetry.
>
> Like so (untested):
>
> if (data->irq_enabled) {
> if (!data->irq_mask ||
> (data->irq_mask & mask_enable) != mask_enable) {
> disable_irq(data->client->irq);
> data->irq_enabled = false;
> }
> }
Yeap this make sense.
>
> Hmm, this whole thing is fiddly and while it solves your problem it doesn't
> allow for solving the more general problem when there are "problematic"
> devices mixed with other devices. At least, I don't see it. And the
> limitations we are walking into with tracking number of enables etc suggests
> that we are attacking this at the wrong level. Maybe you should try to work
> around the hw limitations not in the pca954x driver, but in the irq core?
I'm looking at the option of getting the hardware changed to not route
the irq for my chips thru the i2c mux. Fortunately the hardware is going thru a
revision for some other changes. Messing with the irq core sounds dangerous
with my level of knowledge.
The other way I think I can tackle it after reading the datasheet for the ltc1760 is that
it'll deassert it's irq (smbalert) line when the host sends a ARA request on the bus segment.
There's a driver in the kernel for this already, but it's not DT enable and doesn't
handle multiple bus segments. I'll have a look at that as well.
Pretty sure it would need the mux to become an irq parent as per patch 1-3 of this series.
This would be so the system can figure out which segment to do the poll on.
But p4-5 could be dropped which is where we're stuck I think.
Looking at this approach it shouldn't matter if the ltc1760 driver has registered yet or not.
This approach possibly has a lot more generic appeal I think.
Thoughts on just submitting p1-3 for now while I figure out the SMB alert approach?
>
> I.e. have the irq core check, for each irq, for a property that specifies
> the depth at which each irq should be unmasked. This new property should
> probably be located in the interrupt-controller node? Then the code can
> unmask interrupts when the depth hits this mark, instead of always unmasking
> the interrupt when the depth changes from zero to one. You are then adding
> the workaround at a level where there is enough information to fix the
> more general problem. I think?
>
> But, once again, I'm no irq expert and would desperately like a second
> opinion on this stuff...
>
>>
>> spin_unlock_irqrestore(&data->lock, flags);
>> }
>> @@ -275,14 +292,18 @@ static void pca954x_irq_mask(struct irq_data *idata)
>> static void pca954x_irq_unmask(struct irq_data *idata)
>> {
>> struct pca954x *data = irq_data_get_irq_chip_data(idata);
>> + unsigned int mask_enable = data->irq_mask_enable;
>> unsigned int pos = idata->hwirq;
>> unsigned long flags;
>>
>> spin_lock_irqsave(&data->lock, flags);
>>
>> - if (!data->irq_mask)
>> - enable_irq(data->client->irq);
>> data->irq_mask |= BIT(pos);
>> + if (!data->irq_enabled
>> + && (data->irq_mask & mask_enable) == mask_enable) {
>
> I think the coding standard says that the && should be at the end of the
> first line. Didn't checkpatch complain?
No it didn't complain. and I wasn't sure which way to do this.
So I had a look at some other code in the i2c folders (which is not definitive I know).
regexp "^\s*&&" showed some hits with this style.
Name Line Text Path
drivers\i2c\busses\i2c-ali1535.c 294 && (timeout++ < MAX_TIMEOUT));
drivers\i2c\busses\i2c-ali15x3.c 301 && (timeout++ < MAX_TIMEOUT));
drivers\i2c\busses\i2c-ismt.c 408 && (size != I2C_SMBUS_I2C_BLOCK_DATA))
drivers\i2c\busses\i2c-mv64xxx.c 258 && (drv_data->byte_posn != 0))) {
...
>
> Cheers,
> peda
>
>> + enable_irq(data->client->irq);
>> + data->irq_enabled = true;
>> + }
>>
>> spin_unlock_irqrestore(&data->lock, flags);
>> }
>> @@ -305,6 +326,7 @@ static int pca954x_irq_setup(struct i2c_mux_core *muxc)
>> {
>> struct pca954x *data = i2c_mux_priv(muxc);
>> struct i2c_client *client = data->client;
>> + u32 irq_mask_enable[PCA954X_MAX_NCHANS] = { 0 };
>> int c, err, irq;
>>
>> if (!data->chip->has_irq || client->irq <= 0)
>> @@ -312,6 +334,9 @@ static int pca954x_irq_setup(struct i2c_mux_core *muxc)
>>
>> spin_lock_init(&data->lock);
>>
>> + of_property_read_u32_array(client->dev.of_node, "nxp,irq-mask-enable",
>> + irq_mask_enable, data->chip->nchans);
>> +
>> data->irq = irq_domain_add_linear(client->dev.of_node,
>> data->chip->nchans,
>> &irq_domain_simple_ops, data);
>> @@ -319,6 +344,8 @@ static int pca954x_irq_setup(struct i2c_mux_core *muxc)
>> return -ENODEV;
>>
>> for (c = 0; c < data->chip->nchans; c++) {
>> + data->irq_mask_enable |= irq_mask_enable[c] ? BIT(c) : 0;
>> + WARN_ON(irq_mask_enable[c] > 1);
>> irq = irq_create_mapping(data->irq, c);
>> irq_set_chip_data(irq, data);
>> irq_set_chip_and_handler(irq, &pca954x_irq_chip,
>>
>
>
>
--
Regards
Phil Reid
^ permalink raw reply
* Re: [PATCH v9 2/5] i2c: Add STM32F4 I2C driver
From: Uwe Kleine-König @ 2017-01-19 8:02 UTC (permalink / raw)
To: M'boumba Cedric Madianga
Cc: Wolfram Sang, Rob Herring, Maxime Coquelin, Alexandre Torgue,
Linus Walleij, Patrice Chotard, Russell King, linux-i2c,
devicetree, linux-arm-kernel, linux-kernel
In-Reply-To: <CAOAejn2LobTMfvsvmW8cCiToFHuM6n26s5QPLLXxLzb-WKkhQw@mail.gmail.com>
Hello Cedric,
On Wed, Jan 18, 2017 at 09:55:39PM +0100, M'boumba Cedric Madianga wrote:
> 2017-01-18 19:42 GMT+01:00 Uwe Kleine-König <u.kleine-koenig@pengutronix.de>:
> > Hello Cedric,
> >
> > On Wed, Jan 18, 2017 at 04:21:17PM +0100, M'boumba Cedric Madianga wrote:
> >> >> + * In standard mode, the maximum allowed SCL rise time is 1000 ns.
> >> >> + * If, in the I2C_CR2 register, the value of FREQ[5:0] bits is equal to
> >> >> + * 0x08 so period = 125 ns therefore the TRISE[5:0] bits must be
> >> >> + * programmed with 09h.(1000 ns / 125 ns = 8 + 1)
> >> >
> >> > * programmed with 0x9.
> >> > (1000 ns / 125 ns = 8)
> >> >
> >> >> + * So, for I2C standard mode TRISE = FREQ[5:0] + 1
> >> >> + *
> >> >> + * In fast mode, the maximum allowed SCL rise time is 300 ns.
> >> >> + * If, in the I2C_CR2 register, the value of FREQ[5:0] bits is equal to
> >> >> + * 0x08 so period = 125 ns therefore the TRISE[5:0] bits must be
> >> >> + * programmed with 03h.(300 ns / 125 ns = 2 + 1)
> >> >
> >> > as above s/03h/0x3/;
> >>
> >> ok
> >>
> >> > s/.(/. (/;
> >> ok
> >>
> >> > s/+ 1//;
> >> This formula is use to understand how we find the result 0x3
> >> So, 0x3 => 300 ns / 125ns = 2 + 1
> >
> > Yeah, I understood that, but writing 300 ns / 125ns = 2 + 1 is
> > irritating at best.
>
> Ok. I will write 0x3 (300 ns / 125 ns + 1) and 0x9 (1000 ns / 125 ns + 1)
>
> >> > [...]
> >> > If DUTY = 1: (to reach 400 kHz)
> >> >
> >> > Strange.
> >> >
> >> >> + val = DIV_ROUND_UP(i2c_dev->parent_rate, 400000 * 3);
> >> >
> >> > the manual reads:
> >> >
> >> > The minimum allowed value is 0x04, except in FAST DUTY mode
> >> > where the minimum allowed value is 0x01
> >> >
> >> > You don't check for that, right?
> >>
> >> As the minimum freq value is 6 Mhz in fast mode the minimum CCR is 5
> >> as described in the comment.
> >> So I don't need to check that again as it is already done by checking
> >> parent frequency.
> >
> > That would then go into a comment.
>
> Is it really needed ?
> Adding some comments to explain implementation choices or hardware
> way of working is clearly useful.
> But for this kind of thing, I am really surprised...
TL;DR: It's not needed, but it probably helps.
Consider someone sees a breakage in your driver in five years. By then
you either have other interests or at least forgot 95 % of the thoughts
you had when implementing the driver.
So when I see:
val = DIV_ROUND_UP(i2c_dev->parent_rate, 400000 * 3);
ccr |= STM32F4_I2C_CCR_CCR(val);
writel_relaxed(ccr, i2c_dev->base + STM32F4_I2C_CCR);
after seeing that the bus freq is wrong the obvious thoughts are:
- Does this use the right algorithm?
- Does this calculation result in values that are usable by the
hardware?
That you thought about this today doesn't mean it's still right in five
years. During that time a new hardware variant is available with a
higher parent freq. Or there is a new errata available for the SoC.
So to help answer the questions above it helps if you add today the
formulas from the manual and a quick reason for why val fits into the
respective bits in the CCR register. That comment might be wrong until
then, too, but that only means you should make it easy to verify.
Something like:
/*
* Function bla_blub made sure that parent_rate is not higher
* than 23 * pi MHz. As a result val is at most 13.2 bits wide
* and so fits into the CCR bits.
*/
This gives you in five years time the opportunity to quickly check
bla_blub if this is still true, add a printk for parent_rate to check
this, or quickly identify the bug in the code or the mismatch to the
manual.
Best regards
Uwe
--
Pengutronix e.K. | Uwe Kleine-König |
Industrial Linux Solutions | http://www.pengutronix.de/ |
^ permalink raw reply
* Re: [PATCH v5 3/5] i2c: mux: pca954x: Add interrupt controller support
From: Peter Rosin @ 2017-01-19 8:27 UTC (permalink / raw)
To: Phil Reid, wsa, robh+dt, mark.rutland, linux-i2c, devicetree
In-Reply-To: <1484640029-22870-4-git-send-email-preid@electromag.com.au>
On 2017-01-17 09:00, Phil Reid wrote:
> Various muxes can aggregate multiple interrupts from each i2c bus.
> All of the muxes with interrupt support combine the active low irq lines
> using an internal 'and' function and generate a combined active low
> output. The muxes do provide the ability to read a control register to
> determine which irq is active. By making the mux an irq controller isr
> latency can potentially be reduced by reading the status register and
> then only calling the registered isr on that bus segment.
>
> As there is no irq masking on the mux irq are disabled until irq_unmask is
> called at least once.
>
> Signed-off-by: Phil Reid <preid@electromag.com.au>
Acked-by: Peter Rosin <peda@axentia.se>
Cheers,
peda
> ---
> drivers/i2c/muxes/i2c-mux-pca954x.c | 141 +++++++++++++++++++++++++++++++++++-
> 1 file changed, 139 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/i2c/muxes/i2c-mux-pca954x.c b/drivers/i2c/muxes/i2c-mux-pca954x.c
> index bbf088e..f55da88 100644
> --- a/drivers/i2c/muxes/i2c-mux-pca954x.c
> +++ b/drivers/i2c/muxes/i2c-mux-pca954x.c
> @@ -41,14 +41,20 @@
> #include <linux/i2c.h>
> #include <linux/i2c-mux.h>
> #include <linux/i2c/pca954x.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> #include <linux/module.h>
> #include <linux/of.h>
> #include <linux/of_device.h>
> +#include <linux/of_irq.h>
> #include <linux/pm.h>
> #include <linux/slab.h>
> +#include <linux/spinlock.h>
>
> #define PCA954X_MAX_NCHANS 8
>
> +#define PCA954X_IRQ_OFFSET 4
> +
> enum pca_type {
> pca_9540,
> pca_9542,
> @@ -63,6 +69,7 @@ enum pca_type {
> struct chip_desc {
> u8 nchans;
> u8 enable; /* used for muxes only */
> + u8 has_irq;
> enum muxtype {
> pca954x_ismux = 0,
> pca954x_isswi
> @@ -75,6 +82,10 @@ struct pca954x {
> u8 last_chan; /* last register value */
> u8 deselect;
> struct i2c_client *client;
> +
> + struct irq_domain *irq;
> + unsigned int irq_mask;
> + spinlock_t lock;
> };
>
> /* Provide specs for the PCA954x types we know about */
> @@ -87,19 +98,23 @@ struct pca954x {
> [pca_9542] = {
> .nchans = 2,
> .enable = 0x4,
> + .has_irq = 1,
> .muxtype = pca954x_ismux,
> },
> [pca_9543] = {
> .nchans = 2,
> + .has_irq = 1,
> .muxtype = pca954x_isswi,
> },
> [pca_9544] = {
> .nchans = 4,
> .enable = 0x4,
> + .has_irq = 1,
> .muxtype = pca954x_ismux,
> },
> [pca_9545] = {
> .nchans = 4,
> + .has_irq = 1,
> .muxtype = pca954x_isswi,
> },
> [pca_9547] = {
> @@ -222,6 +237,114 @@ static int pca954x_deselect_mux(struct i2c_mux_core *muxc, u32 chan)
> return pca954x_reg_write(muxc->parent, client, data->last_chan);
> }
>
> +static irqreturn_t pca954x_irq_handler(int irq, void *dev_id)
> +{
> + struct pca954x *data = dev_id;
> + unsigned int child_irq;
> + int ret, i, handled;
> +
> + ret = i2c_smbus_read_byte(data->client);
> + if (ret < 0)
> + return IRQ_NONE;
> +
> + for (i = 0; i < data->chip->nchans; i++) {
> + if (ret & BIT(PCA954X_IRQ_OFFSET + i)) {
> + child_irq = irq_linear_revmap(data->irq, i);
> + handle_nested_irq(child_irq);
> + handled++;
> + }
> + }
> + return handled ? IRQ_HANDLED : IRQ_NONE;
> +}
> +
> +static void pca954x_irq_mask(struct irq_data *idata)
> +{
> + struct pca954x *data = irq_data_get_irq_chip_data(idata);
> + unsigned int pos = idata->hwirq;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&data->lock, flags);
> +
> + data->irq_mask &= ~BIT(pos);
> + if (!data->irq_mask)
> + disable_irq(data->client->irq);
> +
> + spin_unlock_irqrestore(&data->lock, flags);
> +}
> +
> +static void pca954x_irq_unmask(struct irq_data *idata)
> +{
> + struct pca954x *data = irq_data_get_irq_chip_data(idata);
> + unsigned int pos = idata->hwirq;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&data->lock, flags);
> +
> + if (!data->irq_mask)
> + enable_irq(data->client->irq);
> + data->irq_mask |= BIT(pos);
> +
> + spin_unlock_irqrestore(&data->lock, flags);
> +}
> +
> +static int pca954x_irq_set_type(struct irq_data *idata, unsigned int type)
> +{
> + if ((type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_LEVEL_LOW)
> + return -EINVAL;
> + return 0;
> +}
> +
> +static struct irq_chip pca954x_irq_chip = {
> + .name = "i2c-mux-pca954x",
> + .irq_mask = pca954x_irq_mask,
> + .irq_unmask = pca954x_irq_unmask,
> + .irq_set_type = pca954x_irq_set_type,
> +};
> +
> +static int pca954x_irq_setup(struct i2c_mux_core *muxc)
> +{
> + struct pca954x *data = i2c_mux_priv(muxc);
> + struct i2c_client *client = data->client;
> + int c, err, irq;
> +
> + if (!data->chip->has_irq || client->irq <= 0)
> + return 0;
> +
> + spin_lock_init(&data->lock);
> +
> + data->irq = irq_domain_add_linear(client->dev.of_node,
> + data->chip->nchans,
> + &irq_domain_simple_ops, data);
> + if (!data->irq)
> + return -ENODEV;
> +
> + for (c = 0; c < data->chip->nchans; c++) {
> + irq = irq_create_mapping(data->irq, c);
> + irq_set_chip_data(irq, data);
> + irq_set_chip_and_handler(irq, &pca954x_irq_chip,
> + handle_simple_irq);
> + }
> +
> + err = devm_request_threaded_irq(&client->dev, data->client->irq, NULL,
> + pca954x_irq_handler,
> + IRQF_ONESHOT | IRQF_SHARED,
> + "pca954x", data);
> + if (err)
> + goto err_req_irq;
> +
> + disable_irq(data->client->irq);
> +
> + return 0;
> +err_req_irq:
> + for (c = 0; c < data->chip->nchans; c++) {
> + irq = irq_find_mapping(data->irq, c);
> + irq_dispose_mapping(irq);
> + }
> + irq_domain_remove(data->irq);
> +
> + return err;
> +}
> +
> /*
> * I2C init/probing/exit functions
> */
> @@ -286,6 +409,10 @@ static int pca954x_probe(struct i2c_client *client,
> idle_disconnect_dt = of_node &&
> of_property_read_bool(of_node, "i2c-mux-idle-disconnect");
>
> + ret = pca954x_irq_setup(muxc);
> + if (ret)
> + goto fail_del_adapters;
> +
> /* Now create an adapter for each channel */
> for (num = 0; num < data->chip->nchans; num++) {
> bool idle_disconnect_pd = false;
> @@ -311,7 +438,7 @@ static int pca954x_probe(struct i2c_client *client,
> dev_err(&client->dev,
> "failed to register multiplexed adapter"
> " %d as bus %d\n", num, force);
> - goto virt_reg_failed;
> + goto fail_del_adapters;
> }
> }
>
> @@ -322,7 +449,7 @@ static int pca954x_probe(struct i2c_client *client,
>
> return 0;
>
> -virt_reg_failed:
> +fail_del_adapters:
> i2c_mux_del_adapters(muxc);
> return ret;
> }
> @@ -330,6 +457,16 @@ static int pca954x_probe(struct i2c_client *client,
> static int pca954x_remove(struct i2c_client *client)
> {
> struct i2c_mux_core *muxc = i2c_get_clientdata(client);
> + struct pca954x *data = i2c_mux_priv(muxc);
> + int c, irq;
> +
> + if (data->irq) {
> + for (c = 0; c < data->chip->nchans; c++) {
> + irq = irq_find_mapping(data->irq, c);
> + irq_dispose_mapping(irq);
> + }
> + irq_domain_remove(data->irq);
> + }
>
> i2c_mux_del_adapters(muxc);
> return 0;
>
^ permalink raw reply
* Re: [PATCH v9 2/5] i2c: Add STM32F4 I2C driver
From: M'boumba Cedric Madianga @ 2017-01-19 8:29 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Wolfram Sang, Rob Herring, Maxime Coquelin, Alexandre Torgue,
Linus Walleij, Patrice Chotard, Russell King,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20170119080258.fovhfy2v6rrtgwgp-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
ok fine
2017-01-19 9:02 GMT+01:00 Uwe Kleine-König <u.kleine-koenig@pengutronix.de>:
> Hello Cedric,
>
> On Wed, Jan 18, 2017 at 09:55:39PM +0100, M'boumba Cedric Madianga wrote:
>> 2017-01-18 19:42 GMT+01:00 Uwe Kleine-König <u.kleine-koenig@pengutronix.de>:
>> > Hello Cedric,
>> >
>> > On Wed, Jan 18, 2017 at 04:21:17PM +0100, M'boumba Cedric Madianga wrote:
>> >> >> + * In standard mode, the maximum allowed SCL rise time is 1000 ns.
>> >> >> + * If, in the I2C_CR2 register, the value of FREQ[5:0] bits is equal to
>> >> >> + * 0x08 so period = 125 ns therefore the TRISE[5:0] bits must be
>> >> >> + * programmed with 09h.(1000 ns / 125 ns = 8 + 1)
>> >> >
>> >> > * programmed with 0x9.
>> >> > (1000 ns / 125 ns = 8)
>> >> >
>> >> >> + * So, for I2C standard mode TRISE = FREQ[5:0] + 1
>> >> >> + *
>> >> >> + * In fast mode, the maximum allowed SCL rise time is 300 ns.
>> >> >> + * If, in the I2C_CR2 register, the value of FREQ[5:0] bits is equal to
>> >> >> + * 0x08 so period = 125 ns therefore the TRISE[5:0] bits must be
>> >> >> + * programmed with 03h.(300 ns / 125 ns = 2 + 1)
>> >> >
>> >> > as above s/03h/0x3/;
>> >>
>> >> ok
>> >>
>> >> > s/.(/. (/;
>> >> ok
>> >>
>> >> > s/+ 1//;
>> >> This formula is use to understand how we find the result 0x3
>> >> So, 0x3 => 300 ns / 125ns = 2 + 1
>> >
>> > Yeah, I understood that, but writing 300 ns / 125ns = 2 + 1 is
>> > irritating at best.
>>
>> Ok. I will write 0x3 (300 ns / 125 ns + 1) and 0x9 (1000 ns / 125 ns + 1)
>>
>> >> > [...]
>> >> > If DUTY = 1: (to reach 400 kHz)
>> >> >
>> >> > Strange.
>> >> >
>> >> >> + val = DIV_ROUND_UP(i2c_dev->parent_rate, 400000 * 3);
>> >> >
>> >> > the manual reads:
>> >> >
>> >> > The minimum allowed value is 0x04, except in FAST DUTY mode
>> >> > where the minimum allowed value is 0x01
>> >> >
>> >> > You don't check for that, right?
>> >>
>> >> As the minimum freq value is 6 Mhz in fast mode the minimum CCR is 5
>> >> as described in the comment.
>> >> So I don't need to check that again as it is already done by checking
>> >> parent frequency.
>> >
>> > That would then go into a comment.
>>
>> Is it really needed ?
>> Adding some comments to explain implementation choices or hardware
>> way of working is clearly useful.
>> But for this kind of thing, I am really surprised...
>
> TL;DR: It's not needed, but it probably helps.
>
> Consider someone sees a breakage in your driver in five years. By then
> you either have other interests or at least forgot 95 % of the thoughts
> you had when implementing the driver.
>
> So when I see:
>
> val = DIV_ROUND_UP(i2c_dev->parent_rate, 400000 * 3);
> ccr |= STM32F4_I2C_CCR_CCR(val);
> writel_relaxed(ccr, i2c_dev->base + STM32F4_I2C_CCR);
>
> after seeing that the bus freq is wrong the obvious thoughts are:
>
> - Does this use the right algorithm?
> - Does this calculation result in values that are usable by the
> hardware?
>
> That you thought about this today doesn't mean it's still right in five
> years. During that time a new hardware variant is available with a
> higher parent freq. Or there is a new errata available for the SoC.
>
> So to help answer the questions above it helps if you add today the
> formulas from the manual and a quick reason for why val fits into the
> respective bits in the CCR register. That comment might be wrong until
> then, too, but that only means you should make it easy to verify.
> Something like:
>
> /*
> * Function bla_blub made sure that parent_rate is not higher
> * than 23 * pi MHz. As a result val is at most 13.2 bits wide
> * and so fits into the CCR bits.
> */
>
> This gives you in five years time the opportunity to quickly check
> bla_blub if this is still true, add a printk for parent_rate to check
> this, or quickly identify the bug in the code or the mismatch to the
> manual.
>
> Best regards
> Uwe
>
> --
> Pengutronix e.K. | Uwe Kleine-König |
> Industrial Linux Solutions | http://www.pengutronix.de/ |
--
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
* [PATCH v10 0/5] Add support for the STM32F4 I2C
From: M'boumba Cedric Madianga @ 2017-01-19 13:25 UTC (permalink / raw)
To: wsa, robh+dt, mcoquelin.stm32, alexandre.torgue, linus.walleij,
patrice.chotard, linux, linux-i2c, devicetree, linux-arm-kernel,
linux-kernel, u.kleine-koenig
Cc: M'boumba Cedric Madianga
This patchset adds support for the I2C controller embedded in STM32F4xx SoC.
It enables I2C transfer in interrupt mode with Standard-mode and Fast-mode bus
speed.
Changes since v9:
- Fix minor typo in some comments
- Add some comments to explain how the driver check TRISE and CCR value have
no chance of overflow
Changes since v8:
- Rework I2C Clock Control Register computation (Uwe)
- Save register accesses as often as possible (Uwe)
- Don't use mask before saving rx buffer (Uwe)
- Add more comments to explain hardware way of working (Uwe)
- Rename stm32f4_i2c_handle_rx_btf function by stm32f4_i2c_handle_rx_done (Uwe)
- Set/Clear Ack position bit during address match phase
Changes since v7:
- Remove unneeded parenthesis in some macro definitions (Uwe)
- Fix some typo (s/KhzkHZ, s/PEC/POC) (Uwe)
- Fix alignment issues in some structures declaration (Uwe)
- Clarify comments to argue i2c_timing values chosen (Uwe)
- Raise an error if parent clk rate is out of scope during I2C hw config (Uwe)
- Use dev_dbg instead of dev_err message when I2C bus is busy (Uwe)
- Add more comments about stuff done by stm32f4_i2c_handle_rx_btf() (Uwe)
- Simplify stm32f4_i2c_isr_error() routine implementation by removing possible
status checking (Uwe)
- Rework stm32f4_i2c_isr_error() routine by removing the loop to check which
status occured (Uwe)
- Add open-drain property for SCL pins (Uwe)
- Rework unneeded mul_ccr field from i2c_timing structure
- Remove min_ccr field from i2c_timing structure as default scl_period is chosen
to have a correct minimal ccr value
- Execute hw_config once during probe
- Remove soft_reset after an I2C error as all errors are now handled and
hw_config is done once during probe
- Generate STOP by software when Acknowledge failure occurs
- Set the max speed mode for I2C pins
- Add bias-disable property for I2C pins
- Use intrinsic limitation of APB bus to set I2C max input clk
Changes since v6:
- Add commit message for the patches in defconfig, .dtsi and .dts files (Alex)
- Order I2C instance base address in .dtsi file (Alex)
- Add commit message for the patch in stm32429i-eval.dts (Alex)
- Add link to the STM32F4 Soc ref manual where I2C device is described (Uwe)
- Use more usal way to define constants with several lines (Uwe)
- Remove rate variable from stm32f4_i2c_timings as it is not used (Uwe)
- Remove irq variable from stm32f4_i2c_dev struct are they are only needed
during probe (Uwe)
- Add comment from datasheet to explain stm32f4_i2c_timings values (Uwe)
- Rework i2c soft_reset implementation (Uwe)
- Replace "it" by "irq" as it is a more usual abbreviation for interrupt (Uwe)
- Add comment from datasheet to explain periph clk freq calculation (Uwe)
- Use DIV_ROUND_UP instead of plain division when required (Uwe)
- Add comment from datasheet to explain timing rise calculation (Uwe)
- Rework timing rise calculation by using shorter computation (Uwe)
- Remove (u8) cast when reading I2C data register (Uwe)
- Rework isr_event routine to handle several events during one call of the
routine (Uwe)
- Precise which type of irq is failed when a irq request error occurs (Uwe)
- Use devm_request_irq() instead of devm_request_threaded_irq() to avoid
spurious evt irq when clearing status registers in threaded context
Changes since v5:
- Change commit header from "ARM: dts:" to "ARM: dts: stm32:" (Alex)
- Change commit header from "ARM: configs:" to "ARM: configs: stm32:" (Alex)
- Fix warnings due to variable set but unused (Wolfram)
- Remove double space in Kconfig (Wolfram)
- Fix warning due to bad type parameter when using clamp() function
(build-bot)
Changes since v4:
- Use clamp() function to use a value in a given range as it was missed in V4
Changes since v3 after Wolfram's review:
- Add COMPILE_TEST flag in Kconfig
- Use correct driver name in Kconfig i.e i2c-stm32f4 instead of i2c-st
- Use more comprehensible name stm32f4_i2c_msg for client specific data
- Don't store reset control node as just needed in probe
- Use clamp() function to test value between 2 ranges
- Use new "i2c_8bit_addr_from_msg() function to build I2C address
- Don't write error messages for timeout
- Remove error message when i2c_add_adapter() fails as it is already handled by
the i2c core driver
Changes since v2:
- remove interrupt configuration management from DT
- remove FIFO configuration management from DT except threshold as it is very
hard to handle it in the driver due to many possible combinations according
to burst and bus width
- update DMA client message in DT documentation file
- specify the order to be used to set per-channel DMA interrupts in the DT
- remove unused enumerations for channel and request ids
- keep as soon as possible 80 lines char for more readability
- replace unsigned int by u32
- return error if burst is not supported in stm32_dma_get_burst()
- return error if bus_width is not supported in stm32_dma_get_width()
- add FIFO configuration management inside the driver except for threshold
- add interrupt configuration management inside the driver
- rework stm32_dma_chan_irq() to handle error interrupt in one way
- rework stm32_dma_set_xfer_param() to be easier to read
- update stm32_dma_tx_status() to always return status from dma_cookie_status()
- disable clk if we don't manage to stop the DMA channel during channel
resources allocation
- set driver as built-in as DMA will be required by other built-in driver
Changes since v1:
- use compatible st,stm32f4-i2c instead of st,i2c-stm32f4 (Rob)
- fix typo s/enmpty/empty (Maxime)
- use one function to handle TX fifo empty and byte xfer finished IT (Maxime)
- set duty cycle in timing struct in Fast mode
- Rework clock management (call prepare/unprepare at probe and remove, call
clk_enable/clk_disable for each I2C transfer)
M'boumba Cedric Madianga (5):
dt-bindings: Document the STM32 I2C bindings
i2c: Add STM32F4 I2C driver
ARM: dts: stm32: Add I2C1 support for STM32F429 SoC
ARM: dts: stm32: Add I2C1 support for STM32429 eval board
ARM: configs: stm32: Add I2C support for STM32 defconfig
.../devicetree/bindings/i2c/i2c-stm32.txt | 33 +
arch/arm/boot/dts/stm32429i-eval.dts | 6 +
arch/arm/boot/dts/stm32f429.dtsi | 23 +
arch/arm/configs/stm32_defconfig | 3 +
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-stm32f4.c | 897 +++++++++++++++++++++
7 files changed, 973 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-stm32.txt
create mode 100644 drivers/i2c/busses/i2c-stm32f4.c
--
1.9.1
^ permalink raw reply
* [PATCH v10 1/5] dt-bindings: Document the STM32 I2C bindings
From: M'boumba Cedric Madianga @ 2017-01-19 13:25 UTC (permalink / raw)
To: wsa, robh+dt, mcoquelin.stm32, alexandre.torgue, linus.walleij,
patrice.chotard, linux, linux-i2c, devicetree, linux-arm-kernel,
linux-kernel, u.kleine-koenig
Cc: M'boumba Cedric Madianga
In-Reply-To: <1484832316-5594-1-git-send-email-cedric.madianga@gmail.com>
This patch adds documentation of device tree bindings for the STM32 I2C
controller.
Signed-off-by: M'boumba Cedric Madianga <cedric.madianga@gmail.com>
Acked-by: Rob Herring <robh@kernel.org>
---
.../devicetree/bindings/i2c/i2c-stm32.txt | 33 ++++++++++++++++++++++
1 file changed, 33 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-stm32.txt
diff --git a/Documentation/devicetree/bindings/i2c/i2c-stm32.txt b/Documentation/devicetree/bindings/i2c/i2c-stm32.txt
new file mode 100644
index 0000000..78eaf7b
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-stm32.txt
@@ -0,0 +1,33 @@
+* I2C controller embedded in STMicroelectronics STM32 I2C platform
+
+Required properties :
+- compatible : Must be "st,stm32f4-i2c"
+- reg : Offset and length of the register set for the device
+- interrupts : Must contain the interrupt id for I2C event and then the
+ interrupt id for I2C error.
+- resets: Must contain the phandle to the reset controller.
+- clocks: Must contain the input clock of the I2C instance.
+- A pinctrl state named "default" must be defined to set pins in mode of
+ operation for I2C transfer
+- #address-cells = <1>;
+- #size-cells = <0>;
+
+Optional properties :
+- clock-frequency : Desired I2C bus clock frequency in Hz. If not specified,
+ the default 100 kHz frequency will be used. As only Normal and Fast modes
+ are supported, possible values are 100000 and 400000.
+
+Example :
+
+ i2c@40005400 {
+ compatible = "st,stm32f4-i2c";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x40005400 0x400>;
+ interrupts = <31>,
+ <32>;
+ resets = <&rcc 277>;
+ clocks = <&rcc 0 149>;
+ pinctrl-0 = <&i2c1_sda_pin>, <&i2c1_scl_pin>;
+ pinctrl-names = "default";
+ };
--
1.9.1
^ permalink raw reply related
* [PATCH v10 3/5] ARM: dts: stm32: Add I2C1 support for STM32F429 SoC
From: M'boumba Cedric Madianga @ 2017-01-19 13:25 UTC (permalink / raw)
To: wsa, robh+dt, mcoquelin.stm32, alexandre.torgue, linus.walleij,
patrice.chotard, linux, linux-i2c, devicetree, linux-arm-kernel,
linux-kernel, u.kleine-koenig
Cc: M'boumba Cedric Madianga
In-Reply-To: <1484832316-5594-1-git-send-email-cedric.madianga@gmail.com>
This patch adds I2C1 support for STM32F429 SoC
Signed-off-by: Patrice Chotard <patrice.chotard@st.com>
Signed-off-by: M'boumba Cedric Madianga <cedric.madianga@gmail.com>
---
arch/arm/boot/dts/stm32f429.dtsi | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/arch/arm/boot/dts/stm32f429.dtsi b/arch/arm/boot/dts/stm32f429.dtsi
index e4dae0e..5b063e9 100644
--- a/arch/arm/boot/dts/stm32f429.dtsi
+++ b/arch/arm/boot/dts/stm32f429.dtsi
@@ -48,6 +48,7 @@
#include "skeleton.dtsi"
#include "armv7-m.dtsi"
#include <dt-bindings/pinctrl/stm32f429-pinfunc.h>
+#include <dt-bindings/mfd/stm32f4-rcc.h>
/ {
clocks {
@@ -153,6 +154,18 @@
status = "disabled";
};
+ i2c1: i2c@40005400 {
+ compatible = "st,stm32f4-i2c";
+ reg = <0x40005400 0x400>;
+ interrupts = <31>,
+ <32>;
+ resets = <&rcc STM32F4_APB1_RESET(I2C1)>;
+ clocks = <&rcc 0 STM32F4_APB1_CLOCK(I2C1)>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
+
usart7: serial@40007800 {
compatible = "st,stm32-usart", "st,stm32-uart";
reg = <0x40007800 0x400>;
@@ -355,6 +368,16 @@
slew-rate = <2>;
};
};
+
+ i2c1_pins_b: i2c1@0 {
+ pins {
+ pinmux = <STM32F429_PB9_FUNC_I2C1_SDA>,
+ <STM32F429_PB6_FUNC_I2C1_SCL>;
+ bias-disable;
+ drive-open-drain;
+ slew-rate = <3>;
+ };
+ };
};
rcc: rcc@40023810 {
--
1.9.1
^ permalink raw reply related
* [PATCH v10 4/5] ARM: dts: stm32: Add I2C1 support for STM32429 eval board
From: M'boumba Cedric Madianga @ 2017-01-19 13:25 UTC (permalink / raw)
To: wsa, robh+dt, mcoquelin.stm32, alexandre.torgue, linus.walleij,
patrice.chotard, linux, linux-i2c, devicetree, linux-arm-kernel,
linux-kernel, u.kleine-koenig
Cc: M'boumba Cedric Madianga
In-Reply-To: <1484832316-5594-1-git-send-email-cedric.madianga@gmail.com>
This patch adds I2C1 instance support for STM32x9I-Eval board.
Signed-off-by: M'boumba Cedric Madianga <cedric.madianga@gmail.com>
---
arch/arm/boot/dts/stm32429i-eval.dts | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/arch/arm/boot/dts/stm32429i-eval.dts b/arch/arm/boot/dts/stm32429i-eval.dts
index 76f7206..c943539 100644
--- a/arch/arm/boot/dts/stm32429i-eval.dts
+++ b/arch/arm/boot/dts/stm32429i-eval.dts
@@ -146,3 +146,9 @@
pinctrl-names = "default";
status = "okay";
};
+
+&i2c1 {
+ pinctrl-0 = <&i2c1_pins_b>;
+ pinctrl-names = "default";
+ status = "okay";
+};
--
1.9.1
^ permalink raw reply related
* [PATCH v10 5/5] ARM: configs: stm32: Add I2C support for STM32 defconfig
From: M'boumba Cedric Madianga @ 2017-01-19 13:25 UTC (permalink / raw)
To: wsa, robh+dt, mcoquelin.stm32, alexandre.torgue, linus.walleij,
patrice.chotard, linux, linux-i2c, devicetree, linux-arm-kernel,
linux-kernel, u.kleine-koenig
Cc: M'boumba Cedric Madianga
In-Reply-To: <1484832316-5594-1-git-send-email-cedric.madianga@gmail.com>
This patch adds I2C support for STM32 default configuration
Signed-off-by: M'boumba Cedric Madianga <cedric.madianga@gmail.com>
---
arch/arm/configs/stm32_defconfig | 3 +++
1 file changed, 3 insertions(+)
diff --git a/arch/arm/configs/stm32_defconfig b/arch/arm/configs/stm32_defconfig
index 5a72d69..323d2a3 100644
--- a/arch/arm/configs/stm32_defconfig
+++ b/arch/arm/configs/stm32_defconfig
@@ -47,6 +47,9 @@ CONFIG_SERIAL_NONSTANDARD=y
CONFIG_SERIAL_STM32=y
CONFIG_SERIAL_STM32_CONSOLE=y
# CONFIG_HW_RANDOM is not set
+CONFIG_I2C=y
+CONFIG_I2C_CHARDEV=y
+CONFIG_I2C_STM32F4=y
# CONFIG_HWMON is not set
# CONFIG_USB_SUPPORT is not set
CONFIG_NEW_LEDS=y
--
1.9.1
^ permalink raw reply related
* [PATCH v10 2/5] i2c: Add STM32F4 I2C driver
From: M'boumba Cedric Madianga @ 2017-01-19 13:25 UTC (permalink / raw)
To: wsa, robh+dt, mcoquelin.stm32, alexandre.torgue, linus.walleij,
patrice.chotard, linux, linux-i2c, devicetree, linux-arm-kernel,
linux-kernel, u.kleine-koenig
Cc: M'boumba Cedric Madianga
In-Reply-To: <1484832316-5594-1-git-send-email-cedric.madianga@gmail.com>
This patch adds support for the STM32F4 I2C controller.
Signed-off-by: M'boumba Cedric Madianga <cedric.madianga@gmail.com>
---
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-stm32f4.c | 897 +++++++++++++++++++++++++++++++++++++++
3 files changed, 908 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-stm32f4.c
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 0cdc844..2719208 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -886,6 +886,16 @@ config I2C_ST
This driver can also be built as module. If so, the module
will be called i2c-st.
+config I2C_STM32F4
+ tristate "STMicroelectronics STM32F4 I2C support"
+ depends on ARCH_STM32 || COMPILE_TEST
+ help
+ Enable this option to add support for STM32 I2C controller embedded
+ in STM32F4 SoCs.
+
+ This driver can also be built as module. If so, the module
+ will be called i2c-stm32f4.
+
config I2C_STU300
tristate "ST Microelectronics DDC I2C interface"
depends on MACH_U300
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 1c1bac8..a2c6ff5 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -85,6 +85,7 @@ obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o
obj-$(CONFIG_I2C_SIMTEC) += i2c-simtec.o
obj-$(CONFIG_I2C_SIRF) += i2c-sirf.o
obj-$(CONFIG_I2C_ST) += i2c-st.o
+obj-$(CONFIG_I2C_STM32F4) += i2c-stm32f4.o
obj-$(CONFIG_I2C_STU300) += i2c-stu300.o
obj-$(CONFIG_I2C_SUN6I_P2WI) += i2c-sun6i-p2wi.o
obj-$(CONFIG_I2C_TEGRA) += i2c-tegra.o
diff --git a/drivers/i2c/busses/i2c-stm32f4.c b/drivers/i2c/busses/i2c-stm32f4.c
new file mode 100644
index 0000000..f9dd7e8
--- /dev/null
+++ b/drivers/i2c/busses/i2c-stm32f4.c
@@ -0,0 +1,897 @@
+/*
+ * Driver for STMicroelectronics STM32 I2C controller
+ *
+ * This I2C controller is described in the STM32F429/439 Soc reference manual.
+ * Please see below a link to the documentation:
+ * http://www.st.com/resource/en/reference_manual/DM00031020.pdf
+ *
+ * Copyright (C) M'boumba Cedric Madianga 2016
+ * Author: M'boumba Cedric Madianga <cedric.madianga@gmail.com>
+ *
+ * This driver is based on i2c-st.c
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+/* STM32F4 I2C offset registers */
+#define STM32F4_I2C_CR1 0x00
+#define STM32F4_I2C_CR2 0x04
+#define STM32F4_I2C_DR 0x10
+#define STM32F4_I2C_SR1 0x14
+#define STM32F4_I2C_SR2 0x18
+#define STM32F4_I2C_CCR 0x1C
+#define STM32F4_I2C_TRISE 0x20
+#define STM32F4_I2C_FLTR 0x24
+
+/* STM32F4 I2C control 1*/
+#define STM32F4_I2C_CR1_POS BIT(11)
+#define STM32F4_I2C_CR1_ACK BIT(10)
+#define STM32F4_I2C_CR1_STOP BIT(9)
+#define STM32F4_I2C_CR1_START BIT(8)
+#define STM32F4_I2C_CR1_PE BIT(0)
+
+/* STM32F4 I2C control 2 */
+#define STM32F4_I2C_CR2_FREQ_MASK GENMASK(5, 0)
+#define STM32F4_I2C_CR2_FREQ(n) ((n) & STM32F4_I2C_CR2_FREQ_MASK)
+#define STM32F4_I2C_CR2_ITBUFEN BIT(10)
+#define STM32F4_I2C_CR2_ITEVTEN BIT(9)
+#define STM32F4_I2C_CR2_ITERREN BIT(8)
+#define STM32F4_I2C_CR2_IRQ_MASK (STM32F4_I2C_CR2_ITBUFEN | \
+ STM32F4_I2C_CR2_ITEVTEN | \
+ STM32F4_I2C_CR2_ITERREN)
+
+/* STM32F4 I2C Status 1 */
+#define STM32F4_I2C_SR1_AF BIT(10)
+#define STM32F4_I2C_SR1_ARLO BIT(9)
+#define STM32F4_I2C_SR1_BERR BIT(8)
+#define STM32F4_I2C_SR1_TXE BIT(7)
+#define STM32F4_I2C_SR1_RXNE BIT(6)
+#define STM32F4_I2C_SR1_BTF BIT(2)
+#define STM32F4_I2C_SR1_ADDR BIT(1)
+#define STM32F4_I2C_SR1_SB BIT(0)
+#define STM32F4_I2C_SR1_ITEVTEN_MASK (STM32F4_I2C_SR1_BTF | \
+ STM32F4_I2C_SR1_ADDR | \
+ STM32F4_I2C_SR1_SB)
+#define STM32F4_I2C_SR1_ITBUFEN_MASK (STM32F4_I2C_SR1_TXE | \
+ STM32F4_I2C_SR1_RXNE)
+#define STM32F4_I2C_SR1_ITERREN_MASK (STM32F4_I2C_SR1_AF | \
+ STM32F4_I2C_SR1_ARLO | \
+ STM32F4_I2C_SR1_BERR)
+
+/* STM32F4 I2C Status 2 */
+#define STM32F4_I2C_SR2_BUSY BIT(1)
+
+/* STM32F4 I2C Control Clock */
+#define STM32F4_I2C_CCR_CCR_MASK GENMASK(11, 0)
+#define STM32F4_I2C_CCR_CCR(n) ((n) & STM32F4_I2C_CCR_CCR_MASK)
+#define STM32F4_I2C_CCR_FS BIT(15)
+#define STM32F4_I2C_CCR_DUTY BIT(14)
+
+/* STM32F4 I2C Trise */
+#define STM32F4_I2C_TRISE_VALUE_MASK GENMASK(5, 0)
+#define STM32F4_I2C_TRISE_VALUE(n) ((n) & STM32F4_I2C_TRISE_VALUE_MASK)
+
+#define STM32F4_I2C_MIN_STANDARD_FREQ 2U
+#define STM32F4_I2C_MIN_FAST_FREQ 6U
+#define STM32F4_I2C_MAX_FREQ 46U
+#define HZ_TO_MHZ 1000000
+
+enum stm32f4_i2c_speed {
+ STM32F4_I2C_SPEED_STANDARD, /* 100 kHz */
+ STM32F4_I2C_SPEED_FAST, /* 400 kHz */
+ STM32F4_I2C_SPEED_END,
+};
+
+/**
+ * struct stm32f4_i2c_msg - client specific data
+ * @addr: 8-bit slave addr, including r/w bit
+ * @count: number of bytes to be transferred
+ * @buf: data buffer
+ * @result: result of the transfer
+ * @stop: last I2C msg to be sent, i.e. STOP to be generated
+ */
+struct stm32f4_i2c_msg {
+ u8 addr;
+ u32 count;
+ u8 *buf;
+ int result;
+ bool stop;
+};
+
+/**
+ * struct stm32f4_i2c_dev - private data of the controller
+ * @adap: I2C adapter for this controller
+ * @dev: device for this controller
+ * @base: virtual memory area
+ * @complete: completion of I2C message
+ * @clk: hw i2c clock
+ * @speed: I2C clock frequency of the controller. Standard or Fast are supported
+ * @parent_rate: I2C clock parent rate in MHz
+ * @msg: I2C transfer information
+ */
+struct stm32f4_i2c_dev {
+ struct i2c_adapter adap;
+ struct device *dev;
+ void __iomem *base;
+ struct completion complete;
+ struct clk *clk;
+ int speed;
+ int parent_rate;
+ struct stm32f4_i2c_msg msg;
+};
+
+static inline void stm32f4_i2c_set_bits(void __iomem *reg, u32 mask)
+{
+ writel_relaxed(readl_relaxed(reg) | mask, reg);
+}
+
+static inline void stm32f4_i2c_clr_bits(void __iomem *reg, u32 mask)
+{
+ writel_relaxed(readl_relaxed(reg) & ~mask, reg);
+}
+
+static void stm32f4_i2c_disable_irq(struct stm32f4_i2c_dev *i2c_dev)
+{
+ void __iomem *reg = i2c_dev->base + STM32F4_I2C_CR2;
+
+ stm32f4_i2c_clr_bits(reg, STM32F4_I2C_CR2_IRQ_MASK);
+}
+
+static int stm32f4_i2c_set_periph_clk_freq(struct stm32f4_i2c_dev *i2c_dev)
+{
+ u32 freq;
+ u32 cr2 = 0;
+
+ i2c_dev->parent_rate = clk_get_rate(i2c_dev->clk);
+ freq = DIV_ROUND_UP(i2c_dev->parent_rate, HZ_TO_MHZ);
+
+ if (i2c_dev->speed == STM32F4_I2C_SPEED_STANDARD) {
+ /*
+ * To reach 100 kHz, the parent clk frequency should be between
+ * a minimum value of 2 MHz and a maximum value of 46 MHz due
+ * to hardware limitation
+ */
+ if (freq < STM32F4_I2C_MIN_STANDARD_FREQ ||
+ freq > STM32F4_I2C_MAX_FREQ) {
+ dev_err(i2c_dev->dev,
+ "bad parent clk freq for standard mode\n");
+ return -EINVAL;
+ }
+ } else {
+ /*
+ * To be as close as possible to 400 kHz, the parent clk
+ * frequency should be between a minimum value of 6 MHz and a
+ * maximum value of 46 MHz due to hardware limitation
+ */
+ if (freq < STM32F4_I2C_MIN_FAST_FREQ ||
+ freq > STM32F4_I2C_MAX_FREQ) {
+ dev_err(i2c_dev->dev,
+ "bad parent clk freq for fast mode\n");
+ return -EINVAL;
+ }
+ }
+
+ cr2 |= STM32F4_I2C_CR2_FREQ(freq);
+ writel_relaxed(cr2, i2c_dev->base + STM32F4_I2C_CR2);
+
+ return 0;
+}
+
+static void stm32f4_i2c_set_rise_time(struct stm32f4_i2c_dev *i2c_dev)
+{
+ u32 freq = DIV_ROUND_UP(i2c_dev->parent_rate, HZ_TO_MHZ);
+ u32 trise;
+
+ /*
+ * These bits must be programmed with the maximum SCL rise time given in
+ * the I2C bus specification, incremented by 1.
+ *
+ * In standard mode, the maximum allowed SCL rise time is 1000 ns.
+ * If, in the I2C_CR2 register, the value of FREQ[5:0] bits is equal to
+ * 0x08 so period = 125 ns therefore the TRISE[5:0] bits must be
+ * programmed with 0x9. (1000 ns / 125 ns + 1)
+ * So, for I2C standard mode TRISE = FREQ[5:0] + 1
+ *
+ * In fast mode, the maximum allowed SCL rise time is 300 ns.
+ * If, in the I2C_CR2 register, the value of FREQ[5:0] bits is equal to
+ * 0x08 so period = 125 ns therefore the TRISE[5:0] bits must be
+ * programmed with 0x3. (300 ns / 125 ns + 1)
+ * So, for I2C fast mode TRISE = FREQ[5:0] * 300 / 1000 + 1
+ *
+ * Function stm32f4_i2c_set_periph_clk_freq made sure that parent rate
+ * is not higher than 46 MHz . As a result trise is at most 4 bits wide
+ * and so fits into the TRISE bits [5:0].
+ */
+ if (i2c_dev->speed == STM32F4_I2C_SPEED_STANDARD)
+ trise = freq + 1;
+ else
+ trise = freq * 3 / 10 + 1;
+
+ writel_relaxed(STM32F4_I2C_TRISE_VALUE(trise),
+ i2c_dev->base + STM32F4_I2C_TRISE);
+}
+
+static void stm32f4_i2c_set_speed_mode(struct stm32f4_i2c_dev *i2c_dev)
+{
+ u32 val;
+ u32 ccr = 0;
+
+ if (i2c_dev->speed == STM32F4_I2C_SPEED_STANDARD) {
+ /*
+ * In standard mode:
+ * t_scl_high = t_scl_low = CCR * I2C parent clk period
+ * So to reach 100 kHz, we have:
+ * CCR = I2C parent rate / 100 kHz >> 1
+ *
+ * For example with parent rate = 2 MHz:
+ * CCR = 2000000 / (100000 << 1) = 10
+ * t_scl_high = t_scl_low = 10 * (1 / 2000000) = 5000 ns
+ * t_scl_high + t_scl_low = 10000 ns so 100 kHz is reached
+ *
+ * Function stm32f4_i2c_set_periph_clk_freq made sure that
+ * parent rate is not higher than 46 MHz . As a result val
+ * is at most 8 bits wide and so fits into the CCR bits [11:0].
+ */
+ val = i2c_dev->parent_rate / (100000 << 1);
+ } else {
+ /*
+ * In fast mode, we compute CCR with duty = 0 as with low
+ * frequencies we are not able to reach 400 kHz.
+ * In that case:
+ * t_scl_high = CCR * I2C parent clk period
+ * t_scl_low = 2 * CCR * I2C parent clk period
+ * So, CCR = I2C parent rate / (400 kHz * 3)
+ *
+ * For example with parent rate = 6 MHz:
+ * CCR = 6000000 / (400000 * 3) = 5
+ * t_scl_high = 5 * (1 / 6000000) = 833 ns > 600 ns
+ * t_scl_low = 2 * 5 * (1 / 6000000) = 1667 ns > 1300 ns
+ * t_scl_high + t_scl_low = 2500 ns so 400 kHz is reached
+ *
+ * Function stm32f4_i2c_set_periph_clk_freq made sure that
+ * parent rate is not higher than 46 MHz . As a result val
+ * is at most 6 bits wide and so fits into the CCR bits [11:0].
+ */
+ val = DIV_ROUND_UP(i2c_dev->parent_rate, 400000 * 3);
+
+ /* Select Fast mode */
+ ccr |= STM32F4_I2C_CCR_FS;
+ }
+
+ ccr |= STM32F4_I2C_CCR_CCR(val);
+ writel_relaxed(ccr, i2c_dev->base + STM32F4_I2C_CCR);
+}
+
+/**
+ * stm32f4_i2c_hw_config() - Prepare I2C block
+ * @i2c_dev: Controller's private data
+ */
+static int stm32f4_i2c_hw_config(struct stm32f4_i2c_dev *i2c_dev)
+{
+ int ret;
+
+ ret = stm32f4_i2c_set_periph_clk_freq(i2c_dev);
+ if (ret)
+ return ret;
+
+ stm32f4_i2c_set_rise_time(i2c_dev);
+
+ stm32f4_i2c_set_speed_mode(i2c_dev);
+
+ /* Enable I2C */
+ writel_relaxed(STM32F4_I2C_CR1_PE, i2c_dev->base + STM32F4_I2C_CR1);
+
+ return 0;
+}
+
+static int stm32f4_i2c_wait_free_bus(struct stm32f4_i2c_dev *i2c_dev)
+{
+ u32 status;
+ int ret;
+
+ ret = readl_relaxed_poll_timeout(i2c_dev->base + STM32F4_I2C_SR2,
+ status,
+ !(status & STM32F4_I2C_SR2_BUSY),
+ 10, 1000);
+ if (ret) {
+ dev_dbg(i2c_dev->dev, "bus not free\n");
+ ret = -EBUSY;
+ }
+
+ return ret;
+}
+
+/**
+ * stm32f4_i2c_write_ byte() - Write a byte in the data register
+ * @i2c_dev: Controller's private data
+ * @byte: Data to write in the register
+ */
+static void stm32f4_i2c_write_byte(struct stm32f4_i2c_dev *i2c_dev, u8 byte)
+{
+ writel_relaxed(byte, i2c_dev->base + STM32F4_I2C_DR);
+}
+
+/**
+ * stm32f4_i2c_write_msg() - Fill the data register in write mode
+ * @i2c_dev: Controller's private data
+ *
+ * This function fills the data register with I2C transfer buffer
+ */
+static void stm32f4_i2c_write_msg(struct stm32f4_i2c_dev *i2c_dev)
+{
+ struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
+
+ stm32f4_i2c_write_byte(i2c_dev, *msg->buf++);
+ msg->count--;
+}
+
+static void stm32f4_i2c_read_msg(struct stm32f4_i2c_dev *i2c_dev)
+{
+ struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
+ u32 rbuf;
+
+ rbuf = readl_relaxed(i2c_dev->base + STM32F4_I2C_DR);
+ *msg->buf++ = rbuf;
+ msg->count--;
+}
+
+static void stm32f4_i2c_terminate_xfer(struct stm32f4_i2c_dev *i2c_dev)
+{
+ struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
+ void __iomem *reg = i2c_dev->base + STM32F4_I2C_CR2;
+
+ stm32f4_i2c_disable_irq(i2c_dev);
+
+ reg = i2c_dev->base + STM32F4_I2C_CR1;
+ if (msg->stop)
+ stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_STOP);
+ else
+ stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_START);
+
+ complete(&i2c_dev->complete);
+}
+
+/**
+ * stm32f4_i2c_handle_write() - Handle FIFO empty interrupt in case of write
+ * @i2c_dev: Controller's private data
+ */
+static void stm32f4_i2c_handle_write(struct stm32f4_i2c_dev *i2c_dev)
+{
+ struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
+ void __iomem *reg = i2c_dev->base + STM32F4_I2C_CR2;
+
+ if (msg->count) {
+ stm32f4_i2c_write_msg(i2c_dev);
+ if (!msg->count) {
+ /*
+ * Disable buffer interrupts for RX not empty and TX
+ * empty events
+ */
+ stm32f4_i2c_clr_bits(reg, STM32F4_I2C_CR2_ITBUFEN);
+ }
+ } else {
+ stm32f4_i2c_terminate_xfer(i2c_dev);
+ }
+}
+
+/**
+ * stm32f4_i2c_handle_read() - Handle FIFO empty interrupt in case of read
+ * @i2c_dev: Controller's private data
+ *
+ * This function is called when a new data is received in data register
+ */
+static void stm32f4_i2c_handle_read(struct stm32f4_i2c_dev *i2c_dev)
+{
+ struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
+ void __iomem *reg = i2c_dev->base + STM32F4_I2C_CR2;
+
+ switch (msg->count) {
+ case 1:
+ stm32f4_i2c_disable_irq(i2c_dev);
+ stm32f4_i2c_read_msg(i2c_dev);
+ complete(&i2c_dev->complete);
+ break;
+ /*
+ * For 2-byte reception, 3-byte reception and for Data N-2, N-1 and N
+ * for N-byte reception with N > 3, we do not have to read the data
+ * register when RX not empty event occurs as we have to wait for byte
+ * transferred finished event before reading data.
+ * So, here we just disable buffer interrupt in order to avoid another
+ * system preemption due to RX not empty event.
+ */
+ case 2:
+ case 3:
+ stm32f4_i2c_clr_bits(reg, STM32F4_I2C_CR2_ITBUFEN);
+ break;
+ /*
+ * For N byte reception with N > 3 we directly read data register
+ * until N-2 data.
+ */
+ default:
+ stm32f4_i2c_read_msg(i2c_dev);
+ }
+}
+
+/**
+ * stm32f4_i2c_handle_rx_done() - Handle byte transfer finished interrupt
+ * in case of read
+ * @i2c_dev: Controller's private data
+ *
+ * This function is called when a new data is received in the shift register
+ * but data register has not been read yet.
+ */
+static void stm32f4_i2c_handle_rx_done(struct stm32f4_i2c_dev *i2c_dev)
+{
+ struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
+ void __iomem *reg;
+ u32 mask;
+ int i;
+
+ switch (msg->count) {
+ case 2:
+ /*
+ * In order to correctly send the Stop or Repeated Start
+ * condition on the I2C bus, the STOP/START bit has to be set
+ * before reading the last two bytes (data N-1 and N).
+ * After that, we could read the last two bytes, disable
+ * remaining interrupts and notify the end of xfer to the
+ * client
+ */
+ reg = i2c_dev->base + STM32F4_I2C_CR1;
+ if (msg->stop)
+ stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_STOP);
+ else
+ stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_START);
+
+ for (i = 2; i > 0; i--)
+ stm32f4_i2c_read_msg(i2c_dev);
+
+ reg = i2c_dev->base + STM32F4_I2C_CR2;
+ mask = STM32F4_I2C_CR2_ITEVTEN | STM32F4_I2C_CR2_ITERREN;
+ stm32f4_i2c_clr_bits(reg, mask);
+
+ complete(&i2c_dev->complete);
+ break;
+ case 3:
+ /*
+ * In order to correctly generate the NACK pulse after the last
+ * received data byte, we have to enable NACK before reading N-2
+ * data
+ */
+ reg = i2c_dev->base + STM32F4_I2C_CR1;
+ stm32f4_i2c_clr_bits(reg, STM32F4_I2C_CR1_ACK);
+ stm32f4_i2c_read_msg(i2c_dev);
+ break;
+ default:
+ stm32f4_i2c_read_msg(i2c_dev);
+ }
+}
+
+/**
+ * stm32f4_i2c_handle_rx_addr() - Handle address matched interrupt in case of
+ * master receiver
+ * @i2c_dev: Controller's private data
+ */
+static void stm32f4_i2c_handle_rx_addr(struct stm32f4_i2c_dev *i2c_dev)
+{
+ struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
+ u32 cr1;
+
+ switch (msg->count) {
+ case 0:
+ stm32f4_i2c_terminate_xfer(i2c_dev);
+
+ /* Clear ADDR flag */
+ readl_relaxed(i2c_dev->base + STM32F4_I2C_SR2);
+ break;
+ case 1:
+ /*
+ * Single byte reception:
+ * Enable NACK and reset POS (Acknowledge position).
+ * Then, clear ADDR flag and set STOP or RepSTART.
+ * In that way, the NACK and STOP or RepStart pulses will be
+ * sent as soon as the byte will be received in shift register
+ */
+ cr1 = readl_relaxed(i2c_dev->base + STM32F4_I2C_CR1);
+ cr1 &= ~(STM32F4_I2C_CR1_ACK | STM32F4_I2C_CR1_POS);
+ writel_relaxed(cr1, i2c_dev->base + STM32F4_I2C_CR1);
+
+ readl_relaxed(i2c_dev->base + STM32F4_I2C_SR2);
+
+ if (msg->stop)
+ cr1 |= STM32F4_I2C_CR1_STOP;
+ else
+ cr1 |= STM32F4_I2C_CR1_START;
+ writel_relaxed(cr1, i2c_dev->base + STM32F4_I2C_CR1);
+ break;
+ case 2:
+ /*
+ * 2-byte reception:
+ * Enable NACK, set POS (NACK position) and clear ADDR flag.
+ * In that way, NACK will be sent for the next byte which will
+ * be received in the shift register instead of the current
+ * one.
+ */
+ cr1 = readl_relaxed(i2c_dev->base + STM32F4_I2C_CR1);
+ cr1 &= ~STM32F4_I2C_CR1_ACK;
+ cr1 |= STM32F4_I2C_CR1_POS;
+ writel_relaxed(cr1, i2c_dev->base + STM32F4_I2C_CR1);
+
+ readl_relaxed(i2c_dev->base + STM32F4_I2C_SR2);
+ break;
+
+ default:
+ /*
+ * N-byte reception:
+ * Enable ACK, reset POS (ACK postion) and clear ADDR flag.
+ * In that way, ACK will be sent as soon as the current byte
+ * will be received in the shift register
+ */
+ cr1 = readl_relaxed(i2c_dev->base + STM32F4_I2C_CR1);
+ cr1 |= STM32F4_I2C_CR1_ACK;
+ cr1 &= ~STM32F4_I2C_CR1_POS;
+ writel_relaxed(cr1, i2c_dev->base + STM32F4_I2C_CR1);
+
+ readl_relaxed(i2c_dev->base + STM32F4_I2C_SR2);
+ break;
+ }
+}
+
+/**
+ * stm32f4_i2c_isr_event() - Interrupt routine for I2C bus event
+ * @irq: interrupt number
+ * @data: Controller's private data
+ */
+static irqreturn_t stm32f4_i2c_isr_event(int irq, void *data)
+{
+ struct stm32f4_i2c_dev *i2c_dev = data;
+ struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
+ u32 possible_status = STM32F4_I2C_SR1_ITEVTEN_MASK;
+ u32 status, ien, event, cr2;
+
+ cr2 = readl_relaxed(i2c_dev->base + STM32F4_I2C_CR2);
+ ien = cr2 & STM32F4_I2C_CR2_IRQ_MASK;
+
+ /* Update possible_status if buffer interrupt is enabled */
+ if (ien & STM32F4_I2C_CR2_ITBUFEN)
+ possible_status |= STM32F4_I2C_SR1_ITBUFEN_MASK;
+
+ status = readl_relaxed(i2c_dev->base + STM32F4_I2C_SR1);
+ event = status & possible_status;
+ if (!event) {
+ dev_dbg(i2c_dev->dev,
+ "spurious evt irq (status=0x%08x, ien=0x%08x)\n",
+ status, ien);
+ return IRQ_NONE;
+ }
+
+ /* Start condition generated */
+ if (event & STM32F4_I2C_SR1_SB)
+ stm32f4_i2c_write_byte(i2c_dev, msg->addr);
+
+ /* I2C Address sent */
+ if (event & STM32F4_I2C_SR1_ADDR) {
+ if (msg->addr & I2C_M_RD)
+ stm32f4_i2c_handle_rx_addr(i2c_dev);
+ else
+ readl_relaxed(i2c_dev->base + STM32F4_I2C_SR2);
+
+ /*
+ * Enable buffer interrupts for RX not empty and TX empty
+ * events
+ */
+ cr2 |= STM32F4_I2C_CR2_ITBUFEN;
+ writel_relaxed(cr2, i2c_dev->base + STM32F4_I2C_CR2);
+ }
+
+ /* TX empty */
+ if ((event & STM32F4_I2C_SR1_TXE) && !(msg->addr & I2C_M_RD))
+ stm32f4_i2c_handle_write(i2c_dev);
+
+ /* RX not empty */
+ if ((event & STM32F4_I2C_SR1_RXNE) && (msg->addr & I2C_M_RD))
+ stm32f4_i2c_handle_read(i2c_dev);
+
+ /*
+ * The BTF (Byte Transfer finished) event occurs when:
+ * - in reception : a new byte is received in the shift register
+ * but the previous byte has not been read yet from data register
+ * - in transmission: a new byte should be sent but the data register
+ * has not been written yet
+ */
+ if (event & STM32F4_I2C_SR1_BTF) {
+ if (msg->addr & I2C_M_RD)
+ stm32f4_i2c_handle_rx_done(i2c_dev);
+ else
+ stm32f4_i2c_handle_write(i2c_dev);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * stm32f4_i2c_isr_error() - Interrupt routine for I2C bus error
+ * @irq: interrupt number
+ * @data: Controller's private data
+ */
+static irqreturn_t stm32f4_i2c_isr_error(int irq, void *data)
+{
+ struct stm32f4_i2c_dev *i2c_dev = data;
+ struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
+ void __iomem *reg;
+ u32 status;
+
+ status = readl_relaxed(i2c_dev->base + STM32F4_I2C_SR1);
+
+ /* Arbitration lost */
+ if (status & STM32F4_I2C_SR1_ARLO) {
+ status &= ~STM32F4_I2C_SR1_ARLO;
+ writel_relaxed(status, i2c_dev->base + STM32F4_I2C_SR1);
+ msg->result = -EAGAIN;
+ }
+
+ /*
+ * Acknowledge failure:
+ * In master transmitter mode a Stop must be generated by software
+ */
+ if (status & STM32F4_I2C_SR1_AF) {
+ if (!(msg->addr & I2C_M_RD)) {
+ reg = i2c_dev->base + STM32F4_I2C_CR1;
+ stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_STOP);
+ }
+ status &= ~STM32F4_I2C_SR1_AF;
+ writel_relaxed(status, i2c_dev->base + STM32F4_I2C_SR1);
+ msg->result = -EIO;
+ }
+
+ /* Bus error */
+ if (status & STM32F4_I2C_SR1_BERR) {
+ status &= ~STM32F4_I2C_SR1_BERR;
+ writel_relaxed(status, i2c_dev->base + STM32F4_I2C_SR1);
+ msg->result = -EIO;
+ }
+
+ stm32f4_i2c_disable_irq(i2c_dev);
+ complete(&i2c_dev->complete);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * stm32f4_i2c_xfer_msg() - Transfer a single I2C message
+ * @i2c_dev: Controller's private data
+ * @msg: I2C message to transfer
+ * @is_first: first message of the sequence
+ * @is_last: last message of the sequence
+ */
+static int stm32f4_i2c_xfer_msg(struct stm32f4_i2c_dev *i2c_dev,
+ struct i2c_msg *msg, bool is_first,
+ bool is_last)
+{
+ struct stm32f4_i2c_msg *f4_msg = &i2c_dev->msg;
+ void __iomem *reg = i2c_dev->base + STM32F4_I2C_CR1;
+ unsigned long timeout;
+ u32 mask;
+ int ret;
+
+ f4_msg->addr = i2c_8bit_addr_from_msg(msg);
+ f4_msg->buf = msg->buf;
+ f4_msg->count = msg->len;
+ f4_msg->result = 0;
+ f4_msg->stop = is_last;
+
+ reinit_completion(&i2c_dev->complete);
+
+ /* Enable events and errors interrupts */
+ mask = STM32F4_I2C_CR2_ITEVTEN | STM32F4_I2C_CR2_ITERREN;
+ stm32f4_i2c_set_bits(i2c_dev->base + STM32F4_I2C_CR2, mask);
+
+ if (is_first) {
+ ret = stm32f4_i2c_wait_free_bus(i2c_dev);
+ if (ret)
+ return ret;
+
+ /* START generation */
+ stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_START);
+ }
+
+ timeout = wait_for_completion_timeout(&i2c_dev->complete,
+ i2c_dev->adap.timeout);
+ ret = f4_msg->result;
+
+ if (!timeout)
+ ret = -ETIMEDOUT;
+
+ return ret;
+}
+
+/**
+ * stm32f4_i2c_xfer() - Transfer combined I2C message
+ * @i2c_adap: Adapter pointer to the controller
+ * @msgs: Pointer to data to be written.
+ * @num: Number of messages to be executed
+ */
+static int stm32f4_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[],
+ int num)
+{
+ struct stm32f4_i2c_dev *i2c_dev = i2c_get_adapdata(i2c_adap);
+ int ret, i;
+
+ ret = clk_enable(i2c_dev->clk);
+ if (ret) {
+ dev_err(i2c_dev->dev, "Failed to enable clock\n");
+ return ret;
+ }
+
+ for (i = 0; i < num && !ret; i++)
+ ret = stm32f4_i2c_xfer_msg(i2c_dev, &msgs[i], i == 0,
+ i == num - 1);
+
+ clk_disable(i2c_dev->clk);
+
+ return (ret < 0) ? ret : num;
+}
+
+static u32 stm32f4_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static struct i2c_algorithm stm32f4_i2c_algo = {
+ .master_xfer = stm32f4_i2c_xfer,
+ .functionality = stm32f4_i2c_func,
+};
+
+static int stm32f4_i2c_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct stm32f4_i2c_dev *i2c_dev;
+ struct resource *res;
+ u32 irq_event, irq_error, clk_rate;
+ struct i2c_adapter *adap;
+ struct reset_control *rst;
+ int ret;
+
+ i2c_dev = devm_kzalloc(&pdev->dev, sizeof(*i2c_dev), GFP_KERNEL);
+ if (!i2c_dev)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ i2c_dev->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(i2c_dev->base))
+ return PTR_ERR(i2c_dev->base);
+
+ irq_event = irq_of_parse_and_map(np, 0);
+ if (!irq_event) {
+ dev_err(&pdev->dev, "IRQ event missing or invalid\n");
+ return -EINVAL;
+ }
+
+ irq_error = irq_of_parse_and_map(np, 1);
+ if (!irq_error) {
+ dev_err(&pdev->dev, "IRQ error missing or invalid\n");
+ return -EINVAL;
+ }
+
+ i2c_dev->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(i2c_dev->clk)) {
+ dev_err(&pdev->dev, "Error: Missing controller clock\n");
+ return PTR_ERR(i2c_dev->clk);
+ }
+ ret = clk_prepare_enable(i2c_dev->clk);
+ if (ret) {
+ dev_err(i2c_dev->dev, "Failed to prepare_enable clock\n");
+ return ret;
+ }
+
+ rst = devm_reset_control_get(&pdev->dev, NULL);
+ if (IS_ERR(rst)) {
+ dev_err(&pdev->dev, "Error: Missing controller reset\n");
+ ret = PTR_ERR(rst);
+ goto clk_free;
+ }
+ reset_control_assert(rst);
+ udelay(2);
+ reset_control_deassert(rst);
+
+ i2c_dev->speed = STM32F4_I2C_SPEED_STANDARD;
+ ret = of_property_read_u32(np, "clock-frequency", &clk_rate);
+ if (!ret && clk_rate >= 400000)
+ i2c_dev->speed = STM32F4_I2C_SPEED_FAST;
+
+ i2c_dev->dev = &pdev->dev;
+
+ ret = devm_request_irq(&pdev->dev, irq_event, stm32f4_i2c_isr_event, 0,
+ pdev->name, i2c_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request irq event %i\n",
+ irq_event);
+ goto clk_free;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq_error, stm32f4_i2c_isr_error, 0,
+ pdev->name, i2c_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request irq error %i\n",
+ irq_error);
+ goto clk_free;
+ }
+
+ ret = stm32f4_i2c_hw_config(i2c_dev);
+ if (ret)
+ goto clk_free;
+
+ adap = &i2c_dev->adap;
+ i2c_set_adapdata(adap, i2c_dev);
+ snprintf(adap->name, sizeof(adap->name), "STM32 I2C(%pa)", &res->start);
+ adap->owner = THIS_MODULE;
+ adap->timeout = 2 * HZ;
+ adap->retries = 0;
+ adap->algo = &stm32f4_i2c_algo;
+ adap->dev.parent = &pdev->dev;
+ adap->dev.of_node = pdev->dev.of_node;
+
+ init_completion(&i2c_dev->complete);
+
+ ret = i2c_add_adapter(adap);
+ if (ret)
+ goto clk_free;
+
+ platform_set_drvdata(pdev, i2c_dev);
+
+ clk_disable(i2c_dev->clk);
+
+ dev_info(i2c_dev->dev, "STM32F4 I2C driver registered\n");
+
+ return 0;
+
+clk_free:
+ clk_disable_unprepare(i2c_dev->clk);
+ return ret;
+}
+
+static int stm32f4_i2c_remove(struct platform_device *pdev)
+{
+ struct stm32f4_i2c_dev *i2c_dev = platform_get_drvdata(pdev);
+
+ i2c_del_adapter(&i2c_dev->adap);
+
+ clk_unprepare(i2c_dev->clk);
+
+ return 0;
+}
+
+static const struct of_device_id stm32f4_i2c_match[] = {
+ { .compatible = "st,stm32f4-i2c", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, stm32f4_i2c_match);
+
+static struct platform_driver stm32f4_i2c_driver = {
+ .driver = {
+ .name = "stm32f4-i2c",
+ .of_match_table = stm32f4_i2c_match,
+ },
+ .probe = stm32f4_i2c_probe,
+ .remove = stm32f4_i2c_remove,
+};
+
+module_platform_driver(stm32f4_i2c_driver);
+
+MODULE_AUTHOR("M'boumba Cedric Madianga <cedric.madianga@gmail.com>");
+MODULE_DESCRIPTION("STMicroelectronics STM32F4 I2C driver");
+MODULE_LICENSE("GPL v2");
--
1.9.1
^ permalink raw reply related
* Re: [PATCH v10 2/5] i2c: Add STM32F4 I2C driver
From: Uwe Kleine-König @ 2017-01-19 13:31 UTC (permalink / raw)
To: M'boumba Cedric Madianga
Cc: wsa, robh+dt, mcoquelin.stm32, alexandre.torgue, linus.walleij,
patrice.chotard, linux, linux-i2c, devicetree, linux-arm-kernel,
linux-kernel
In-Reply-To: <1484832316-5594-3-git-send-email-cedric.madianga@gmail.com>
On Thu, Jan 19, 2017 at 02:25:13PM +0100, M'boumba Cedric Madianga wrote:
> This patch adds support for the STM32F4 I2C controller.
>
> Signed-off-by: M'boumba Cedric Madianga <cedric.madianga@gmail.com>
Acked-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Thanks
Uwe
--
Pengutronix e.K. | Uwe Kleine-König |
Industrial Linux Solutions | http://www.pengutronix.de/ |
^ permalink raw reply
* nested I2C muxes
From: Adrian Fiergolski @ 2017-01-19 18:03 UTC (permalink / raw)
To: linux-i2c
Hi,
I haven't found any information regarding support for nested i2c muxes.
Is the below device tree supported by the current driver ?
&i2c0 {
status = "okay";
clock-frequency = <400000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c0_default>;
i2cswitch@74 {
compatible = "nxp,pca9548";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x74>;
i2c@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
si570: clock-generator@5d {
#clock-cells = <0>;
compatible = "silabs,si570";
temperature-stability = <50>;
reg = <0x5d>;
factory-fout = <156250000>;
clock-frequency = <148500000>;
};
};
i2c@2 {
#address-cells = <1>;
#size-cells = <0>;
reg = <5>;
i2cswitch_hpc@74 {
compatible = "nxp,pca9548";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x74>;
hpc_caribou_sources_enable@76 {
compatible= "nxp,pca9539";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x76>;
};
};
};
};
};
With such tree I am getting error
[2.173385] i2c i2c-2: of_i2c: invalid addr=0 on
/amba/i2c@e0004000/i2cswitch@74/i2c@2/i2cswitch_hpc@74
Regards,
Adrian Fiergolski
^ permalink raw reply
* Re: nested I2C muxes
From: Peter Rosin @ 2017-01-19 18:34 UTC (permalink / raw)
To: Adrian Fiergolski, linux-i2c
In-Reply-To: <595ed81b-546b-a60f-1bf1-b21f14c15b3c@cern.ch>
On 2017-01-19 19:03, Adrian Fiergolski wrote:
> Hi,
>
> I haven't found any information regarding support for nested i2c muxes.
> Is the below device tree supported by the current driver ?
No, it is not since you cannot have two devices with address 0x74
visible at the same time. You would have to set a different address
on one of the muxes using its A0-A2 pins. Then it should work.
Look in Documentation/i2c/i2c-topology for more information.
Cheers,
peda
> &i2c0 {
> status = "okay";
> clock-frequency = <400000>;
> pinctrl-names = "default";
> pinctrl-0 = <&pinctrl_i2c0_default>;
>
> i2cswitch@74 {
> compatible = "nxp,pca9548";
> #address-cells = <1>;
> #size-cells = <0>;
> reg = <0x74>;
>
> i2c@0 {
> #address-cells = <1>;
> #size-cells = <0>;
> reg = <0>;
> si570: clock-generator@5d {
> #clock-cells = <0>;
> compatible = "silabs,si570";
> temperature-stability = <50>;
> reg = <0x5d>;
> factory-fout = <156250000>;
> clock-frequency = <148500000>;
> };
> };
>
> i2c@2 {
> #address-cells = <1>;
> #size-cells = <0>;
> reg = <5>;
> i2cswitch_hpc@74 {
> compatible = "nxp,pca9548";
> #address-cells = <1>;
> #size-cells = <0>;
> reg = <0x74>;
> hpc_caribou_sources_enable@76 {
>
> compatible= "nxp,pca9539";
>
> #address-cells = <1>;
>
> #size-cells = <0>;
>
> reg = <0x76>;
> };
> };
>
> };
>
> };
>
> };
>
> With such tree I am getting error
>
> [2.173385] i2c i2c-2: of_i2c: invalid addr=0 on
> /amba/i2c@e0004000/i2cswitch@74/i2c@2/i2cswitch_hpc@74
>
>
> Regards,
>
> Adrian Fiergolski
>
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
^ permalink raw reply
* Re: nested I2C muxes
From: Peter Rosin @ 2017-01-19 22:50 UTC (permalink / raw)
To: Adrian Fiergolski, linux-i2c
In-Reply-To: <693bca75-e610-a96e-45d8-06af861500a9@axentia.se>
On 2017-01-19 19:34, Peter Rosin wrote:
> On 2017-01-19 19:03, Adrian Fiergolski wrote:
>> Hi,
>>
>> I haven't found any information regarding support for nested i2c muxes.
>> Is the below device tree supported by the current driver ?
>
> No, it is not since you cannot have two devices with address 0x74
> visible at the same time. You would have to set a different address
> on one of the muxes using its A0-A2 pins. Then it should work.
>
> Look in Documentation/i2c/i2c-topology for more information.
>
> Cheers,
> peda
>
>> &i2c0 {
>> status = "okay";
>> clock-frequency = <400000>;
>> pinctrl-names = "default";
>> pinctrl-0 = <&pinctrl_i2c0_default>;
>>
>> i2cswitch@74 {
>> compatible = "nxp,pca9548";
>> #address-cells = <1>;
>> #size-cells = <0>;
>> reg = <0x74>;
>>
>> i2c@0 {
>> #address-cells = <1>;
>> #size-cells = <0>;
>> reg = <0>;
>> si570: clock-generator@5d {
>> #clock-cells = <0>;
>> compatible = "silabs,si570";
>> temperature-stability = <50>;
>> reg = <0x5d>;
>> factory-fout = <156250000>;
>> clock-frequency = <148500000>;
>> };
>> };
>>
>> i2c@2 {
>> #address-cells = <1>;
>> #size-cells = <0>;
>> reg = <5>;
Also, this <5> is bogus. It should match the @2 a few lines up.
Cheers,
peda
>> i2cswitch_hpc@74 {
>> compatible = "nxp,pca9548";
>> #address-cells = <1>;
>> #size-cells = <0>;
>> reg = <0x74>;
>> hpc_caribou_sources_enable@76 {
>> compatible= "nxp,pca9539";
>> #address-cells = <1>;
>> #size-cells = <0>;
>> reg = <0x76>;
>> };
>> };
>>
>> };
>>
>> };
>>
>> };
>>
>> With such tree I am getting error
>>
>> [2.173385] i2c i2c-2: of_i2c: invalid addr=0 on
>> /amba/i2c@e0004000/i2cswitch@74/i2c@2/i2cswitch_hpc@74
>>
>>
>> Regards,
>>
>> Adrian Fiergolski
>>
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox