* [PATCH v13 01/10] devres: trivial whitespace fix
From: Peter Rosin @ 2017-04-13 16:43 UTC (permalink / raw)
To: linux-kernel, Greg Kroah-Hartman
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, linux-i2c, devicetree,
linux-iio, linux-doc, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel
In-Reply-To: <1492101794-13444-1-git-send-email-peda@axentia.se>
Everything else is indented with two spaces, so fix the odd one out.
Acked-by: Jonathan Cameron <jic23@kernel.org>
Signed-off-by: Peter Rosin <peda@axentia.se>
---
Documentation/driver-model/devres.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Documentation/driver-model/devres.txt b/Documentation/driver-model/devres.txt
index bf34d5b3a733..efb8200819d6 100644
--- a/Documentation/driver-model/devres.txt
+++ b/Documentation/driver-model/devres.txt
@@ -335,7 +335,7 @@ MEM
devm_kzalloc()
MFD
- devm_mfd_add_devices()
+ devm_mfd_add_devices()
PER-CPU MEM
devm_alloc_percpu()
--
2.1.4
^ permalink raw reply related
* [PATCH v13 02/10] dt-bindings: document devicetree bindings for mux-controllers and gpio-mux
From: Peter Rosin @ 2017-04-13 16:43 UTC (permalink / raw)
To: linux-kernel, Greg Kroah-Hartman
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, linux-i2c, devicetree,
linux-iio, linux-doc, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel
In-Reply-To: <1492101794-13444-1-git-send-email-peda@axentia.se>
Allow specifying that a single multiplexer controller can be used to
control several parallel multiplexers, thus enabling sharing of the
multiplexer controller by different consumers.
Add a binding for a first mux controller in the form of a GPIO based mux
controller.
Acked-by: Jonathan Cameron <jic23@kernel.org>
Acked-by: Rob Herring <robh@kernel.org>
Signed-off-by: Peter Rosin <peda@axentia.se>
---
Documentation/devicetree/bindings/mux/gpio-mux.txt | 69 +++++++++
.../devicetree/bindings/mux/mux-controller.txt | 157 +++++++++++++++++++++
MAINTAINERS | 6 +
include/dt-bindings/mux/mux.h | 16 +++
4 files changed, 248 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mux/gpio-mux.txt
create mode 100644 Documentation/devicetree/bindings/mux/mux-controller.txt
create mode 100644 include/dt-bindings/mux/mux.h
diff --git a/Documentation/devicetree/bindings/mux/gpio-mux.txt b/Documentation/devicetree/bindings/mux/gpio-mux.txt
new file mode 100644
index 000000000000..b8f746344d80
--- /dev/null
+++ b/Documentation/devicetree/bindings/mux/gpio-mux.txt
@@ -0,0 +1,69 @@
+GPIO-based multiplexer controller bindings
+
+Define what GPIO pins are used to control a multiplexer. Or several
+multiplexers, if the same pins control more than one multiplexer.
+
+Required properties:
+- compatible : "gpio-mux"
+- mux-gpios : list of gpios used to control the multiplexer, least
+ significant bit first.
+- #mux-control-cells : <0>
+* Standard mux-controller bindings as decribed in mux-controller.txt
+
+Optional properties:
+- idle-state : if present, the state the mux will have when idle. The
+ special state MUX_IDLE_AS_IS is the default.
+
+The multiplexer state is defined as the number represented by the
+multiplexer GPIO pins, where the first pin is the least significant
+bit. An active pin is a binary 1, an inactive pin is a binary 0.
+
+Example:
+
+ mux: mux-controller {
+ compatible = "gpio-mux";
+ #mux-control-cells = <0>;
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
+ <&pioA 1 GPIO_ACTIVE_HIGH>;
+ };
+
+ adc-mux {
+ compatible = "io-channel-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ mux-controls = <&mux>;
+
+ channels = "sync-1", "in", "out", "sync-2";
+ };
+
+ i2c-mux {
+ compatible = "i2c-mux";
+ i2c-parent = <&i2c1>;
+
+ mux-controls = <&mux>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ i2c@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ssd1307: oled@3c {
+ /* ... */
+ };
+ };
+
+ i2c@3 {
+ reg = <3>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pca9555: pca9555@20 {
+ /* ... */
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/mux/mux-controller.txt b/Documentation/devicetree/bindings/mux/mux-controller.txt
new file mode 100644
index 000000000000..4f47e4bd2fa0
--- /dev/null
+++ b/Documentation/devicetree/bindings/mux/mux-controller.txt
@@ -0,0 +1,157 @@
+Common multiplexer controller bindings
+======================================
+
+A multiplexer (or mux) controller will have one, or several, consumer devices
+that uses the mux controller. Thus, a mux controller can possibly control
+several parallel multiplexers. Presumably there will be at least one
+multiplexer needed by each consumer, but a single mux controller can of course
+control several multiplexers for a single consumer.
+
+A mux controller provides a number of states to its consumers, and the state
+space is a simple zero-based enumeration. I.e. 0-1 for a 2-way multiplexer,
+0-7 for an 8-way multiplexer, etc.
+
+
+Consumers
+---------
+
+Mux controller consumers should specify a list of mux controllers that they
+want to use with a property containing a 'mux-ctrl-list':
+
+ mux-ctrl-list ::= <single-mux-ctrl> [mux-ctrl-list]
+ single-mux-ctrl ::= <mux-ctrl-phandle> [mux-ctrl-specifier]
+ mux-ctrl-phandle : phandle to mux controller node
+ mux-ctrl-specifier : array of #mux-control-cells specifying the
+ given mux controller (controller specific)
+
+Mux controller properties should be named "mux-controls". The exact meaning of
+each mux controller property must be documented in the device tree binding for
+each consumer. An optional property "mux-control-names" may contain a list of
+strings to label each of the mux controllers listed in the "mux-controls"
+property.
+
+Drivers for devices that use more than a single mux controller can use the
+"mux-control-names" property to map the name of the requested mux controller
+to an index into the list given by the "mux-controls" property.
+
+mux-ctrl-specifier typically encodes the chip-relative mux controller number.
+If the mux controller chip only provides a single mux controller, the
+mux-ctrl-specifier can typically be left out.
+
+Example:
+
+ /* One consumer of a 2-way mux controller (one GPIO-line) */
+ mux: mux-controller {
+ compatible = "gpio-mux";
+ #mux-control-cells = <0>;
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>;
+ };
+
+ adc-mux {
+ compatible = "io-channel-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ mux-controls = <&mux>;
+ mux-control-names = "adc";
+
+ channels = "sync", "in";
+ };
+
+Note that in the example above, specifying the "mux-control-names" is redundant
+because there is only one mux controller in the list. However, if the driver
+for the consumer node in fact asks for a named mux controller, that name is of
+course still required.
+
+ /*
+ * Two consumers (one for an ADC line and one for an i2c bus) of
+ * parallel 4-way multiplexers controlled by the same two GPIO-lines.
+ */
+ mux: mux-controller {
+ compatible = "gpio-mux";
+ #mux-control-cells = <0>;
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
+ <&pioA 1 GPIO_ACTIVE_HIGH>;
+ };
+
+ adc-mux {
+ compatible = "io-channel-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ mux-controls = <&mux>;
+
+ channels = "sync-1", "in", "out", "sync-2";
+ };
+
+ i2c-mux {
+ compatible = "i2c-mux";
+ i2c-parent = <&i2c1>;
+
+ mux-controls = <&mux>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ i2c@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ssd1307: oled@3c {
+ /* ... */
+ };
+ };
+
+ i2c@3 {
+ reg = <3>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pca9555: pca9555@20 {
+ /* ... */
+ };
+ };
+ };
+
+
+Mux controller nodes
+--------------------
+
+Mux controller nodes must specify the number of cells used for the
+specifier using the '#mux-control-cells' property.
+
+Optionally, mux controller nodes can also specify the state the mux should
+have when it is idle. The idle-state property is used for this. If the
+idle-state is not present, the mux controller is typically left as is when
+it is idle. For multiplexer chips that expose several mux controllers, the
+idle-state property is an array with one idle state for each mux controller.
+
+The special value (-1) may be used to indicate that the mux should be left
+as is when it is idle. This is the default, but can still be useful for
+mux controller chips with more than one mux controller, particularly when
+there is a need to "step past" a mux controller and set some other idle
+state for a mux controller with a higher index.
+
+Some mux controllers have the ability to disconnect the input/output of the
+multiplexer. Using this disconnected high-impedance state as the idle state
+is indicated with idle state (-2).
+
+These constants are available in
+
+ #include <dt-bindings/mux/mux.h>
+
+as MUX_IDLE_AS_IS (-1) and MUX_IDLE_DISCONNECT (-2).
+
+An example mux controller node look like this (the adg972a chip is a triple
+4-way multiplexer):
+
+ mux: mux-controller@50 {
+ compatible = "adi,adg792a";
+ reg = <0x50>;
+ #mux-control-cells = <1>;
+
+ idle-state = <MUX_IDLE_DISCONNECT MUX_IDLE_AS_IS 2>;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index c265a5fe4848..7fc06739c8ad 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8558,6 +8558,12 @@ S: Orphan
F: drivers/mmc/host/mmc_spi.c
F: include/linux/spi/mmc_spi.h
+MULTIPLEXER SUBSYSTEM
+M: Peter Rosin <peda@axentia.se>
+S: Maintained
+F: Documentation/devicetree/bindings/mux/
+F: include/linux/dt-bindings/mux/
+
MULTISOUND SOUND DRIVER
M: Andrew Veliath <andrewtv@usa.net>
S: Maintained
diff --git a/include/dt-bindings/mux/mux.h b/include/dt-bindings/mux/mux.h
new file mode 100644
index 000000000000..c8e855c4a609
--- /dev/null
+++ b/include/dt-bindings/mux/mux.h
@@ -0,0 +1,16 @@
+/*
+ * This header provides constants for most Multiplexer bindings.
+ *
+ * Most Multiplexer bindings specify an idle state. In most cases, the
+ * the multiplexer can be left as is when idle, and in some cases it can
+ * disconnect the input/output and leave the multiplexer in a high
+ * impedance state.
+ */
+
+#ifndef _DT_BINDINGS_MUX_MUX_H
+#define _DT_BINDINGS_MUX_MUX_H
+
+#define MUX_IDLE_AS_IS (-1)
+#define MUX_IDLE_DISCONNECT (-2)
+
+#endif
--
2.1.4
^ permalink raw reply related
* [PATCH v13 03/10] mux: minimal mux subsystem and gpio-based mux controller
From: Peter Rosin @ 2017-04-13 16:43 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA, Greg Kroah-Hartman
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel-bIcnvbaLZ9MEGnE8C9+IrQ
In-Reply-To: <1492101794-13444-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
Add a new minimalistic subsystem that handles multiplexer controllers.
When multiplexers are used in various places in the kernel, and the
same multiplexer controller can be used for several independent things,
there should be one place to implement support for said multiplexer
controller.
A single multiplexer controller can also be used to control several
parallel multiplexers, that are in turn used by different subsystems
in the kernel, leading to a need to coordinate multiplexer accesses.
The multiplexer subsystem handles this coordination.
This new mux controller subsystem initially comes with a single backend
driver that controls gpio based multiplexers. Even though not needed by
this initial driver, the mux controller subsystem is prepared to handle
chips with multiple (independent) mux controllers.
Reviewed-by: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Signed-off-by: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
Documentation/driver-model/devres.txt | 8 +
MAINTAINERS | 2 +
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/mux/Kconfig | 34 +++
drivers/mux/Makefile | 6 +
drivers/mux/mux-core.c | 422 ++++++++++++++++++++++++++++++++++
drivers/mux/mux-gpio.c | 114 +++++++++
include/linux/mux.h | 252 ++++++++++++++++++++
9 files changed, 841 insertions(+)
create mode 100644 drivers/mux/Kconfig
create mode 100644 drivers/mux/Makefile
create mode 100644 drivers/mux/mux-core.c
create mode 100644 drivers/mux/mux-gpio.c
create mode 100644 include/linux/mux.h
diff --git a/Documentation/driver-model/devres.txt b/Documentation/driver-model/devres.txt
index efb8200819d6..e2343d9cbec7 100644
--- a/Documentation/driver-model/devres.txt
+++ b/Documentation/driver-model/devres.txt
@@ -337,6 +337,14 @@ MEM
MFD
devm_mfd_add_devices()
+MUX
+ devm_mux_chip_alloc()
+ devm_mux_chip_free()
+ devm_mux_chip_register()
+ devm_mux_chip_unregister()
+ devm_mux_control_get()
+ devm_mux_control_put()
+
PER-CPU MEM
devm_alloc_percpu()
devm_free_percpu()
diff --git a/MAINTAINERS b/MAINTAINERS
index 7fc06739c8ad..591eba737678 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8563,6 +8563,8 @@ M: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
S: Maintained
F: Documentation/devicetree/bindings/mux/
F: include/linux/dt-bindings/mux/
+F: include/linux/mux.h
+F: drivers/mux/
MULTISOUND SOUND DRIVER
M: Andrew Veliath <andrewtv-Jdbf3xiKgS8@public.gmane.org>
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 117ca14ccf85..a7ea13e1b869 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -204,4 +204,6 @@ source "drivers/fpga/Kconfig"
source "drivers/fsi/Kconfig"
+source "drivers/mux/Kconfig"
+
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 2eced9afba53..c0436f6dd5a9 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -177,3 +177,4 @@ obj-$(CONFIG_ANDROID) += android/
obj-$(CONFIG_NVMEM) += nvmem/
obj-$(CONFIG_FPGA) += fpga/
obj-$(CONFIG_FSI) += fsi/
+obj-$(CONFIG_MULTIPLEXER) += mux/
diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig
new file mode 100644
index 000000000000..41dfe08ead84
--- /dev/null
+++ b/drivers/mux/Kconfig
@@ -0,0 +1,34 @@
+#
+# Multiplexer devices
+#
+
+menuconfig MULTIPLEXER
+ tristate "Multiplexer subsystem"
+ help
+ Multiplexer controller subsystem. Multiplexers are used in a
+ variety of settings, and this subsystem abstracts their use
+ so that the rest of the kernel sees a common interface. When
+ multiple parallel multiplexers are controlled by one single
+ multiplexer controller, this subsystem also coordinates the
+ multiplexer accesses.
+
+ To compile the subsystem as a module, choose M here: the module will
+ be called mux-core.
+
+if MULTIPLEXER
+
+config MUX_GPIO
+ tristate "GPIO-controlled Multiplexer"
+ depends on OF && GPIOLIB
+ help
+ GPIO-controlled Multiplexer controller.
+
+ The driver builds a single multiplexer controller using a number
+ of gpio pins. For N pins, there will be 2^N possible multiplexer
+ states. The GPIO pins can be connected (by the hardware) to several
+ multiplexers, which in that case will be operated in parallel.
+
+ To compile the driver as a module, choose M here: the module will
+ be called mux-gpio.
+
+endif
diff --git a/drivers/mux/Makefile b/drivers/mux/Makefile
new file mode 100644
index 000000000000..bb16953f6290
--- /dev/null
+++ b/drivers/mux/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for multiplexer devices.
+#
+
+obj-$(CONFIG_MULTIPLEXER) += mux-core.o
+obj-$(CONFIG_MUX_GPIO) += mux-gpio.o
diff --git a/drivers/mux/mux-core.c b/drivers/mux/mux-core.c
new file mode 100644
index 000000000000..66a8bccfc3d7
--- /dev/null
+++ b/drivers/mux/mux-core.c
@@ -0,0 +1,422 @@
+/*
+ * Multiplexer subsystem
+ *
+ * Copyright (C) 2017 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) "mux-core: " fmt
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/idr.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+
+/*
+ * The idle-as-is "state" is not an actual state that may be selected, it
+ * only implies that the state should not be changed. So, use that state
+ * as indication that the cached state of the multiplexer is unknown.
+ */
+#define MUX_CACHE_UNKNOWN MUX_IDLE_AS_IS
+
+static struct class mux_class = {
+ .name = "mux",
+ .owner = THIS_MODULE,
+};
+
+static int __init mux_init(void)
+{
+ return class_register(&mux_class);
+}
+
+static DEFINE_IDA(mux_ida);
+
+static void mux_chip_release(struct device *dev)
+{
+ struct mux_chip *mux_chip = to_mux_chip(dev);
+
+ ida_simple_remove(&mux_ida, mux_chip->id);
+ kfree(mux_chip);
+}
+
+static struct device_type mux_type = {
+ .name = "mux-chip",
+ .release = mux_chip_release,
+};
+
+struct mux_chip *mux_chip_alloc(struct device *dev,
+ unsigned int controllers, size_t sizeof_priv)
+{
+ struct mux_chip *mux_chip;
+ int i;
+
+ if (WARN_ON(!dev || !controllers))
+ return NULL;
+
+ mux_chip = kzalloc(sizeof(*mux_chip) +
+ controllers * sizeof(*mux_chip->mux) +
+ sizeof_priv, GFP_KERNEL);
+ if (!mux_chip)
+ return NULL;
+
+ mux_chip->mux = (struct mux_control *)(mux_chip + 1);
+ mux_chip->dev.class = &mux_class;
+ mux_chip->dev.type = &mux_type;
+ mux_chip->dev.parent = dev;
+ mux_chip->dev.of_node = dev->of_node;
+ dev_set_drvdata(&mux_chip->dev, mux_chip);
+
+ mux_chip->id = ida_simple_get(&mux_ida, 0, 0, GFP_KERNEL);
+ if (mux_chip->id < 0) {
+ pr_err("muxchipX failed to get a device id\n");
+ kfree(mux_chip);
+ return NULL;
+ }
+ dev_set_name(&mux_chip->dev, "muxchip%d", mux_chip->id);
+
+ mux_chip->controllers = controllers;
+ for (i = 0; i < controllers; ++i) {
+ struct mux_control *mux = &mux_chip->mux[i];
+
+ mux->chip = mux_chip;
+ init_rwsem(&mux->lock);
+ mux->cached_state = MUX_CACHE_UNKNOWN;
+ mux->idle_state = MUX_IDLE_AS_IS;
+ }
+
+ device_initialize(&mux_chip->dev);
+
+ return mux_chip;
+}
+EXPORT_SYMBOL_GPL(mux_chip_alloc);
+
+static int mux_control_set(struct mux_control *mux, int state)
+{
+ int ret = mux->chip->ops->set(mux, state);
+
+ mux->cached_state = ret < 0 ? MUX_CACHE_UNKNOWN : state;
+
+ return ret;
+}
+
+int mux_chip_register(struct mux_chip *mux_chip)
+{
+ int i;
+ int ret;
+
+ for (i = 0; i < mux_chip->controllers; ++i) {
+ struct mux_control *mux = &mux_chip->mux[i];
+
+ if (mux->idle_state == mux->cached_state)
+ continue;
+
+ ret = mux_control_set(mux, mux->idle_state);
+ if (ret < 0) {
+ dev_err(&mux_chip->dev, "unable to set idle state\n");
+ return ret;
+ }
+ }
+
+ ret = device_add(&mux_chip->dev);
+ if (ret < 0)
+ dev_err(&mux_chip->dev,
+ "device_add failed in mux_chip_register: %d\n", ret);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mux_chip_register);
+
+void mux_chip_unregister(struct mux_chip *mux_chip)
+{
+ device_del(&mux_chip->dev);
+}
+EXPORT_SYMBOL_GPL(mux_chip_unregister);
+
+void mux_chip_free(struct mux_chip *mux_chip)
+{
+ if (!mux_chip)
+ return;
+
+ put_device(&mux_chip->dev);
+}
+EXPORT_SYMBOL_GPL(mux_chip_free);
+
+static void devm_mux_chip_release(struct device *dev, void *res)
+{
+ struct mux_chip *mux_chip = *(struct mux_chip **)res;
+
+ mux_chip_free(mux_chip);
+}
+
+struct mux_chip *devm_mux_chip_alloc(struct device *dev,
+ unsigned int controllers,
+ size_t sizeof_priv)
+{
+ struct mux_chip **ptr, *mux_chip;
+
+ ptr = devres_alloc(devm_mux_chip_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return NULL;
+
+ mux_chip = mux_chip_alloc(dev, controllers, sizeof_priv);
+ if (!mux_chip) {
+ devres_free(ptr);
+ return NULL;
+ }
+
+ *ptr = mux_chip;
+ devres_add(dev, ptr);
+
+ return mux_chip;
+}
+EXPORT_SYMBOL_GPL(devm_mux_chip_alloc);
+
+static int devm_mux_chip_match(struct device *dev, void *res, void *data)
+{
+ struct mux_chip **r = res;
+
+ if (WARN_ON(!r || !*r))
+ return 0;
+
+ return *r == data;
+}
+
+void devm_mux_chip_free(struct device *dev, struct mux_chip *mux_chip)
+{
+ WARN_ON(devres_release(dev, devm_mux_chip_release,
+ devm_mux_chip_match, mux_chip));
+}
+EXPORT_SYMBOL_GPL(devm_mux_chip_free);
+
+static void devm_mux_chip_reg_release(struct device *dev, void *res)
+{
+ struct mux_chip *mux_chip = *(struct mux_chip **)res;
+
+ mux_chip_unregister(mux_chip);
+}
+
+int devm_mux_chip_register(struct device *dev,
+ struct mux_chip *mux_chip)
+{
+ struct mux_chip **ptr;
+ int res;
+
+ ptr = devres_alloc(devm_mux_chip_reg_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return -ENOMEM;
+
+ res = mux_chip_register(mux_chip);
+ if (res) {
+ devres_free(ptr);
+ return res;
+ }
+
+ *ptr = mux_chip;
+ devres_add(dev, ptr);
+
+ return res;
+}
+EXPORT_SYMBOL_GPL(devm_mux_chip_register);
+
+void devm_mux_chip_unregister(struct device *dev, struct mux_chip *mux_chip)
+{
+ WARN_ON(devres_release(dev, devm_mux_chip_reg_release,
+ devm_mux_chip_match, mux_chip));
+}
+EXPORT_SYMBOL_GPL(devm_mux_chip_unregister);
+
+int mux_control_select(struct mux_control *mux, int state)
+{
+ int ret;
+
+ if (down_read_trylock(&mux->lock)) {
+ if (mux->cached_state == state)
+ return 0;
+
+ /* Sigh, the mux needs updating... */
+ up_read(&mux->lock);
+ }
+
+ /* ...or it's just contended. */
+ down_write(&mux->lock);
+
+ if (mux->cached_state == state) {
+ /*
+ * Hmmm, someone else changed the mux to my liking.
+ * That makes me wonder how long I waited for nothing?
+ */
+ downgrade_write(&mux->lock);
+ return 0;
+ }
+
+ ret = mux_control_set(mux, state);
+ if (ret < 0) {
+ if (mux->idle_state != MUX_IDLE_AS_IS)
+ mux_control_set(mux, mux->idle_state);
+
+ up_write(&mux->lock);
+ return ret;
+ }
+
+ downgrade_write(&mux->lock);
+
+ return 1;
+}
+EXPORT_SYMBOL_GPL(mux_control_select);
+
+int mux_control_deselect(struct mux_control *mux)
+{
+ int ret = 0;
+
+ if (mux->idle_state != MUX_IDLE_AS_IS &&
+ mux->idle_state != mux->cached_state)
+ ret = mux_control_set(mux, mux->idle_state);
+
+ up_read(&mux->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mux_control_deselect);
+
+static int of_dev_node_match(struct device *dev, const void *data)
+{
+ return dev->of_node == data;
+}
+
+static struct mux_chip *of_find_mux_chip_by_node(struct device_node *np)
+{
+ struct device *dev;
+
+ dev = class_find_device(&mux_class, NULL, np, of_dev_node_match);
+
+ return dev ? to_mux_chip(dev) : NULL;
+}
+
+struct mux_control *mux_control_get(struct device *dev, const char *mux_name)
+{
+ struct device_node *np = dev->of_node;
+ struct of_phandle_args args;
+ struct mux_chip *mux_chip;
+ unsigned int controller;
+ int index = 0;
+ int ret;
+
+ if (mux_name) {
+ index = of_property_match_string(np, "mux-control-names",
+ mux_name);
+ if (index < 0) {
+ dev_err(dev, "mux controller '%s' not found\n",
+ mux_name);
+ return ERR_PTR(index);
+ }
+ }
+
+ ret = of_parse_phandle_with_args(np,
+ "mux-controls", "#mux-control-cells",
+ index, &args);
+ if (ret) {
+ dev_err(dev, "%s: failed to get mux-control %s(%i)\n",
+ np->full_name, mux_name ?: "", index);
+ return ERR_PTR(ret);
+ }
+
+ mux_chip = of_find_mux_chip_by_node(args.np);
+ of_node_put(args.np);
+ if (!mux_chip)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ if (args.args_count > 1 ||
+ (!args.args_count && (mux_chip->controllers > 1))) {
+ dev_err(dev, "%s: wrong #mux-control-cells for %s\n",
+ np->full_name, args.np->full_name);
+ return ERR_PTR(-EINVAL);
+ }
+
+ controller = 0;
+ if (args.args_count)
+ controller = args.args[0];
+
+ if (controller >= mux_chip->controllers) {
+ dev_err(dev, "%s: bad mux controller %u specified in %s\n",
+ np->full_name, controller, args.np->full_name);
+ return ERR_PTR(-EINVAL);
+ }
+
+ get_device(&mux_chip->dev);
+ return &mux_chip->mux[controller];
+}
+EXPORT_SYMBOL_GPL(mux_control_get);
+
+void mux_control_put(struct mux_control *mux)
+{
+ put_device(&mux->chip->dev);
+}
+EXPORT_SYMBOL_GPL(mux_control_put);
+
+static void devm_mux_control_release(struct device *dev, void *res)
+{
+ struct mux_control *mux = *(struct mux_control **)res;
+
+ mux_control_put(mux);
+}
+
+struct mux_control *devm_mux_control_get(struct device *dev,
+ const char *mux_name)
+{
+ struct mux_control **ptr, *mux;
+
+ ptr = devres_alloc(devm_mux_control_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ mux = mux_control_get(dev, mux_name);
+ if (IS_ERR(mux)) {
+ devres_free(ptr);
+ return mux;
+ }
+
+ *ptr = mux;
+ devres_add(dev, ptr);
+
+ return mux;
+}
+EXPORT_SYMBOL_GPL(devm_mux_control_get);
+
+static int devm_mux_control_match(struct device *dev, void *res, void *data)
+{
+ struct mux_control **r = res;
+
+ if (WARN_ON(!r || !*r))
+ return 0;
+
+ return *r == data;
+}
+
+void devm_mux_control_put(struct device *dev, struct mux_control *mux)
+{
+ WARN_ON(devres_release(dev, devm_mux_control_release,
+ devm_mux_control_match, mux));
+}
+EXPORT_SYMBOL_GPL(devm_mux_control_put);
+
+/*
+ * Using subsys_initcall instead of module_init here to ensure - for the
+ * non-modular case - that the subsystem is initialized when mux consumers
+ * and mux controllers start to use it /without/ relying on link order.
+ * For the modular case, the ordering is ensured with module dependencies.
+ */
+subsys_initcall(mux_init);
+
+MODULE_DESCRIPTION("Multiplexer subsystem");
+MODULE_AUTHOR("Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mux/mux-gpio.c b/drivers/mux/mux-gpio.c
new file mode 100644
index 000000000000..227d3572e6db
--- /dev/null
+++ b/drivers/mux/mux-gpio.c
@@ -0,0 +1,114 @@
+/*
+ * GPIO-controlled multiplexer driver
+ *
+ * Copyright (C) 2017 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+struct mux_gpio {
+ struct gpio_descs *gpios;
+ int *val;
+};
+
+static int mux_gpio_set(struct mux_control *mux, int state)
+{
+ struct mux_gpio *mux_gpio = mux_chip_priv(mux->chip);
+ int i;
+
+ for (i = 0; i < mux_gpio->gpios->ndescs; i++)
+ mux_gpio->val[i] = (state >> i) & 1;
+
+ gpiod_set_array_value_cansleep(mux_gpio->gpios->ndescs,
+ mux_gpio->gpios->desc,
+ mux_gpio->val);
+
+ return 0;
+}
+
+static const struct mux_control_ops mux_gpio_ops = {
+ .set = mux_gpio_set,
+};
+
+static const struct of_device_id mux_gpio_dt_ids[] = {
+ { .compatible = "gpio-mux", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mux_gpio_dt_ids);
+
+static int mux_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mux_chip *mux_chip;
+ struct mux_gpio *mux_gpio;
+ int pins;
+ s32 idle_state;
+ int ret;
+
+ pins = gpiod_count(dev, "mux");
+ if (pins < 0)
+ return pins;
+
+ mux_chip = devm_mux_chip_alloc(dev, 1, sizeof(*mux_gpio) +
+ pins * sizeof(*mux_gpio->val));
+ if (!mux_chip)
+ return -ENOMEM;
+
+ mux_gpio = mux_chip_priv(mux_chip);
+ mux_gpio->val = (int *)(mux_gpio + 1);
+ mux_chip->ops = &mux_gpio_ops;
+
+ mux_gpio->gpios = devm_gpiod_get_array(dev, "mux", GPIOD_OUT_LOW);
+ if (IS_ERR(mux_gpio->gpios)) {
+ ret = PTR_ERR(mux_gpio->gpios);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to get gpios\n");
+ return ret;
+ }
+ WARN_ON(pins != mux_gpio->gpios->ndescs);
+ mux_chip->mux->states = 1 << pins;
+
+ ret = device_property_read_u32(dev, "idle-state", (u32 *)&idle_state);
+ if (ret >= 0 && idle_state != MUX_IDLE_AS_IS) {
+ if (idle_state < 0 || idle_state >= mux_chip->mux->states) {
+ dev_err(dev, "invalid idle-state %u\n", idle_state);
+ return -EINVAL;
+ }
+
+ mux_chip->mux->idle_state = idle_state;
+ }
+
+ ret = devm_mux_chip_register(dev, mux_chip);
+ if (ret < 0)
+ return ret;
+
+ dev_info(dev, "%u-way mux-controller registered\n",
+ mux_chip->mux->states);
+
+ return 0;
+}
+
+static struct platform_driver mux_gpio_driver = {
+ .driver = {
+ .name = "gpio-mux",
+ .of_match_table = of_match_ptr(mux_gpio_dt_ids),
+ },
+ .probe = mux_gpio_probe,
+};
+module_platform_driver(mux_gpio_driver);
+
+MODULE_DESCRIPTION("GPIO-controlled multiplexer driver");
+MODULE_AUTHOR("Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mux.h b/include/linux/mux.h
new file mode 100644
index 000000000000..febdde4246df
--- /dev/null
+++ b/include/linux/mux.h
@@ -0,0 +1,252 @@
+/*
+ * mux.h - definitions for the multiplexer interface
+ *
+ * Copyright (C) 2017 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _LINUX_MUX_H
+#define _LINUX_MUX_H
+
+#include <linux/device.h>
+#include <linux/rwsem.h>
+
+struct mux_chip;
+struct mux_control;
+struct platform_device;
+
+/**
+ * struct mux_control_ops - Mux controller operations for a mux chip.
+ * @set: Set the state of the given mux controller.
+ */
+struct mux_control_ops {
+ int (*set)(struct mux_control *mux, int state);
+};
+
+/* These defines match the constants from the dt-bindings. On purpose. */
+#define MUX_IDLE_AS_IS (-1)
+#define MUX_IDLE_DISCONNECT (-2)
+
+/**
+ * struct mux_control - Represents a mux controller.
+ * @lock: Protects the mux controller state.
+ * @chip: The mux chip that is handling this mux controller.
+ * @states: The number of mux controller states.
+ * @cached_state: The current mux controller state, or -1 if none.
+ * @idle_state: The mux controller state to use when inactive, or one
+ * of MUX_IDLE_AS_IS and MUX_IDLE_DISCONNECT.
+ */
+struct mux_control {
+ struct rw_semaphore lock; /* protects the state of the mux */
+
+ struct mux_chip *chip;
+
+ unsigned int states;
+ int cached_state;
+ int idle_state;
+};
+
+/**
+ * struct mux_chip - Represents a chip holding mux controllers.
+ * @controllers: Number of mux controllers handled by the chip.
+ * @mux: Array of mux controllers that are handled.
+ * @dev: Device structure.
+ * @id: Used to identify the device internally.
+ * @ops: Mux controller operations.
+ */
+struct mux_chip {
+ unsigned int controllers;
+ struct mux_control *mux;
+ struct device dev;
+ int id;
+
+ const struct mux_control_ops *ops;
+};
+
+#define to_mux_chip(x) container_of((x), struct mux_chip, dev)
+
+/**
+ * mux_chip_priv() - Get the extra memory reserved by mux_chip_alloc().
+ * @mux_chip: The mux-chip to get the private memory from.
+ *
+ * Return: Pointer to the private memory reserved by the allocator.
+ */
+static inline void *mux_chip_priv(struct mux_chip *mux_chip)
+{
+ return &mux_chip->mux[mux_chip->controllers];
+}
+
+/**
+ * mux_chip_alloc() - Allocate a mux-chip.
+ * @dev: The parent device implementing the mux interface.
+ * @controllers: The number of mux controllers to allocate for this chip.
+ * @sizeof_priv: Size of extra memory area for private use by the caller.
+ *
+ * Return: A pointer to the new mux-chip, NULL on failure.
+ */
+struct mux_chip *mux_chip_alloc(struct device *dev,
+ unsigned int controllers, size_t sizeof_priv);
+
+/**
+ * mux_chip_register() - Register a mux-chip, thus readying the controllers
+ * for use.
+ * @mux_chip: The mux-chip to register.
+ *
+ * Do not retry registration of the same mux-chip on failure. You should
+ * instead put it away with mux_chip_free() and allocate a new one, if you
+ * for some reason would like to retry registration.
+ *
+ * Return: Zero on success or a negative errno on error.
+ */
+int mux_chip_register(struct mux_chip *mux_chip);
+
+/**
+ * mux_chip_unregister() - Take the mux-chip off-line.
+ * @mux_chip: The mux-chip to unregister.
+ *
+ * mux_chip_unregister() reverses the effects of mux_chip_register().
+ * But not completely, you should not try to call mux_chip_register()
+ * on a mux-chip that has been registered before.
+ */
+void mux_chip_unregister(struct mux_chip *mux_chip);
+
+/**
+ * mux_chip_free() - Free the mux-chip for good.
+ * @mux_chip: The mux-chip to free.
+ *
+ * mux_chip_free() reverses the effects of mux_chip_alloc().
+ */
+void mux_chip_free(struct mux_chip *mux_chip);
+
+/**
+ * devm_mux_chip_alloc() - Resource-managed version of mux_chip_alloc().
+ * @dev: The parent device implementing the mux interface.
+ * @controllers: The number of mux controllers to allocate for this chip.
+ * @sizeof_priv: Size of extra memory area for private use by the caller.
+ *
+ * See mux_chip_alloc() for more details.
+ *
+ * Return: A pointer to the new mux-chip, NULL on failure.
+ */
+struct mux_chip *devm_mux_chip_alloc(struct device *dev,
+ unsigned int controllers,
+ size_t sizeof_priv);
+
+/**
+ * devm_mux_chip_register() - Resource-managed version mux_chip_register().
+ * @dev: The parent device implementing the mux interface.
+ * @mux_chip: The mux-chip to register.
+ *
+ * See mux_chip_register() for more details.
+ *
+ * Return: Zero on success or a negative errno on error.
+ */
+int devm_mux_chip_register(struct device *dev, struct mux_chip *mux_chip);
+
+/**
+ * devm_mux_chip_unregister() - Resource-managed version mux_chip_unregister().
+ * @dev: The device that originally registered the mux-chip.
+ * @mux_chip: The mux-chip to unregister.
+ *
+ * See mux_chip_unregister() for more details.
+ *
+ * Note that you do not normally need to call this function.
+ */
+void devm_mux_chip_unregister(struct device *dev, struct mux_chip *mux_chip);
+
+/**
+ * devm_mux_chip_free() - Resource-managed version mux_chip_free().
+ * @dev: The device that originally got the mux-chip.
+ * @mux_chip: The mux-chip to free.
+ *
+ * See mux_chip_free() for more details.
+ *
+ * Note that you do not normally need to call this function.
+ */
+void devm_mux_chip_free(struct device *dev, struct mux_chip *mux_chip);
+
+/**
+ * mux_control_select() - Select the given multiplexer state.
+ * @mux: The mux-control to request a change of state from.
+ * @state: The new requested state.
+ *
+ * Make sure to call mux_control_deselect() when the operation is complete and
+ * the mux-control is free for others to use, but do not call
+ * mux_control_deselect() if mux_control_select() fails.
+ *
+ * Return: 0 if the requested state was already active, or 1 it the
+ * mux-control state was changed to the requested state. Or a negative
+ * errno on error.
+ *
+ * Note that the difference in return value of zero or one is of
+ * questionable value; especially if the mux-control has several independent
+ * consumers, which is something the consumers should perhaps not be making
+ * assumptions about.
+ */
+int mux_control_select(struct mux_control *mux, int state);
+
+/**
+ * mux_control_deselect() - Deselect the previously selected multiplexer state.
+ * @mux: The mux-control to deselect.
+ *
+ * Return: 0 on success and a negative errno on error. An error can only
+ * occur if the mux has an idle state. Note that even if an error occurs, the
+ * mux-control is unlocked and is thus free for the next access.
+ */
+int mux_control_deselect(struct mux_control *mux);
+
+/**
+ * mux_control_get_index() - Get the index of the given mux controller
+ * @mux: The mux-control to get the index for.
+ *
+ * Return: The index of the mux controller within the mux chip the mux
+ * controller is a part of.
+ */
+static inline unsigned int mux_control_get_index(struct mux_control *mux)
+{
+ return mux - mux->chip->mux;
+}
+
+/**
+ * mux_control_get() - Get the mux-control for a device.
+ * @dev: The device that needs a mux-control.
+ * @mux_name: The name identifying the mux-control.
+ *
+ * Return: A pointer to the mux-control, or an ERR_PTR with a negative errno.
+ */
+struct mux_control *mux_control_get(struct device *dev, const char *mux_name);
+
+/**
+ * mux_control_put() - Put away the mux-control for good.
+ * @mux: The mux-control to put away.
+ *
+ * mux_control_put() reverses the effects of mux_control_get().
+ */
+void mux_control_put(struct mux_control *mux);
+
+/**
+ * devm_mux_control_get() - Get the mux-control for a device, with resource
+ * management.
+ * @dev: The device that needs a mux-control.
+ * @mux_name: The name identifying the mux-control.
+ *
+ * Return: Pointer to the mux-control, or an ERR_PTR with a negative errno.
+ */
+struct mux_control *devm_mux_control_get(struct device *dev,
+ const char *mux_name);
+
+/**
+ * devm_mux_control_put() - Resource-managed version mux_control_put().
+ * @dev: The device that originally got the mux-control.
+ * @mux: The mux-control to put away.
+ *
+ * Note that you do not normally need to call this function.
+ */
+void devm_mux_control_put(struct device *dev, struct mux_control *mux);
+
+#endif /* _LINUX_MUX_H */
--
2.1.4
^ permalink raw reply related
* [PATCH v13 04/10] iio: inkern: api for manipulating ext_info of iio channels
From: Peter Rosin @ 2017-04-13 16:43 UTC (permalink / raw)
To: linux-kernel, Greg Kroah-Hartman
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, linux-i2c, devicetree,
linux-iio, linux-doc, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel
In-Reply-To: <1492101794-13444-1-git-send-email-peda@axentia.se>
Extend the inkern api with functions for reading and writing ext_info
of iio channels.
Acked-by: Jonathan Cameron <jic23@kernel.org>
Signed-off-by: Peter Rosin <peda@axentia.se>
---
drivers/iio/inkern.c | 60 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/iio/consumer.h | 37 +++++++++++++++++++++++++++
2 files changed, 97 insertions(+)
diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
index 7a13535dc3e9..8292ad4435ea 100644
--- a/drivers/iio/inkern.c
+++ b/drivers/iio/inkern.c
@@ -869,3 +869,63 @@ int iio_write_channel_raw(struct iio_channel *chan, int val)
return ret;
}
EXPORT_SYMBOL_GPL(iio_write_channel_raw);
+
+unsigned int iio_get_channel_ext_info_count(struct iio_channel *chan)
+{
+ const struct iio_chan_spec_ext_info *ext_info;
+ unsigned int i = 0;
+
+ if (!chan->channel->ext_info)
+ return i;
+
+ for (ext_info = chan->channel->ext_info; ext_info->name; ext_info++)
+ ++i;
+
+ return i;
+}
+EXPORT_SYMBOL_GPL(iio_get_channel_ext_info_count);
+
+static const struct iio_chan_spec_ext_info *iio_lookup_ext_info(
+ const struct iio_channel *chan,
+ const char *attr)
+{
+ const struct iio_chan_spec_ext_info *ext_info;
+
+ if (!chan->channel->ext_info)
+ return NULL;
+
+ for (ext_info = chan->channel->ext_info; ext_info->name; ++ext_info) {
+ if (!strcmp(attr, ext_info->name))
+ return ext_info;
+ }
+
+ return NULL;
+}
+
+ssize_t iio_read_channel_ext_info(struct iio_channel *chan,
+ const char *attr, char *buf)
+{
+ const struct iio_chan_spec_ext_info *ext_info;
+
+ ext_info = iio_lookup_ext_info(chan, attr);
+ if (!ext_info)
+ return -EINVAL;
+
+ return ext_info->read(chan->indio_dev, ext_info->private,
+ chan->channel, buf);
+}
+EXPORT_SYMBOL_GPL(iio_read_channel_ext_info);
+
+ssize_t iio_write_channel_ext_info(struct iio_channel *chan, const char *attr,
+ const char *buf, size_t len)
+{
+ const struct iio_chan_spec_ext_info *ext_info;
+
+ ext_info = iio_lookup_ext_info(chan, attr);
+ if (!ext_info)
+ return -EINVAL;
+
+ return ext_info->write(chan->indio_dev, ext_info->private,
+ chan->channel, buf, len);
+}
+EXPORT_SYMBOL_GPL(iio_write_channel_ext_info);
diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h
index 47eeec3218b5..5e347a9805fd 100644
--- a/include/linux/iio/consumer.h
+++ b/include/linux/iio/consumer.h
@@ -312,4 +312,41 @@ int iio_read_channel_scale(struct iio_channel *chan, int *val,
int iio_convert_raw_to_processed(struct iio_channel *chan, int raw,
int *processed, unsigned int scale);
+/**
+ * iio_get_channel_ext_info_count() - get number of ext_info attributes
+ * connected to the channel.
+ * @chan: The channel being queried
+ *
+ * Returns the number of ext_info attributes
+ */
+unsigned int iio_get_channel_ext_info_count(struct iio_channel *chan);
+
+/**
+ * iio_read_channel_ext_info() - read ext_info attribute from a given channel
+ * @chan: The channel being queried.
+ * @attr: The ext_info attribute to read.
+ * @buf: Where to store the attribute value. Assumed to hold
+ * at least PAGE_SIZE bytes.
+ *
+ * Returns the number of bytes written to buf (perhaps w/o zero termination;
+ * it need not even be a string), or an error code.
+ */
+ssize_t iio_read_channel_ext_info(struct iio_channel *chan,
+ const char *attr, char *buf);
+
+/**
+ * iio_write_channel_ext_info() - write ext_info attribute from a given channel
+ * @chan: The channel being queried.
+ * @attr: The ext_info attribute to read.
+ * @buf: The new attribute value. Strings needs to be zero-
+ * terminated, but the terminator should not be included
+ * in the below len.
+ * @len: The size of the new attribute value.
+ *
+ * Returns the number of accepted bytes, which should be the same as len.
+ * An error code can also be returned.
+ */
+ssize_t iio_write_channel_ext_info(struct iio_channel *chan, const char *attr,
+ const char *buf, size_t len);
+
#endif
--
2.1.4
^ permalink raw reply related
* [PATCH v13 05/10] dt-bindings: iio: io-channel-mux: document io-channel-mux bindings
From: Peter Rosin @ 2017-04-13 16:43 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA, Greg Kroah-Hartman
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel-bIcnvbaLZ9MEGnE8C9+IrQ
In-Reply-To: <1492101794-13444-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
Describe how a multiplexer can be used to select which signal is fed to
an io-channel.
Acked-by: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Signed-off-by: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
.../bindings/iio/multiplexer/io-channel-mux.txt | 39 ++++++++++++++++++++++
MAINTAINERS | 6 ++++
2 files changed, 45 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/multiplexer/io-channel-mux.txt
diff --git a/Documentation/devicetree/bindings/iio/multiplexer/io-channel-mux.txt b/Documentation/devicetree/bindings/iio/multiplexer/io-channel-mux.txt
new file mode 100644
index 000000000000..c82794002595
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/multiplexer/io-channel-mux.txt
@@ -0,0 +1,39 @@
+I/O channel multiplexer bindings
+
+If a multiplexer is used to select which hardware signal is fed to
+e.g. an ADC channel, these bindings describe that situation.
+
+Required properties:
+- compatible : "io-channel-mux"
+- io-channels : Channel node of the parent channel that has multiplexed
+ input.
+- io-channel-names : Should be "parent".
+- #address-cells = <1>;
+- #size-cells = <0>;
+- mux-controls : Mux controller node to use for operating the mux
+- channels : List of strings, labeling the mux controller states.
+
+For each non-empty string in the channels property, an io-channel will
+be created. The number of this io-channel is the same as the index into
+the list of strings in the channels property, and also matches the mux
+controller state. The mux controller state is described in
+../mux/mux-controller.txt
+
+Example:
+ mux: mux-controller {
+ compatible = "mux-gpio";
+ #mux-control-cells = <0>;
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
+ <&pioA 1 GPIO_ACTIVE_HIGH>;
+ };
+
+ adc-mux {
+ compatible = "io-channel-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ mux-controls = <&mux>;
+
+ channels = "sync", "in", "system-regulator";
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 591eba737678..4af912305d2c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6360,6 +6360,12 @@ F: Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector
F: Documentation/devicetree/bindings/iio/adc/envelope-detector.txt
F: drivers/iio/adc/envelope-detector.c
+IIO MULTIPLEXER
+M: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
+L: linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
+S: Maintained
+F: Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt
+
IIO SUBSYSTEM AND DRIVERS
M: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
R: Hartmut Knaack <knaack.h-Mmb7MZpHnFY@public.gmane.org>
--
2.1.4
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH v13 06/10] iio: multiplexer: new iio category and iio-mux driver
From: Peter Rosin @ 2017-04-13 16:43 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA, Greg Kroah-Hartman
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel-bIcnvbaLZ9MEGnE8C9+IrQ
In-Reply-To: <1492101794-13444-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
When a multiplexer changes how an iio device behaves (for example
by feeding different signals to an ADC), this driver can be used
to create one virtual iio channel for each multiplexer state.
Depends on the generic multiplexer subsystem.
Cache any ext_info values from the parent iio channel, creating a private
copy of the ext_info attributes for each multiplexer state/channel.
Reviewed-by: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Signed-off-by: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
MAINTAINERS | 1 +
drivers/iio/Kconfig | 1 +
drivers/iio/Makefile | 1 +
drivers/iio/multiplexer/Kconfig | 18 ++
drivers/iio/multiplexer/Makefile | 6 +
drivers/iio/multiplexer/iio-mux.c | 459 ++++++++++++++++++++++++++++++++++++++
6 files changed, 486 insertions(+)
create mode 100644 drivers/iio/multiplexer/Kconfig
create mode 100644 drivers/iio/multiplexer/Makefile
create mode 100644 drivers/iio/multiplexer/iio-mux.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 4af912305d2c..23cfd5bc2158 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6365,6 +6365,7 @@ M: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
L: linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
S: Maintained
F: Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt
+F: drivers/iio/multiplexer/iio-mux.c
IIO SUBSYSTEM AND DRIVERS
M: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index a918270d6f54..b3c8c6ef0dff 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -83,6 +83,7 @@ source "drivers/iio/humidity/Kconfig"
source "drivers/iio/imu/Kconfig"
source "drivers/iio/light/Kconfig"
source "drivers/iio/magnetometer/Kconfig"
+source "drivers/iio/multiplexer/Kconfig"
source "drivers/iio/orientation/Kconfig"
if IIO_TRIGGER
source "drivers/iio/trigger/Kconfig"
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index 33fa4026f92c..93c769cd99bf 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -28,6 +28,7 @@ obj-y += humidity/
obj-y += imu/
obj-y += light/
obj-y += magnetometer/
+obj-y += multiplexer/
obj-y += orientation/
obj-y += potentiometer/
obj-y += potentiostat/
diff --git a/drivers/iio/multiplexer/Kconfig b/drivers/iio/multiplexer/Kconfig
new file mode 100644
index 000000000000..70a044510686
--- /dev/null
+++ b/drivers/iio/multiplexer/Kconfig
@@ -0,0 +1,18 @@
+#
+# Multiplexer drivers
+#
+# When adding new entries keep the list in alphabetical order
+
+menu "Multiplexers"
+
+config IIO_MUX
+ tristate "IIO multiplexer driver"
+ select MULTIPLEXER
+ depends on OF
+ help
+ Say yes here to build support for the IIO multiplexer.
+
+ To compile this driver as a module, choose M here: the
+ module will be called iio-mux.
+
+endmenu
diff --git a/drivers/iio/multiplexer/Makefile b/drivers/iio/multiplexer/Makefile
new file mode 100644
index 000000000000..68be3c4abd07
--- /dev/null
+++ b/drivers/iio/multiplexer/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for industrial I/O multiplexer drivers
+#
+
+# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_IIO_MUX) += iio-mux.o
diff --git a/drivers/iio/multiplexer/iio-mux.c b/drivers/iio/multiplexer/iio-mux.c
new file mode 100644
index 000000000000..bab9e6902090
--- /dev/null
+++ b/drivers/iio/multiplexer/iio-mux.c
@@ -0,0 +1,459 @@
+/*
+ * IIO multiplexer driver
+ *
+ * Copyright (C) 2017 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/err.h>
+#include <linux/iio/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mux.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+struct mux_ext_info_cache {
+ char *data;
+ ssize_t size;
+};
+
+struct mux_child {
+ struct mux_ext_info_cache *ext_info_cache;
+};
+
+struct mux {
+ int cached_state;
+ struct mux_control *control;
+ struct iio_channel *parent;
+ struct iio_dev *indio_dev;
+ struct iio_chan_spec *chan;
+ struct iio_chan_spec_ext_info *ext_info;
+ struct mux_child *child;
+};
+
+static int iio_mux_select(struct mux *mux, int idx)
+{
+ struct mux_child *child = &mux->child[idx];
+ struct iio_chan_spec const *chan = &mux->chan[idx];
+ int ret;
+ int i;
+
+ ret = mux_control_select(mux->control, chan->channel);
+ if (ret < 0) {
+ mux->cached_state = -1;
+ return ret;
+ }
+
+ if (mux->cached_state == chan->channel)
+ return 0;
+
+ if (chan->ext_info) {
+ for (i = 0; chan->ext_info[i].name; ++i) {
+ const char *attr = chan->ext_info[i].name;
+ struct mux_ext_info_cache *cache;
+
+ cache = &child->ext_info_cache[i];
+
+ if (cache->size < 0)
+ continue;
+
+ ret = iio_write_channel_ext_info(mux->parent, attr,
+ cache->data,
+ cache->size);
+
+ if (ret < 0) {
+ mux_control_deselect(mux->control);
+ mux->cached_state = -1;
+ return ret;
+ }
+ }
+ }
+ mux->cached_state = chan->channel;
+
+ return 0;
+}
+
+static void iio_mux_deselect(struct mux *mux)
+{
+ mux_control_deselect(mux->control);
+}
+
+static int mux_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct mux *mux = iio_priv(indio_dev);
+ int idx = chan - mux->chan;
+ int ret;
+
+ ret = iio_mux_select(mux, idx);
+ if (ret < 0)
+ return ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = iio_read_channel_raw(mux->parent, val);
+ break;
+
+ case IIO_CHAN_INFO_SCALE:
+ ret = iio_read_channel_scale(mux->parent, val, val2);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ iio_mux_deselect(mux);
+
+ return ret;
+}
+
+static int mux_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct mux *mux = iio_priv(indio_dev);
+ int idx = chan - mux->chan;
+ int ret;
+
+ ret = iio_mux_select(mux, idx);
+ if (ret < 0)
+ return ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ *type = IIO_VAL_INT;
+ ret = iio_read_avail_channel_raw(mux->parent, vals, length);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ iio_mux_deselect(mux);
+
+ return ret;
+}
+
+static int mux_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct mux *mux = iio_priv(indio_dev);
+ int idx = chan - mux->chan;
+ int ret;
+
+ ret = iio_mux_select(mux, idx);
+ if (ret < 0)
+ return ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = iio_write_channel_raw(mux->parent, val);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ iio_mux_deselect(mux);
+
+ return ret;
+}
+
+static const struct iio_info mux_info = {
+ .read_raw = mux_read_raw,
+ .read_avail = mux_read_avail,
+ .write_raw = mux_write_raw,
+ .driver_module = THIS_MODULE,
+};
+
+static ssize_t mux_read_ext_info(struct iio_dev *indio_dev, uintptr_t private,
+ struct iio_chan_spec const *chan, char *buf)
+{
+ struct mux *mux = iio_priv(indio_dev);
+ int idx = chan - mux->chan;
+ ssize_t ret;
+
+ ret = iio_mux_select(mux, idx);
+ if (ret < 0)
+ return ret;
+
+ ret = iio_read_channel_ext_info(mux->parent,
+ mux->ext_info[private].name,
+ buf);
+
+ iio_mux_deselect(mux);
+
+ return ret;
+}
+
+static ssize_t mux_write_ext_info(struct iio_dev *indio_dev, uintptr_t private,
+ struct iio_chan_spec const *chan,
+ const char *buf, size_t len)
+{
+ struct device *dev = indio_dev->dev.parent;
+ struct mux *mux = iio_priv(indio_dev);
+ int idx = chan - mux->chan;
+ char *new;
+ ssize_t ret;
+
+ if (len >= PAGE_SIZE)
+ return -EINVAL;
+
+ ret = iio_mux_select(mux, idx);
+ if (ret < 0)
+ return ret;
+
+ new = devm_kmemdup(dev, buf, len + 1, GFP_KERNEL);
+ if (!new) {
+ iio_mux_deselect(mux);
+ return -ENOMEM;
+ }
+
+ new[len] = 0;
+
+ ret = iio_write_channel_ext_info(mux->parent,
+ mux->ext_info[private].name,
+ buf, len);
+ if (ret < 0) {
+ iio_mux_deselect(mux);
+ devm_kfree(dev, new);
+ return ret;
+ }
+
+ devm_kfree(dev, mux->child[idx].ext_info_cache[private].data);
+ mux->child[idx].ext_info_cache[private].data = new;
+ mux->child[idx].ext_info_cache[private].size = len;
+
+ iio_mux_deselect(mux);
+
+ return ret;
+}
+
+static int mux_configure_channel(struct device *dev, struct mux *mux,
+ u32 state, const char *label, int idx)
+{
+ struct mux_child *child = &mux->child[idx];
+ struct iio_chan_spec *chan = &mux->chan[idx];
+ struct iio_chan_spec const *pchan = mux->parent->channel;
+ char *page = NULL;
+ int num_ext_info;
+ int i;
+ int ret;
+
+ chan->indexed = 1;
+ chan->output = pchan->output;
+ chan->datasheet_name = label;
+ chan->ext_info = mux->ext_info;
+
+ ret = iio_get_channel_type(mux->parent, &chan->type);
+ if (ret < 0) {
+ dev_err(dev, "failed to get parent channel type\n");
+ return ret;
+ }
+
+ if (iio_channel_has_info(pchan, IIO_CHAN_INFO_RAW))
+ chan->info_mask_separate |= BIT(IIO_CHAN_INFO_RAW);
+ if (iio_channel_has_info(pchan, IIO_CHAN_INFO_SCALE))
+ chan->info_mask_separate |= BIT(IIO_CHAN_INFO_SCALE);
+
+ if (iio_channel_has_available(pchan, IIO_CHAN_INFO_RAW))
+ chan->info_mask_separate_available |= BIT(IIO_CHAN_INFO_RAW);
+
+ if (state >= mux->control->states) {
+ dev_err(dev, "too many channels\n");
+ return -EINVAL;
+ }
+
+ chan->channel = state;
+
+ num_ext_info = iio_get_channel_ext_info_count(mux->parent);
+ if (num_ext_info) {
+ page = devm_kzalloc(dev, PAGE_SIZE, GFP_KERNEL);
+ if (!page)
+ return -ENOMEM;
+ }
+ child->ext_info_cache = devm_kzalloc(dev,
+ sizeof(*child->ext_info_cache) *
+ num_ext_info, GFP_KERNEL);
+ for (i = 0; i < num_ext_info; ++i) {
+ child->ext_info_cache[i].size = -1;
+
+ if (!pchan->ext_info[i].write)
+ continue;
+ if (!pchan->ext_info[i].read)
+ continue;
+
+ ret = iio_read_channel_ext_info(mux->parent,
+ mux->ext_info[i].name,
+ page);
+ if (ret < 0) {
+ dev_err(dev, "failed to get ext_info '%s'\n",
+ pchan->ext_info[i].name);
+ return ret;
+ }
+ if (ret >= PAGE_SIZE) {
+ dev_err(dev, "too large ext_info '%s'\n",
+ pchan->ext_info[i].name);
+ return -EINVAL;
+ }
+
+ child->ext_info_cache[i].data = devm_kmemdup(dev, page, ret + 1,
+ GFP_KERNEL);
+ child->ext_info_cache[i].data[ret] = 0;
+ child->ext_info_cache[i].size = ret;
+ }
+
+ if (page)
+ devm_kfree(dev, page);
+
+ return 0;
+}
+
+/*
+ * Same as of_property_for_each_string(), but also keeps track of the
+ * index of each string.
+ */
+#define of_property_for_each_string_index(np, propname, prop, s, i) \
+ for (prop = of_find_property(np, propname, NULL), \
+ s = of_prop_next_string(prop, NULL), \
+ i = 0; \
+ s; \
+ s = of_prop_next_string(prop, s), \
+ i++)
+
+static int mux_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = pdev->dev.of_node;
+ struct iio_dev *indio_dev;
+ struct iio_channel *parent;
+ struct mux *mux;
+ struct property *prop;
+ const char *label;
+ u32 state;
+ int sizeof_ext_info;
+ int children;
+ int sizeof_priv;
+ int i;
+ int ret;
+
+ if (!np)
+ return -ENODEV;
+
+ parent = devm_iio_channel_get(dev, "parent");
+ if (IS_ERR(parent)) {
+ if (PTR_ERR(parent) != -EPROBE_DEFER)
+ dev_err(dev, "failed to get parent channel\n");
+ return PTR_ERR(parent);
+ }
+
+ sizeof_ext_info = iio_get_channel_ext_info_count(parent);
+ if (sizeof_ext_info) {
+ sizeof_ext_info += 1; /* one extra entry for the sentinel */
+ sizeof_ext_info *= sizeof(*mux->ext_info);
+ }
+
+ children = 0;
+ of_property_for_each_string(np, "channels", prop, label) {
+ if (*label)
+ children++;
+ }
+ if (children <= 0) {
+ dev_err(dev, "not even a single child\n");
+ return -EINVAL;
+ }
+
+ sizeof_priv = sizeof(*mux);
+ sizeof_priv += sizeof(*mux->child) * children;
+ sizeof_priv += sizeof(*mux->chan) * children;
+ sizeof_priv += sizeof_ext_info;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof_priv);
+ if (!indio_dev)
+ return -ENOMEM;
+
+ mux = iio_priv(indio_dev);
+ mux->child = (struct mux_child *)(mux + 1);
+ mux->chan = (struct iio_chan_spec *)(mux->child + children);
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ mux->parent = parent;
+ mux->cached_state = -1;
+
+ indio_dev->name = dev_name(dev);
+ indio_dev->dev.parent = dev;
+ indio_dev->info = &mux_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = mux->chan;
+ indio_dev->num_channels = children;
+ if (sizeof_ext_info) {
+ mux->ext_info = devm_kmemdup(dev,
+ parent->channel->ext_info,
+ sizeof_ext_info, GFP_KERNEL);
+ if (!mux->ext_info)
+ return -ENOMEM;
+
+ for (i = 0; mux->ext_info[i].name; ++i) {
+ if (parent->channel->ext_info[i].read)
+ mux->ext_info[i].read = mux_read_ext_info;
+ if (parent->channel->ext_info[i].write)
+ mux->ext_info[i].write = mux_write_ext_info;
+ mux->ext_info[i].private = i;
+ }
+ }
+
+ mux->control = devm_mux_control_get(dev, NULL);
+ if (IS_ERR(mux->control)) {
+ if (PTR_ERR(mux->control) != -EPROBE_DEFER)
+ dev_err(dev, "failed to get control-mux\n");
+ return PTR_ERR(mux->control);
+ }
+
+ i = 0;
+ of_property_for_each_string_index(np, "channels", prop, label, state) {
+ if (!*label)
+ continue;
+
+ ret = mux_configure_channel(dev, mux, state, label, i++);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret) {
+ dev_err(dev, "failed to register iio device\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id mux_match[] = {
+ { .compatible = "io-channel-mux" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mux_match);
+
+static struct platform_driver mux_driver = {
+ .probe = mux_probe,
+ .driver = {
+ .name = "iio-mux",
+ .of_match_table = mux_match,
+ },
+};
+module_platform_driver(mux_driver);
+
+MODULE_DESCRIPTION("IIO multiplexer driver");
+MODULE_AUTHOR("Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
--
2.1.4
^ permalink raw reply related
* [PATCH v13 07/10] dt-bindings: i2c: i2c-mux: document general purpose i2c-mux bindings
From: Peter Rosin @ 2017-04-13 16:43 UTC (permalink / raw)
To: linux-kernel, Greg Kroah-Hartman
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, linux-i2c, devicetree,
linux-iio, linux-doc, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel
In-Reply-To: <1492101794-13444-1-git-send-email-peda@axentia.se>
Describe how a general purpose multiplexer controller is used to mux an
i2c bus.
Acked-by: Jonathan Cameron <jic23@kernel.org>
Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Peter Rosin <peda@axentia.se>
---
.../devicetree/bindings/i2c/i2c-mux-gpmux.txt | 99 ++++++++++++++++++++++
1 file changed, 99 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-mux-gpmux.txt
diff --git a/Documentation/devicetree/bindings/i2c/i2c-mux-gpmux.txt b/Documentation/devicetree/bindings/i2c/i2c-mux-gpmux.txt
new file mode 100644
index 000000000000..2907dab56298
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-mux-gpmux.txt
@@ -0,0 +1,99 @@
+General Purpose 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
+- 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
+
+Optional properties:
+- mux-locked: If present, explicitly allow unrelated I2C transactions on the
+ parent I2C adapter at these times:
+ + during setup of the multiplexer
+ + between setup of the multiplexer and the child bus I2C transaction
+ + between the child bus I2C transaction and releasing of the multiplexer
+ + during releasing of the multiplexer
+ However, I2C transactions to devices behind all I2C multiplexers connected
+ to the same parent adapter that this multiplexer is connected to are blocked
+ for the full duration of the complete multiplexed I2C transaction (i.e.
+ including the times covered by the above list).
+ If mux-locked is not present, the multiplexer is assumed to be parent-locked.
+ This means that no unrelated I2C transactions are allowed on the parent I2C
+ adapter for the complete multiplexed I2C transaction.
+ The properties of mux-locked and parent-locked multiplexers are discussed
+ in more detail in Documentation/i2c/i2c-topology.
+
+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 = "gpio-mux";
+ #mux-control-cells = <0>;
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
+ <&pioA 1 GPIO_ACTIVE_HIGH>;
+ };
+
+ i2c-mux {
+ compatible = "i2c-mux";
+ 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 v13 08/10] i2c: i2c-mux-gpmux: new driver
From: Peter Rosin @ 2017-04-13 16:43 UTC (permalink / raw)
To: linux-kernel, Greg Kroah-Hartman
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, linux-i2c, devicetree,
linux-iio, linux-doc, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel
In-Reply-To: <1492101794-13444-1-git-send-email-peda@axentia.se>
This is a general purpose i2c mux that uses a multiplexer controlled by
the 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@kernel.org>
Acked-by: Wolfram Sang <wsa@the-dreams.de>
Signed-off-by: Peter Rosin <peda@axentia.se>
---
drivers/i2c/muxes/Kconfig | 13 +++
drivers/i2c/muxes/Makefile | 1 +
drivers/i2c/muxes/i2c-mux-gpmux.c | 173 ++++++++++++++++++++++++++++++++++++++
3 files changed, 187 insertions(+)
create mode 100644 drivers/i2c/muxes/i2c-mux-gpmux.c
diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig
index 10b3d17ae3ea..11115fb34f24 100644
--- a/drivers/i2c/muxes/Kconfig
+++ b/drivers/i2c/muxes/Kconfig
@@ -30,6 +30,19 @@ config I2C_MUX_GPIO
This driver can also be built as a module. If so, the module
will be called i2c-mux-gpio.
+config I2C_MUX_GPMUX
+ tristate "General Purpose I2C multiplexer"
+ select MULTIPLEXER
+ depends on OF
+ help
+ If you say yes to this option, support will be included for a
+ general purpose I2C multiplexer. This driver provides access to
+ I2C busses connected through a MUX, which in turn is controlled
+ by a MUX-controller from the MUX subsystem.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-mux-gpmux.
+
config I2C_MUX_PCA9541
tristate "NXP PCA9541 I2C Master Selector"
help
diff --git a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile
index 9948fa45037f..af43c6c3e861 100644
--- a/drivers/i2c/muxes/Makefile
+++ b/drivers/i2c/muxes/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_I2C_ARB_GPIO_CHALLENGE) += i2c-arb-gpio-challenge.o
obj-$(CONFIG_I2C_DEMUX_PINCTRL) += i2c-demux-pinctrl.o
obj-$(CONFIG_I2C_MUX_GPIO) += i2c-mux-gpio.o
+obj-$(CONFIG_I2C_MUX_GPMUX) += i2c-mux-gpmux.o
obj-$(CONFIG_I2C_MUX_MLXCPLD) += i2c-mux-mlxcpld.o
obj-$(CONFIG_I2C_MUX_PCA9541) += i2c-mux-pca9541.o
obj-$(CONFIG_I2C_MUX_PCA954x) += i2c-mux-pca954x.o
diff --git a/drivers/i2c/muxes/i2c-mux-gpmux.c b/drivers/i2c/muxes/i2c-mux-gpmux.c
new file mode 100644
index 000000000000..fb23b2278462
--- /dev/null
+++ b/drivers/i2c/muxes/i2c-mux-gpmux.c
@@ -0,0 +1,173 @@
+/*
+ * General Purpose I2C multiplexer
+ *
+ * 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/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>
+
+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", },
+ {},
+};
+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;
+ 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);
+
+ muxc->mux_locked = of_property_read_bool(np, "mux-locked");
+
+ 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-gpmux",
+ .of_match_table = i2c_mux_of_match,
+ },
+};
+module_platform_driver(i2c_mux_driver);
+
+MODULE_DESCRIPTION("General Purpose I2C multiplexer driver");
+MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
+MODULE_LICENSE("GPL v2");
--
2.1.4
^ permalink raw reply related
* [PATCH v13 09/10] dt-bindings: mux-adg792a: document devicetree bindings for ADG792A/G mux
From: Peter Rosin @ 2017-04-13 16:43 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA, Greg Kroah-Hartman
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel-bIcnvbaLZ9MEGnE8C9+IrQ
In-Reply-To: <1492101794-13444-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
Analog Devices ADG792A/G is a triple 4:1 mux.
Acked-by: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Reviewed-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Signed-off-by: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
.../devicetree/bindings/mux/adi,adg792a.txt | 75 ++++++++++++++++++++++
1 file changed, 75 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mux/adi,adg792a.txt
diff --git a/Documentation/devicetree/bindings/mux/adi,adg792a.txt b/Documentation/devicetree/bindings/mux/adi,adg792a.txt
new file mode 100644
index 000000000000..96b787a69f50
--- /dev/null
+++ b/Documentation/devicetree/bindings/mux/adi,adg792a.txt
@@ -0,0 +1,75 @@
+Bindings for Analog Devices ADG792A/G Triple 4:1 Multiplexers
+
+Required properties:
+- compatible : "adi,adg792a" or "adi,adg792g"
+- #mux-control-cells : <0> if parallel (the three muxes are bound together
+ with a single mux controller controlling all three muxes), or <1> if
+ not (one mux controller for each mux).
+* Standard mux-controller bindings as described in mux-controller.txt
+
+Optional properties for ADG792G:
+- gpio-controller : if present, #gpio-cells below is required.
+- #gpio-cells : should be <2>
+ - First cell is the GPO line number, i.e. 0 or 1
+ - Second cell is used to specify active high (0)
+ or active low (1)
+
+Optional properties:
+- idle-state : if present, array of states that the mux controllers will have
+ when idle. The special state MUX_IDLE_AS_IS is the default and
+ MUX_IDLE_DISCONNECT is also supported.
+
+States 0 through 3 correspond to signals A through D in the datasheet.
+
+Example:
+
+ /*
+ * Three independent mux controllers (of which one is used).
+ * Mux 0 is disconnected when idle, mux 1 idles in the previously
+ * selected state and mux 2 idles with signal B.
+ */
+ &i2c0 {
+ mux: mux-controller@50 {
+ compatible = "adi,adg792a";
+ reg = <0x50>;
+ #mux-control-cells = <1>;
+
+ idle-state = <MUX_IDLE_DISCONNECT MUX_IDLE_AS_IS 1>;
+ };
+ };
+
+ adc-mux {
+ compatible = "io-channel-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ mux-controls = <&mux 2>;
+
+ channels = "sync-1", "", "out";
+ };
+
+
+ /*
+ * Three parallel muxes with one mux controller, useful e.g. if
+ * the adc is differential, thus needing two signals to be muxed
+ * simultaneously for correct operation.
+ */
+ &i2c0 {
+ pmux: mux-controller@50 {
+ compatible = "adi,adg792a";
+ reg = <0x50>;
+ #mux-control-cells = <0>;
+
+ idle-state = <1>;
+ };
+ };
+
+ diff-adc-mux {
+ compatible = "io-channel-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ mux-controls = <&pmux>;
+
+ channels = "sync-1", "", "out";
+ };
--
2.1.4
^ permalink raw reply related
* [PATCH v13 10/10] mux: adg792a: add mux controller driver for ADG792A/G
From: Peter Rosin @ 2017-04-13 16:43 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA, Greg Kroah-Hartman
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet,
linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA, Andrew Morton, Colin Ian King,
Paul Gortmaker, Philipp Zabel, kernel-bIcnvbaLZ9MEGnE8C9+IrQ
In-Reply-To: <1492101794-13444-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
Analog Devices ADG792A/G is a triple 4:1 mux.
Reviewed-by: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Signed-off-by: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
drivers/mux/Kconfig | 12 ++++
drivers/mux/Makefile | 1 +
drivers/mux/mux-adg792a.c | 141 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 154 insertions(+)
create mode 100644 drivers/mux/mux-adg792a.c
diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig
index 41dfe08ead84..86668b4d2fc5 100644
--- a/drivers/mux/Kconfig
+++ b/drivers/mux/Kconfig
@@ -17,6 +17,18 @@ menuconfig MULTIPLEXER
if MULTIPLEXER
+config MUX_ADG792A
+ tristate "Analog Devices ADG792A/ADG792G Multiplexers"
+ depends on I2C
+ help
+ ADG792A and ADG792G Wide Bandwidth Triple 4:1 Multiplexers
+
+ The driver supports both operating the three multiplexers in
+ parallel and operating them independently.
+
+ To compile the driver as a module, choose M here: the module will
+ be called mux-adg792a.
+
config MUX_GPIO
tristate "GPIO-controlled Multiplexer"
depends on OF && GPIOLIB
diff --git a/drivers/mux/Makefile b/drivers/mux/Makefile
index bb16953f6290..b00a7d37d2fb 100644
--- a/drivers/mux/Makefile
+++ b/drivers/mux/Makefile
@@ -3,4 +3,5 @@
#
obj-$(CONFIG_MULTIPLEXER) += mux-core.o
+obj-$(CONFIG_MUX_ADG792A) += mux-adg792a.o
obj-$(CONFIG_MUX_GPIO) += mux-gpio.o
diff --git a/drivers/mux/mux-adg792a.c b/drivers/mux/mux-adg792a.c
new file mode 100644
index 000000000000..58c0ecf49a4a
--- /dev/null
+++ b/drivers/mux/mux-adg792a.c
@@ -0,0 +1,141 @@
+/*
+ * Multiplexer driver for Analog Devices ADG792A/G Triple 4:1 mux
+ *
+ * Copyright (C) 2017 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+
+#define ADG792A_LDSW BIT(0)
+#define ADG792A_RESETB BIT(1) /* active low, reset when zero */
+#define ADG792A_DISABLE(mux) (0x50 | (mux))
+#define ADG792A_DISABLE_ALL (0x5f)
+#define ADG792A_MUX(mux, state) (0xc0 | (((mux) + 1) << 2) | (state))
+#define ADG792A_MUX_ALL(state) (0xc0 | (state))
+
+static int adg792a_set(struct mux_control *mux, int state)
+{
+ struct i2c_client *i2c = to_i2c_client(mux->chip->dev.parent);
+ u8 cmd;
+
+ if (mux->chip->controllers == 1) {
+ /* parallel mux controller operation */
+ if (state == MUX_IDLE_DISCONNECT)
+ cmd = ADG792A_DISABLE_ALL;
+ else
+ cmd = ADG792A_MUX_ALL(state);
+ } else {
+ unsigned int controller = mux_control_get_index(mux);
+
+ if (state == MUX_IDLE_DISCONNECT)
+ cmd = ADG792A_DISABLE(controller);
+ else
+ cmd = ADG792A_MUX(controller, state);
+ }
+
+ return i2c_smbus_write_byte_data(i2c, cmd,
+ ADG792A_RESETB | ADG792A_LDSW);
+}
+
+static const struct mux_control_ops adg792a_ops = {
+ .set = adg792a_set,
+};
+
+static int adg792a_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &i2c->dev;
+ struct mux_chip *mux_chip;
+ u32 cells;
+ int ret;
+ int i;
+
+ ret = of_property_read_u32(dev->of_node, "#mux-control-cells", &cells);
+ if (ret < 0)
+ return ret;
+ if (cells >= 2)
+ return -EINVAL;
+
+ mux_chip = devm_mux_chip_alloc(dev, cells ? 3 : 1, 0);
+ if (!mux_chip)
+ return -ENOMEM;
+
+ mux_chip->ops = &adg792a_ops;
+
+ ret = i2c_smbus_write_byte_data(i2c, ADG792A_DISABLE_ALL,
+ ADG792A_LDSW);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < mux_chip->controllers; ++i) {
+ struct mux_control *mux = &mux_chip->mux[i];
+ s32 idle_state;
+
+ mux->states = 4;
+
+ ret = of_property_read_u32_index(dev->of_node, "idle-state", i,
+ (s32 *)&idle_state);
+ if (ret < 0)
+ continue;
+
+ switch (idle_state) {
+ case 0 ... 4:
+ case MUX_IDLE_DISCONNECT:
+ mux_chip->mux[i].idle_state = idle_state;
+ break;
+ case MUX_IDLE_AS_IS:
+ break;
+ default:
+ dev_err(dev, "invalid idle-state %d\n", idle_state);
+ return -EINVAL;
+ }
+ }
+
+ ret = devm_mux_chip_register(dev, mux_chip);
+ if (ret < 0)
+ return ret;
+
+ if (cells)
+ dev_info(dev, "3x single pole quadruple throw muxes registered\n");
+ else
+ dev_info(dev, "triple pole quadruple throw mux registered\n");
+
+ return 0;
+}
+
+static const struct i2c_device_id adg792a_id[] = {
+ { .name = "adg792a", },
+ { .name = "adg792g", },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, adg792a_id);
+
+static const struct of_device_id adg792a_of_match[] = {
+ { .compatible = "adi,adg792a", },
+ { .compatible = "adi,adg792g", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adg792a_of_match);
+
+static struct i2c_driver adg792a_driver = {
+ .driver = {
+ .name = "adg792a",
+ .of_match_table = of_match_ptr(adg792a_of_match),
+ },
+ .probe = adg792a_probe,
+ .id_table = adg792a_id,
+};
+module_i2c_driver(adg792a_driver);
+
+MODULE_DESCRIPTION("Analog Devices ADG792A/G Triple 4:1 mux driver");
+MODULE_AUTHOR("Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
--
2.1.4
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* Re: [RFC net-next] of: mdio: Honor hints from MDIO bus drivers
From: David Miller @ 2017-04-13 16:49 UTC (permalink / raw)
To: f.fainelli-Re5JQEeQqe8AvxtiuMwx3w
Cc: netdev-u79uwXL29TY76Z2rM5mHXA, andrew-g2DYL2Zd6BY,
robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
frowand.list-Re5JQEeQqe8AvxtiuMwx3w,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20170410214258.9409-1-f.fainelli-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
From: Florian Fainelli <f.fainelli-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Date: Mon, 10 Apr 2017 14:42:58 -0700
> A MDIO bus driver can set phy_mask to indicate which PHYs should be
> probed and which should not. Right now, of_mdiobus_register() always
> sets mdio->phy_mask to ~0 which means: don't probe anything yourself,
> and let the Device Tree scanning do it based on the availability of
> child nodes.
>
> When MDIO buses are stacked together (on purpose, as is done by DSA), we
> run into possible double probing which is, at best unnecessary, and at
> worse, can cause problems if that's not expected (e.g: during probe
> deferral).
>
> Fix this by remember the original mdio->phy_mask, and make sure that if
> it was set to all 0xF, we set it to zero internally in order not to
> influence how the child PHY/MDIO device registration is going to behave.
> When the original mdio->phy_mask is set to something non-zero, we honor
> this value and utilize it as a hint to register only the child nodes
> that we have both found, and indicated to be necessary.
>
> Signed-off-by: Florian Fainelli <f.fainelli-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
I don't think it's valid to have a unique OF node appear twice in the
device tree hiearchy.
Even if you can somehow hack this situation into working, you are
asking for all kinds of problems in the long run by doing things that
way.
If you have to, instantiate a new dummy device (perhaps a
platform_device, which thus can have private attributes you can store
in a structure whose layout you control) to act as the placeholder for
operation interception and property duplication.
--
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
* Re: [PATCH v3 2/2] mtd: spi-nor: add driver for STM32 quad spi flash controller
From: Ludovic BARRE @ 2017-04-13 16:57 UTC (permalink / raw)
To: Cyrille Pitchen, Cyrille Pitchen, Marek Vasut
Cc: Boris Brezillon, Alexandre Torgue, devicetree, Richard Weinberger,
linux-kernel, Rob Herring, linux-mtd, Brian Norris,
David Woodhouse
In-Reply-To: <819a043f-c34d-56f6-c612-69fdc45cc785@wedev4u.fr>
[-- Attachment #1: Type: text/plain, Size: 25122 bytes --]
Hi Cyrille
thanks for review
comment below
and the V4 coming
On 04/12/2017 10:28 PM, Cyrille Pitchen wrote:
> Hi Ludovic,
>
> Globally I will say this is pretty good and, IMHO, almost ready to be
> merged into the github/spi-nor tree.
>
> I have few comments, all but one are mainly recommendations hence not
> blocking on my side. However one should be fixed. Even if not critical
> and very unlikely to happen, I think it is more a potential bug than a
> simple cosmetic issue.
>
> So if you have time to prepare a new series, I will try if possible to
> add your patch into the PR, which should be sent ideally before the end
> of the week. I can't give you any guarantee since the timing is really
> short now. Besides Marek still needs to add his Acked-by / Reviewed-by
> once satisfied, otherwise your patch will have to wait for the next release.
>
> If something I say is wrong or if you disagree with me, do not hesitate:
> we can discuss and I can change my mind.
>
> So my comments to follow:
>
> Le 12/04/2017 � 19:06, Ludovic Barre a �crit :
>> From: Ludovic Barre <ludovic.barre@st.com>
>>
>> The quadspi is a specialized communication interface targeting single,
>> dual or quad SPI Flash memories.
>>
>> It can operate in any of the following modes:
>> -indirect mode: all the operations are performed using the quadspi
>> registers
>> -read memory-mapped mode: the external Flash memory is mapped to the
>> microcontroller address space and is seen by the system as if it was
>> an internal memory
>>
>> Signed-off-by: Ludovic Barre <ludovic.barre@st.com>
>> ---
>> drivers/mtd/spi-nor/Kconfig | 7 +
>> drivers/mtd/spi-nor/Makefile | 1 +
>> drivers/mtd/spi-nor/stm32-quadspi.c | 693 ++++++++++++++++++++++++++++++++++++
>> 3 files changed, 701 insertions(+)
>> create mode 100644 drivers/mtd/spi-nor/stm32-quadspi.c
>>
>> diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
>> index 7252087..bfdfb1e 100644
>> --- a/drivers/mtd/spi-nor/Kconfig
>> +++ b/drivers/mtd/spi-nor/Kconfig
>> @@ -106,4 +106,11 @@ config SPI_INTEL_SPI_PLATFORM
>> To compile this driver as a module, choose M here: the module
>> will be called intel-spi-platform.
>>
>> +config SPI_STM32_QUADSPI
>> + tristate "STM32 Quad SPI controller"
>> + depends on ARCH_STM32
>> + help
>> + This enables support for the STM32 Quad SPI controller.
>> + We only connect the NOR to this controller.
>> +
>> endif # MTD_SPI_NOR
>> diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile
>> index 72238a7..285aab8 100644
>> --- a/drivers/mtd/spi-nor/Makefile
>> +++ b/drivers/mtd/spi-nor/Makefile
>> @@ -8,3 +8,4 @@ obj-$(CONFIG_MTD_MT81xx_NOR) += mtk-quadspi.o
>> obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o
>> obj-$(CONFIG_SPI_INTEL_SPI) += intel-spi.o
>> obj-$(CONFIG_SPI_INTEL_SPI_PLATFORM) += intel-spi-platform.o
>> +obj-$(CONFIG_SPI_STM32_QUADSPI) += stm32-quadspi.o
>> \ No newline at end of file
>> diff --git a/drivers/mtd/spi-nor/stm32-quadspi.c b/drivers/mtd/spi-nor/stm32-quadspi.c
>> new file mode 100644
>> index 0000000..9e90dee
>> --- /dev/null
>> +++ b/drivers/mtd/spi-nor/stm32-quadspi.c
>> @@ -0,0 +1,693 @@
>> +/*
>> + * stm32_quadspi.c
>> + *
>> + * Copyright (C) 2017, Ludovic Barre
>> + *
>> + * License terms: GNU General Public License (GPL), version 2
>> + */
>> +#include <linux/clk.h>
>> +#include <linux/errno.h>
>> +#include <linux/io.h>
>> +#include <linux/iopoll.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/module.h>
>> +#include <linux/mtd/mtd.h>
>> +#include <linux/mtd/partitions.h>
>> +#include <linux/mtd/spi-nor.h>
>> +#include <linux/mutex.h>
>> +#include <linux/of.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/reset.h>
>> +
>> +#define QUADSPI_CR 0x00
>> +#define CR_EN BIT(0)
>> +#define CR_ABORT BIT(1)
>> +#define CR_DMAEN BIT(2)
>> +#define CR_TCEN BIT(3)
>> +#define CR_SSHIFT BIT(4)
>> +#define CR_DFM BIT(6)
>> +#define CR_FSEL BIT(7)
>> +#define CR_FTHRES_SHIFT 8
>> +#define CR_FTHRES_MASK GENMASK(12, 8)
>> +#define CR_FTHRES(n) (((n) << CR_FTHRES_SHIFT) & CR_FTHRES_MASK)
>> +#define CR_TEIE BIT(16)
>> +#define CR_TCIE BIT(17)
>> +#define CR_FTIE BIT(18)
>> +#define CR_SMIE BIT(19)
>> +#define CR_TOIE BIT(20)
>> +#define CR_PRESC_SHIFT 24
>> +#define CR_PRESC_MASK GENMASK(31, 24)
>> +#define CR_PRESC(n) (((n) << CR_PRESC_SHIFT) & CR_PRESC_MASK)
>> +
>> +#define QUADSPI_DCR 0x04
>> +#define DCR_CSHT_SHIFT 8
>> +#define DCR_CSHT_MASK GENMASK(10, 8)
>> +#define DCR_CSHT(n) (((n) << DCR_CSHT_SHIFT) & DCR_CSHT_MASK)
>> +#define DCR_FSIZE_SHIFT 16
>> +#define DCR_FSIZE_MASK GENMASK(20, 16)
>> +#define DCR_FSIZE(n) (((n) << DCR_FSIZE_SHIFT) & DCR_FSIZE_MASK)
>> +
>> +#define QUADSPI_SR 0x08
>> +#define SR_TEF BIT(0)
>> +#define SR_TCF BIT(1)
>> +#define SR_FTF BIT(2)
>> +#define SR_SMF BIT(3)
>> +#define SR_TOF BIT(4)
>> +#define SR_BUSY BIT(5)
>> +#define SR_FLEVEL_SHIFT 8
>> +#define SR_FLEVEL_MASK GENMASK(13, 8)
>> +
>> +#define QUADSPI_FCR 0x0c
>> +#define FCR_CTCF BIT(1)
>> +
>> +#define QUADSPI_DLR 0x10
>> +
>> +#define QUADSPI_CCR 0x14
>> +#define CCR_INST_SHIFT 0
>> +#define CCR_INST_MASK GENMASK(7, 0)
>> +#define CCR_INST(n) (((n) << CCR_INST_SHIFT) & CCR_INST_MASK)
>> +#define CCR_IMODE_NONE (0 << 8)
>> +#define CCR_IMODE_1 (1 << 8)
>> +#define CCR_IMODE_2 (2 << 8)
>> +#define CCR_IMODE_4 (3 << 8)
>> +#define CCR_ADMODE_NONE (0 << 10)
>> +#define CCR_ADMODE_1 (1 << 10)
>> +#define CCR_ADMODE_2 (2 << 10)
>> +#define CCR_ADMODE_4 (3 << 10)
> For bitfields, it would have been better to use (1u << 8) that is
> unsigned int instead of signed int. It might avoid some warnings or even
> bugs depending on how you use those macros, especially if you were using
> BIT(31). The GENMASK() macro generates an unsigned int on its side.
>
> However, I guess you have tested your driver so if you have noticed any
> issue, I will say this is not blocking for me :)
>
> You don't use BIT(31) and I don't see any use of the >> operator so I
> think it should be safe. My comment is just a recommendation for the
> next time!
>
> To be fair, you can point out the atmel_qspi.c driver and I did the same
> mistake but like you, I didn't use the >> operator with the bitmask I
> have defined :p
that make sense to add 'U' (1U << 8), registers are unsigned :-)
So, like I rebase, I take this opportunity to add 'U'
>> +#define CCR_ADSIZE_SHIFT 12
>> +#define CCR_ADSIZE_MASK GENMASK(13, 12)
>> +#define CCR_ADSIZE(n) (((n) << CCR_ADSIZE_SHIFT) & CCR_ADSIZE_MASK)
>> +#define CCR_ABMODE_NONE (0 << 14)
>> +#define CCR_ABMODE_1 (1 << 14)
>> +#define CCR_ABMODE_2 (2 << 14)
>> +#define CCR_ABMODE_4 (3 << 14)
>> +#define CCR_ABSIZE_8 (0 << 16)
>> +#define CCR_ABSIZE_16 (1 << 16)
>> +#define CCR_ABSIZE_24 (2 << 16)
>> +#define CCR_ABSIZE_32 (3 << 16)
>> +#define CCR_DCYC_SHIFT 18
>> +#define CCR_DCYC_MASK GENMASK(22, 18)
>> +#define CCR_DCYC(n) (((n) << CCR_DCYC_SHIFT) & CCR_DCYC_MASK)
>> +#define CCR_DMODE_NONE (0 << 24)
>> +#define CCR_DMODE_1 (1 << 24)
>> +#define CCR_DMODE_2 (2 << 24)
>> +#define CCR_DMODE_4 (3 << 24)
>> +#define CCR_FMODE_INDW (0 << 26)
>> +#define CCR_FMODE_INDR (1 << 26)
>> +#define CCR_FMODE_APM (2 << 26)
>> +#define CCR_FMODE_MM (3 << 26)
>> +
>> +#define QUADSPI_AR 0x18
>> +#define QUADSPI_ABR 0x1c
>> +#define QUADSPI_DR 0x20
>> +#define QUADSPI_PSMKR 0x24
>> +#define QUADSPI_PSMAR 0x28
>> +#define QUADSPI_PIR 0x2c
>> +#define QUADSPI_LPTR 0x30
>> +#define LPTR_DFT_TIMEOUT 0x10
>> +
>> +#define FSIZE_VAL(size) (__fls(size) - 1)
>> +
>> +#define STM32_MAX_MMAP_SZ SZ_256M
>> +#define STM32_MAX_NORCHIP 2
>> +
>> +#define STM32_QSPI_FIFO_TIMEOUT_US 30000
>> +#define STM32_QSPI_BUSY_TIMEOUT_US 100000
>> +
>> +struct stm32_qspi_flash {
>> + struct spi_nor nor;
>> + u32 cs;
>> + u32 fsize;
>> + u32 presc;
>> + u32 read_mode;
>> + struct stm32_qspi *qspi;
>> +};
>> +
>> +struct stm32_qspi {
>> + struct device *dev;
>> + void __iomem *io_base;
>> + void __iomem *mm_base;
>> + resource_size_t mm_size;
>> + u32 nor_num;
>> + struct clk *clk;
>> + u32 clk_rate;
>> + struct stm32_qspi_flash flash[STM32_MAX_NORCHIP];
>> + struct completion cmd_completion;
>> +
>> + /*
>> + * to protect device configuration, could be different between
>> + * 2 flash access (bk1, bk2)
>> + */
>> + struct mutex lock;
>> +};
>> +
>> +struct stm32_qspi_cmd {
>> + u8 addr_width;
>> + u8 dummy;
>> + bool tx_data;
>> + u8 opcode;
>> + u32 framemode;
>> + u32 qspimode;
>> + u32 addr;
>> + size_t len;
>> + void *buf;
>> +};
>> +
>> +static int stm32_qspi_wait_cmd(struct stm32_qspi *qspi)
>> +{
>> + u32 cr;
>> + int err = 0;
>> +
>> + if (readl_relaxed(qspi->io_base + QUADSPI_SR) & SR_TCF)
>> + return 0;
>> +
>> + reinit_completion(&qspi->cmd_completion);
>> + cr = readl_relaxed(qspi->io_base + QUADSPI_CR);
>> + writel_relaxed(cr | CR_TCIE, qspi->io_base + QUADSPI_CR);
>> +
>> + if (!wait_for_completion_interruptible_timeout(&qspi->cmd_completion,
>> + msecs_to_jiffies(1000)))
>> + err = -ETIMEDOUT;
>> +
>> + writel_relaxed(cr, qspi->io_base + QUADSPI_CR);
>> + return err;
>> +}
>> +
>> +static int stm32_qspi_wait_nobusy(struct stm32_qspi *qspi)
>> +{
>> + u32 sr;
>> +
>> + return readl_relaxed_poll_timeout(qspi->io_base + QUADSPI_SR, sr,
>> + !(sr & SR_BUSY), 10,
>> + STM32_QSPI_BUSY_TIMEOUT_US);
>> +}
>> +
>> +static void stm32_qspi_set_framemode(struct spi_nor *nor,
>> + struct stm32_qspi_cmd *cmd, bool read)
>> +{
>> + u32 dmode = CCR_DMODE_1;
>> +
>> + cmd->framemode = CCR_IMODE_1;
>> +
>> + if (read) {
>> + switch (nor->flash_read) {
>> + case SPI_NOR_NORMAL:
>> + case SPI_NOR_FAST:
>> + dmode = CCR_DMODE_1;
>> + break;
>> + case SPI_NOR_DUAL:
>> + dmode = CCR_DMODE_2;
>> + break;
>> + case SPI_NOR_QUAD:
>> + dmode = CCR_DMODE_4;
>> + break;
>> + }
>> + }
>> +
>> + cmd->framemode |= cmd->tx_data ? dmode : 0;
>> + cmd->framemode |= cmd->addr_width ? CCR_ADMODE_1 : 0;
>> +}
>> +
>> +static void stm32_qspi_read_fifo(u8 *val, void __iomem *addr)
>> +{
>> + *val = readb_relaxed(addr);
>> +}
>> +
>> +static void stm32_qspi_write_fifo(u8 *val, void __iomem *addr)
>> +{
>> + writeb_relaxed(*val, addr);
>> +}
>> +
>> +static int stm32_qspi_tx_poll(struct stm32_qspi *qspi,
>> + const struct stm32_qspi_cmd *cmd)
>> +{
>> + void (*tx_fifo)(u8 *, void __iomem *);
>> + u32 len = cmd->len, sr;
>> + u8 *buf = cmd->buf;
>> + int ret;
>> +
>> + if (cmd->qspimode == CCR_FMODE_INDW)
>> + tx_fifo = stm32_qspi_write_fifo;
>> + else
>> + tx_fifo = stm32_qspi_read_fifo;
>> +
>> + while (len--) {
>> + ret = readl_relaxed_poll_timeout(qspi->io_base + QUADSPI_SR,
>> + sr, (sr & SR_FTF), 10,
>> + STM32_QSPI_FIFO_TIMEOUT_US);
>> + if (ret) {
>> + dev_err(qspi->dev, "fifo timeout (stat:%#x)\n", sr);
>> + break;
>> + }
>> + tx_fifo(buf++, qspi->io_base + QUADSPI_DR);
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +static int stm32_qspi_tx_mm(struct stm32_qspi *qspi,
>> + const struct stm32_qspi_cmd *cmd)
>> +{
>> + memcpy_fromio(cmd->buf, qspi->mm_base + cmd->addr, cmd->len);
>> + return 0;
>> +}
>> +
>> +static int stm32_qspi_tx(struct stm32_qspi *qspi,
>> + const struct stm32_qspi_cmd *cmd)
>> +{
>> + if (!cmd->tx_data)
>> + return 0;
>> +
>> + if (cmd->qspimode == CCR_FMODE_MM)
>> + return stm32_qspi_tx_mm(qspi, cmd);
>> +
>> + return stm32_qspi_tx_poll(qspi, cmd);
>> +}
>> +
>> +static int stm32_qspi_send(struct stm32_qspi_flash *flash,
>> + const struct stm32_qspi_cmd *cmd)
>> +{
>> + struct stm32_qspi *qspi = flash->qspi;
>> + u32 ccr, dcr, cr;
>> + int err;
>> +
>> + err = stm32_qspi_wait_nobusy(qspi);
>> + if (err)
>> + goto abort;
>> +
>> + dcr = readl_relaxed(qspi->io_base + QUADSPI_DCR) & ~DCR_FSIZE_MASK;
>> + dcr |= DCR_FSIZE(flash->fsize);
>> + writel_relaxed(dcr, qspi->io_base + QUADSPI_DCR);
>> +
>> + cr = readl_relaxed(qspi->io_base + QUADSPI_CR);
>> + cr &= ~CR_PRESC_MASK & ~CR_FSEL;
>> + cr |= CR_PRESC(flash->presc);
>> + cr |= flash->cs ? CR_FSEL : 0;
>> + writel_relaxed(cr, qspi->io_base + QUADSPI_CR);
>> +
>> + if (cmd->tx_data)
>> + writel_relaxed(cmd->len - 1, qspi->io_base + QUADSPI_DLR);
>> +
>> + ccr = cmd->framemode | cmd->qspimode;
>> +
>> + if (cmd->dummy)
>> + ccr |= CCR_DCYC(cmd->dummy);
>> +
>> + if (cmd->addr_width)
>> + ccr |= CCR_ADSIZE(cmd->addr_width - 1);
>> +
>> + ccr |= CCR_INST(cmd->opcode);
>> + writel_relaxed(ccr, qspi->io_base + QUADSPI_CCR);
>> +
>> + if (cmd->addr_width && cmd->qspimode != CCR_FMODE_MM)
>> + writel_relaxed(cmd->addr, qspi->io_base + QUADSPI_AR);
>> +
>> + err = stm32_qspi_tx(qspi, cmd);
>> + if (err)
>> + goto abort;
>> +
>> + if (cmd->qspimode != CCR_FMODE_MM) {
>> + err = stm32_qspi_wait_cmd(qspi);
>> + if (err)
>> + goto abort;
>> + writel_relaxed(FCR_CTCF, qspi->io_base + QUADSPI_FCR);
>> + }
>> +
>> + return err;
>> +
>> +abort:
>> + cr = readl_relaxed(qspi->io_base + QUADSPI_CR) | CR_ABORT;
>> + writel_relaxed(cr, qspi->io_base + QUADSPI_CR);
>> +
>> + dev_err(qspi->dev, "%s abort err:%d\n", __func__, err);
>> + return err;
>> +}
>> +
>> +static int stm32_qspi_read_reg(struct spi_nor *nor,
>> + u8 opcode, u8 *buf, int len)
>> +{
>> + struct stm32_qspi_flash *flash = nor->priv;
>> + struct device *dev = flash->qspi->dev;
>> + struct stm32_qspi_cmd cmd;
>> +
>> + dev_dbg(dev, "read_reg: cmd:%#.2x buf:%p len:%#x\n", opcode, buf, len);
>> +
>> + memset(&cmd, 0, sizeof(cmd));
>> + cmd.opcode = opcode;
>> + cmd.tx_data = true;
>> + cmd.len = len;
>> + cmd.buf = buf;
>> + cmd.qspimode = CCR_FMODE_INDR;
>> +
>> + stm32_qspi_set_framemode(nor, &cmd, false);
>> +
>> + return stm32_qspi_send(flash, &cmd);
>> +}
>> +
>> +static int stm32_qspi_write_reg(struct spi_nor *nor, u8 opcode,
>> + u8 *buf, int len)
>> +{
>> + struct stm32_qspi_flash *flash = nor->priv;
>> + struct device *dev = flash->qspi->dev;
>> + struct stm32_qspi_cmd cmd;
>> +
>> + dev_dbg(dev, "write_reg: cmd:%#.2x buf:%p len:%#x\n", opcode, buf, len);
>> +
>> + memset(&cmd, 0, sizeof(cmd));
>> + cmd.opcode = opcode;
>> + cmd.tx_data = !!(buf && len > 0);
>> + cmd.len = len;
>> + cmd.buf = buf;
>> + cmd.qspimode = CCR_FMODE_INDW;
>> +
>> + stm32_qspi_set_framemode(nor, &cmd, false);
>> +
>> + return stm32_qspi_send(flash, &cmd);
>> +}
>> +
>> +static ssize_t stm32_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
>> + u_char *buf)
>> +{
>> + struct stm32_qspi_flash *flash = nor->priv;
>> + struct stm32_qspi *qspi = flash->qspi;
>> + struct stm32_qspi_cmd cmd;
>> + int err;
>> +
>> + dev_dbg(qspi->dev, "read(%#.2x): buf:%p from:%#.8x len:%#x\n",
>> + nor->read_opcode, buf, (u32)from, len);
>> +
>> + memset(&cmd, 0, sizeof(cmd));
>> + cmd.opcode = nor->read_opcode;
>> + cmd.addr_width = nor->addr_width;
>> + cmd.addr = (u32)from;
>> + cmd.tx_data = true;
>> + cmd.dummy = nor->read_dummy;
>> + cmd.len = len;
>> + cmd.buf = buf;
>> + cmd.qspimode = flash->read_mode;
>> +
>> + stm32_qspi_set_framemode(nor, &cmd, true);
>> + err = stm32_qspi_send(flash, &cmd);
>> +
>> + return err ? err : len;
>> +}
>> +
>> +static ssize_t stm32_qspi_write(struct spi_nor *nor, loff_t to, size_t len,
>> + const u_char *buf)
>> +{
>> + struct stm32_qspi_flash *flash = nor->priv;
>> + struct device *dev = flash->qspi->dev;
>> + struct stm32_qspi_cmd cmd;
>> + int err;
>> +
>> + dev_dbg(dev, "write(%#.2x): buf:%p to:%#.8x len:%#x\n",
>> + nor->program_opcode, buf, (u32)to, len);
>> +
>> + memset(&cmd, 0, sizeof(cmd));
>> + cmd.opcode = nor->program_opcode;
>> + cmd.addr_width = nor->addr_width;
>> + cmd.addr = (u32)to;
>> + cmd.tx_data = true;
>> + cmd.len = len;
>> + cmd.buf = (void *)buf;
>> + cmd.qspimode = CCR_FMODE_INDW;
>> +
>> + stm32_qspi_set_framemode(nor, &cmd, false);
>> + err = stm32_qspi_send(flash, &cmd);
>> +
>> + return err ? err : len;
>> +}
>> +
>> +static int stm32_qspi_erase(struct spi_nor *nor, loff_t offs)
>> +{
>> + struct stm32_qspi_flash *flash = nor->priv;
>> + struct device *dev = flash->qspi->dev;
>> + struct stm32_qspi_cmd cmd;
>> +
>> + dev_dbg(dev, "erase(%#.2x):offs:%#x\n", nor->erase_opcode, (u32)offs);
>> +
>> + memset(&cmd, 0, sizeof(cmd));
>> + cmd.opcode = nor->erase_opcode;
>> + cmd.addr_width = nor->addr_width;
>> + cmd.addr = (u32)offs;
>> + cmd.qspimode = CCR_FMODE_INDW;
>> +
>> + stm32_qspi_set_framemode(nor, &cmd, false);
>> +
>> + return stm32_qspi_send(flash, &cmd);
>> +}
>> +
>> +static irqreturn_t stm32_qspi_irq(int irq, void *dev_id)
>> +{
>> + struct stm32_qspi *qspi = (struct stm32_qspi *)dev_id;
>> + u32 cr, sr, fcr = 0;
>> +
>> + cr = readl_relaxed(qspi->io_base + QUADSPI_CR);
>> + sr = readl_relaxed(qspi->io_base + QUADSPI_SR);
>> +
>> + if ((cr & CR_TCIE) && (sr & SR_TCF)) {
>> + /* tx complete */
>> + fcr |= FCR_CTCF;
>> + complete(&qspi->cmd_completion);
>> + } else {
>> + dev_info_ratelimited(qspi->dev, "spurious interrupt\n");
>> + }
>> +
>> + writel_relaxed(fcr, qspi->io_base + QUADSPI_FCR);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static int stm32_qspi_prep(struct spi_nor *nor, enum spi_nor_ops ops)
>> +{
>> + struct stm32_qspi_flash *flash = nor->priv;
>> + struct stm32_qspi *qspi = flash->qspi;
>> +
>> + mutex_lock(&qspi->lock);
>> + return 0;
>> +}
>> +
>> +static void stm32_qspi_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
>> +{
>> + struct stm32_qspi_flash *flash = nor->priv;
>> + struct stm32_qspi *qspi = flash->qspi;
>> +
>> + mutex_unlock(&qspi->lock);
>> +}
>> +
>> +static int stm32_qspi_flash_setup(struct stm32_qspi *qspi,
>> + struct device_node *np)
>> +{
>> + u32 width, flash_read, presc, cs_num, max_rate = 0;
>> + struct stm32_qspi_flash *flash;
>> + struct mtd_info *mtd;
>> + int ret;
>> +
>> + of_property_read_u32(np, "reg", &cs_num);
>> + if (cs_num >= STM32_MAX_NORCHIP)
>> + return -EINVAL;
>> +
>> + of_property_read_u32(np, "spi-max-frequency", &max_rate);
>> + if (!max_rate)
>> + return -EINVAL;
>> +
>> + presc = DIV_ROUND_UP(qspi->clk_rate, max_rate) - 1;
>> +
>> + if (of_property_read_u32(np, "spi-rx-bus-width", &width))
>> + width = 1;
>> +
>> + if (width == 4)
>> + flash_read = SPI_NOR_QUAD;
>> + else if (width == 2)
>> + flash_read = SPI_NOR_DUAL;
>> + else if (width == 1)
>> + flash_read = SPI_NOR_NORMAL;
>> + else
>> + return -EINVAL;
>> +
>> + flash = &qspi->flash[cs_num];
>> + flash->qspi = qspi;
>> + flash->cs = cs_num;
>> + flash->presc = presc;
>> +
>> + flash->nor.dev = qspi->dev;
>> + spi_nor_set_flash_node(&flash->nor, np);
>> + flash->nor.priv = flash;
>> + mtd = &flash->nor.mtd;
>> + mtd->priv = &flash->nor;
> This line is not needed: spi_nor_scan() already initializes mtd->priv
> with the nor pointer. Not blocking anyway.
OK, thanks
>> +
>> + flash->nor.read = stm32_qspi_read;
>> + flash->nor.write = stm32_qspi_write;
>> + flash->nor.erase = stm32_qspi_erase;
>> + flash->nor.read_reg = stm32_qspi_read_reg;
>> + flash->nor.write_reg = stm32_qspi_write_reg;
>> + flash->nor.prepare = stm32_qspi_prep;
>> + flash->nor.unprepare = stm32_qspi_unprep;
> A shared comment: your implementations of all those handlers seem good
> to me. I didn't notice any particular mistake or issue :)
>
>> +
>> + writel_relaxed(LPTR_DFT_TIMEOUT, qspi->io_base + QUADSPI_LPTR);
>> +
>> + writel_relaxed(CR_PRESC(presc) | CR_FTHRES(3) | CR_TCEN | CR_SSHIFT
>> + | CR_EN, qspi->io_base + QUADSPI_CR);
>> +
>> + /*
>> + * in stm32 qspi controller, QUADSPI_DCR register has a fsize field
>> + * which define the size of nor flash.
>> + * if fsize is NULL, the controller can't sent spi-nor command.
>> + * set a temporary value just to discover the nor flash with
>> + * "spi_nor_scan". After, the right value (mtd->size) can be set.
>> + */
>> + flash->fsize = FSIZE_VAL(SZ_1K);
>> +
>> + ret = spi_nor_scan(&flash->nor, NULL, flash_read);
>> + if (ret) {
>> + dev_err(qspi->dev, "device scan failed\n");
> At this point, (flash->qspi != NULL)
>
> Hence in the control flow, once you have returned from
> stm32_qspi__flash_setup() back into stm32_qspi_probe(), you handle the
> reported error calling stm32_qspi_mtd_free().
>
> This latest function test flash->qspi, which is not NULL, before calling
> mtd_device_unregister(). However you did not register flash->nor.mtd at all.
>
> Though not critical, can you fix this anyway, please?
> I think, it's more a potential bug than a costmetic issue.
Effectively, gross error. :-(
I add a boolean 'registered' in stm32_qspi_flash, and keep a centralized
function
"stm32_qspi_mtd_free" to unregister mtd device
>
>> + return ret;
>> + }
>> +
>> + flash->fsize = FSIZE_VAL(mtd->size);
>> +
>> + flash->read_mode = CCR_FMODE_MM;
>> + if (mtd->size > qspi->mm_size)
>> + flash->read_mode = CCR_FMODE_INDR;
>> +
>> + writel_relaxed(DCR_CSHT(1), qspi->io_base + QUADSPI_DCR);
>> +
>> + ret = mtd_device_register(mtd, NULL, 0);
>> + if (ret) {
>> + dev_err(qspi->dev, "mtd device parse failed\n");
> Almost the same mistake here: you shouldn't call mtd_device_unregister()
> later from stm32_qspi_mtd_free() if mtd_devire_register() fails here.
>
>> + return ret;
>> + }
>> +
>> + dev_dbg(qspi->dev, "read mm:%s cs:%d bus:%d\n",
>> + flash->read_mode == CCR_FMODE_MM ? "yes" : "no", cs_num, width);
>> +
>> + return 0;
>> +}
>> +
>> +static void stm32_qspi_mtd_free(struct stm32_qspi *qspi)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < STM32_MAX_NORCHIP; i++) {
>> + if (qspi->flash[i].qspi)
>> + mtd_device_unregister(&qspi->flash[i].nor.mtd);
>> + }
> IMHO, I guess braces {} in the for() loop are not needed here. So if
> checkpatch doesn't complain it's ok for me, you can leave it as is if
> you want. Otherwise, please fix!
I use "checkpatch --strict" and no output error on this point.
I share the same opinion on braces, so I delete the braces
>> +}
>> +
>> +static int stm32_qspi_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct device_node *flash_np;
>> + struct reset_control *rstc;
>> + struct stm32_qspi *qspi;
>> + struct resource *res;
>> + int ret, irq;
>> +
>> + qspi = devm_kzalloc(dev, sizeof(*qspi), GFP_KERNEL);
>> + if (!qspi)
>> + return -ENOMEM;
>> +
>> + qspi->nor_num = of_get_child_count(dev->of_node);
>> + if (!qspi->nor_num || qspi->nor_num > STM32_MAX_NORCHIP)
>> + return -ENODEV;
>> +
>> + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi");
>> + qspi->io_base = devm_ioremap_resource(dev, res);
>> + if (IS_ERR(qspi->io_base))
>> + return PTR_ERR(qspi->io_base);
>> +
>> + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi_mm");
>> + qspi->mm_base = devm_ioremap_resource(dev, res);
>> + if (IS_ERR(qspi->mm_base))
>> + return PTR_ERR(qspi->mm_base);
>> +
>> + qspi->mm_size = resource_size(res);
>> +
>> + irq = platform_get_irq(pdev, 0);
>> + ret = devm_request_irq(dev, irq, stm32_qspi_irq, 0,
>> + dev_name(dev), qspi);
>> + if (ret) {
>> + dev_err(dev, "failed to request irq\n");
>> + return ret;
>> + }
>> +
>> + init_completion(&qspi->cmd_completion);
>> +
>> + qspi->clk = devm_clk_get(dev, NULL);
>> + if (IS_ERR(qspi->clk))
>> + return PTR_ERR(qspi->clk);
>> +
>> + qspi->clk_rate = clk_get_rate(qspi->clk);
>> + if (!qspi->clk_rate)
>> + return -EINVAL;
>> +
>> + ret = clk_prepare_enable(qspi->clk);
>> + if (ret) {
>> + dev_err(dev, "can not enable the clock\n");
>> + return ret;
>> + }
>> +
>> + rstc = devm_reset_control_get(dev, NULL);
>> + if (!IS_ERR(rstc)) {
>> + reset_control_assert(rstc);
>> + udelay(2);
>> + reset_control_deassert(rstc);
>> + }
>> +
>> + qspi->dev = dev;
>> + platform_set_drvdata(pdev, qspi);
>> + mutex_init(&qspi->lock);
>> +
>> + for_each_available_child_of_node(dev->of_node, flash_np) {
>> + ret = stm32_qspi_flash_setup(qspi, flash_np);
>> + if (ret) {
>> + dev_err(dev, "unable to setup flash chip\n");
>> + goto err_flash;
>> + }
>> + }
>> +
>> + return 0;
>> +
>> +err_flash:
>> + mutex_destroy(&qspi->lock);
>> + stm32_qspi_mtd_free(qspi);
>> +
>> + clk_disable_unprepare(qspi->clk);
>> + return ret;
>> +}
>> +
>> +static int stm32_qspi_remove(struct platform_device *pdev)
>> +{
>> + struct stm32_qspi *qspi = platform_get_drvdata(pdev);
>> +
>> + /* disable qspi */
>> + writel_relaxed(0, qspi->io_base + QUADSPI_CR);
>> +
>> + stm32_qspi_mtd_free(qspi);
>> + mutex_destroy(&qspi->lock);
>> +
>> + clk_disable_unprepare(qspi->clk);
>> + return 0;
>> +}
>> +
>> +static const struct of_device_id stm32_qspi_match[] = {
>> + {.compatible = "st,stm32f469-qspi"},
>> + {}
>> +};
>> +MODULE_DEVICE_TABLE(of, stm32_qspi_match);
>> +
>> +static struct platform_driver stm32_qspi_driver = {
>> + .probe = stm32_qspi_probe,
>> + .remove = stm32_qspi_remove,
>> + .driver = {
>> + .name = "stm32-quadspi",
>> + .of_match_table = stm32_qspi_match,
>> + },
>> +};
>> +module_platform_driver(stm32_qspi_driver);
>> +
>> +MODULE_ALIAS("platform:" DRIVER_NAME);
>> +MODULE_AUTHOR("Ludovic Barre <ludovic.barre@st.com>");
>> +MODULE_DESCRIPTION("STMicroelectronics STM32 quad spi driver");
>> +MODULE_LICENSE("GPL v2");
>>
> Best regards,
>
> Cyrille
BR
Ludo
[-- Attachment #2: Type: text/html, Size: 25945 bytes --]
^ permalink raw reply
* [PATCH v4 0/2] mtd: spi-nor: add stm32 qspi driver
From: Ludovic Barre @ 2017-04-13 17:15 UTC (permalink / raw)
To: Cyrille Pitchen, Marek Vasut
Cc: David Woodhouse, Brian Norris, Boris Brezillon,
Richard Weinberger, Alexandre Torgue, Rob Herring,
linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA
From: Ludovic Barre <ludovic.barre-qxv4g6HH51o@public.gmane.org>
This patch set adds a SPI-NOR driver for stm32 QSPI controller.
It is a specialized SPI interface for serial Flash devices.
It supports 1 or 2 Flash device with single, dual and quad SPI Flash memories.
It can operate in any of the following modes:
-indirect mode: all the operations are performed using the quadspi
registers
-read memory-mapped mode: the external Flash memory is mapped to the
microcontroller address space and is seen by the system as if it was
an internal memory
Changes in v4:
-unsigned bitfields: (1U << 8)
-remove "mtd->priv"
-error management: issue on no mtd device registered
-remove braces in stm32_qspi_mtd_free
Changes in v3:
-dt-bindings: qspi_mm not optional
-dt-bindings: see ../spi/spi-bus.txt
-dt-bindings: spi@...
-cmd flat struct
-ratelimit
-macro to avoid hard value "25"
Changes in v2:
-awful construct: s/u32/u8
-add define for timeout
-Use a helper variable
-add comment on "stm32 qspi controller fsize issue
Ludovic Barre (2):
dt-bindings: Document the STM32 QSPI bindings
mtd: spi-nor: add driver for STM32 quad spi flash controller
.../devicetree/bindings/mtd/stm32-quadspi.txt | 43 ++
drivers/mtd/spi-nor/Kconfig | 7 +
drivers/mtd/spi-nor/Makefile | 1 +
drivers/mtd/spi-nor/stm32-quadspi.c | 694 +++++++++++++++++++++
4 files changed, 745 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mtd/stm32-quadspi.txt
create mode 100644 drivers/mtd/spi-nor/stm32-quadspi.c
--
2.7.4
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [PATCH v4 1/2] dt-bindings: Document the STM32 QSPI bindings
From: Ludovic Barre @ 2017-04-13 17:15 UTC (permalink / raw)
To: Cyrille Pitchen, Marek Vasut
Cc: David Woodhouse, Brian Norris, Boris Brezillon,
Richard Weinberger, Alexandre Torgue, Rob Herring,
linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1492103757-22375-1-git-send-email-ludovic.Barre-qxv4g6HH51o@public.gmane.org>
From: Ludovic Barre <ludovic.barre-qxv4g6HH51o@public.gmane.org>
This patch adds documentation of device tree bindings for the STM32
QSPI controller.
Signed-off-by: Ludovic Barre <ludovic.barre-qxv4g6HH51o@public.gmane.org>
---
.../devicetree/bindings/mtd/stm32-quadspi.txt | 43 ++++++++++++++++++++++
1 file changed, 43 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mtd/stm32-quadspi.txt
diff --git a/Documentation/devicetree/bindings/mtd/stm32-quadspi.txt b/Documentation/devicetree/bindings/mtd/stm32-quadspi.txt
new file mode 100644
index 0000000..ddd18c1
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/stm32-quadspi.txt
@@ -0,0 +1,43 @@
+* STMicroelectronics Quad Serial Peripheral Interface(QuadSPI)
+
+Required properties:
+- compatible: should be "st,stm32f469-qspi"
+- reg: the first contains the register location and length.
+ the second contains the memory mapping address and length
+- reg-names: should contain the reg names "qspi" "qspi_mm"
+- interrupts: should contain the interrupt for the device
+- clocks: the phandle of the clock needed by the QSPI controller
+- A pinctrl must be defined to set pins in mode of operation for QSPI transfer
+
+Optional properties:
+- resets: must contain the phandle to the reset controller.
+
+A spi flash must be a child of the nor_flash node and could have some
+properties. Also see jedec,spi-nor.txt.
+
+Required properties:
+- reg: chip-Select number (QSPI controller may connect 2 nor flashes)
+- spi-max-frequency: max frequency of spi bus
+
+Optional property:
+- spi-rx-bus-width: see ../spi/spi-bus.txt for the description
+
+Example:
+
+qspi: spi@a0001000 {
+ compatible = "st,stm32f469-qspi";
+ reg = <0xa0001000 0x1000>, <0x90000000 0x10000000>;
+ reg-names = "qspi", "qspi_mm";
+ interrupts = <91>;
+ resets = <&rcc STM32F4_AHB3_RESET(QSPI)>;
+ clocks = <&rcc 0 STM32F4_AHB3_CLOCK(QSPI)>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_qspi0>;
+
+ flash@0 {
+ reg = <0>;
+ spi-rx-bus-width = <4>;
+ spi-max-frequency = <108000000>;
+ ...
+ };
+};
--
2.7.4
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH v4 2/2] mtd: spi-nor: add driver for STM32 quad spi flash controller
From: Ludovic Barre @ 2017-04-13 17:15 UTC (permalink / raw)
To: Cyrille Pitchen, Marek Vasut
Cc: David Woodhouse, Brian Norris, Boris Brezillon,
Richard Weinberger, Alexandre Torgue, Rob Herring,
linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1492103757-22375-1-git-send-email-ludovic.Barre-qxv4g6HH51o@public.gmane.org>
From: Ludovic Barre <ludovic.barre-qxv4g6HH51o@public.gmane.org>
The quadspi is a specialized communication interface targeting single,
dual or quad SPI Flash memories.
It can operate in any of the following modes:
-indirect mode: all the operations are performed using the quadspi
registers
-read memory-mapped mode: the external Flash memory is mapped to the
microcontroller address space and is seen by the system as if it was
an internal memory
Signed-off-by: Ludovic Barre <ludovic.barre-qxv4g6HH51o@public.gmane.org>
---
drivers/mtd/spi-nor/Kconfig | 7 +
drivers/mtd/spi-nor/Makefile | 1 +
drivers/mtd/spi-nor/stm32-quadspi.c | 694 ++++++++++++++++++++++++++++++++++++
3 files changed, 702 insertions(+)
create mode 100644 drivers/mtd/spi-nor/stm32-quadspi.c
diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
index 7252087..bfdfb1e 100644
--- a/drivers/mtd/spi-nor/Kconfig
+++ b/drivers/mtd/spi-nor/Kconfig
@@ -106,4 +106,11 @@ config SPI_INTEL_SPI_PLATFORM
To compile this driver as a module, choose M here: the module
will be called intel-spi-platform.
+config SPI_STM32_QUADSPI
+ tristate "STM32 Quad SPI controller"
+ depends on ARCH_STM32
+ help
+ This enables support for the STM32 Quad SPI controller.
+ We only connect the NOR to this controller.
+
endif # MTD_SPI_NOR
diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile
index 72238a7..285aab8 100644
--- a/drivers/mtd/spi-nor/Makefile
+++ b/drivers/mtd/spi-nor/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_MTD_MT81xx_NOR) += mtk-quadspi.o
obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o
obj-$(CONFIG_SPI_INTEL_SPI) += intel-spi.o
obj-$(CONFIG_SPI_INTEL_SPI_PLATFORM) += intel-spi-platform.o
+obj-$(CONFIG_SPI_STM32_QUADSPI) += stm32-quadspi.o
\ No newline at end of file
diff --git a/drivers/mtd/spi-nor/stm32-quadspi.c b/drivers/mtd/spi-nor/stm32-quadspi.c
new file mode 100644
index 0000000..116a4c5
--- /dev/null
+++ b/drivers/mtd/spi-nor/stm32-quadspi.c
@@ -0,0 +1,694 @@
+/*
+ * stm32_quadspi.c
+ *
+ * Copyright (C) 2017, Ludovic Barre
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+#include <linux/clk.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/spi-nor.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#define QUADSPI_CR 0x00
+#define CR_EN BIT(0)
+#define CR_ABORT BIT(1)
+#define CR_DMAEN BIT(2)
+#define CR_TCEN BIT(3)
+#define CR_SSHIFT BIT(4)
+#define CR_DFM BIT(6)
+#define CR_FSEL BIT(7)
+#define CR_FTHRES_SHIFT 8
+#define CR_FTHRES_MASK GENMASK(12, 8)
+#define CR_FTHRES(n) (((n) << CR_FTHRES_SHIFT) & CR_FTHRES_MASK)
+#define CR_TEIE BIT(16)
+#define CR_TCIE BIT(17)
+#define CR_FTIE BIT(18)
+#define CR_SMIE BIT(19)
+#define CR_TOIE BIT(20)
+#define CR_PRESC_SHIFT 24
+#define CR_PRESC_MASK GENMASK(31, 24)
+#define CR_PRESC(n) (((n) << CR_PRESC_SHIFT) & CR_PRESC_MASK)
+
+#define QUADSPI_DCR 0x04
+#define DCR_CSHT_SHIFT 8
+#define DCR_CSHT_MASK GENMASK(10, 8)
+#define DCR_CSHT(n) (((n) << DCR_CSHT_SHIFT) & DCR_CSHT_MASK)
+#define DCR_FSIZE_SHIFT 16
+#define DCR_FSIZE_MASK GENMASK(20, 16)
+#define DCR_FSIZE(n) (((n) << DCR_FSIZE_SHIFT) & DCR_FSIZE_MASK)
+
+#define QUADSPI_SR 0x08
+#define SR_TEF BIT(0)
+#define SR_TCF BIT(1)
+#define SR_FTF BIT(2)
+#define SR_SMF BIT(3)
+#define SR_TOF BIT(4)
+#define SR_BUSY BIT(5)
+#define SR_FLEVEL_SHIFT 8
+#define SR_FLEVEL_MASK GENMASK(13, 8)
+
+#define QUADSPI_FCR 0x0c
+#define FCR_CTCF BIT(1)
+
+#define QUADSPI_DLR 0x10
+
+#define QUADSPI_CCR 0x14
+#define CCR_INST_SHIFT 0
+#define CCR_INST_MASK GENMASK(7, 0)
+#define CCR_INST(n) (((n) << CCR_INST_SHIFT) & CCR_INST_MASK)
+#define CCR_IMODE_NONE (0U << 8)
+#define CCR_IMODE_1 (1U << 8)
+#define CCR_IMODE_2 (2U << 8)
+#define CCR_IMODE_4 (3U << 8)
+#define CCR_ADMODE_NONE (0U << 10)
+#define CCR_ADMODE_1 (1U << 10)
+#define CCR_ADMODE_2 (2U << 10)
+#define CCR_ADMODE_4 (3U << 10)
+#define CCR_ADSIZE_SHIFT 12
+#define CCR_ADSIZE_MASK GENMASK(13, 12)
+#define CCR_ADSIZE(n) (((n) << CCR_ADSIZE_SHIFT) & CCR_ADSIZE_MASK)
+#define CCR_ABMODE_NONE (0U << 14)
+#define CCR_ABMODE_1 (1U << 14)
+#define CCR_ABMODE_2 (2U << 14)
+#define CCR_ABMODE_4 (3U << 14)
+#define CCR_ABSIZE_8 (0U << 16)
+#define CCR_ABSIZE_16 (1U << 16)
+#define CCR_ABSIZE_24 (2U << 16)
+#define CCR_ABSIZE_32 (3U << 16)
+#define CCR_DCYC_SHIFT 18
+#define CCR_DCYC_MASK GENMASK(22, 18)
+#define CCR_DCYC(n) (((n) << CCR_DCYC_SHIFT) & CCR_DCYC_MASK)
+#define CCR_DMODE_NONE (0U << 24)
+#define CCR_DMODE_1 (1U << 24)
+#define CCR_DMODE_2 (2U << 24)
+#define CCR_DMODE_4 (3U << 24)
+#define CCR_FMODE_INDW (0U << 26)
+#define CCR_FMODE_INDR (1U << 26)
+#define CCR_FMODE_APM (2U << 26)
+#define CCR_FMODE_MM (3U << 26)
+
+#define QUADSPI_AR 0x18
+#define QUADSPI_ABR 0x1c
+#define QUADSPI_DR 0x20
+#define QUADSPI_PSMKR 0x24
+#define QUADSPI_PSMAR 0x28
+#define QUADSPI_PIR 0x2c
+#define QUADSPI_LPTR 0x30
+#define LPTR_DFT_TIMEOUT 0x10
+
+#define FSIZE_VAL(size) (__fls(size) - 1)
+
+#define STM32_MAX_MMAP_SZ SZ_256M
+#define STM32_MAX_NORCHIP 2
+
+#define STM32_QSPI_FIFO_TIMEOUT_US 30000
+#define STM32_QSPI_BUSY_TIMEOUT_US 100000
+
+struct stm32_qspi_flash {
+ struct spi_nor nor;
+ struct stm32_qspi *qspi;
+ u32 cs;
+ u32 fsize;
+ u32 presc;
+ u32 read_mode;
+ bool registered;
+};
+
+struct stm32_qspi {
+ struct device *dev;
+ void __iomem *io_base;
+ void __iomem *mm_base;
+ resource_size_t mm_size;
+ u32 nor_num;
+ struct clk *clk;
+ u32 clk_rate;
+ struct stm32_qspi_flash flash[STM32_MAX_NORCHIP];
+ struct completion cmd_completion;
+
+ /*
+ * to protect device configuration, could be different between
+ * 2 flash access (bk1, bk2)
+ */
+ struct mutex lock;
+};
+
+struct stm32_qspi_cmd {
+ u8 addr_width;
+ u8 dummy;
+ bool tx_data;
+ u8 opcode;
+ u32 framemode;
+ u32 qspimode;
+ u32 addr;
+ size_t len;
+ void *buf;
+};
+
+static int stm32_qspi_wait_cmd(struct stm32_qspi *qspi)
+{
+ u32 cr;
+ int err = 0;
+
+ if (readl_relaxed(qspi->io_base + QUADSPI_SR) & SR_TCF)
+ return 0;
+
+ reinit_completion(&qspi->cmd_completion);
+ cr = readl_relaxed(qspi->io_base + QUADSPI_CR);
+ writel_relaxed(cr | CR_TCIE, qspi->io_base + QUADSPI_CR);
+
+ if (!wait_for_completion_interruptible_timeout(&qspi->cmd_completion,
+ msecs_to_jiffies(1000)))
+ err = -ETIMEDOUT;
+
+ writel_relaxed(cr, qspi->io_base + QUADSPI_CR);
+ return err;
+}
+
+static int stm32_qspi_wait_nobusy(struct stm32_qspi *qspi)
+{
+ u32 sr;
+
+ return readl_relaxed_poll_timeout(qspi->io_base + QUADSPI_SR, sr,
+ !(sr & SR_BUSY), 10,
+ STM32_QSPI_BUSY_TIMEOUT_US);
+}
+
+static void stm32_qspi_set_framemode(struct spi_nor *nor,
+ struct stm32_qspi_cmd *cmd, bool read)
+{
+ u32 dmode = CCR_DMODE_1;
+
+ cmd->framemode = CCR_IMODE_1;
+
+ if (read) {
+ switch (nor->flash_read) {
+ case SPI_NOR_NORMAL:
+ case SPI_NOR_FAST:
+ dmode = CCR_DMODE_1;
+ break;
+ case SPI_NOR_DUAL:
+ dmode = CCR_DMODE_2;
+ break;
+ case SPI_NOR_QUAD:
+ dmode = CCR_DMODE_4;
+ break;
+ }
+ }
+
+ cmd->framemode |= cmd->tx_data ? dmode : 0;
+ cmd->framemode |= cmd->addr_width ? CCR_ADMODE_1 : 0;
+}
+
+static void stm32_qspi_read_fifo(u8 *val, void __iomem *addr)
+{
+ *val = readb_relaxed(addr);
+}
+
+static void stm32_qspi_write_fifo(u8 *val, void __iomem *addr)
+{
+ writeb_relaxed(*val, addr);
+}
+
+static int stm32_qspi_tx_poll(struct stm32_qspi *qspi,
+ const struct stm32_qspi_cmd *cmd)
+{
+ void (*tx_fifo)(u8 *, void __iomem *);
+ u32 len = cmd->len, sr;
+ u8 *buf = cmd->buf;
+ int ret;
+
+ if (cmd->qspimode == CCR_FMODE_INDW)
+ tx_fifo = stm32_qspi_write_fifo;
+ else
+ tx_fifo = stm32_qspi_read_fifo;
+
+ while (len--) {
+ ret = readl_relaxed_poll_timeout(qspi->io_base + QUADSPI_SR,
+ sr, (sr & SR_FTF), 10,
+ STM32_QSPI_FIFO_TIMEOUT_US);
+ if (ret) {
+ dev_err(qspi->dev, "fifo timeout (stat:%#x)\n", sr);
+ break;
+ }
+ tx_fifo(buf++, qspi->io_base + QUADSPI_DR);
+ }
+
+ return ret;
+}
+
+static int stm32_qspi_tx_mm(struct stm32_qspi *qspi,
+ const struct stm32_qspi_cmd *cmd)
+{
+ memcpy_fromio(cmd->buf, qspi->mm_base + cmd->addr, cmd->len);
+ return 0;
+}
+
+static int stm32_qspi_tx(struct stm32_qspi *qspi,
+ const struct stm32_qspi_cmd *cmd)
+{
+ if (!cmd->tx_data)
+ return 0;
+
+ if (cmd->qspimode == CCR_FMODE_MM)
+ return stm32_qspi_tx_mm(qspi, cmd);
+
+ return stm32_qspi_tx_poll(qspi, cmd);
+}
+
+static int stm32_qspi_send(struct stm32_qspi_flash *flash,
+ const struct stm32_qspi_cmd *cmd)
+{
+ struct stm32_qspi *qspi = flash->qspi;
+ u32 ccr, dcr, cr;
+ int err;
+
+ err = stm32_qspi_wait_nobusy(qspi);
+ if (err)
+ goto abort;
+
+ dcr = readl_relaxed(qspi->io_base + QUADSPI_DCR) & ~DCR_FSIZE_MASK;
+ dcr |= DCR_FSIZE(flash->fsize);
+ writel_relaxed(dcr, qspi->io_base + QUADSPI_DCR);
+
+ cr = readl_relaxed(qspi->io_base + QUADSPI_CR);
+ cr &= ~CR_PRESC_MASK & ~CR_FSEL;
+ cr |= CR_PRESC(flash->presc);
+ cr |= flash->cs ? CR_FSEL : 0;
+ writel_relaxed(cr, qspi->io_base + QUADSPI_CR);
+
+ if (cmd->tx_data)
+ writel_relaxed(cmd->len - 1, qspi->io_base + QUADSPI_DLR);
+
+ ccr = cmd->framemode | cmd->qspimode;
+
+ if (cmd->dummy)
+ ccr |= CCR_DCYC(cmd->dummy);
+
+ if (cmd->addr_width)
+ ccr |= CCR_ADSIZE(cmd->addr_width - 1);
+
+ ccr |= CCR_INST(cmd->opcode);
+ writel_relaxed(ccr, qspi->io_base + QUADSPI_CCR);
+
+ if (cmd->addr_width && cmd->qspimode != CCR_FMODE_MM)
+ writel_relaxed(cmd->addr, qspi->io_base + QUADSPI_AR);
+
+ err = stm32_qspi_tx(qspi, cmd);
+ if (err)
+ goto abort;
+
+ if (cmd->qspimode != CCR_FMODE_MM) {
+ err = stm32_qspi_wait_cmd(qspi);
+ if (err)
+ goto abort;
+ writel_relaxed(FCR_CTCF, qspi->io_base + QUADSPI_FCR);
+ }
+
+ return err;
+
+abort:
+ cr = readl_relaxed(qspi->io_base + QUADSPI_CR) | CR_ABORT;
+ writel_relaxed(cr, qspi->io_base + QUADSPI_CR);
+
+ dev_err(qspi->dev, "%s abort err:%d\n", __func__, err);
+ return err;
+}
+
+static int stm32_qspi_read_reg(struct spi_nor *nor,
+ u8 opcode, u8 *buf, int len)
+{
+ struct stm32_qspi_flash *flash = nor->priv;
+ struct device *dev = flash->qspi->dev;
+ struct stm32_qspi_cmd cmd;
+
+ dev_dbg(dev, "read_reg: cmd:%#.2x buf:%p len:%#x\n", opcode, buf, len);
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opcode = opcode;
+ cmd.tx_data = true;
+ cmd.len = len;
+ cmd.buf = buf;
+ cmd.qspimode = CCR_FMODE_INDR;
+
+ stm32_qspi_set_framemode(nor, &cmd, false);
+
+ return stm32_qspi_send(flash, &cmd);
+}
+
+static int stm32_qspi_write_reg(struct spi_nor *nor, u8 opcode,
+ u8 *buf, int len)
+{
+ struct stm32_qspi_flash *flash = nor->priv;
+ struct device *dev = flash->qspi->dev;
+ struct stm32_qspi_cmd cmd;
+
+ dev_dbg(dev, "write_reg: cmd:%#.2x buf:%p len:%#x\n", opcode, buf, len);
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opcode = opcode;
+ cmd.tx_data = !!(buf && len > 0);
+ cmd.len = len;
+ cmd.buf = buf;
+ cmd.qspimode = CCR_FMODE_INDW;
+
+ stm32_qspi_set_framemode(nor, &cmd, false);
+
+ return stm32_qspi_send(flash, &cmd);
+}
+
+static ssize_t stm32_qspi_read(struct spi_nor *nor, loff_t from, size_t len,
+ u_char *buf)
+{
+ struct stm32_qspi_flash *flash = nor->priv;
+ struct stm32_qspi *qspi = flash->qspi;
+ struct stm32_qspi_cmd cmd;
+ int err;
+
+ dev_dbg(qspi->dev, "read(%#.2x): buf:%p from:%#.8x len:%#x\n",
+ nor->read_opcode, buf, (u32)from, len);
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opcode = nor->read_opcode;
+ cmd.addr_width = nor->addr_width;
+ cmd.addr = (u32)from;
+ cmd.tx_data = true;
+ cmd.dummy = nor->read_dummy;
+ cmd.len = len;
+ cmd.buf = buf;
+ cmd.qspimode = flash->read_mode;
+
+ stm32_qspi_set_framemode(nor, &cmd, true);
+ err = stm32_qspi_send(flash, &cmd);
+
+ return err ? err : len;
+}
+
+static ssize_t stm32_qspi_write(struct spi_nor *nor, loff_t to, size_t len,
+ const u_char *buf)
+{
+ struct stm32_qspi_flash *flash = nor->priv;
+ struct device *dev = flash->qspi->dev;
+ struct stm32_qspi_cmd cmd;
+ int err;
+
+ dev_dbg(dev, "write(%#.2x): buf:%p to:%#.8x len:%#x\n",
+ nor->program_opcode, buf, (u32)to, len);
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opcode = nor->program_opcode;
+ cmd.addr_width = nor->addr_width;
+ cmd.addr = (u32)to;
+ cmd.tx_data = true;
+ cmd.len = len;
+ cmd.buf = (void *)buf;
+ cmd.qspimode = CCR_FMODE_INDW;
+
+ stm32_qspi_set_framemode(nor, &cmd, false);
+ err = stm32_qspi_send(flash, &cmd);
+
+ return err ? err : len;
+}
+
+static int stm32_qspi_erase(struct spi_nor *nor, loff_t offs)
+{
+ struct stm32_qspi_flash *flash = nor->priv;
+ struct device *dev = flash->qspi->dev;
+ struct stm32_qspi_cmd cmd;
+
+ dev_dbg(dev, "erase(%#.2x):offs:%#x\n", nor->erase_opcode, (u32)offs);
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opcode = nor->erase_opcode;
+ cmd.addr_width = nor->addr_width;
+ cmd.addr = (u32)offs;
+ cmd.qspimode = CCR_FMODE_INDW;
+
+ stm32_qspi_set_framemode(nor, &cmd, false);
+
+ return stm32_qspi_send(flash, &cmd);
+}
+
+static irqreturn_t stm32_qspi_irq(int irq, void *dev_id)
+{
+ struct stm32_qspi *qspi = (struct stm32_qspi *)dev_id;
+ u32 cr, sr, fcr = 0;
+
+ cr = readl_relaxed(qspi->io_base + QUADSPI_CR);
+ sr = readl_relaxed(qspi->io_base + QUADSPI_SR);
+
+ if ((cr & CR_TCIE) && (sr & SR_TCF)) {
+ /* tx complete */
+ fcr |= FCR_CTCF;
+ complete(&qspi->cmd_completion);
+ } else {
+ dev_info_ratelimited(qspi->dev, "spurious interrupt\n");
+ }
+
+ writel_relaxed(fcr, qspi->io_base + QUADSPI_FCR);
+
+ return IRQ_HANDLED;
+}
+
+static int stm32_qspi_prep(struct spi_nor *nor, enum spi_nor_ops ops)
+{
+ struct stm32_qspi_flash *flash = nor->priv;
+ struct stm32_qspi *qspi = flash->qspi;
+
+ mutex_lock(&qspi->lock);
+ return 0;
+}
+
+static void stm32_qspi_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
+{
+ struct stm32_qspi_flash *flash = nor->priv;
+ struct stm32_qspi *qspi = flash->qspi;
+
+ mutex_unlock(&qspi->lock);
+}
+
+static int stm32_qspi_flash_setup(struct stm32_qspi *qspi,
+ struct device_node *np)
+{
+ u32 width, flash_read, presc, cs_num, max_rate = 0;
+ struct stm32_qspi_flash *flash;
+ struct mtd_info *mtd;
+ int ret;
+
+ of_property_read_u32(np, "reg", &cs_num);
+ if (cs_num >= STM32_MAX_NORCHIP)
+ return -EINVAL;
+
+ of_property_read_u32(np, "spi-max-frequency", &max_rate);
+ if (!max_rate)
+ return -EINVAL;
+
+ presc = DIV_ROUND_UP(qspi->clk_rate, max_rate) - 1;
+
+ if (of_property_read_u32(np, "spi-rx-bus-width", &width))
+ width = 1;
+
+ if (width == 4)
+ flash_read = SPI_NOR_QUAD;
+ else if (width == 2)
+ flash_read = SPI_NOR_DUAL;
+ else if (width == 1)
+ flash_read = SPI_NOR_NORMAL;
+ else
+ return -EINVAL;
+
+ flash = &qspi->flash[cs_num];
+ flash->qspi = qspi;
+ flash->cs = cs_num;
+ flash->presc = presc;
+
+ flash->nor.dev = qspi->dev;
+ spi_nor_set_flash_node(&flash->nor, np);
+ flash->nor.priv = flash;
+ mtd = &flash->nor.mtd;
+
+ flash->nor.read = stm32_qspi_read;
+ flash->nor.write = stm32_qspi_write;
+ flash->nor.erase = stm32_qspi_erase;
+ flash->nor.read_reg = stm32_qspi_read_reg;
+ flash->nor.write_reg = stm32_qspi_write_reg;
+ flash->nor.prepare = stm32_qspi_prep;
+ flash->nor.unprepare = stm32_qspi_unprep;
+
+ writel_relaxed(LPTR_DFT_TIMEOUT, qspi->io_base + QUADSPI_LPTR);
+
+ writel_relaxed(CR_PRESC(presc) | CR_FTHRES(3) | CR_TCEN | CR_SSHIFT
+ | CR_EN, qspi->io_base + QUADSPI_CR);
+
+ /*
+ * in stm32 qspi controller, QUADSPI_DCR register has a fsize field
+ * which define the size of nor flash.
+ * if fsize is NULL, the controller can't sent spi-nor command.
+ * set a temporary value just to discover the nor flash with
+ * "spi_nor_scan". After, the right value (mtd->size) can be set.
+ */
+ flash->fsize = FSIZE_VAL(SZ_1K);
+
+ ret = spi_nor_scan(&flash->nor, NULL, flash_read);
+ if (ret) {
+ dev_err(qspi->dev, "device scan failed\n");
+ return ret;
+ }
+
+ flash->fsize = FSIZE_VAL(mtd->size);
+
+ flash->read_mode = CCR_FMODE_MM;
+ if (mtd->size > qspi->mm_size)
+ flash->read_mode = CCR_FMODE_INDR;
+
+ writel_relaxed(DCR_CSHT(1), qspi->io_base + QUADSPI_DCR);
+
+ ret = mtd_device_register(mtd, NULL, 0);
+ if (ret) {
+ dev_err(qspi->dev, "mtd device parse failed\n");
+ return ret;
+ }
+
+ flash->registered = true;
+
+ dev_dbg(qspi->dev, "read mm:%s cs:%d bus:%d\n",
+ flash->read_mode == CCR_FMODE_MM ? "yes" : "no", cs_num, width);
+
+ return 0;
+}
+
+static void stm32_qspi_mtd_free(struct stm32_qspi *qspi)
+{
+ int i;
+
+ for (i = 0; i < STM32_MAX_NORCHIP; i++)
+ if (qspi->flash[i].registered)
+ mtd_device_unregister(&qspi->flash[i].nor.mtd);
+}
+
+static int stm32_qspi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *flash_np;
+ struct reset_control *rstc;
+ struct stm32_qspi *qspi;
+ struct resource *res;
+ int ret, irq;
+
+ qspi = devm_kzalloc(dev, sizeof(*qspi), GFP_KERNEL);
+ if (!qspi)
+ return -ENOMEM;
+
+ qspi->nor_num = of_get_child_count(dev->of_node);
+ if (!qspi->nor_num || qspi->nor_num > STM32_MAX_NORCHIP)
+ return -ENODEV;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi");
+ qspi->io_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(qspi->io_base))
+ return PTR_ERR(qspi->io_base);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qspi_mm");
+ qspi->mm_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(qspi->mm_base))
+ return PTR_ERR(qspi->mm_base);
+
+ qspi->mm_size = resource_size(res);
+
+ irq = platform_get_irq(pdev, 0);
+ ret = devm_request_irq(dev, irq, stm32_qspi_irq, 0,
+ dev_name(dev), qspi);
+ if (ret) {
+ dev_err(dev, "failed to request irq\n");
+ return ret;
+ }
+
+ init_completion(&qspi->cmd_completion);
+
+ qspi->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(qspi->clk))
+ return PTR_ERR(qspi->clk);
+
+ qspi->clk_rate = clk_get_rate(qspi->clk);
+ if (!qspi->clk_rate)
+ return -EINVAL;
+
+ ret = clk_prepare_enable(qspi->clk);
+ if (ret) {
+ dev_err(dev, "can not enable the clock\n");
+ return ret;
+ }
+
+ rstc = devm_reset_control_get(dev, NULL);
+ if (!IS_ERR(rstc)) {
+ reset_control_assert(rstc);
+ udelay(2);
+ reset_control_deassert(rstc);
+ }
+
+ qspi->dev = dev;
+ platform_set_drvdata(pdev, qspi);
+ mutex_init(&qspi->lock);
+
+ for_each_available_child_of_node(dev->of_node, flash_np) {
+ ret = stm32_qspi_flash_setup(qspi, flash_np);
+ if (ret) {
+ dev_err(dev, "unable to setup flash chip\n");
+ goto err_flash;
+ }
+ }
+
+ return 0;
+
+err_flash:
+ mutex_destroy(&qspi->lock);
+ stm32_qspi_mtd_free(qspi);
+
+ clk_disable_unprepare(qspi->clk);
+ return ret;
+}
+
+static int stm32_qspi_remove(struct platform_device *pdev)
+{
+ struct stm32_qspi *qspi = platform_get_drvdata(pdev);
+
+ /* disable qspi */
+ writel_relaxed(0, qspi->io_base + QUADSPI_CR);
+
+ stm32_qspi_mtd_free(qspi);
+ mutex_destroy(&qspi->lock);
+
+ clk_disable_unprepare(qspi->clk);
+ return 0;
+}
+
+static const struct of_device_id stm32_qspi_match[] = {
+ {.compatible = "st,stm32f469-qspi"},
+ {}
+};
+MODULE_DEVICE_TABLE(of, stm32_qspi_match);
+
+static struct platform_driver stm32_qspi_driver = {
+ .probe = stm32_qspi_probe,
+ .remove = stm32_qspi_remove,
+ .driver = {
+ .name = "stm32-quadspi",
+ .of_match_table = stm32_qspi_match,
+ },
+};
+module_platform_driver(stm32_qspi_driver);
+
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_AUTHOR("Ludovic Barre <ludovic.barre-qxv4g6HH51o@public.gmane.org>");
+MODULE_DESCRIPTION("STMicroelectronics STM32 quad spi driver");
+MODULE_LICENSE("GPL v2");
--
2.7.4
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* Re: [PATCH v3 0/4] TI Bluetooth serdev support
From: Marcel Holtmann @ 2017-04-13 17:24 UTC (permalink / raw)
To: Rob Herring
Cc: Linux Bluetooth, linux-arm-kernel, Gustavo F. Padovan,
Johan Hedberg, Mark Rutland, Wei Xu, Eyal Reizer, Satish Patel,
Network Development, devicetree-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <20170413150353.7389-1-robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
Hi Rob,
> This series adds serdev support to the HCI LL protocol used on TI BT
> modules and enables support on HiKey board with with the WL1835 module.
> With this the custom TI UIM daemon and btattach are no longer needed.
>
> The series is available on this git branch[1]. This version is rebased on
> bluetooth-next tree containing its dependencies.
>
> Rob
>
> [1] git://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git ti-bluetooth
>
> Rob Herring (4):
> dt-bindings: net: Add TI WiLink shared transport binding
> bluetooth: hci_uart: remove unused hci_uart_init_tty
> bluetooth: hci_uart: add LL protocol serdev driver support
> arm64: dts: hikey: add WL1835 Bluetooth device node
>
> .../devicetree/bindings/net/ti,wilink-st.txt | 35 +++
> arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts | 5 +
> drivers/bluetooth/hci_ldisc.c | 19 --
> drivers/bluetooth/hci_ll.c | 262 ++++++++++++++++++++-
> drivers/bluetooth/hci_uart.h | 1 -
> 5 files changed, 301 insertions(+), 21 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/net/ti,wilink-st.txt
all 4 patches have been applied to bluetooth-next tree.
Regards
Marcel
^ permalink raw reply
* [PATCH v2 00/22] eeprom: at24: Add OF device ID table
From: Javier Martinez Canillas @ 2017-04-13 18:28 UTC (permalink / raw)
To: linux-kernel
Cc: Mark Rutland, Andrew Lunn, Wolfram Sang, Tony Lindgren,
Catalin Marinas, Will Deacon, Russell King, Masahiro Yamada,
Alexandre Belloni, linux-i2c, Hongtao Jia, Mark Jackson,
Herbert Xu, Horia Geantă, Michael Ellerman, Magnus Damm,
Michal Simek, Javier Martinez Canillas, Andy Shevchenko,
linux-arm-kernel, Benjamin Herrenschmidt, Jason Cooper
Hello Wolfram,
This series is a follow-up to patch [0] that added an OF device ID table
to the at24 EEPROM driver. As you suggested [1], this version instead of
adding entries for every used <vendor,device> tuple, only adds a single
entry for each chip type using the "atmel" vendor as a generic fallback.
The first patch adds the OF device ID table for the at24 driver and the
next patches adds a generic fallback compatible string to each DTS that
defines a compatible I2C EEPROM device node.
Patches can be applied independently since the DTS change without the
driver change is a no-op and the OF device table won't be used without
the DTS changes.
[0]: https://lkml.org/lkml/2017/3/14/589
[1]: https://lkml.org/lkml/2017/3/15/99
Best regards,
Javier
Changes in v2:
- Only add a single OF device ID entry for each device type (Wolfram Sang).
Javier Martinez Canillas (22):
dt-bindings: i2c: eeprom: Document manufacturer used as generic
fallback
eeprom: at24: Add OF device ID table
ARM: dts: omap: Add generic compatible string for I2C EEPROM
ARM: dts: turris-omnia: Add generic compatible string for I2C EEPROM
ARM: dts: at91: Add generic compatible string for I2C EEPROM
ARM: dts: efm32: Add generic compatible string for I2C EEPROM
ARM: dts: imx: Add generic compatible string for I2C EEPROM
ARM: dts: keystone: Add generic compatible string for I2C EEPROM
ARM: dts: lpc18xx: Add generic compatible string for I2C EEPROM
ARM: dts: r7s72100: Add generic compatible string for I2C EEPROM
ARM: dts: koelsch: Add generic compatible string for I2C EEPROM
ARM: dts: socfpga: Add generic compatible string for I2C EEPROM
ARM: dts: uniphier: Add generic compatible string for I2C EEPROM
ARM: dts: zynq: Add generic compatible string for I2C EEPROM
arm64: dts: ls1043a: Add generic compatible string for I2C EEPROM
arm64: zynqmp: Add generic compatible string for I2C EEPROM
powerpc/5200: Add generic compatible string for I2C EEPROM
powerpc/fsl: Add generic compatible string for I2C EEPROM
powerpc/512x: Add generic compatible string for I2C EEPROM
powerpc/83xx: Add generic compatible string for I2C EEPROM
powerpc/5200: Add generic compatible string for I2C EEPROM
powerpc/44x: Add generic compatible string for I2C EEPROM
.../devicetree/bindings/eeprom/eeprom.txt | 3 +-
arch/arm/boot/dts/am335x-baltos.dtsi | 2 +-
arch/arm/boot/dts/am335x-base0033.dts | 2 +-
arch/arm/boot/dts/am335x-bone-common.dtsi | 10 ++--
arch/arm/boot/dts/am335x-nano.dts | 2 +-
arch/arm/boot/dts/am335x-pepper.dts | 2 +-
arch/arm/boot/dts/am335x-shc.dts | 2 +-
arch/arm/boot/dts/am335x-sl50.dts | 2 +-
arch/arm/boot/dts/am437x-idk-evm.dts | 2 +-
arch/arm/boot/dts/am437x-sk-evm.dts | 2 +-
arch/arm/boot/dts/am43x-epos-evm.dts | 2 +-
arch/arm/boot/dts/am57xx-beagle-x15-common.dtsi | 2 +-
arch/arm/boot/dts/armada-385-turris-omnia.dts | 2 +-
arch/arm/boot/dts/at91-linea.dtsi | 2 +-
arch/arm/boot/dts/at91-tse850-3.dts | 2 +-
arch/arm/boot/dts/efm32gg-dk3750.dts | 2 +-
arch/arm/boot/dts/imx27-phytec-phycard-s-som.dtsi | 2 +-
arch/arm/boot/dts/imx27-phytec-phycore-som.dtsi | 2 +-
arch/arm/boot/dts/imx28-evk.dts | 2 +-
arch/arm/boot/dts/imx53-tqma53.dtsi | 2 +-
arch/arm/boot/dts/imx6q-cm-fx6.dts | 2 +-
arch/arm/boot/dts/imx6q-utilite-pro.dts | 2 +-
arch/arm/boot/dts/keystone-k2e-evm.dts | 2 +-
arch/arm/boot/dts/keystone-k2hk-evm.dts | 2 +-
arch/arm/boot/dts/keystone-k2l-evm.dts | 2 +-
arch/arm/boot/dts/lpc4337-ciaa.dts | 6 +-
arch/arm/boot/dts/lpc4350-hitex-eval.dts | 2 +-
arch/arm/boot/dts/lpc4357-ea4357-devkit.dts | 2 +-
arch/arm/boot/dts/omap3-cm-t3x.dtsi | 2 +-
arch/arm/boot/dts/omap3-gta04.dtsi | 2 +-
arch/arm/boot/dts/omap3-sb-t35.dtsi | 2 +-
arch/arm/boot/dts/omap4-var-som-om44.dtsi | 2 +-
arch/arm/boot/dts/omap5-cm-t54.dts | 2 +-
arch/arm/boot/dts/omap5-sbc-t54.dts | 2 +-
arch/arm/boot/dts/r7s72100-genmai.dts | 2 +-
arch/arm/boot/dts/r8a7791-koelsch.dts | 2 +-
arch/arm/boot/dts/socfpga_cyclone5_vining_fpga.dts | 2 +-
arch/arm/boot/dts/uniphier-pro4-ace.dts | 2 +-
arch/arm/boot/dts/uniphier-pro4-sanji.dts | 2 +-
arch/arm/boot/dts/uniphier-pxs2-gentil.dts | 2 +-
arch/arm/boot/dts/zynq-zc702.dts | 2 +-
arch/arm/boot/dts/zynq-zc706.dts | 2 +-
arch/arm64/boot/dts/freescale/fsl-ls1043a-rdb.dts | 4 +-
arch/arm64/boot/dts/xilinx/zynqmp-ep108.dts | 4 +-
arch/powerpc/boot/dts/digsy_mtc.dts | 2 +-
arch/powerpc/boot/dts/fsl/b4qds.dtsi | 8 +--
arch/powerpc/boot/dts/fsl/c293pcie.dts | 2 +-
arch/powerpc/boot/dts/fsl/p1010rdb.dtsi | 2 +-
arch/powerpc/boot/dts/fsl/p1023rdb.dts | 2 +-
arch/powerpc/boot/dts/fsl/p2041rdb.dts | 4 +-
arch/powerpc/boot/dts/fsl/p3041ds.dts | 4 +-
arch/powerpc/boot/dts/fsl/p4080ds.dts | 4 +-
arch/powerpc/boot/dts/fsl/p5020ds.dts | 4 +-
arch/powerpc/boot/dts/fsl/p5040ds.dts | 4 +-
arch/powerpc/boot/dts/fsl/t208xqds.dtsi | 8 +--
arch/powerpc/boot/dts/fsl/t4240qds.dts | 12 ++--
arch/powerpc/boot/dts/fsl/t4240rdb.dts | 6 +-
arch/powerpc/boot/dts/mpc5121ads.dts | 2 +-
arch/powerpc/boot/dts/mpc8308_p1m.dts | 2 +-
arch/powerpc/boot/dts/mpc8349emitx.dts | 2 +-
arch/powerpc/boot/dts/mpc8377_rdb.dts | 2 +-
arch/powerpc/boot/dts/mpc8377_wlan.dts | 2 +-
arch/powerpc/boot/dts/mpc8378_rdb.dts | 2 +-
arch/powerpc/boot/dts/mpc8379_rdb.dts | 2 +-
arch/powerpc/boot/dts/pcm030.dts | 2 +-
arch/powerpc/boot/dts/pcm032.dts | 2 +-
arch/powerpc/boot/dts/warp.dts | 2 +-
drivers/misc/eeprom/at24.c | 69 +++++++++++++++++++++-
68 files changed, 162 insertions(+), 94 deletions(-)
--
2.9.3
^ permalink raw reply
* [PATCH v2 01/22] dt-bindings: i2c: eeprom: Document manufacturer used as generic fallback
From: Javier Martinez Canillas @ 2017-04-13 18:28 UTC (permalink / raw)
To: linux-kernel
Cc: Wolfram Sang, Javier Martinez Canillas, devicetree, Sekhar Nori,
David Lechner, Rob Herring, Mark Rutland
In-Reply-To: <20170413182839.25381-1-javier@osg.samsung.com>
The at24 driver allows to register I2C EEPROM chips using different vendor
and devices, but the I2C subsystem does not take the vendor into account
when matching using the I2C table since it only has device entries.
But when matching using an OF table, both the vendor and device has to be
taken into account so the driver defines only a set of compatible strings
using the "atmel" vendor as a generic fallback for compatible I2C devices.
Document in the Device Tree binding document that this manufacturer should
be used as the generic fallback.
Suggested-by: Wolfram Sang <wsa@the-dreams.de>
Signed-off-by: Javier Martinez Canillas <javier@osg.samsung.com>
---
Changes in v2: None
Documentation/devicetree/bindings/eeprom/eeprom.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/eeprom/eeprom.txt b/Documentation/devicetree/bindings/eeprom/eeprom.txt
index 5696eb508e95..d0395f14e2b3 100644
--- a/Documentation/devicetree/bindings/eeprom/eeprom.txt
+++ b/Documentation/devicetree/bindings/eeprom/eeprom.txt
@@ -17,7 +17,8 @@ Required properties:
"renesas,r1ex24002"
If there is no specific driver for <manufacturer>, a generic
- driver based on <type> is selected. Possible types are:
+ driver based on <type> and manufacturer "atmel" is selected.
+ Possible types are:
"24c00", "24c01", "24c02", "24c04", "24c08", "24c16", "24c32", "24c64",
"24c128", "24c256", "24c512", "24c1024", "spd"
--
2.9.3
^ permalink raw reply related
* [PATCH v2 03/22] ARM: dts: omap: Add generic compatible string for I2C EEPROM
From: Javier Martinez Canillas @ 2017-04-13 18:28 UTC (permalink / raw)
To: linux-kernel
Cc: Wolfram Sang, Javier Martinez Canillas, linux-arm-kernel,
Benoît Cousson, devicetree, Rob Herring, Tony Lindgren,
Mark Rutland, linux-omap, Russell King, Mark Jackson
In-Reply-To: <20170413182839.25381-1-javier@osg.samsung.com>
The at24 driver allows to register I2C EEPROM chips using different vendor
and devices, but the I2C subsystem does not take the vendor into account
when matching using the I2C table since it only has device entries.
But when matching using an OF table, both the vendor and device has to be
taken into account so the driver defines only a set of compatible strings
using the "atmel" vendor as a generic fallback for compatible I2C devices.
So add this generic fallback to the device node compatible string to make
the device to match the driver using the OF device ID table.
Signed-off-by: Javier Martinez Canillas <javier@osg.samsung.com>
---
Changes in v2: None
arch/arm/boot/dts/am335x-baltos.dtsi | 2 +-
arch/arm/boot/dts/am335x-base0033.dts | 2 +-
arch/arm/boot/dts/am335x-bone-common.dtsi | 10 +++++-----
arch/arm/boot/dts/am335x-nano.dts | 2 +-
arch/arm/boot/dts/am335x-pepper.dts | 2 +-
arch/arm/boot/dts/am335x-shc.dts | 2 +-
arch/arm/boot/dts/am335x-sl50.dts | 2 +-
arch/arm/boot/dts/am437x-idk-evm.dts | 2 +-
arch/arm/boot/dts/am437x-sk-evm.dts | 2 +-
arch/arm/boot/dts/am43x-epos-evm.dts | 2 +-
arch/arm/boot/dts/am57xx-beagle-x15-common.dtsi | 2 +-
arch/arm/boot/dts/omap3-cm-t3x.dtsi | 2 +-
arch/arm/boot/dts/omap3-gta04.dtsi | 2 +-
arch/arm/boot/dts/omap3-sb-t35.dtsi | 2 +-
arch/arm/boot/dts/omap4-var-som-om44.dtsi | 2 +-
arch/arm/boot/dts/omap5-cm-t54.dts | 2 +-
arch/arm/boot/dts/omap5-sbc-t54.dts | 2 +-
17 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/arch/arm/boot/dts/am335x-baltos.dtsi b/arch/arm/boot/dts/am335x-baltos.dtsi
index d42b98f15e8b..ed6785d54ca4 100644
--- a/arch/arm/boot/dts/am335x-baltos.dtsi
+++ b/arch/arm/boot/dts/am335x-baltos.dtsi
@@ -255,7 +255,7 @@
};
at24@50 {
- compatible = "at24,24c02";
+ compatible = "at24,24c02","atmel,24c02";
pagesize = <8>;
reg = <0x50>;
};
diff --git a/arch/arm/boot/dts/am335x-base0033.dts b/arch/arm/boot/dts/am335x-base0033.dts
index c2bee452dab8..681323937e89 100644
--- a/arch/arm/boot/dts/am335x-base0033.dts
+++ b/arch/arm/boot/dts/am335x-base0033.dts
@@ -89,7 +89,7 @@
&i2c0 {
eeprom: eeprom@50 {
- compatible = "at,24c256";
+ compatible = "at,24c256","atmel,24c256";
reg = <0x50>;
};
};
diff --git a/arch/arm/boot/dts/am335x-bone-common.dtsi b/arch/arm/boot/dts/am335x-bone-common.dtsi
index bf6b26abe35b..5e42ab7d3e93 100644
--- a/arch/arm/boot/dts/am335x-bone-common.dtsi
+++ b/arch/arm/boot/dts/am335x-bone-common.dtsi
@@ -232,7 +232,7 @@
};
baseboard_eeprom: baseboard_eeprom@50 {
- compatible = "at,24c256";
+ compatible = "at,24c256","atmel,24c256";
reg = <0x50>;
#address-cells = <1>;
@@ -251,7 +251,7 @@
clock-frequency = <100000>;
cape_eeprom0: cape_eeprom0@54 {
- compatible = "at,24c256";
+ compatible = "at,24c256","atmel,24c256";
reg = <0x54>;
#address-cells = <1>;
#size-cells = <1>;
@@ -261,7 +261,7 @@
};
cape_eeprom1: cape_eeprom1@55 {
- compatible = "at,24c256";
+ compatible = "at,24c256","atmel,24c256";
reg = <0x55>;
#address-cells = <1>;
#size-cells = <1>;
@@ -271,7 +271,7 @@
};
cape_eeprom2: cape_eeprom2@56 {
- compatible = "at,24c256";
+ compatible = "at,24c256","atmel,24c256";
reg = <0x56>;
#address-cells = <1>;
#size-cells = <1>;
@@ -281,7 +281,7 @@
};
cape_eeprom3: cape_eeprom3@57 {
- compatible = "at,24c256";
+ compatible = "at,24c256","atmel,24c256";
reg = <0x57>;
#address-cells = <1>;
#size-cells = <1>;
diff --git a/arch/arm/boot/dts/am335x-nano.dts b/arch/arm/boot/dts/am335x-nano.dts
index 807494bc722b..241f4f1b9be0 100644
--- a/arch/arm/boot/dts/am335x-nano.dts
+++ b/arch/arm/boot/dts/am335x-nano.dts
@@ -224,7 +224,7 @@
};
eeprom@53 {
- compatible = "microchip,24c02";
+ compatible = "microchip,24c02","atmel,24c02";
reg = <0x53>;
pagesize = <8>;
};
diff --git a/arch/arm/boot/dts/am335x-pepper.dts b/arch/arm/boot/dts/am335x-pepper.dts
index 30e2f8770aaf..d7fbc0e42ac2 100644
--- a/arch/arm/boot/dts/am335x-pepper.dts
+++ b/arch/arm/boot/dts/am335x-pepper.dts
@@ -67,7 +67,7 @@
};
eeprom: eeprom@50 {
- compatible = "at,24c256";
+ compatible = "at,24c256","atmel,24c256";
reg = <0x50>;
};
diff --git a/arch/arm/boot/dts/am335x-shc.dts b/arch/arm/boot/dts/am335x-shc.dts
index bf8727a19ece..7e8ea8376be5 100644
--- a/arch/arm/boot/dts/am335x-shc.dts
+++ b/arch/arm/boot/dts/am335x-shc.dts
@@ -188,7 +188,7 @@
};
at24@50 {
- compatible = "at24,24c32";
+ compatible = "at24,24c32","atmel,24c32";
pagesize = <32>;
reg = <0x50>;
};
diff --git a/arch/arm/boot/dts/am335x-sl50.dts b/arch/arm/boot/dts/am335x-sl50.dts
index c5d2589c55fc..42c941448d24 100644
--- a/arch/arm/boot/dts/am335x-sl50.dts
+++ b/arch/arm/boot/dts/am335x-sl50.dts
@@ -309,7 +309,7 @@
};
eeprom: eeprom@50 {
- compatible = "at,24c256";
+ compatible = "at,24c256","atmel,24c256";
reg = <0x50>;
};
diff --git a/arch/arm/boot/dts/am437x-idk-evm.dts b/arch/arm/boot/dts/am437x-idk-evm.dts
index c1f7f9336e64..677d3772a5a0 100644
--- a/arch/arm/boot/dts/am437x-idk-evm.dts
+++ b/arch/arm/boot/dts/am437x-idk-evm.dts
@@ -339,7 +339,7 @@
clock-frequency = <400000>;
at24@50 {
- compatible = "at24,24c256";
+ compatible = "at24,24c256","atmel,24c256";
pagesize = <64>;
reg = <0x50>;
};
diff --git a/arch/arm/boot/dts/am437x-sk-evm.dts b/arch/arm/boot/dts/am437x-sk-evm.dts
index 4dc54bee2f36..449f93526fce 100644
--- a/arch/arm/boot/dts/am437x-sk-evm.dts
+++ b/arch/arm/boot/dts/am437x-sk-evm.dts
@@ -511,7 +511,7 @@
};
at24@50 {
- compatible = "at24,24c256";
+ compatible = "at24,24c256","atmel,24c256";
pagesize = <64>;
reg = <0x50>;
};
diff --git a/arch/arm/boot/dts/am43x-epos-evm.dts b/arch/arm/boot/dts/am43x-epos-evm.dts
index 9acd4ccdec4e..6f4a0a570b9e 100644
--- a/arch/arm/boot/dts/am43x-epos-evm.dts
+++ b/arch/arm/boot/dts/am43x-epos-evm.dts
@@ -477,7 +477,7 @@
};
at24@50 {
- compatible = "at24,24c256";
+ compatible = "at24,24c256","atmel,24c256";
pagesize = <64>;
reg = <0x50>;
};
diff --git a/arch/arm/boot/dts/am57xx-beagle-x15-common.dtsi b/arch/arm/boot/dts/am57xx-beagle-x15-common.dtsi
index 585d792a8fdd..1a8ebc4cc5ae 100644
--- a/arch/arm/boot/dts/am57xx-beagle-x15-common.dtsi
+++ b/arch/arm/boot/dts/am57xx-beagle-x15-common.dtsi
@@ -388,7 +388,7 @@
};
eeprom: eeprom@50 {
- compatible = "at,24c32";
+ compatible = "at,24c32","atmel,24c32";
reg = <0x50>;
};
};
diff --git a/arch/arm/boot/dts/omap3-cm-t3x.dtsi b/arch/arm/boot/dts/omap3-cm-t3x.dtsi
index 57b9a028a49a..27965b619bb9 100644
--- a/arch/arm/boot/dts/omap3-cm-t3x.dtsi
+++ b/arch/arm/boot/dts/omap3-cm-t3x.dtsi
@@ -188,7 +188,7 @@
clock-frequency = <400000>;
at24@50 {
- compatible = "at24,24c02";
+ compatible = "at24,24c02","atmel,24c02";
pagesize = <16>;
reg = <0x50>;
};
diff --git a/arch/arm/boot/dts/omap3-gta04.dtsi b/arch/arm/boot/dts/omap3-gta04.dtsi
index b3a8b1f24499..9d2661229b8e 100644
--- a/arch/arm/boot/dts/omap3-gta04.dtsi
+++ b/arch/arm/boot/dts/omap3-gta04.dtsi
@@ -418,7 +418,7 @@
/* RFID EEPROM */
m24lr64@50 {
- compatible = "at,24c64";
+ compatible = "at,24c64","atmel,24c64";
reg = <0x50>;
};
};
diff --git a/arch/arm/boot/dts/omap3-sb-t35.dtsi b/arch/arm/boot/dts/omap3-sb-t35.dtsi
index 73643fabde5d..aa90d1cce34a 100644
--- a/arch/arm/boot/dts/omap3-sb-t35.dtsi
+++ b/arch/arm/boot/dts/omap3-sb-t35.dtsi
@@ -90,7 +90,7 @@
clock-frequency = <400000>;
at24@50 {
- compatible = "at24,24c02";
+ compatible = "at24,24c02","atmel,24c02";
pagesize = <16>;
reg = <0x50>;
};
diff --git a/arch/arm/boot/dts/omap4-var-som-om44.dtsi b/arch/arm/boot/dts/omap4-var-som-om44.dtsi
index 758b6eb7ae43..f360b220f263 100644
--- a/arch/arm/boot/dts/omap4-var-som-om44.dtsi
+++ b/arch/arm/boot/dts/omap4-var-som-om44.dtsi
@@ -241,7 +241,7 @@
};
eeprom@50 {
- compatible = "microchip,24c32";
+ compatible = "microchip,24c32","atmel,24c32";
reg = <0x50>;
};
};
diff --git a/arch/arm/boot/dts/omap5-cm-t54.dts b/arch/arm/boot/dts/omap5-cm-t54.dts
index b153f604932a..41c030c75d48 100644
--- a/arch/arm/boot/dts/omap5-cm-t54.dts
+++ b/arch/arm/boot/dts/omap5-cm-t54.dts
@@ -404,7 +404,7 @@
clock-frequency = <400000>;
at24@50 {
- compatible = "at24,24c02";
+ compatible = "at24,24c02","atmel,24c02";
pagesize = <16>;
reg = <0x50>;
};
diff --git a/arch/arm/boot/dts/omap5-sbc-t54.dts b/arch/arm/boot/dts/omap5-sbc-t54.dts
index 337bbbc01a35..5a63354f174b 100644
--- a/arch/arm/boot/dts/omap5-sbc-t54.dts
+++ b/arch/arm/boot/dts/omap5-sbc-t54.dts
@@ -44,7 +44,7 @@
clock-frequency = <400000>;
at24@50 {
- compatible = "at24,24c02";
+ compatible = "at24,24c02","atmel,24c02";
pagesize = <16>;
reg = <0x50>;
};
--
2.9.3
^ permalink raw reply related
* [PATCH v2 04/22] ARM: dts: turris-omnia: Add generic compatible string for I2C EEPROM
From: Javier Martinez Canillas @ 2017-04-13 18:28 UTC (permalink / raw)
To: linux-kernel
Cc: Wolfram Sang, Javier Martinez Canillas, devicetree,
Gregory Clement, Sebastian Hesselbarth, Jason Cooper, Andrew Lunn,
Rob Herring, Mark Rutland, Russell King, linux-arm-kernel
In-Reply-To: <20170413182839.25381-1-javier@osg.samsung.com>
The at24 driver allows to register I2C EEPROM chips using different vendor
and devices, but the I2C subsystem does not take the vendor into account
when matching using the I2C table since it only has device entries.
But when matching using an OF table, both the vendor and device has to be
taken into account so the driver defines only a set of compatible strings
using the "atmel" vendor as a generic fallback for compatible I2C devices.
So add this generic fallback to the device node compatible string to make
the device to match the driver using the OF device ID table.
Signed-off-by: Javier Martinez Canillas <javier@osg.samsung.com>
---
Changes in v2: None
arch/arm/boot/dts/armada-385-turris-omnia.dts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/arm/boot/dts/armada-385-turris-omnia.dts b/arch/arm/boot/dts/armada-385-turris-omnia.dts
index 28eede180e4f..4185f6b1ba44 100644
--- a/arch/arm/boot/dts/armada-385-turris-omnia.dts
+++ b/arch/arm/boot/dts/armada-385-turris-omnia.dts
@@ -171,7 +171,7 @@
/* leds device (in STM32F0) at address 0x2b */
eeprom@54 {
- compatible = "at,24c64";
+ compatible = "at,24c64","atmel,24c64";
reg = <0x54>;
/* The EEPROM contains data for bootloader.
--
2.9.3
^ permalink raw reply related
* [PATCH v2 05/22] ARM: dts: at91: Add generic compatible string for I2C EEPROM
From: Javier Martinez Canillas @ 2017-04-13 18:28 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: Wolfram Sang, Javier Martinez Canillas,
devicetree-u79uwXL29TY76Z2rM5mHXA, Russell King,
Alexandre Belloni, Rob Herring, Nicolas Ferre, Peter Rosin,
Mark Rutland, linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <20170413182839.25381-1-javier-JPH+aEBZ4P+UEJcrhfAQsw@public.gmane.org>
The at24 driver allows to register I2C EEPROM chips using different vendor
and devices, but the I2C subsystem does not take the vendor into account
when matching using the I2C table since it only has device entries.
But when matching using an OF table, both the vendor and device has to be
taken into account so the driver defines only a set of compatible strings
using the "atmel" vendor as a generic fallback for compatible I2C devices.
So add this generic fallback to the device node compatible string to make
the device to match the driver using the OF device ID table.
Signed-off-by: Javier Martinez Canillas <javier-JPH+aEBZ4P+UEJcrhfAQsw@public.gmane.org>
---
Changes in v2: None
arch/arm/boot/dts/at91-linea.dtsi | 2 +-
arch/arm/boot/dts/at91-tse850-3.dts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/arm/boot/dts/at91-linea.dtsi b/arch/arm/boot/dts/at91-linea.dtsi
index 0721c8472509..9a19080fd3bf 100644
--- a/arch/arm/boot/dts/at91-linea.dtsi
+++ b/arch/arm/boot/dts/at91-linea.dtsi
@@ -31,7 +31,7 @@
status = "okay";
eeprom@51 {
- compatible = "st,24c64";
+ compatible = "st,24c64","atmel,24c64";
reg = <0x51>;
pagesize = <32>;
};
diff --git a/arch/arm/boot/dts/at91-tse850-3.dts b/arch/arm/boot/dts/at91-tse850-3.dts
index 7a68805a4eb5..6f005c14a6b0 100644
--- a/arch/arm/boot/dts/at91-tse850-3.dts
+++ b/arch/arm/boot/dts/at91-tse850-3.dts
@@ -239,7 +239,7 @@
};
eeprom@50 {
- compatible = "nxp,24c02";
+ compatible = "nxp,24c02","atmel,24c02";
reg = <0x50>;
pagesize = <16>;
};
--
2.9.3
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH v2 06/22] ARM: dts: efm32: Add generic compatible string for I2C EEPROM
From: Javier Martinez Canillas @ 2017-04-13 18:28 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: Wolfram Sang, Javier Martinez Canillas,
devicetree-u79uwXL29TY76Z2rM5mHXA, Rob Herring,
Uwe Kleine-König, Mark Rutland, Russell King,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <20170413182839.25381-1-javier-JPH+aEBZ4P+UEJcrhfAQsw@public.gmane.org>
The at24 driver allows to register I2C EEPROM chips using different vendor
and devices, but the I2C subsystem does not take the vendor into account
when matching using the I2C table since it only has device entries.
But when matching using an OF table, both the vendor and device has to be
taken into account so the driver defines only a set of compatible strings
using the "atmel" vendor as a generic fallback for compatible I2C devices.
So add this generic fallback to the device node compatible string to make
the device to match the driver using the OF device ID table.
Signed-off-by: Javier Martinez Canillas <javier-JPH+aEBZ4P+UEJcrhfAQsw@public.gmane.org>
---
Changes in v2: None
arch/arm/boot/dts/efm32gg-dk3750.dts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/arm/boot/dts/efm32gg-dk3750.dts b/arch/arm/boot/dts/efm32gg-dk3750.dts
index 98fc667d22c7..2f34491881ae 100644
--- a/arch/arm/boot/dts/efm32gg-dk3750.dts
+++ b/arch/arm/boot/dts/efm32gg-dk3750.dts
@@ -36,7 +36,7 @@
};
eeprom@50 {
- compatible = "microchip,24c02";
+ compatible = "microchip,24c02","atmel,24c02";
reg = <0x50>;
pagesize = <16>;
};
--
2.9.3
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH v2 07/22] ARM: dts: imx: Add generic compatible string for I2C EEPROM
From: Javier Martinez Canillas @ 2017-04-13 18:28 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: Wolfram Sang, Javier Martinez Canillas, Fabio Estevam,
devicetree-u79uwXL29TY76Z2rM5mHXA, Sascha Hauer, Rob Herring,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Mark Rutland,
Russell King, Shawn Guo
In-Reply-To: <20170413182839.25381-1-javier-JPH+aEBZ4P+UEJcrhfAQsw@public.gmane.org>
The at24 driver allows to register I2C EEPROM chips using different vendor
and devices, but the I2C subsystem does not take the vendor into account
when matching using the I2C table since it only has device entries.
But when matching using an OF table, both the vendor and device has to be
taken into account so the driver defines only a set of compatible strings
using the "atmel" vendor as a generic fallback for compatible I2C devices.
So add this generic fallback to the device node compatible string to make
the device to match the driver using the OF device ID table.
Signed-off-by: Javier Martinez Canillas <javier-JPH+aEBZ4P+UEJcrhfAQsw@public.gmane.org>
---
Changes in v2: None
arch/arm/boot/dts/imx27-phytec-phycard-s-som.dtsi | 2 +-
arch/arm/boot/dts/imx27-phytec-phycore-som.dtsi | 2 +-
arch/arm/boot/dts/imx28-evk.dts | 2 +-
arch/arm/boot/dts/imx53-tqma53.dtsi | 2 +-
arch/arm/boot/dts/imx6q-cm-fx6.dts | 2 +-
arch/arm/boot/dts/imx6q-utilite-pro.dts | 2 +-
6 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/arch/arm/boot/dts/imx27-phytec-phycard-s-som.dtsi b/arch/arm/boot/dts/imx27-phytec-phycard-s-som.dtsi
index 4f3e0f473581..61e741092efa 100644
--- a/arch/arm/boot/dts/imx27-phytec-phycard-s-som.dtsi
+++ b/arch/arm/boot/dts/imx27-phytec-phycard-s-som.dtsi
@@ -40,7 +40,7 @@
status = "okay";
at24@52 {
- compatible = "at,24c32";
+ compatible = "at,24c32","atmel,24c32";
pagesize = <32>;
reg = <0x52>;
};
diff --git a/arch/arm/boot/dts/imx27-phytec-phycore-som.dtsi b/arch/arm/boot/dts/imx27-phytec-phycore-som.dtsi
index 82fec935ce83..5b6b651af18f 100644
--- a/arch/arm/boot/dts/imx27-phytec-phycore-som.dtsi
+++ b/arch/arm/boot/dts/imx27-phytec-phycore-som.dtsi
@@ -193,7 +193,7 @@
status = "okay";
at24@52 {
- compatible = "at,24c32";
+ compatible = "at,24c32","atmel,24c32";
pagesize = <32>;
reg = <0x52>;
};
diff --git a/arch/arm/boot/dts/imx28-evk.dts b/arch/arm/boot/dts/imx28-evk.dts
index a5ba669b4eaa..5ab990ac36b4 100644
--- a/arch/arm/boot/dts/imx28-evk.dts
+++ b/arch/arm/boot/dts/imx28-evk.dts
@@ -203,7 +203,7 @@
};
at24@51 {
- compatible = "at24,24c32";
+ compatible = "at24,24c32","atmel,24c32";
pagesize = <32>;
reg = <0x51>;
};
diff --git a/arch/arm/boot/dts/imx53-tqma53.dtsi b/arch/arm/boot/dts/imx53-tqma53.dtsi
index 85972f2201c2..c8bc0522a1e9 100644
--- a/arch/arm/boot/dts/imx53-tqma53.dtsi
+++ b/arch/arm/boot/dts/imx53-tqma53.dtsi
@@ -272,7 +272,7 @@
};
eeprom: 24c64@50 {
- compatible = "at,24c64";
+ compatible = "at,24c64","atmel,24c64";
pagesize = <32>;
reg = <0x50>;
};
diff --git a/arch/arm/boot/dts/imx6q-cm-fx6.dts b/arch/arm/boot/dts/imx6q-cm-fx6.dts
index 66cac5328b86..8cf478c67f83 100644
--- a/arch/arm/boot/dts/imx6q-cm-fx6.dts
+++ b/arch/arm/boot/dts/imx6q-cm-fx6.dts
@@ -215,7 +215,7 @@
clock-frequency = <100000>;
eeprom@50 {
- compatible = "at24,24c02";
+ compatible = "at24,24c02","atmel,24c02";
reg = <0x50>;
pagesize = <16>;
};
diff --git a/arch/arm/boot/dts/imx6q-utilite-pro.dts b/arch/arm/boot/dts/imx6q-utilite-pro.dts
index 69bdd82ce21f..644889d813d0 100644
--- a/arch/arm/boot/dts/imx6q-utilite-pro.dts
+++ b/arch/arm/boot/dts/imx6q-utilite-pro.dts
@@ -128,7 +128,7 @@
#size-cells = <0>;
eeprom@50 {
- compatible = "at24,24c02";
+ compatible = "at24,24c02","atmel,24c02";
reg = <0x50>;
pagesize = <16>;
};
--
2.9.3
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH v2 08/22] ARM: dts: keystone: Add generic compatible string for I2C EEPROM
From: Javier Martinez Canillas @ 2017-04-13 18:28 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: Wolfram Sang, Javier Martinez Canillas,
devicetree-u79uwXL29TY76Z2rM5mHXA, Santosh Shilimkar, Rob Herring,
Mark Rutland, Russell King,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <20170413182839.25381-1-javier-JPH+aEBZ4P+UEJcrhfAQsw@public.gmane.org>
The at24 driver allows to register I2C EEPROM chips using different vendor
and devices, but the I2C subsystem does not take the vendor into account
when matching using the I2C table since it only has device entries.
But when matching using an OF table, both the vendor and device has to be
taken into account so the driver defines only a set of compatible strings
using the "atmel" vendor as a generic fallback for compatible I2C devices.
So add this generic fallback to the device node compatible string to make
the device to match the driver using the OF device ID table.
Signed-off-by: Javier Martinez Canillas <javier-JPH+aEBZ4P+UEJcrhfAQsw@public.gmane.org>
---
Changes in v2: None
arch/arm/boot/dts/keystone-k2e-evm.dts | 2 +-
arch/arm/boot/dts/keystone-k2hk-evm.dts | 2 +-
arch/arm/boot/dts/keystone-k2l-evm.dts | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/arch/arm/boot/dts/keystone-k2e-evm.dts b/arch/arm/boot/dts/keystone-k2e-evm.dts
index ae1ebe7ee021..0540e49dcece 100644
--- a/arch/arm/boot/dts/keystone-k2e-evm.dts
+++ b/arch/arm/boot/dts/keystone-k2e-evm.dts
@@ -69,7 +69,7 @@
&i2c0 {
dtt@50 {
- compatible = "at,24c1024";
+ compatible = "at,24c1024","atmel,24c1024";
reg = <0x50>;
};
};
diff --git a/arch/arm/boot/dts/keystone-k2hk-evm.dts b/arch/arm/boot/dts/keystone-k2hk-evm.dts
index 2156ff92d08f..c278d2c10c67 100644
--- a/arch/arm/boot/dts/keystone-k2hk-evm.dts
+++ b/arch/arm/boot/dts/keystone-k2hk-evm.dts
@@ -145,7 +145,7 @@
&i2c0 {
dtt@50 {
- compatible = "at,24c1024";
+ compatible = "at,24c1024","atmel,24c1024";
reg = <0x50>;
};
};
diff --git a/arch/arm/boot/dts/keystone-k2l-evm.dts b/arch/arm/boot/dts/keystone-k2l-evm.dts
index 056b42f99d7a..d0e62890cd95 100644
--- a/arch/arm/boot/dts/keystone-k2l-evm.dts
+++ b/arch/arm/boot/dts/keystone-k2l-evm.dts
@@ -42,7 +42,7 @@
&i2c0 {
dtt@50 {
- compatible = "at,24c1024";
+ compatible = "at,24c1024","atmel,24c1024";
reg = <0x50>;
};
};
--
2.9.3
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH v2 09/22] ARM: dts: lpc18xx: Add generic compatible string for I2C EEPROM
From: Javier Martinez Canillas @ 2017-04-13 18:28 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: Wolfram Sang, Javier Martinez Canillas,
devicetree-u79uwXL29TY76Z2rM5mHXA, Joachim Eastwood, Rob Herring,
Mark Rutland, Russell King,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <20170413182839.25381-1-javier-JPH+aEBZ4P+UEJcrhfAQsw@public.gmane.org>
The at24 driver allows to register I2C EEPROM chips using different vendor
and devices, but the I2C subsystem does not take the vendor into account
when matching using the I2C table since it only has device entries.
But when matching using an OF table, both the vendor and device has to be
taken into account so the driver defines only a set of compatible strings
using the "atmel" vendor as a generic fallback for compatible I2C devices.
So add this generic fallback to the device node compatible string to make
the device to match the driver using the OF device ID table.
Signed-off-by: Javier Martinez Canillas <javier-JPH+aEBZ4P+UEJcrhfAQsw@public.gmane.org>
---
Changes in v2: None
arch/arm/boot/dts/lpc4337-ciaa.dts | 6 +++---
arch/arm/boot/dts/lpc4350-hitex-eval.dts | 2 +-
arch/arm/boot/dts/lpc4357-ea4357-devkit.dts | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/arch/arm/boot/dts/lpc4337-ciaa.dts b/arch/arm/boot/dts/lpc4337-ciaa.dts
index 7c16d639a1b4..8b584b39db7d 100644
--- a/arch/arm/boot/dts/lpc4337-ciaa.dts
+++ b/arch/arm/boot/dts/lpc4337-ciaa.dts
@@ -174,17 +174,17 @@
clock-frequency = <400000>;
eeprom@50 {
- compatible = "microchip,24c512";
+ compatible = "microchip,24c512","atmel,24c512";
reg = <0x50>;
};
eeprom@51 {
- compatible = "microchip,24c02";
+ compatible = "microchip,24c02","atmel,24c02";
reg = <0x51>;
};
eeprom@54 {
- compatible = "microchip,24c512";
+ compatible = "microchip,24c512","atmel,24c512";
reg = <0x54>;
};
};
diff --git a/arch/arm/boot/dts/lpc4350-hitex-eval.dts b/arch/arm/boot/dts/lpc4350-hitex-eval.dts
index 874c75d44013..32a512926a8f 100644
--- a/arch/arm/boot/dts/lpc4350-hitex-eval.dts
+++ b/arch/arm/boot/dts/lpc4350-hitex-eval.dts
@@ -429,7 +429,7 @@
};
eeprom@50 {
- compatible = "nxp,24c02";
+ compatible = "nxp,24c02","atmel,24c02";
reg = <0x50>;
};
diff --git a/arch/arm/boot/dts/lpc4357-ea4357-devkit.dts b/arch/arm/boot/dts/lpc4357-ea4357-devkit.dts
index 9b5fad622522..7000a565f50b 100644
--- a/arch/arm/boot/dts/lpc4357-ea4357-devkit.dts
+++ b/arch/arm/boot/dts/lpc4357-ea4357-devkit.dts
@@ -490,7 +490,7 @@
};
eeprom@57 {
- compatible = "microchip,24c64";
+ compatible = "microchip,24c64","atmel,24c64";
reg = <0x57>;
};
};
--
2.9.3
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
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