Devicetree
 help / color / mirror / Atom feed
* Re: [PATCH 0/3] arm64: dts: r8a7796: Add CAN/CAN FD support
From: Geert Uytterhoeven @ 2016-11-24 16:41 UTC (permalink / raw)
  To: Chris Paterson
  Cc: Simon Horman, Marc Kleine-Budde, Wolfgang Grandegger, Magnus Damm,
	Rob Herring, Mark Rutland, Ramesh Shanmugasundaram,
	linux-renesas-soc-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org,
	linux-can-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
In-Reply-To: <HK2PR0601MB1329C747CA5C6B5222C02C3FB7B60-5BHi1SMfQIfsvBovKiDY8NK/flDYrvD0nBOFsp37pqbUKgpGm//BTAC/G2K4zDHf@public.gmane.org>

Hi Chris,

On Thu, Nov 24, 2016 at 3:25 PM, Chris Paterson
<Chris.Paterson2-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org> wrote:
> From: Simon Horman [mailto:horms-/R6kz+dDXgpPR4JQBCEnsQ@public.gmane.org]
> Sent: 24 November 2016 10:18
>> On Thu, Nov 24, 2016 at 10:05:08AM +0000, Chris Paterson wrote:
>> > From: Simon Horman [mailto:horms-/R6kz+dDXgpPR4JQBCEnsQ@public.gmane.org]
>> > > Regarding the arch/arm64/boot/dts/renesas/ portion, I would like
>> > > some consideration given to what effect enabling memory above 4Gb
>> > > (64bit
>> > > addressing) would have.
>> >
>> > Can you give me some guidance here? I'm not sure what you're referring
>> > to. As far as I know the DT reg definition here is 64-bit, or are you
>> > referring to DMA usage? If the later, neither CAN driver uses DMA.
>>
>> Sorry for not being clearer.
>>
>> What I would like to know is if there are any problems in the CAN driver or
>> hardware that would prevent it from functioning with memory that requires
>> 64bit addressing present.
>>
>> If the CAN hardware cannot use DMA then DMA doesn't need to be taken
>> into account. But if it DMA could be enabled in future for CAN, for example
>> after some driver enhancements, then it would be good to know if 64bit
>> memory can be supported - if not it would imply DMA cannot be enabled.
>
> Thank you for the clarification.
>
> The CAN interface for r8a7795/6 does not support DMA.
>
> With CAN FD there is currently a H/W issue that means DMA is unusable.

Is that issue present on R-Car M3-W, or only on R-Car H3 ES1.x?

> Potentially this issue could be fixed in the future and DMA support could
> be added to the driver. If this happens I can see no reason why the CAN FD
> IP wouldn't be able to handle DMA transfers when using 64bit addressing.

Yep, AFAIK it uses SYS-DMAC, which supports 64-bit addressing.

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert-Td1EMuHUCqxL1ZNQvxDV9g@public.gmane.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
--
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 v2 3/3] arm64: dts: r8a7796: Add CAN FD support
From: Chris Paterson @ 2016-11-24 16:13 UTC (permalink / raw)
  To: Simon Horman
  Cc: Marc Kleine-Budde, Rob Herring, Mark Rutland, Geert Uytterhoeven,
	Ramesh Shanmugasundaram, linux-arm-kernel, devicetree,
	linux-renesas-soc, Chris Paterson
In-Reply-To: <1480004021-4037-1-git-send-email-chris.paterson2@renesas.com>

Adds CAN FD controller node for r8a7796.

Based on a patch for r8a7795 by Ramesh Shanmugasundaram.

Signed-off-by: Chris Paterson <chris.paterson2@renesas.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Acked-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
 arch/arm64/boot/dts/renesas/r8a7796.dtsi | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/arch/arm64/boot/dts/renesas/r8a7796.dtsi b/arch/arm64/boot/dts/renesas/r8a7796.dtsi
index 47fa29c..1ed1886 100644
--- a/arch/arm64/boot/dts/renesas/r8a7796.dtsi
+++ b/arch/arm64/boot/dts/renesas/r8a7796.dtsi
@@ -392,6 +392,30 @@
 			status = "disabled";
 		};
 
+		canfd: can@e66c0000 {
+			compatible = "renesas,r8a7796-canfd",
+				     "renesas,rcar-gen3-canfd";
+			reg = <0 0xe66c0000 0 0x8000>;
+			interrupts = <GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>,
+				   <GIC_SPI 30 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&cpg CPG_MOD 914>,
+			       <&cpg CPG_CORE R8A7796_CLK_CANFD>,
+			       <&can_clk>;
+			clock-names = "fck", "canfd", "can_clk";
+			assigned-clocks = <&cpg CPG_CORE R8A7796_CLK_CANFD>;
+			assigned-clock-rates = <40000000>;
+			power-domains = <&sysc R8A7796_PD_ALWAYS_ON>;
+			status = "disabled";
+
+			channel0 {
+				status = "disabled";
+			};
+
+			channel1 {
+				status = "disabled";
+			};
+		};
+
 		scif2: serial@e6e88000 {
 			compatible = "renesas,scif-r8a7796",
 				     "renesas,rcar-gen3-scif", "renesas,scif";
-- 
1.9.1

^ permalink raw reply related

* [PATCH v2 2/3] arm64: dts: r8a7796: Add CAN support
From: Chris Paterson @ 2016-11-24 16:13 UTC (permalink / raw)
  To: Simon Horman
  Cc: Marc Kleine-Budde, Rob Herring, Mark Rutland, Geert Uytterhoeven,
	Ramesh Shanmugasundaram, linux-arm-kernel, devicetree,
	linux-renesas-soc, Chris Paterson
In-Reply-To: <1480004021-4037-1-git-send-email-chris.paterson2@renesas.com>

Adds CAN controller nodes for r8a7796.

Based on a patch for r8a7795 by Ramesh Shanmugasundaram.

Signed-off-by: Chris Paterson <chris.paterson2@renesas.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Acked-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
 arch/arm64/boot/dts/renesas/r8a7796.dtsi | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/arch/arm64/boot/dts/renesas/r8a7796.dtsi b/arch/arm64/boot/dts/renesas/r8a7796.dtsi
index 61d165b..47fa29c 100644
--- a/arch/arm64/boot/dts/renesas/r8a7796.dtsi
+++ b/arch/arm64/boot/dts/renesas/r8a7796.dtsi
@@ -362,6 +362,36 @@
 			status = "disabled";
 		};
 
+		can0: can@e6c30000 {
+			compatible = "renesas,can-r8a7796",
+				     "renesas,rcar-gen3-can";
+			reg = <0 0xe6c30000 0 0x1000>;
+			interrupts = <GIC_SPI 186 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&cpg CPG_MOD 916>,
+			       <&cpg CPG_CORE R8A7796_CLK_CANFD>,
+			       <&can_clk>;
+			clock-names = "clkp1", "clkp2", "can_clk";
+			assigned-clocks = <&cpg CPG_CORE R8A7796_CLK_CANFD>;
+			assigned-clock-rates = <40000000>;
+			power-domains = <&sysc R8A7796_PD_ALWAYS_ON>;
+			status = "disabled";
+		};
+
+		can1: can@e6c38000 {
+			compatible = "renesas,can-r8a7796",
+				     "renesas,rcar-gen3-can";
+			reg = <0 0xe6c38000 0 0x1000>;
+			interrupts = <GIC_SPI 187 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&cpg CPG_MOD 915>,
+			       <&cpg CPG_CORE R8A7796_CLK_CANFD>,
+			       <&can_clk>;
+			clock-names = "clkp1", "clkp2", "can_clk";
+			assigned-clocks = <&cpg CPG_CORE R8A7796_CLK_CANFD>;
+			assigned-clock-rates = <40000000>;
+			power-domains = <&sysc R8A7796_PD_ALWAYS_ON>;
+			status = "disabled";
+		};
+
 		scif2: serial@e6e88000 {
 			compatible = "renesas,scif-r8a7796",
 				     "renesas,rcar-gen3-scif", "renesas,scif";
-- 
1.9.1

^ permalink raw reply related

* [PATCH v2 1/3] arm64: dts: r8a7796: Add CAN external clock support
From: Chris Paterson @ 2016-11-24 16:13 UTC (permalink / raw)
  To: Simon Horman
  Cc: Marc Kleine-Budde, Rob Herring, Mark Rutland, Geert Uytterhoeven,
	Ramesh Shanmugasundaram,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-renesas-soc-u79uwXL29TY76Z2rM5mHXA, Chris Paterson
In-Reply-To: <1480004021-4037-1-git-send-email-chris.paterson2-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>

Adds external CAN clock node for r8a7796. This clock can be used as
fCAN clock of CAN and CAN FD controller.

Based on a patch for r8a7795 by Ramesh Shanmugasundaram.

Signed-off-by: Chris Paterson <chris.paterson2-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>
Reviewed-by: Geert Uytterhoeven <geert+renesas-gXvu3+zWzMSzQB+pC5nmwQ@public.gmane.org>
Acked-by: Marc Kleine-Budde <mkl-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
---
 arch/arm64/boot/dts/renesas/r8a7796.dtsi | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/arch/arm64/boot/dts/renesas/r8a7796.dtsi b/arch/arm64/boot/dts/renesas/r8a7796.dtsi
index c34c684..61d165b 100644
--- a/arch/arm64/boot/dts/renesas/r8a7796.dtsi
+++ b/arch/arm64/boot/dts/renesas/r8a7796.dtsi
@@ -69,6 +69,13 @@
 		clock-frequency = <0>;
 	};
 
+	/* External CAN clock - to be overridden by boards that provide it */
+	can_clk: can {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <0>;
+	};
+
 	/* External SCIF clock - to be overridden by boards that provide it */
 	scif_clk: scif {
 		compatible = "fixed-clock";
-- 
1.9.1

--
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 0/3] arm64: dts: r8a7796: Add CAN/CAN FD support
From: Chris Paterson @ 2016-11-24 16:13 UTC (permalink / raw)
  To: Simon Horman
  Cc: Marc Kleine-Budde, Rob Herring, Mark Rutland, Geert Uytterhoeven,
	Ramesh Shanmugasundaram,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-renesas-soc-u79uwXL29TY76Z2rM5mHXA, Chris Paterson
In-Reply-To: <1479903243-1860-1-git-send-email-chris.paterson2-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>

This series adds CAN and CAN FD support to the r8a7796.

Changes since v1:
- Split device tree changes from bindings documentation.
- Rebased on renesas-devel-20161123v2-v4.9-rc6.


Chris Paterson (3):
  arm64: dts: r8a7796: Add CAN external clock support
  arm64: dts: r8a7796: Add CAN support
  arm64: dts: r8a7796: Add CAN FD support

 arch/arm64/boot/dts/renesas/r8a7796.dtsi | 61 ++++++++++++++++++++++++++++++++
 1 file changed, 61 insertions(+)

-- 
1.9.1

--
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 v2 2/2] can: rcar_canfd: Add r8a7796 support
From: Chris Paterson @ 2016-11-24 16:11 UTC (permalink / raw)
  To: Marc Kleine-Budde
  Cc: Wolfgang Grandegger, Rob Herring, Mark Rutland,
	Geert Uytterhoeven, Simon Horman, Ramesh Shanmugasundaram,
	linux-can, netdev, devicetree, linux-renesas-soc, Chris Paterson
In-Reply-To: <1480003917-3953-1-git-send-email-chris.paterson2@renesas.com>

Signed-off-by: Chris Paterson <chris.paterson2@renesas.com>
Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
Acked-by: Marc Kleine-Budde <mkl@pengutronix.de>
---
 Documentation/devicetree/bindings/net/can/rcar_canfd.txt | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/Documentation/devicetree/bindings/net/can/rcar_canfd.txt b/Documentation/devicetree/bindings/net/can/rcar_canfd.txt
index 22a6f10..788f273 100644
--- a/Documentation/devicetree/bindings/net/can/rcar_canfd.txt
+++ b/Documentation/devicetree/bindings/net/can/rcar_canfd.txt
@@ -5,6 +5,7 @@ Required properties:
 - compatible: Must contain one or more of the following:
   - "renesas,rcar-gen3-canfd" for R-Car Gen3 compatible controller.
   - "renesas,r8a7795-canfd" for R8A7795 (R-Car H3) compatible controller.
+  - "renesas,r8a7796-canfd" for R8A7796 (R-Car M3) compatible controller.
 
   When compatible with the generic version, nodes must list the
   SoC-specific version corresponding to the platform first, followed by the
@@ -23,11 +24,12 @@ The name of the child nodes are "channel0" and "channel1" respectively. Each
 child node supports the "status" property only, which is used to
 enable/disable the respective channel.
 
-Required properties for "renesas,r8a7795-canfd" compatible:
-In R8A7795 SoC, canfd clock is a div6 clock and can be used by both CAN
-and CAN FD controller at the same time. It needs to be scaled to maximum
-frequency if any of these controllers use it. This is done using the
-below properties.
+Required properties for "renesas,r8a7795-canfd" and "renesas,r8a7796-canfd"
+compatible:
+In R8A7795 and R8A7796 SoCs, canfd clock is a div6 clock and can be used by both
+CAN and CAN FD controller at the same time. It needs to be scaled to maximum
+frequency if any of these controllers use it. This is done using the below
+properties:
 
 - assigned-clocks: phandle of canfd clock.
 - assigned-clock-rates: maximum frequency of this clock.
-- 
1.9.1

^ permalink raw reply related

* [PATCH v2 1/2] can: rcar_can: Add r8a7796 support
From: Chris Paterson @ 2016-11-24 16:11 UTC (permalink / raw)
  To: Marc Kleine-Budde
  Cc: Wolfgang Grandegger, Rob Herring, Mark Rutland,
	Geert Uytterhoeven, Simon Horman, Ramesh Shanmugasundaram,
	linux-can-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-renesas-soc-u79uwXL29TY76Z2rM5mHXA, Chris Paterson
In-Reply-To: <1480003917-3953-1-git-send-email-chris.paterson2-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>

Signed-off-by: Chris Paterson <chris.paterson2-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>
Reviewed-by: Geert Uytterhoeven <geert+renesas-gXvu3+zWzMSzQB+pC5nmwQ@public.gmane.org>
Acked-by: Marc Kleine-Budde <mkl-bIcnvbaLZ9MEGnE8C9+IrQ@public.gmane.org>
---
 Documentation/devicetree/bindings/net/can/rcar_can.txt | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/Documentation/devicetree/bindings/net/can/rcar_can.txt b/Documentation/devicetree/bindings/net/can/rcar_can.txt
index 8d40ab2..06bb7cc 100644
--- a/Documentation/devicetree/bindings/net/can/rcar_can.txt
+++ b/Documentation/devicetree/bindings/net/can/rcar_can.txt
@@ -10,6 +10,7 @@ Required properties:
 	      "renesas,can-r8a7793" if CAN controller is a part of R8A7793 SoC.
 	      "renesas,can-r8a7794" if CAN controller is a part of R8A7794 SoC.
 	      "renesas,can-r8a7795" if CAN controller is a part of R8A7795 SoC.
+	      "renesas,can-r8a7796" if CAN controller is a part of R8A7796 SoC.
 	      "renesas,rcar-gen1-can" for a generic R-Car Gen1 compatible device.
 	      "renesas,rcar-gen2-can" for a generic R-Car Gen2 compatible device.
 	      "renesas,rcar-gen3-can" for a generic R-Car Gen3 compatible device.
@@ -24,11 +25,12 @@ Required properties:
 - pinctrl-0: pin control group to be used for this controller.
 - pinctrl-names: must be "default".
 
-Required properties for "renesas,can-r8a7795" compatible:
-In R8A7795 SoC, "clkp2" can be CANFD clock. This is a div6 clock and can be
-used by both CAN and CAN FD controller at the same time. It needs to be scaled
-to maximum frequency if any of these controllers use it. This is done using
-the below properties.
+Required properties for "renesas,can-r8a7795" and "renesas,can-r8a7796"
+compatible:
+In R8A7795 and R8A7796 SoCs, "clkp2" can be CANFD clock. This is a div6 clock
+and can be used by both CAN and CAN FD controller at the same time. It needs to
+be scaled to maximum frequency if any of these controllers use it. This is done
+using the below properties:
 
 - assigned-clocks: phandle of clkp2(CANFD) clock.
 - assigned-clock-rates: maximum frequency of this clock.
-- 
1.9.1

--
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 0/2] can: Add r8a7796 support to CAN/CAN FD
From: Chris Paterson @ 2016-11-24 16:11 UTC (permalink / raw)
  To: Marc Kleine-Budde
  Cc: Wolfgang Grandegger, Rob Herring, Mark Rutland,
	Geert Uytterhoeven, Simon Horman, Ramesh Shanmugasundaram,
	linux-can, netdev, devicetree, linux-renesas-soc, Chris Paterson
In-Reply-To: <1479903243-1860-1-git-send-email-chris.paterson2@renesas.com>

This series adds support for r8a7796 to rcar_can and rcar_canfd.

Original series: [PATCH 0/3] arm64: dts: r8a7796: Add CAN/CAN FD support.

Changes since v1:
- Split bindings documentation changes from device tree changes.
- Rebased on renesas-devel-20161123v2-v4.9-rc6.


Chris Paterson (2):
  can: rcar_can: Add r8a7796 support
  can: rcar_canfd: Add r8a7796 support

 Documentation/devicetree/bindings/net/can/rcar_can.txt   | 12 +++++++-----
 Documentation/devicetree/bindings/net/can/rcar_canfd.txt | 12 +++++++-----
 2 files changed, 14 insertions(+), 10 deletions(-)

-- 
1.9.1

^ permalink raw reply

* Re: [PATCH RESEND 2/2] gpio: axp209: add pinctrl support
From: Chen-Yu Tsai @ 2016-11-24 16:08 UTC (permalink / raw)
  To: Quentin Schulz
  Cc: Linus Walleij, Alexandre Courbot, Rob Herring, Mark Rutland,
	Chen-Yu Tsai, Maxime Ripard, linux-gpio@vger.kernel.org,
	devicetree, linux-kernel, linux-arm-kernel, Thomas Petazzoni
In-Reply-To: <20161123141151.25315-3-quentin.schulz@free-electrons.com>

On Wed, Nov 23, 2016 at 10:11 PM, Quentin Schulz
<quentin.schulz@free-electrons.com> wrote:
> The GPIOs present in the AXP209 PMIC have multiple functions. They
> typically allow a pin to be used as GPIO input or output and can also be
> used as ADC or regulator for example.[1]
>
> This adds the possibility to use all functions of the GPIOs present in
> the AXP209 PMIC thanks to pinctrl subsystem.
>
> [1] see registers 90H, 92H and 93H at
>     http://dl.linux-sunxi.org/AXP/AXP209_Datasheet_v1.0en.pdf
>
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> ---
>  .../devicetree/bindings/gpio/gpio-axp209.txt       |  28 +-
>  drivers/gpio/gpio-axp209.c                         | 551 ++++++++++++++++++---
>  2 files changed, 503 insertions(+), 76 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/gpio/gpio-axp209.txt b/Documentation/devicetree/bindings/gpio/gpio-axp209.txt
> index a661130..a5bfe87 100644
> --- a/Documentation/devicetree/bindings/gpio/gpio-axp209.txt
> +++ b/Documentation/devicetree/bindings/gpio/gpio-axp209.txt
> @@ -1,4 +1,4 @@
> -AXP209 GPIO controller
> +AXP209 GPIO & pinctrl controller
>
>  This driver follows the usual GPIO bindings found in
>  Documentation/devicetree/bindings/gpio/gpio.txt
> @@ -28,3 +28,29 @@ axp209: pmic@34 {
>                 #gpio-cells = <2>;
>         };
>  };
> +
> +The GPIOs can be muxed to other functions and therefore, must be a subnode of
> +axp_gpio.
> +
> +Example:
> +
> +&axp_gpio {
> +       gpio0_adc: gpio0_adc {
> +               pin = "GPIO0";
> +               function = "adc";
> +       };
> +};
> +
> +&example_node {
> +       pinctrl-names = "default";
> +       pinctrl-0 = <&gpio0_adc>;
> +};
> +
> +GPIOs and their functions
> +-------------------------
> +
> +GPIO   |       Functions
> +------------------------
> +GPIO0  |       gpio_in, gpio_out, ldo, adc
> +GPIO1  |       gpio_in, gpio_out, ldo, adc
> +GPIO2  |       gpio_in, gpio_out
> diff --git a/drivers/gpio/gpio-axp209.c b/drivers/gpio/gpio-axp209.c
> index 4a346b7..0a64cfc 100644
> --- a/drivers/gpio/gpio-axp209.c
> +++ b/drivers/gpio/gpio-axp209.c
> @@ -1,7 +1,8 @@
>  /*
> - * AXP20x GPIO driver
> + * AXP20x Pin control driver
>   *
>   * Copyright (C) 2016 Maxime Ripard <maxime.ripard@free-electrons.com>
> + * Copyright (C) 2016 Quentin Schulz <quentin.schulz@free-electrons.com>
>   *
>   * This program is free software; you can redistribute it and/or modify it
>   * under  the terms of the GNU General  Public License as published by the
> @@ -21,52 +22,103 @@
>  #include <linux/platform_device.h>
>  #include <linux/regmap.h>
>  #include <linux/slab.h>
> +#include <linux/pinctrl/pinctrl.h>
> +#include <linux/pinctrl/pinmux.h>
> +#include <linux/pinctrl/pinconf-generic.h>
>
>  #define AXP20X_GPIO_FUNCTIONS          0x7
>  #define AXP20X_GPIO_FUNCTION_OUT_LOW   0
>  #define AXP20X_GPIO_FUNCTION_OUT_HIGH  1
>  #define AXP20X_GPIO_FUNCTION_INPUT     2
>
> -struct axp20x_gpio {
> -       struct gpio_chip        chip;
> -       struct regmap           *regmap;
> -};
> +#define AXP20X_PINCTRL_PIN(_pin_num, _pin, _regs)              \
> +       {                                                       \
> +               .number = _pin_num,                             \
> +               .name = _pin,                                   \
> +               .drv_data = _regs,                              \
> +       }
>
> -static int axp20x_gpio_get_reg(unsigned offset)
> -{
> -       switch (offset) {
> -       case 0:
> -               return AXP20X_GPIO0_CTRL;
> -       case 1:
> -               return AXP20X_GPIO1_CTRL;
> -       case 2:
> -               return AXP20X_GPIO2_CTRL;
> +#define AXP20X_PIN(_pin, ...)                                  \
> +       {                                                       \
> +               .pin = _pin,                                    \
> +               .functions = (struct axp20x_desc_function[]) {  \
> +                             __VA_ARGS__, { } },               \
>         }
>
> -       return -EINVAL;
> -}
> +#define AXP20X_FUNCTION(_val, _name)                           \
> +       {                                                       \
> +               .name = _name,                                  \
> +               .muxval = _val,                                 \
> +       }
>
> -static int axp20x_gpio_input(struct gpio_chip *chip, unsigned offset)
> -{
> -       struct axp20x_gpio *gpio = gpiochip_get_data(chip);
> -       int reg;
> +struct axp20x_desc_function {
> +       const char      *name;
> +       u8              muxval;
> +};
>
> -       reg = axp20x_gpio_get_reg(offset);
> -       if (reg < 0)
> -               return reg;
> +struct axp20x_desc_pin {
> +       struct pinctrl_pin_desc         pin;
> +       struct axp20x_desc_function     *functions;
> +};
>
> -       return regmap_update_bits(gpio->regmap, reg,
> -                                 AXP20X_GPIO_FUNCTIONS,
> -                                 AXP20X_GPIO_FUNCTION_INPUT);
> -}
> +struct axp20x_pinctrl_desc {
> +       const struct axp20x_desc_pin    *pins;
> +       int                             npins;
> +       unsigned int                    pin_base;

You do not need pin_base.

> +};
> +
> +struct axp20x_pinctrl_function {
> +       const char      *name;
> +       const char      **groups;
> +       unsigned int    ngroups;
> +};
> +
> +struct axp20x_pinctrl_group {
> +       const char      *name;
> +       unsigned long   config;
> +       unsigned int    pin;
> +};
> +
> +struct axp20x_pctl {
> +       struct pinctrl_dev                      *pctl_dev;
> +       struct device                           *dev;
> +       struct gpio_chip                        chip;
> +       struct regmap                           *regmap;
> +       const struct axp20x_pinctrl_desc        *desc;
> +       struct axp20x_pinctrl_group             *groups;
> +       unsigned int                            ngroups;
> +       struct axp20x_pinctrl_function          *functions;
> +       unsigned int                            nfunctions;
> +};
> +
> +static const struct axp20x_desc_pin axp209_pins[] = {
> +       AXP20X_PIN(AXP20X_PINCTRL_PIN(0, "GPIO0", (void *)AXP20X_GPIO0_CTRL),
> +                  AXP20X_FUNCTION(0x0, "gpio_out"),
> +                  AXP20X_FUNCTION(0x2, "gpio_in"),
> +                  AXP20X_FUNCTION(0x3, "ldo"),
> +                  AXP20X_FUNCTION(0x4, "adc")),
> +       AXP20X_PIN(AXP20X_PINCTRL_PIN(1, "GPIO1", (void *)AXP20X_GPIO1_CTRL),
> +                  AXP20X_FUNCTION(0x0, "gpio_out"),
> +                  AXP20X_FUNCTION(0x2, "gpio_in"),
> +                  AXP20X_FUNCTION(0x3, "ldo"),
> +                  AXP20X_FUNCTION(0x4, "adc")),
> +       AXP20X_PIN(AXP20X_PINCTRL_PIN(2, "GPIO2", (void *)AXP20X_GPIO2_CTRL),
> +                  AXP20X_FUNCTION(0x0, "gpio_out"),
> +                  AXP20X_FUNCTION(0x2, "gpio_in")),
> +};
> +
> +static const struct axp20x_pinctrl_desc axp20x_pinctrl_data = {
> +       .pins   = axp209_pins,
> +       .npins  = ARRAY_SIZE(axp209_pins),
> +};
>
>  static int axp20x_gpio_get(struct gpio_chip *chip, unsigned offset)
>  {
> -       struct axp20x_gpio *gpio = gpiochip_get_data(chip);
> +       struct axp20x_pctl *pctl = gpiochip_get_data(chip);
>         unsigned int val;
>         int ret;
>
> -       ret = regmap_read(gpio->regmap, AXP20X_GPIO20_SS, &val);
> +       ret = regmap_read(pctl->regmap, AXP20X_GPIO20_SS, &val);
>         if (ret)
>                 return ret;
>
> @@ -75,15 +127,12 @@ static int axp20x_gpio_get(struct gpio_chip *chip, unsigned offset)
>
>  static int axp20x_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
>  {
> -       struct axp20x_gpio *gpio = gpiochip_get_data(chip);
> +       struct axp20x_pctl *pctl = gpiochip_get_data(chip);
> +       int pin_reg = (int)pctl->desc->pins[offset].pin.drv_data;
>         unsigned int val;
> -       int reg, ret;
> -
> -       reg = axp20x_gpio_get_reg(offset);
> -       if (reg < 0)
> -               return reg;
> +       int ret;
>
> -       ret = regmap_read(gpio->regmap, reg, &val);
> +       ret = regmap_read(pctl->regmap, pin_reg, &val);
>         if (ret)
>                 return ret;
>
> @@ -102,33 +151,335 @@ static int axp20x_gpio_get_direction(struct gpio_chip *chip, unsigned offset)
>         return val & 2;
>  }
>
> -static int axp20x_gpio_output(struct gpio_chip *chip, unsigned offset,
> +static void axp20x_gpio_set(struct gpio_chip *chip, unsigned int offset,
> +                           int value)
> +{
> +       struct axp20x_pctl *pctl = gpiochip_get_data(chip);
> +       int pin_reg = (int)pctl->desc->pins[offset].pin.drv_data;
> +
> +       regmap_update_bits(pctl->regmap, pin_reg,
> +                          AXP20X_GPIO_FUNCTIONS,
> +                          value ? AXP20X_GPIO_FUNCTION_OUT_HIGH
> +                                : AXP20X_GPIO_FUNCTION_OUT_LOW);
> +}
> +
> +static int axp20x_gpio_input(struct gpio_chip *chip, unsigned int offset)
> +{
> +       return pinctrl_gpio_direction_input(chip->base + offset);
> +}
> +
> +static int axp20x_gpio_output(struct gpio_chip *chip, unsigned int offset,
>                               int value)
>  {
> -       struct axp20x_gpio *gpio = gpiochip_get_data(chip);
> -       int reg;
> +       chip->set(chip, offset, value);
>
> -       reg = axp20x_gpio_get_reg(offset);
> -       if (reg < 0)
> -               return reg;
> +       return 0;
> +}
>
> -       return regmap_update_bits(gpio->regmap, reg,
> -                                 AXP20X_GPIO_FUNCTIONS,
> -                                 value ? AXP20X_GPIO_FUNCTION_OUT_HIGH
> -                                 : AXP20X_GPIO_FUNCTION_OUT_LOW);
> +static int axp20x_pmx_set(struct pinctrl_dev *pctldev, unsigned int offset,
> +                         u8 config)
> +{
> +       struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
> +       int pin_reg = (int)pctl->desc->pins[offset].pin.drv_data;
> +
> +       return regmap_update_bits(pctl->regmap, pin_reg, AXP20X_GPIO_FUNCTIONS,
> +                                 config);
>  }
>
> -static void axp20x_gpio_set(struct gpio_chip *chip, unsigned offset,
> -                           int value)
> +static int axp20x_pmx_func_cnt(struct pinctrl_dev *pctldev)
> +{
> +       struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
> +
> +       return pctl->nfunctions;
> +}
> +
> +static const char *axp20x_pmx_func_name(struct pinctrl_dev *pctldev,
> +                                       unsigned int selector)
> +{
> +       struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
> +
> +       return pctl->functions[selector].name;
> +}
> +
> +static int axp20x_pmx_func_groups(struct pinctrl_dev *pctldev,
> +                                 unsigned int selector,
> +                                 const char * const **groups,
> +                                 unsigned int *num_groups)
> +{
> +       struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
> +
> +       *groups = pctl->functions[selector].groups;
> +       *num_groups = pctl->functions[selector].ngroups;
> +
> +       return 0;
> +}
> +
> +static struct axp20x_desc_function *
> +axp20x_pinctrl_desc_find_func_by_name(struct axp20x_pctl *pctl,
> +                                     const char *group, const char *func)
> +{
> +       const struct axp20x_desc_pin *pin;
> +       struct axp20x_desc_function *desc_func;
> +       int i;
> +
> +       for (i = 0; i < pctl->desc->npins; i++) {
> +               pin = &pctl->desc->pins[i];
> +
> +               if (!strcmp(pin->pin.name, group)) {
> +                       desc_func = pin->functions;
> +
> +                       while (desc_func->name) {
> +                               if (!strcmp(desc_func->name, func))
> +                                       return desc_func;
> +                               desc_func++;
> +                       }
> +
> +                       /*
> +                        * Pins are uniquely named. Groups are named after one
> +                        * pin name. If one pin matches group name but its
> +                        * function cannot be found, no other pin will match
> +                        * group name.
> +                        */
> +                       return NULL;
> +               }
> +       }
> +
> +       return NULL;
> +}
> +
> +static int axp20x_pmx_set_mux(struct pinctrl_dev *pctldev,
> +                             unsigned int function, unsigned int group)
> +{
> +       struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
> +       struct axp20x_pinctrl_group *g = pctl->groups + group;
> +       struct axp20x_pinctrl_function *func = pctl->functions + function;
> +       struct axp20x_desc_function *desc_func =
> +               axp20x_pinctrl_desc_find_func_by_name(pctl, g->name,
> +                                                     func->name);
> +       if (!desc_func)
> +               return -EINVAL;
> +
> +       return axp20x_pmx_set(pctldev, g->pin, desc_func->muxval);
> +}
> +
> +static struct axp20x_desc_function *
> +axp20x_pctl_desc_find_func_by_pin(struct axp20x_pctl *pctl, unsigned int offset,
> +                                 const char *func)
> +{
> +       const struct axp20x_desc_pin *pin;
> +       struct axp20x_desc_function *desc_func;
> +       int i;
> +
> +       for (i = 0; i < pctl->desc->npins; i++) {
> +               pin = &pctl->desc->pins[i];
> +
> +               if (pin->pin.number == offset) {
> +                       desc_func = pin->functions;
> +
> +                       while (desc_func->name) {
> +                               if (!strcmp(desc_func->name, func))
> +                                       return desc_func;
> +
> +                               desc_func++;
> +                       }
> +               }
> +       }
> +
> +       return NULL;
> +}
> +
> +static int axp20x_pmx_gpio_set_direction(struct pinctrl_dev *pctldev,
> +                                        struct pinctrl_gpio_range *range,
> +                                        unsigned int offset, bool input)
> +{
> +       struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
> +       struct axp20x_desc_function *desc_func;
> +       const char *func;
> +
> +       if (input)
> +               func = "gpio_in";
> +       else
> +               func = "gpio_out";
> +
> +       desc_func = axp20x_pctl_desc_find_func_by_pin(pctl, offset, func);
> +       if (!desc_func)
> +               return -EINVAL;
> +
> +       return axp20x_pmx_set(pctldev, offset, desc_func->muxval);
> +}
> +
> +static const struct pinmux_ops axp20x_pmx_ops = {
> +       .get_functions_count    = axp20x_pmx_func_cnt,
> +       .get_function_name      = axp20x_pmx_func_name,
> +       .get_function_groups    = axp20x_pmx_func_groups,
> +       .set_mux                = axp20x_pmx_set_mux,
> +       .gpio_set_direction     = axp20x_pmx_gpio_set_direction,
> +       .strict                 = true,
> +};
> +
> +static int axp20x_groups_cnt(struct pinctrl_dev *pctldev)
> +{
> +       struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
> +
> +       return pctl->ngroups;
> +}
> +
> +static int axp20x_group_pins(struct pinctrl_dev *pctldev, unsigned int selector,
> +                            const unsigned int **pins, unsigned int *num_pins)
> +{
> +       struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
> +       struct axp20x_pinctrl_group *g = pctl->groups + selector;
> +
> +       *pins = (unsigned int *)&g->pin;
> +       *num_pins = 1;
> +
> +       return 0;
> +}
> +
> +static const char *axp20x_group_name(struct pinctrl_dev *pctldev,
> +                                    unsigned int selector)
> +{
> +       struct axp20x_pctl *pctl = pinctrl_dev_get_drvdata(pctldev);
> +
> +       return pctl->groups[selector].name;
> +}
> +
> +static const struct pinctrl_ops axp20x_pctrl_ops = {
> +       .dt_node_to_map         = pinconf_generic_dt_node_to_map_group,
> +       .dt_free_map            = pinconf_generic_dt_free_map,
> +       .get_groups_count       = axp20x_groups_cnt,
> +       .get_group_name         = axp20x_group_name,
> +       .get_group_pins         = axp20x_group_pins,
> +};
> +
> +static struct axp20x_pinctrl_function *
> +axp20x_pinctrl_function_by_name(struct axp20x_pctl *pctl, const char *name)
> +{
> +       struct axp20x_pinctrl_function *func = pctl->functions;
> +
> +       while (func->name) {
> +               if (!strcmp(func->name, name))
> +                       return func;
> +               func++;
> +       }
> +
> +       return NULL;
> +}
> +
> +static int axp20x_pinctrl_add_function(struct axp20x_pctl *pctl,
> +                                      const char *name)
>  {
> -       axp20x_gpio_output(chip, offset, value);
> +       struct axp20x_pinctrl_function *func = pctl->functions;
> +
> +       while (func->name) {
> +               if (!strcmp(func->name, name)) {
> +                       func->ngroups++;
> +                       return -EEXIST;
> +               }
> +
> +               func++;
> +       }
> +
> +       func->name = name;
> +       func->ngroups = 1;
> +
> +       pctl->nfunctions++;
> +
> +       return 0;
>  }
>
> -static int axp20x_gpio_probe(struct platform_device *pdev)
> +static int axp20x_attach_group_function(struct platform_device *pdev,
> +                                       const struct axp20x_desc_pin *pin)
> +{
> +       struct axp20x_pctl *pctl = platform_get_drvdata(pdev);
> +       struct axp20x_desc_function *desc_func = pin->functions;
> +       struct axp20x_pinctrl_function *func;
> +       const char **func_grp;
> +
> +       while (desc_func->name) {
> +               func = axp20x_pinctrl_function_by_name(pctl, desc_func->name);
> +               if (!func)
> +                       return -EINVAL;
> +
> +               if (!func->groups) {
> +                       func->groups = devm_kzalloc(&pdev->dev,
> +                                                   func->ngroups * sizeof(const char *),
> +                                                   GFP_KERNEL);
> +                       if (!func->groups)
> +                               return -ENOMEM;
> +               }
> +
> +               func_grp = func->groups;
> +               while (*func_grp)
> +                       func_grp++;
> +
> +               *func_grp = pin->pin.name;
> +               desc_func++;
> +       }
> +
> +       return 0;
> +}
> +
> +static int axp20x_build_state(struct platform_device *pdev)
> +{
> +       struct axp20x_pctl *pctl = platform_get_drvdata(pdev);
> +       unsigned int npins = pctl->desc->npins;
> +       const struct axp20x_desc_pin *pin;
> +       struct axp20x_desc_function *func;
> +       int i, ret;
> +
> +       pctl->ngroups = npins;
> +       pctl->groups = devm_kzalloc(&pdev->dev,
> +                                   pctl->ngroups * sizeof(*pctl->groups),
> +                                   GFP_KERNEL);
> +       if (!pctl->groups)
> +               return -ENOMEM;
> +
> +       for (i = 0; i < npins; i++) {
> +               pctl->groups[i].name = pctl->desc->pins[i].pin.name;
> +               pctl->groups[i].pin = pctl->desc->pins[i].pin.number;
> +       }
> +
> +       /* We assume 4 functions per pin should be enough as a default max */
> +       pctl->functions = devm_kzalloc(&pdev->dev,
> +                                      npins * 4 * sizeof(*pctl->functions),
> +                                      GFP_KERNEL);
> +       if (!pctl->functions)
> +               return -ENOMEM;
> +
> +       /* Create a list of uniquely named functions */
> +       for (i = 0; i < npins; i++) {
> +               pin = &pctl->desc->pins[i];
> +               func = pin->functions;
> +
> +               while (func->name) {
> +                       axp20x_pinctrl_add_function(pctl, func->name);
> +                       func++;
> +               }
> +       }
> +
> +       pctl->functions = krealloc(pctl->functions,
> +                                  pctl->nfunctions * sizeof(*pctl->functions),
> +                                  GFP_KERNEL);
> +
> +       for (i = 0; i < npins; i++) {
> +               pin = &pctl->desc->pins[i];
> +               ret = axp20x_attach_group_function(pdev, pin);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static int axp20x_pctl_probe(struct platform_device *pdev)
>  {
>         struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
> -       struct axp20x_gpio *gpio;
> -       int ret;
> +       const struct axp20x_desc_pin *pin;
> +       struct axp20x_pctl *pctl;
> +       struct pinctrl_desc *pctrl_desc;
> +       struct pinctrl_pin_desc *pins;
> +       int ret, i;
>
>         if (!of_device_is_available(pdev->dev.of_node))
>                 return -ENODEV;
> @@ -138,51 +489,101 @@ static int axp20x_gpio_probe(struct platform_device *pdev)
>                 return -EINVAL;
>         }
>
> -       gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
> -       if (!gpio)
> +       pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL);
> +       if (!pctl)
> +               return -ENOMEM;
> +
> +       pctl->chip.base                 = -1;
> +       pctl->chip.can_sleep            = true;
> +       pctl->chip.request              = gpiochip_generic_request;
> +       pctl->chip.free                 = gpiochip_generic_free;
> +       pctl->chip.parent               = &pdev->dev;
> +       pctl->chip.label                = dev_name(&pdev->dev);
> +       pctl->chip.owner                = THIS_MODULE;
> +       pctl->chip.get                  = axp20x_gpio_get;
> +       pctl->chip.get_direction        = axp20x_gpio_get_direction;
> +       pctl->chip.set                  = axp20x_gpio_set;
> +       pctl->chip.direction_input      = axp20x_gpio_input;
> +       pctl->chip.direction_output     = axp20x_gpio_output;
> +       pctl->chip.ngpio                = 3;
> +       pctl->chip.can_sleep            = true;
> +
> +       pctl->regmap = axp20x->regmap;
> +
> +       pctl->desc = &axp20x_pinctrl_data;
> +       pctl->dev = &pdev->dev;
> +
> +       platform_set_drvdata(pdev, pctl);
> +
> +       ret = axp20x_build_state(pdev);
> +       if (ret)
> +               return ret;
> +
> +       pins = devm_kzalloc(&pdev->dev, pctl->desc->npins * sizeof(*pins),
> +                           GFP_KERNEL);
> +       if (!pins)
>                 return -ENOMEM;
>
> -       gpio->chip.base                 = -1;
> -       gpio->chip.can_sleep            = true;
> -       gpio->chip.parent               = &pdev->dev;
> -       gpio->chip.label                = dev_name(&pdev->dev);
> -       gpio->chip.owner                = THIS_MODULE;
> -       gpio->chip.get                  = axp20x_gpio_get;
> -       gpio->chip.get_direction        = axp20x_gpio_get_direction;
> -       gpio->chip.set                  = axp20x_gpio_set;
> -       gpio->chip.direction_input      = axp20x_gpio_input;
> -       gpio->chip.direction_output     = axp20x_gpio_output;
> -       gpio->chip.ngpio                = 3;
> -
> -       gpio->regmap = axp20x->regmap;
> -
> -       ret = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio);
> +       for (i = 0; i < pctl->desc->npins; i++)
> +               pins[i] = pctl->desc->pins[i].pin;
> +
> +       pctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*pctrl_desc), GFP_KERNEL);
> +       if (!pctrl_desc)
> +               return -ENOMEM;
> +
> +       pctrl_desc->name = dev_name(&pdev->dev);
> +       pctrl_desc->owner = THIS_MODULE;
> +       pctrl_desc->pins = pins;
> +       pctrl_desc->npins = pctl->desc->npins;
> +       pctrl_desc->pctlops = &axp20x_pctrl_ops;
> +       pctrl_desc->pmxops = &axp20x_pmx_ops;
> +
> +       pctl->pctl_dev = devm_pinctrl_register(&pdev->dev, pctrl_desc, pctl);
> +       if (IS_ERR(pctl->pctl_dev)) {
> +               dev_err(&pdev->dev, "couldn't register pinctrl driver\n");
> +               return PTR_ERR(pctl->pctl_dev);
> +       }
> +
> +       ret = devm_gpiochip_add_data(&pdev->dev, &pctl->chip, pctl);
>         if (ret) {
>                 dev_err(&pdev->dev, "Failed to register GPIO chip\n");
>                 return ret;
>         }
>
> +       for (i = 0; i < pctl->desc->npins; i++) {
> +               pin = pctl->desc->pins + i;
> +
> +               ret = gpiochip_add_pin_range(&pctl->chip, dev_name(&pdev->dev),
> +                                            pin->pin.number, pin->pin.number,
> +                                            1);

The pins, unlike in sunxi, are sequential and contiguous. There's no need for
the loop. Just add them in one go.

> +               if (ret) {
> +                       dev_err(&pdev->dev, "failed to add pin range\n");
> +                       return ret;
> +               }
> +       }
> +
>         dev_info(&pdev->dev, "AXP209 GPIO driver loaded\n");
>
>         return 0;
>  }
>
> -static const struct of_device_id axp20x_gpio_match[] = {
> +static const struct of_device_id axp20x_pctl_match[] = {
>         { .compatible = "x-powers,axp209-gpio" },
>         { }
>  };
> -MODULE_DEVICE_TABLE(of, axp20x_gpio_match);
> +MODULE_DEVICE_TABLE(of, axp20x_pctl_match);
>
> -static struct platform_driver axp20x_gpio_driver = {
> -       .probe          = axp20x_gpio_probe,
> +static struct platform_driver axp20x_pctl_driver = {
> +       .probe          = axp20x_pctl_probe,
>         .driver = {
>                 .name           = "axp20x-gpio",
> -               .of_match_table = axp20x_gpio_match,
> +               .of_match_table = axp20x_pctl_match,
>         },
>  };
>
> -module_platform_driver(axp20x_gpio_driver);
> +module_platform_driver(axp20x_pctl_driver);
>
>  MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
>  MODULE_DESCRIPTION("AXP20x PMIC GPIO driver");
>  MODULE_LICENSE("GPL");
> --
> 2.9.3
>

Apart from the minor comments above, and Thomas' earlier comments,
this patch looks good to me.

ChenYu

^ permalink raw reply

* Re: [net-next PATCH v1 0/2] stmmac: dwmac-meson8b: configurable RGMII TX delay
From: Jerome Brunet @ 2016-11-24 16:08 UTC (permalink / raw)
  To: Martin Blumenstingl, linux-amlogic, devicetree, netdev, davem,
	khilman, mark.rutland, robh+dt
  Cc: linux-arm-kernel, alexandre.torgue, peppe.cavallaro, carlo
In-Reply-To: <20161124143417.10178-1-martin.blumenstingl@googlemail.com>

On Thu, 2016-11-24 at 15:34 +0100, Martin Blumenstingl wrote:
> Currently the dwmac-meson8b stmmac glue driver uses a hardcoded 1/4
> cycle TX clock delay. This seems to work fine for many boards (for
> example Odroid-C2 or Amlogic's reference boards) but there are some
> others where TX traffic is simply broken.
> There are probably multiple reasons why it's working on some boards
> while it's broken on others:
> - some of Amlogic's reference boards are using a Micrel PHY
> - hardware circuit design
> - maybe more...
> 
> This raises a question though:
> Which device is supposed to enable the TX delay when both MAC and PHY
> support it? And should we implement it for each PHY / MAC separately
> or should we think about a more generic solution (currently it's not
> possible to disable the TX delay generated by the RTL8211F PHY via
> devicetree when using phy-mode "rgmii")?
> 
> iperf3 results on my Mecool BB2 board (Meson GXM, RTL8211F PHY) with
> TX clock delay disabled on the MAC (as it's enabled in the PHY
> driver).
> TX throughput was virtually zero before:
> $ iperf3 -c 192.168.1.100 -R          
> Connecting to host 192.168.1.100, port 5201
> Reverse mode, remote host 192.168.1.100 is sending
> [  4] local 192.168.1.206 port 52828 connected to 192.168.1.100 port
> 5201
> [ ID] Interval           Transfer     Bandwidth
> [  4]   0.00-1.00   sec   108 MBytes   901
> Mbits/sec                  
> [  4]   1.00-2.00   sec  94.2 MBytes   791
> Mbits/sec                  
> [  4]   2.00-3.00   sec  96.5 MBytes   810
> Mbits/sec                  
> [  4]   3.00-4.00   sec  96.2 MBytes   808
> Mbits/sec                  
> [  4]   4.00-5.00   sec  96.6 MBytes   810
> Mbits/sec                  
> [  4]   5.00-6.00   sec  96.5 MBytes   810
> Mbits/sec                  
> [  4]   6.00-7.00   sec  96.6 MBytes   810
> Mbits/sec                  
> [  4]   7.00-8.00   sec  96.5 MBytes   809
> Mbits/sec                  
> [  4]   8.00-9.00   sec   105 MBytes   884
> Mbits/sec                  
> [  4]   9.00-10.00  sec   111 MBytes   934
> Mbits/sec                  
> - - - - - - - - - - - - - - - - - - - - - - - - -
> [ ID] Interval           Transfer     Bandwidth       Retr
> [  4]   0.00-10.00  sec  1000 MBytes   839
> Mbits/sec    0             sender
> [  4]   0.00-10.00  sec   998 MBytes   837
> Mbits/sec                  receiver
> 
> iperf Done.
> $ iperf3 -c 192.168.1.100   
> Connecting to host 192.168.1.100, port 5201
> [  4] local 192.168.1.206 port 52832 connected to 192.168.1.100 port
> 5201
> [ ID] Interval           Transfer     Bandwidth       Retr  Cwnd
> [  4]   0.00-1.01   sec  99.5 MBytes   829 Mbits/sec  117    139
> KBytes       
> [  4]   1.01-2.00   sec   105 MBytes   884 Mbits/sec  129   70.7
> KBytes       
> [  4]   2.00-3.01   sec   107 MBytes   889 Mbits/sec  106    187
> KBytes       
> [  4]   3.01-4.01   sec   105 MBytes   878 Mbits/sec   92    143
> KBytes       
> [  4]   4.01-5.00   sec   105 MBytes   882 Mbits/sec  140    129
> KBytes       
> [  4]   5.00-6.01   sec   106 MBytes   883 Mbits/sec  115    195
> KBytes       
> [  4]   6.01-7.00   sec   102 MBytes   863 Mbits/sec  133   70.7
> KBytes       
> [  4]   7.00-8.01   sec   106 MBytes   884 Mbits/sec  143   97.6
> KBytes       
> [  4]   8.01-9.01   sec   104 MBytes   875 Mbits/sec  124    107
> KBytes       
> [  4]   9.01-10.01  sec   105 MBytes   876 Mbits/sec   90    139
> KBytes       
> - - - - - - - - - - - - - - - - - - - - - - - - -
> [ ID] Interval           Transfer     Bandwidth       Retr
> [  4]   0.00-10.01  sec  1.02 GBytes   874
> Mbits/sec  1189             sender
> [  4]   0.00-10.01  sec  1.02 GBytes   873
> Mbits/sec                  receiver
> 

Cool, one more board working ;)
I tried your patch on another board (MXQ_V2.1, with sheep printed on
the PCB). It 's not improving the situation for this one unfortunately.
Actually I already tried playing with the TX delay on the MAC and PHY
but I could get any good results with the boards I have.

It is strange that we can adjust the delay by 2ns steps, when delay
seens by the phy should be 2ns ...

> iperf Done.
> 
> 
> Martin Blumenstingl (2):
>   net: dt-bindings: add RGMII TX delay configuration to meson8b-dwmac
>   net: stmmac: dwmac-meson8b: make the RGMII TX delay configurable
> 
>  Documentation/devicetree/bindings/net/meson-dwmac.txt | 11
> +++++++++++
>  drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c   | 16
> +++++++++++-----
>  include/dt-bindings/net/dwmac-meson8b.h               | 18
> ++++++++++++++++++
>  3 files changed, 40 insertions(+), 5 deletions(-)
>  create mode 100644 include/dt-bindings/net/dwmac-meson8b.h
> 

^ permalink raw reply

* Re: [PATCH 1/3] of: Support parsing phandle argument lists through a nexus node
From: Pantelis Antoniou @ 2016-11-24 16:05 UTC (permalink / raw)
  To: Stephen Boyd
  Cc: Rob Herring, Frank Rowand, linux-arm-kernel,
	linux-kernel @ vger . kernel . org, devicetree, linux-gpio,
	Linus Walleij, Mark Brown
In-Reply-To: <20161124102529.20212-2-stephen.boyd@linaro.org>

Hi Stephen,

> On Nov 24, 2016, at 12:25 , Stephen Boyd <stephen.boyd@linaro.org> wrote:
> 
> Platforms like 96boards have a standardized connector/expansion
> slot that exposes signals like GPIOs to expansion boards in an
> SoC agnostic way. We'd like the DT overlays for the expansion
> boards to be written once without knowledge of the SoC on the
> other side of the connector. This avoids the unscalable
> combinatorial explosion of a different DT overlay for each
> expansion board and SoC pair.
> 
> We need a way to describe the GPIOs routed through the connector
> in an SoC agnostic way. Let's introduce nexus property parsing
> into the OF core to do this. This is largely based on the
> interrupt nexus support we already have. This allows us to remap
> a phandle list in a consumer node (e.g. reset-gpios) through a
> connector in a generic way (e.g. via gpio-map). Do this in a
> generic routine so that we can remap any sort of variable length
> phandle list.
> 
> Taking GPIOs as an example, the connector would be a GPIO nexus,
> supporting the remapping of a GPIO specifier space to multiple
> GPIO providers on the SoC. DT would look as shown below, where
> 'soc_gpio1' and 'soc_gpio2' are inside the SoC, 'connector' is an
> expansion port where boards can be plugged in, and
> 'expansion_device' is a device on the expansion board.
> 
> 	soc {
> 		soc_gpio1: gpio-controller1 {
> 			#gpio-cells = <2>;
> 		};
> 
> 		soc_gpio2: gpio-controller2 {
> 			#gpio-cells = <2>;
> 		};
> 	};
> 
> 	connector: connector {
> 		#gpio-cells = <2>;
> 		gpio-map = <0 GPIO_ACTIVE_LOW &soc_gpio1 1 GPIO_ACTIVE_LOW>,
> 			   <1 GPIO_ACTIVE_LOW &soc_gpio2 4 GPIO_ACTIVE_LOW>,
> 			   <2 GPIO_ACTIVE_LOW &soc_gpio1 3 GPIO_ACTIVE_LOW>,
> 			   <3 GPIO_ACTIVE_LOW &soc_gpio2 2 GPIO_ACTIVE_LOW>;
> 		gpio-map-mask = <0xf 0x1>;
> 	};
> 
> 	expansion_device {
> 		reset-gpios = <&connector 2 GPIO_ACTIVE_LOW>;
> 	};
> 
> The GPIO core would use of_parse_phandle_with_args_map() instead
> of of_parse_phandle_with_args() and arrive at the same type of
> result, a phandle and argument list. The difference is that the
> phandle and arguments will be remapped through the nexus node to
> the underlying SoC GPIO controller node. In the example above,
> we would remap 'reset-gpios' from <&connector 2 GPIO_ACTIVE_LOW>
> to <&soc_gpio1 3 GPIO_ACTIVE_LOW>.
> 

Very good. My only point would be to elaborate a little bit on the
documentation part about how there might be different #list-cells values
pointed at, and how the lookup is performed in steps.

> Cc: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
> Cc: Linus Walleij <linus.walleij@linaro.org>
> Cc: Mark Brown <broonie@kernel.org>
> Signed-off-by: Stephen Boyd <stephen.boyd@linaro.org>
> ---
> drivers/of/base.c  | 146 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> include/linux/of.h |  14 +++++
> 2 files changed, 160 insertions(+)
> 
> diff --git a/drivers/of/base.c b/drivers/of/base.c
> index d687e6de24a0..693b73f33675 100644
> --- a/drivers/of/base.c
> +++ b/drivers/of/base.c
> @@ -1772,6 +1772,152 @@ int of_parse_phandle_with_args(const struct device_node *np, const char *list_na
> EXPORT_SYMBOL(of_parse_phandle_with_args);
> 
> /**
> + * of_parse_phandle_with_args_map() - Find a node pointed by phandle in a list and remap it
> + * @np:		pointer to a device tree node containing a list
> + * @list_name:	property name that contains a list
> + * @cells_name:	property name that specifies phandles' arguments count
> + * @index:	index of a phandle to parse out
> + * @out_args:	optional pointer to output arguments structure (will be filled)
> + *
> + * This function is useful to parse lists of phandles and their arguments.
> + * Returns 0 on success and fills out_args, on error returns appropriate
> + * errno value.
> + *
> + * Caller is responsible to call of_node_put() on the returned out_args->np
> + * pointer.
> + *
> + * Example:
> + *
> + * phandle1: node1 {
> + *	#list-cells = <2>;
> + * }
> + *
> + * phandle2: node2 {
> + *	#list-cells = <1>;
> + * }
> + *
> + * phandle3: node3 {
> + * 	#list-cells = <1>;
> + * 	list-map = <0 &phandle2 3>,
> + * 		   <1 &phandle2 2>,
> + * 		   <2 &phandle1 5 1>;
> + *	list-map-mask = <0x3>;
> + * };
> + *
> + * node4 {
> + *	list = <&phandle1 1 2 &phandle3 0>;
> + * }
> + *
> + * To get a device_node of the `node2' node you may call this:
> + * of_parse_phandle_with_args(node4, "list", "#list-cells", "list-map",
> + * 			      "list-map-mask", 1, &args);
> + */
> +int of_parse_phandle_with_args_map(const struct device_node *np,
> +				   const char *list_name,
> +				   const char *cells_name,
> +				   const char *map_name,
> +				   const char *mask_name,
> +				   int index, struct of_phandle_args *out_args)
> +{
> +	struct device_node *cur, *new = NULL;
> +	const __be32 *map, *mask, *tmp;
> +	const __be32 dummy_mask[] = { [0 ... MAX_PHANDLE_ARGS] = ~0 };
> +	__be32 initial_match_array[MAX_PHANDLE_ARGS];
> +	const __be32 *match_array = initial_match_array;
> +	int i, ret, map_len, match;
> +	u32 list_size, new_size;
> +
> +	if (index < 0)
> +		return -EINVAL;
> +
> +	ret = __of_parse_phandle_with_args(np, list_name, cells_name, 0, index,
> +					   out_args);
> +	if (ret)
> +		return ret;
> +
> +	/* Get the #<list>-cells property */
> +	cur = out_args->np;
> +	ret = of_property_read_u32(cur, cells_name, &list_size);
> +	if (ret < 0)
> +		goto fail;
> +
> +	/* Precalculate the match array - this simplifies match loop */
> +	for (i = 0; i < list_size; i++)
> +		initial_match_array[i] = cpu_to_be32(out_args->args[i]);
> +
> +	while (cur) {
> +		/* Get the <list>-map property */
> +		map = of_get_property(cur, map_name, &map_len);
> +		if (!map)
> +			return 0;
> +		map_len /= sizeof(u32);
> +
> +		/* Get the <list>-map-mask property (optional) */
> +		mask = of_get_property(cur, mask_name, NULL);
> +		if (!mask)
> +			mask = dummy_mask;
> +
> +		/* Iterate through <list>-map property */
> +		match = 0;
> +		while (map_len > (list_size + 1) && !match) {
> +			/* Compare specifiers */
> +			match = 1;
> +			for (i = 0; i < list_size; i++, map_len--)
> +				match &= !((match_array[i] ^ *map++) & mask[i]);
> +
> +			of_node_put(new);
> +			new = of_find_node_by_phandle(be32_to_cpup(map));
> +			map++;
> +			map_len--;
> +
> +			/* Check if not found */
> +			if (!new)
> +				goto fail;
> +
> +			if (!of_device_is_available(new))
> +				match = 0;
> +
> +			tmp = of_get_property(new, cells_name, NULL);
> +			if (!tmp)
> +				goto fail;
> +
> +			new_size = be32_to_cpu(*tmp);
> +
> +			/* Check for malformed properties */
> +			if (WARN_ON(new_size > MAX_PHANDLE_ARGS))
> +				goto fail;
> +			if (map_len < new_size)
> +				goto fail;
> +
> +			/* Move forward by new node's #<list>-cells amount */
> +			map += new_size;
> +			map_len -= new_size;
> +		}
> +		if (!match)
> +			goto fail;
> +
> +		/*
> +		 * Successfully parsed a <list>-map translation; copy new
> +		 * specifier into the out_args structure.
> +		 */
> +		match_array = map - new_size;
> +		for (i = 0; i < new_size; i++)
> +			out_args->args[i] = be32_to_cpup(map - new_size + i);
> +		out_args->args_count = list_size = new_size;
> +		/* Iterate again with new provider */
> +		out_args->np = new;
> +		of_node_put(cur);
> +		cur = new;
> +	}
> +fail:
> +	of_node_put(cur);
> +	of_node_put(new);
> +
> +	return -EINVAL;
> +}
> +EXPORT_SYMBOL(of_parse_phandle_with_args_map);
> +
> +/**
>  * of_parse_phandle_with_fixed_args() - Find a node pointed by phandle in a list
>  * @np:		pointer to a device tree node containing a list
>  * @list_name:	property name that contains a list
> diff --git a/include/linux/of.h b/include/linux/of.h
> index d3a9c2e69001..65ff306403a2 100644
> --- a/include/linux/of.h
> +++ b/include/linux/of.h
> @@ -344,6 +344,9 @@ extern struct device_node *of_parse_phandle(const struct device_node *np,
> extern int of_parse_phandle_with_args(const struct device_node *np,
> 	const char *list_name, const char *cells_name, int index,
> 	struct of_phandle_args *out_args);
> +extern int of_parse_phandle_with_args_map(const struct device_node *np,
> +	const char *list_name, const char *cells_name, const char *map_name,
> +	const char *mask_name, int index, struct of_phandle_args *out_args);
> extern int of_parse_phandle_with_fixed_args(const struct device_node *np,
> 	const char *list_name, int cells_count, int index,
> 	struct of_phandle_args *out_args);
> @@ -738,6 +741,17 @@ static inline int of_parse_phandle_with_args(const struct device_node *np,
> 	return -ENOSYS;
> }
> 
> +static inline int of_parse_phandle_with_args_map(const struct device_node *np,
> +						 const char *list_name,
> +						 const char *cells_name,
> +						 const char *map_name,
> +						 const char *mask_name,
> +						 int index,
> +						 struct of_phandle_args *out_args)
> +{
> +	return -ENOSYS;
> +}
> +
> static inline int of_parse_phandle_with_fixed_args(const struct device_node *np,
> 	const char *list_name, int cells_count, int index,
> 	struct of_phandle_args *out_args)
> -- 
> 2.10.0.297.gf6727b0
> 


^ permalink raw reply

* Re: [RFC PATCH net v2 0/3] Fix OdroidC2 Gigabit Tx link issue
From: Jerome Brunet @ 2016-11-24 16:01 UTC (permalink / raw)
  To: Martin Blumenstingl
  Cc: netdev-u79uwXL29TY76Z2rM5mHXA, devicetree-u79uwXL29TY76Z2rM5mHXA,
	Florian Fainelli, Carlo Caione, Kevin Hilman, Giuseppe Cavallaro,
	Alexandre TORGUE, Andre Roth, Neil Armstrong,
	linux-amlogic-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <CAFBinCCexmS_z9FCX-ud5NgGhhP7xJ_cLxpC7TEc=mLAdafosg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>

On Thu, 2016-11-24 at 15:40 +0100, Martin Blumenstingl wrote:
> Hi Jerome,
> 
> On Mon, Nov 21, 2016 at 4:35 PM, Jerome Brunet <jbrunet-rdvid1DuHRBWk0Htik3J/w@public.gmane.org>
> wrote:
> > 
> > This patchset fixes an issue with the OdroidC2 board (DWMAC +
> > RTL8211F).
> > Initially reported as a low Tx throughput issue at gigabit speed,
> > the
> > platform enters LPI too often. This eventually break the link (both
> > Tx
> > and Rx), and require to bring the interface down and up again to
> > get the
> > Rx path working again.
> > 
> > The root cause of this issue is not fully understood yet but
> > disabling EEE
> > advertisement on the PHY prevent this feature to be negotiated.
> > With this change, the link is stable and reliable, with the
> > expected
> > throughput performance.
> I have just sent a series which allows configuring the TX delay on
> the
> MAC (dwmac-meson8b glue) side: [0]
> Disabling the TX delay generated by the MAC fixes TX throughput for
> me, even when leaving EEE enabled in the RTL8211F PHY driver!
> 
> Unfortunately the RTL8211F PHY is a black-box for the community
> because there is no public datasheeet available.
> *maybe* (pure speculation!) they're enabling the TX delay based on
> some internal magic only when EEE is enabled.

Hi already tried acting on the register setting the TX_delay. I also
tried on the PHY. I never been able to improve situation on the
Odroic2. Only disabling EEE improved the situation.

To make sure, i tried again with your patch but the result remains
unchanged. With Tx_delay disabled (either the mac or the phy), the
situation is even worse, it seems that nothing gets through

> 
> Jerome, could you please re-test the behavior on your Odroid-C2 when
> you have EEE still enabled but the TX-delay disabled?
> In my case throughput is fine, and "$ ethtool -S eth0 | grep lpi"
> gives:
>     irq_tx_path_in_lpi_mode_n: 0
>     irq_tx_path_exit_lpi_mode_n: 0
>     irq_rx_path_in_lpi_mode_n: 0
>     irq_rx_path_exit_lpi_mode_n: 0
> 

I still have lpi interrupts on my side. I don't get how a properly
configured tx_delay would disable EEE. I must be missing something
here.

> 
> Regards,
> Martin
> 
> 
> [0] http://lists.infradead.org/pipermail/linux-amlogic/2016-November/
> 001674.html
--
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 v5 2/2] DW DMAC: add multi-block property to device tree
From: Andy Shevchenko @ 2016-11-24 15:58 UTC (permalink / raw)
  To: Eugeniy Paltsev, devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	vinod.koul-ral2JQCrhuEAvxtiuMwx3w,
	dmaengine-u79uwXL29TY76Z2rM5mHXA, arnd-r2nGTMty4D4,
	linux-snps-arc-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	vireshk-DgEjT+Ai2ygdnm+yROfE0A,
	shiraz.linux.kernel-Re5JQEeQqe8AvxtiuMwx3w,
	christian.ruppert-Yycd8EPnGM5BDgjK7y7TUQ
In-Reply-To: <1480002728.20074.15.camel-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>

On Thu, 2016-11-24 at 17:52 +0200, Andy Shevchenko wrote:
> On Thu, 2016-11-24 at 18:04 +0300, Eugeniy Paltsev wrote:
> > Several versions of DW DMAC have multi block transfers hardware
> > support. Hardware support of multi block transfers is disabled
> > by default if we use DT to configure DMAC and software emulation
> > of multi block transfers used instead.
> > Add multi-block property, so it is possible to enable hardware
> > multi block transfers (if present) via DT.
> > 
> > Switch from per device is_nollp variable to multi_block array
> > to be able enable/disable multi block transfers separately per
> > channel.
> 
> Thanks for an update. Basically I'm fine with this one.
> 
> So, we still have question about autoconfiguration in SPEAr SoCs, and
> your ARC SoC but it's a different story. I would expect once you will
> clarify it.
> 
> Another one is minor listed below, otherwise
> 
> Acked-by: Andy Shevchenko <andriy.shevchenko-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>

> > @@ -152,6 +154,11 @@ dw_dma_parse_dt(struct platform_device *pdev)
> >  			pdata->data_width[tmp] = BIT(arr[tmp] &
> > 0x07);
> >  	}
> >  
> > +	if (!of_property_read_u32_array(np, "multi-block", chan,
> > nr_channels)) {
> > +		for (tmp = 0; tmp < nr_channels; tmp++)
> > +			pdata->multi_block[tmp] = chan[tmp];
> 
> ...mb (as short of multi-block) would suit better.

Oh, sorry, guys, but one more important thing. If there is no such
property, keep a default to "supported". Otherwise you will break old
(working) DTBs.

-- 
Andy Shevchenko <andriy.shevchenko-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>
Intel Finland Oy
--
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: [net-next PATCH v1 0/2] stmmac: dwmac-meson8b: configurable RGMII TX delay
From: Jerome Brunet @ 2016-11-24 15:56 UTC (permalink / raw)
  To: Martin Blumenstingl, linux-amlogic, devicetree, netdev, davem,
	khilman, mark.rutland, robh+dt
  Cc: linux-arm-kernel, alexandre.torgue, peppe.cavallaro, carlo
In-Reply-To: <20161124143417.10178-1-martin.blumenstingl@googlemail.com>

On Thu, 2016-11-24 at 15:34 +0100, Martin Blumenstingl wrote:
> Currently the dwmac-meson8b stmmac glue driver uses a hardcoded 1/4
> cycle TX clock delay. This seems to work fine for many boards (for
> example Odroid-C2 or Amlogic's reference boards) but there are some
> others where TX traffic is simply broken.
> There are probably multiple reasons why it's working on some boards
> while it's broken on others:
> - some of Amlogic's reference boards are using a Micrel PHY
> - hardware circuit design
> - maybe more...
> 
> This raises a question though:
> Which device is supposed to enable the TX delay when both MAC and PHY
> support it? And should we implement it for each PHY / MAC separately
> or should we think about a more generic solution (currently it's not
> possible to disable the TX delay generated by the RTL8211F PHY via
> devicetree when using phy-mode "rgmii")?

Actually you can skip the part which activate the Tx-delay on the phy
by setting "phy-mode = "rgmii-id" instead of "rgmii"

phy->interface will no longer be PHY_INTERFACE_MODE_RGMII
but PHY_INTERFACE_MODE_RGMII_ID.

> 
> iperf3 results on my Mecool BB2 board (Meson GXM, RTL8211F PHY) with
> TX clock delay disabled on the MAC (as it's enabled in the PHY
> driver).
> TX throughput was virtually zero before:
> $ iperf3 -c 192.168.1.100 -R          
> Connecting to host 192.168.1.100, port 5201
> Reverse mode, remote host 192.168.1.100 is sending
> [  4] local 192.168.1.206 port 52828 connected to 192.168.1.100 port
> 5201
> [ ID] Interval           Transfer     Bandwidth
> [  4]   0.00-1.00   sec   108 MBytes   901
> Mbits/sec                  
> [  4]   1.00-2.00   sec  94.2 MBytes   791
> Mbits/sec                  
> [  4]   2.00-3.00   sec  96.5 MBytes   810
> Mbits/sec                  
> [  4]   3.00-4.00   sec  96.2 MBytes   808
> Mbits/sec                  
> [  4]   4.00-5.00   sec  96.6 MBytes   810
> Mbits/sec                  
> [  4]   5.00-6.00   sec  96.5 MBytes   810
> Mbits/sec                  
> [  4]   6.00-7.00   sec  96.6 MBytes   810
> Mbits/sec                  
> [  4]   7.00-8.00   sec  96.5 MBytes   809
> Mbits/sec                  
> [  4]   8.00-9.00   sec   105 MBytes   884
> Mbits/sec                  
> [  4]   9.00-10.00  sec   111 MBytes   934
> Mbits/sec                  
> - - - - - - - - - - - - - - - - - - - - - - - - -
> [ ID] Interval           Transfer     Bandwidth       Retr
> [  4]   0.00-10.00  sec  1000 MBytes   839
> Mbits/sec    0             sender
> [  4]   0.00-10.00  sec   998 MBytes   837
> Mbits/sec                  receiver
> 
> iperf Done.
> $ iperf3 -c 192.168.1.100   
> Connecting to host 192.168.1.100, port 5201
> [  4] local 192.168.1.206 port 52832 connected to 192.168.1.100 port
> 5201
> [ ID] Interval           Transfer     Bandwidth       Retr  Cwnd
> [  4]   0.00-1.01   sec  99.5 MBytes   829 Mbits/sec  117    139
> KBytes       
> [  4]   1.01-2.00   sec   105 MBytes   884 Mbits/sec  129   70.7
> KBytes       
> [  4]   2.00-3.01   sec   107 MBytes   889 Mbits/sec  106    187
> KBytes       
> [  4]   3.01-4.01   sec   105 MBytes   878 Mbits/sec   92    143
> KBytes       
> [  4]   4.01-5.00   sec   105 MBytes   882 Mbits/sec  140    129
> KBytes       
> [  4]   5.00-6.01   sec   106 MBytes   883 Mbits/sec  115    195
> KBytes       
> [  4]   6.01-7.00   sec   102 MBytes   863 Mbits/sec  133   70.7
> KBytes       
> [  4]   7.00-8.01   sec   106 MBytes   884 Mbits/sec  143   97.6
> KBytes       
> [  4]   8.01-9.01   sec   104 MBytes   875 Mbits/sec  124    107
> KBytes       
> [  4]   9.01-10.01  sec   105 MBytes   876 Mbits/sec   90    139
> KBytes       
> - - - - - - - - - - - - - - - - - - - - - - - - -
> [ ID] Interval           Transfer     Bandwidth       Retr
> [  4]   0.00-10.01  sec  1.02 GBytes   874
> Mbits/sec  1189             sender
> [  4]   0.00-10.01  sec  1.02 GBytes   873
> Mbits/sec                  receiver
> 
> iperf Done.

> 
> 
> Martin Blumenstingl (2):
>   net: dt-bindings: add RGMII TX delay configuration to meson8b-dwmac
>   net: stmmac: dwmac-meson8b: make the RGMII TX delay configurable
> 
>  Documentation/devicetree/bindings/net/meson-dwmac.txt | 11
> +++++++++++
>  drivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c   | 16
> +++++++++++-----
>  include/dt-bindings/net/dwmac-meson8b.h               | 18
> ++++++++++++++++++
>  3 files changed, 40 insertions(+), 5 deletions(-)
>  create mode 100644 include/dt-bindings/net/dwmac-meson8b.h
> 

^ permalink raw reply

* Re: [PATCH v5 2/2] DW DMAC: add multi-block property to device tree
From: Andy Shevchenko @ 2016-11-24 15:52 UTC (permalink / raw)
  To: Eugeniy Paltsev, devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	vinod.koul-ral2JQCrhuEAvxtiuMwx3w,
	dmaengine-u79uwXL29TY76Z2rM5mHXA, arnd-r2nGTMty4D4,
	linux-snps-arc-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	vireshk-DgEjT+Ai2ygdnm+yROfE0A,
	shiraz.linux.kernel-Re5JQEeQqe8AvxtiuMwx3w,
	christian.ruppert-Yycd8EPnGM5BDgjK7y7TUQ
In-Reply-To: <1479999878-19120-3-git-send-email-Eugeniy.Paltsev-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>

On Thu, 2016-11-24 at 18:04 +0300, Eugeniy Paltsev wrote:
> Several versions of DW DMAC have multi block transfers hardware
> support. Hardware support of multi block transfers is disabled
> by default if we use DT to configure DMAC and software emulation
> of multi block transfers used instead.
> Add multi-block property, so it is possible to enable hardware
> multi block transfers (if present) via DT.
> 
> Switch from per device is_nollp variable to multi_block array
> to be able enable/disable multi block transfers separately per
> channel.

Thanks for an update. Basically I'm fine with this one.

So, we still have question about autoconfiguration in SPEAr SoCs, and
your ARC SoC but it's a different story. I would expect once you will
clarify it.

Another one is minor listed below, otherwise

Acked-by: Andy Shevchenko <andriy.shevchenko-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>

> Signed-off-by: Eugeniy Paltsev <Eugeniy.Paltsev-HKixBCOQz3hWk0Htik3J/w@public.gmane.org>

> --- a/drivers/dma/dw/platform.c
> +++ b/drivers/dma/dw/platform.c
> @@ -102,7 +102,7 @@ dw_dma_parse_dt(struct platform_device *pdev)
>  {
>  	struct device_node *np = pdev->dev.of_node;
>  	struct dw_dma_platform_data *pdata;
> -	u32 tmp, arr[DW_DMA_MAX_NR_MASTERS];
> +	u32 tmp, arr[DW_DMA_MAX_NR_MASTERS],
> chan[DW_DMA_MAX_NR_CHANNELS];

chan here will confuse people...

> @@ -152,6 +154,11 @@ dw_dma_parse_dt(struct platform_device *pdev)
>  			pdata->data_width[tmp] = BIT(arr[tmp] &
> 0x07);
>  	}
>  
> +	if (!of_property_read_u32_array(np, "multi-block", chan,
> nr_channels)) {
> +		for (tmp = 0; tmp < nr_channels; tmp++)
> +			pdata->multi_block[tmp] = chan[tmp];

...mb (as short of multi-block) would suit better.

-- 
Andy Shevchenko <andriy.shevchenko-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>
Intel Finland Oy
--
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: [net-next PATCH v1 1/2] net: dt-bindings: add RGMII TX delay configuration to meson8b-dwmac
From: Andrew Lunn @ 2016-11-24 15:48 UTC (permalink / raw)
  To: Martin Blumenstingl
  Cc: linux-amlogic, devicetree, netdev, davem, khilman, mark.rutland,
	robh+dt, linux-arm-kernel, alexandre.torgue, peppe.cavallaro,
	carlo, jbrunet
In-Reply-To: <20161124143417.10178-2-martin.blumenstingl@googlemail.com>

> The configuration values are provided as preprocessor macros to make the
> devicetree files easier to read.

Hi Martin

If i'm reading the code/comments correctly, you can set the delay to
0, 2, 4 or 6ns? So calling this property amlogic,tx-delay-ns would be
even easier to read.

     Andrew

^ permalink raw reply

* GOOD DAY FRIEND
From: Mr. Piyush Gupta @ 2016-11-24 15:39 UTC (permalink / raw)




-- 
Dear Friend,

Good Day, I am Mr. Piyush Gupta as a  (DBSHK) banker, I have funds worth 
of $25.500,000.00 to secretly secure and transfer in to your account of 
my late client who dead with next of kin, which i will like to invest in 
profitable business in your country.

Please if you are interested to help me without betrayed me, do and 
write me for more details. Here is my Email: infopiyushg-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org

Regards,
Mr. Piyush Gupta.
(DBSHK)
Hong Kong.
--
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 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
From: Ziji Hu @ 2016-11-24 15:37 UTC (permalink / raw)
  To: Ulf Hansson
  Cc: Gregory CLEMENT, Adrian Hunter, linux-mmc@vger.kernel.org,
	Jason Cooper, Andrew Lunn, Sebastian Hesselbarth, Rob Herring,
	devicetree@vger.kernel.org, Thomas Petazzoni,
	linux-arm-kernel@lists.infradead.org, Jimmy Xu, Jisheng Zhang,
	Nadav Haklai, Ryan Gao, Doug Jones, Victor Gu, Wei(SOCP) Liu,
	Wilson Ding
In-Reply-To: <CAPDyKFrskyZvMwhmj8ioyaowU8GL5=d-6ipGQcmmig_a9E9AQQ@mail.gmail.com>

Hi Ulf,

On 2016/11/24 22:33, Ulf Hansson wrote:
> [...]
> 
>>>
>>>>
>>>> +
>>>> +static int __xenon_emmc_delay_adj_test(struct mmc_card *card)
>>>> +{
>>>> +       int err;
>>>> +       u8 *ext_csd = NULL;
>>>> +
>>>> +       err = mmc_get_ext_csd(card, &ext_csd);
>>>> +       kfree(ext_csd);
>>>
>>> Why do you read the ext csd here?
>>>
>>    I would like to simply introduce the PHY setting of our SDHC.
>>    The target of the PHY setting is to achieve a perfect sampling
>>    point for transfers, during card initialization.
> 
> Okay, so the phy is involved when running the tuning sequence.
> 
    Actually, all the transfers pass our host PHY.

>>
>>    For HS200/HS400/SDR104 whose SDCLK is more than 50MHz, SDHC HW
>>    will search for this sampling point with DLL's help.
> 
> Apologize for my ignorance, but what is a "DLL" in this case?
> 
   DLL is Delay-locked Loop. It is a HW module similar to PLL.

>>
>>    For other speed mode whose SDLCK is less than or equals to 50MHz,
>>    SW has to scan the PHY delay line to find out this perfect sampling
>>    point. Our driver sends a command to verify a sampling point
>>    in current environment.
> 
> Ahh, okay! I guess the important part here is to not only send a
> command, but also to make sure data becomes transferred on the DAT
> lines, as to confirm your tuning sequence!?

   Yes.
   It is the best if the test command can transfer on DAT lines.

> 
> In cases of HS200/HS400/SDR104 you should be able to use the
> mmc_send_tuning() API, don't you think?

   For HS200/HS400/SDR104, we finally call sdhci_execute_tuning() to
   execute tuning. Those test commands are not used.
   In HS200/HS400/SDR104, HW will provide our host driver with suitable
   tuning step. Our host driver set the tuning step in SDHCI register and
   then start standard tuning sequence. The tuning step value provided
   by our host HW will enhance tuning. 

> 
> For the other cases (lower speed modes) which cards doesn't support
> the tuning command, perhaps you can just assume the PHY scan succeeded
> and then allow to core to continue with the card initialization
> sequence? Or do you foresee any issues with that? My point is that, if
> it will fail - it will fail anyway.

  Usually, our host driver will always successfully scan and select a
  perfect sampling point.
  If driver cannot find any suitable sampling point, it is likely that
  transfers will also fail after init. But usually it is a issue, caused by
  incorrect setting on boards/SOC/other PHY parameters, especially in development.
  We will fix the issue and then scan will succeed in final product.

> 
>>
>>    As result, our SDHC driver has to implement the functionality to
>>    send commands and check the results, in host layer.
>>    If directly calling mmc_wait_for_cmd() is improper, could you please
>>    give us some suggestions?
>>
>>    For eMMC, CMD8 is used to test current sampling point set in PHY.
> 
> Try to use mmc_send_tuning().
> 

    Could you please tell me the requirement of "op_code" parameter in
    mmc_send_tuning()?
    According to mmc_send_tuning(),it seems that tuning command(CMD19/CMD21)
    is required. Thus device will not response mmc_send_tuning() if current
    speed mode doesn't support tuning command.
    Please correct me if I am wrong.
    
>>
>>>> +
>>>> +       return err;
>>>> +}
>>>> +
>>>> +static int __xenon_sdio_delay_adj_test(struct mmc_card *card)
>>>> +{
>>>> +       struct mmc_command cmd = {0};
>>>> +       int err;
>>>> +
>>>> +       cmd.opcode = SD_IO_RW_DIRECT;
>>>> +       cmd.flags = MMC_RSP_R5 | MMC_CMD_AC;
>>>> +
>>>> +       err = mmc_wait_for_cmd(card->host, &cmd, 0);
>>>> +       if (err)
>>>> +               return err;
>>>> +
>>>> +       if (cmd.resp[0] & R5_ERROR)
>>>> +               return -EIO;
>>>> +       if (cmd.resp[0] & R5_FUNCTION_NUMBER)
>>>> +               return -EINVAL;
>>>> +       if (cmd.resp[0] & R5_OUT_OF_RANGE)
>>>> +               return -ERANGE;
>>>> +       return 0;
>>>
>>> No thanks! MMC/SD/SDIO protocol code belongs in the core.
>>>
>>    For SDIO, SD_IO_RW_DIRECT command is sent to test current sampling point
>>    in PHY.
>>    Please help provide some suggestion to implement the command transfer.
> 
> Again, I think mmc_send_tuning() should be possible for you to use.
> 
> [...]
> 
>>>> +       if (mmc->card)
>>>> +               card = mmc->card;
>>>> +       else
>>>> +               /*
>>>> +                * Only valid during initialization
>>>> +                * before mmc->card is set
>>>> +                */
>>>> +               card = priv->card_candidate;
>>>> +       if (unlikely(!card)) {
>>>> +               dev_warn(mmc_dev(mmc), "card is not present\n");
>>>> +               return -EINVAL;
>>>> +       }
>>>
>>> That your host need to hold a copy of the card pointer, tells me that
>>> something is not really correct.
>>>
>>> I might be wrong, if this turns out to be a special case, but I doubt
>>> it. Although, if it *is* a special such case, we shall most likely try
>>> to extend the the mmc core layer instead of adding all these hacks in
>>> your host driver.
>>>
>>     This card pointer copies the temporary structure mmc_card
>>     used in mmc_init_card(), mmc_sd_init_card() and mmc_sdio_init_card().
>>     Since we call mmc_wait_for_cmd() to send test commands, we need a copy
>>     of that temporary mmc_card here in our host driver.
> 
> I see, thanks for clarifying.
> 
>>
>>     During PHY setting in card initialization, mmc_host->card is not updated
>>     yet with that temporary mmc_card. Thus we are not able to directly use
>>     mmc_host->card. Instead, this card pointer is introduced to enable
>>     mmc_wait_for_cmd().
>>
>>     If we can improve our host driver to send test commands without mmc_card,
>>     this card pointer can be removed.
>>     Could you please share your opinion please?
> 
> The mmc_send_tuning() API takes the mmc_host as parameter. If you
> convert to that, perhaps you would be able to remove the need to hold
> the card pointer.
> 
> BTW, the reason why mmc_send_tuning() doesn't take the card as a
> parameter, is exactly those you just described above.
> 
   Got it.
   Thanks a lot for the information.

   Thank you for the great help.

Best regards,
Hu Ziji

> [...]
> 
> Kind regards
> Uffe
> 

^ permalink raw reply

* Re: [PATCH 0/2] OF phandle nexus support + GPIO nexus
From: Linus Walleij @ 2016-11-24 15:27 UTC (permalink / raw)
  To: Stephen Boyd
  Cc: Rob Herring, Frank Rowand,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	Pantelis Antoniou, Mark Brown
In-Reply-To: <20161124102529.20212-1-stephen.boyd-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>

On Thu, Nov 24, 2016 at 11:25 AM, Stephen Boyd <stephen.boyd-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org> wrote:

> This is one small chunk of work related to DT overlays for expansion
> boards. It would be good to have a way to expose #<list>-cells types of
> providers through a connector in a standard way. So we introduce a way
> to make "nexus" nodes for these types of properties to remap the consumer
> number space to the other side of the connector's number space. It's
> basically a copy of the interrupt nexus implementation, but without
> the address space matching design and interrupt-parent walking.
>
> The first patch implements a generic method to do this, and the second patch
> adds a unit test for it. The third patch is more of an example than anything
> else. It shows how we would modify frameworks to use the new API.
>
> Stephen Boyd (3):
>   of: Support parsing phandle argument lists through a nexus node
>   of: unittest: Add phandle remapping test
>   gpio: Support gpio nexus dt bindings

Looks perfectly reasonable to me. But it's mainly for the DT people to review
I guess.

I have no idea about the eventual merge path though, I guess it needs to
go through the OF tree with my ACK.

Yours,
Linus Walleij
--
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 7/7] i2c: i2c-mux-simple: new driver
From: Peter Rosin @ 2016-11-24 15:18 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
  Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
	Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, Jonathan Corbet, Arnd Bergmann,
	Greg Kroah-Hartman, linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-iio-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1480000687-5630-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>

This is a generic simple i2c mux that uses the generic multiplexer
subsystem to do the muxing.

The user can select if the mux is to be mux-locked and parent-locked
as described in Documentation/i2c/i2c-topology.

Signed-off-by: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
 drivers/i2c/muxes/Kconfig          |  13 +++
 drivers/i2c/muxes/Makefile         |   1 +
 drivers/i2c/muxes/i2c-mux-simple.c | 179 +++++++++++++++++++++++++++++++++++++
 3 files changed, 193 insertions(+)
 create mode 100644 drivers/i2c/muxes/i2c-mux-simple.c

diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig
index 10b3d17ae3ea..565921e09a96 100644
--- a/drivers/i2c/muxes/Kconfig
+++ b/drivers/i2c/muxes/Kconfig
@@ -73,6 +73,19 @@ config I2C_MUX_REG
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-mux-reg.
 
+config I2C_MUX_SIMPLE
+	tristate "Simple I2C multiplexer"
+	select MULTIPLEXER
+	depends on OF
+	help
+	  If you say yes to this option, support will be included for a
+	  simple generic I2C multiplexer. This driver provides access to
+	  I2C busses connected through a MUX, which is controlled
+	  by a generic MUX controller.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called i2c-mux-simple.
+
 config I2C_DEMUX_PINCTRL
 	tristate "pinctrl-based I2C demultiplexer"
 	depends on PINCTRL && OF
diff --git a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile
index 9948fa45037f..6821d95c92a3 100644
--- a/drivers/i2c/muxes/Makefile
+++ b/drivers/i2c/muxes/Makefile
@@ -11,5 +11,6 @@ obj-$(CONFIG_I2C_MUX_PCA9541)	+= i2c-mux-pca9541.o
 obj-$(CONFIG_I2C_MUX_PCA954x)	+= i2c-mux-pca954x.o
 obj-$(CONFIG_I2C_MUX_PINCTRL)	+= i2c-mux-pinctrl.o
 obj-$(CONFIG_I2C_MUX_REG)	+= i2c-mux-reg.o
+obj-$(CONFIG_I2C_MUX_SIMPLE)	+= i2c-mux-simple.o
 
 ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG
diff --git a/drivers/i2c/muxes/i2c-mux-simple.c b/drivers/i2c/muxes/i2c-mux-simple.c
new file mode 100644
index 000000000000..461d9c21b7db
--- /dev/null
+++ b/drivers/i2c/muxes/i2c-mux-simple.c
@@ -0,0 +1,179 @@
+/*
+ * Generic simple I2C multiplexer
+ *
+ * Copyright (C) 2016 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/i2c.h>
+#include <linux/i2c-mux.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+struct mux {
+	struct mux_control *control;
+
+	bool do_not_deselect;
+};
+
+static int i2c_mux_select(struct i2c_mux_core *muxc, u32 chan)
+{
+	struct mux *mux = i2c_mux_priv(muxc);
+	int ret;
+
+	ret = mux_control_select(mux->control, chan);
+	mux->do_not_deselect = ret < 0;
+
+	return ret;
+}
+
+static int i2c_mux_deselect(struct i2c_mux_core *muxc, u32 chan)
+{
+	struct mux *mux = i2c_mux_priv(muxc);
+
+	if (mux->do_not_deselect)
+		return 0;
+
+	return mux_control_deselect(mux->control);
+}
+
+static struct i2c_adapter *mux_parent_adapter(struct device *dev)
+{
+	struct device_node *np = dev->of_node;
+	struct device_node *parent_np;
+	struct i2c_adapter *parent;
+
+	parent_np = of_parse_phandle(np, "i2c-parent", 0);
+	if (!parent_np) {
+		dev_err(dev, "Cannot parse i2c-parent\n");
+		return ERR_PTR(-ENODEV);
+	}
+	parent = of_find_i2c_adapter_by_node(parent_np);
+	of_node_put(parent_np);
+	if (!parent)
+		return ERR_PTR(-EPROBE_DEFER);
+
+	return parent;
+}
+
+static const struct of_device_id i2c_mux_of_match[] = {
+	{ .compatible = "i2c-mux-simple,parent-locked",
+	  .data = (void *)0, },
+	{ .compatible = "i2c-mux-simple,mux-locked",
+	  .data = (void *)1, },
+	{},
+};
+MODULE_DEVICE_TABLE(of, i2c_mux_of_match);
+
+static int i2c_mux_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct device_node *child;
+	const struct of_device_id *match;
+	struct i2c_mux_core *muxc;
+	struct mux *mux;
+	struct i2c_adapter *parent;
+	int children;
+	int ret;
+
+	if (!np)
+		return -ENODEV;
+
+	mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
+	if (!mux)
+		return -ENOMEM;
+
+	mux->control = devm_mux_control_get(dev);
+	if (IS_ERR(mux->control)) {
+		if (PTR_ERR(mux->control) != -EPROBE_DEFER)
+			dev_err(dev, "failed to get control-mux\n");
+		return PTR_ERR(mux->control);
+	}
+
+	parent = mux_parent_adapter(dev);
+	if (IS_ERR(parent)) {
+		if (PTR_ERR(parent) != -EPROBE_DEFER)
+			dev_err(dev, "failed to get i2c-parent adapter\n");
+		return PTR_ERR(parent);
+	}
+
+	children = of_get_child_count(np);
+
+	muxc = i2c_mux_alloc(parent, dev, children, 0, 0,
+			     i2c_mux_select, i2c_mux_deselect);
+	if (!muxc) {
+		ret = -ENOMEM;
+		goto err_parent;
+	}
+	muxc->priv = mux;
+
+	platform_set_drvdata(pdev, muxc);
+
+	match = of_match_device(of_match_ptr(i2c_mux_of_match), dev);
+	if (match)
+		muxc->mux_locked = !!of_device_get_match_data(dev);
+
+	for_each_child_of_node(np, child) {
+		u32 chan;
+
+		ret = of_property_read_u32(child, "reg", &chan);
+		if (ret < 0) {
+			dev_err(dev, "no reg property for node '%s'\n",
+				child->name);
+			goto err_children;
+		}
+
+		if (chan >= mux->control->states) {
+			dev_err(dev, "invalid reg %u\n", chan);
+			ret = -EINVAL;
+			goto err_children;
+		}
+
+		ret = i2c_mux_add_adapter(muxc, 0, chan, 0);
+		if (ret)
+			goto err_children;
+	}
+
+	dev_info(dev, "%d-port mux on %s adapter\n", children, parent->name);
+
+	return 0;
+
+err_children:
+	i2c_mux_del_adapters(muxc);
+err_parent:
+	i2c_put_adapter(parent);
+
+	return ret;
+}
+
+static int i2c_mux_remove(struct platform_device *pdev)
+{
+	struct i2c_mux_core *muxc = platform_get_drvdata(pdev);
+
+	i2c_mux_del_adapters(muxc);
+	i2c_put_adapter(muxc->parent);
+
+	return 0;
+}
+
+static struct platform_driver i2c_mux_driver = {
+	.probe	= i2c_mux_probe,
+	.remove	= i2c_mux_remove,
+	.driver	= {
+		.name	= "i2c-mux-simple",
+		.of_match_table = i2c_mux_of_match,
+	},
+};
+module_platform_driver(i2c_mux_driver);
+
+MODULE_DESCRIPTION("Simple I2C multiplexer driver");
+MODULE_AUTHOR("Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
-- 
2.1.4

^ permalink raw reply related

* [PATCH v4 6/7] dt-bindings: i2c: i2c-mux-simple: document i2c-mux-simple bindings
From: Peter Rosin @ 2016-11-24 15:18 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
  Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
	Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, Jonathan Corbet, Arnd Bergmann,
	Greg Kroah-Hartman, linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-iio-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1480000687-5630-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>

Signed-off-by: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
 .../devicetree/bindings/i2c/i2c-mux-simple.txt     | 76 ++++++++++++++++++++++
 1 file changed, 76 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/i2c/i2c-mux-simple.txt

diff --git a/Documentation/devicetree/bindings/i2c/i2c-mux-simple.txt b/Documentation/devicetree/bindings/i2c/i2c-mux-simple.txt
new file mode 100644
index 000000000000..ae534a0f87f3
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-mux-simple.txt
@@ -0,0 +1,76 @@
+Simple I2C Bus Mux
+
+This binding describes an I2C bus multiplexer that uses a mux controller
+from the mux subsystem to route the I2C signals.
+
+                                  .-----.  .-----.
+                                  | dev |  | dev |
+    .------------.                '-----'  '-----'
+    | SoC        |                   |        |
+    |            |          .--------+--------'
+    |   .------. |  .------+    child bus A, on MUX value set to 0
+    |   | I2C  |-|--| Mux  |
+    |   '------' |  '--+---+    child bus B, on MUX value set to 1
+    |   .------. |     |    '----------+--------+--------.
+    |   | MUX- | |     |               |        |        |
+    |   | Ctrl |-|-----+            .-----.  .-----.  .-----.
+    |   '------' |                  | dev |  | dev |  | dev |
+    '------------'                  '-----'  '-----'  '-----'
+
+Required properties:
+- compatible: i2c-mux-simple,mux-locked or i2c-mux-simple,parent-locked
+- i2c-parent: The phandle of the I2C bus that this multiplexer's master-side
+  port is connected to.
+* 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 ../misc/mux-controller.txt
+
+For each i2c child node, an I2C child bus will be created. They will
+be numbered based on their order in the device tree.
+
+Whenever an access is made to a device on a child bus, the value set
+in the relevant node's reg property will be set as the state in the
+mux controller.
+
+Example:
+	i2c-mux {
+		compatible = "i2c-mux-simple,mux-locked";
+		i2c-parent = <&i2c1>;
+
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		mux-controller {
+			compatible = "mux-gpio";
+
+			mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
+				    <&pioA 1 GPIO_ACTIVE_HIGH>;
+		};
+
+		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

--
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 5/7] iio: multiplexer: new iio category and iio-mux driver
From: Peter Rosin @ 2016-11-24 15:18 UTC (permalink / raw)
  To: linux-kernel
  Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
	Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, Jonathan Corbet, Arnd Bergmann,
	Greg Kroah-Hartman, linux-i2c, devicetree, linux-iio, linux-doc
In-Reply-To: <1480000687-5630-1-git-send-email-peda@axentia.se>

When a multiplexer changes how an iio device behaves (for example
by feeding different signals to an ADC), this driver can be used
create one virtual iio channel for each multiplexer state.

Depends on the generic multiplexer subsystem.

Cache any ext_info values from the parent iio channel, creating a private
copy of the ext_info attributes for each multiplexer state/channel.

Signed-off-by: Peter Rosin <peda@axentia.se>
---
 MAINTAINERS                       |   1 +
 drivers/iio/Kconfig               |   1 +
 drivers/iio/Makefile              |   1 +
 drivers/iio/multiplexer/Kconfig   |  18 ++
 drivers/iio/multiplexer/Makefile  |   6 +
 drivers/iio/multiplexer/iio-mux.c | 457 ++++++++++++++++++++++++++++++++++++++
 6 files changed, 484 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 7f02f58dfc37..52312a12769f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6238,6 +6238,7 @@ M:	Peter Rosin <peda@axentia.se>
 L:	linux-iio@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt
+F:	drivers/iio/multiplexer/iio-mux.c
 
 IIO SUBSYSTEM AND DRIVERS
 M:	Jonathan Cameron <jic23@kernel.org>
diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig
index a918270d6f54..b3c8c6ef0dff 100644
--- a/drivers/iio/Kconfig
+++ b/drivers/iio/Kconfig
@@ -83,6 +83,7 @@ source "drivers/iio/humidity/Kconfig"
 source "drivers/iio/imu/Kconfig"
 source "drivers/iio/light/Kconfig"
 source "drivers/iio/magnetometer/Kconfig"
+source "drivers/iio/multiplexer/Kconfig"
 source "drivers/iio/orientation/Kconfig"
 if IIO_TRIGGER
    source "drivers/iio/trigger/Kconfig"
diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile
index 33fa4026f92c..93c769cd99bf 100644
--- a/drivers/iio/Makefile
+++ b/drivers/iio/Makefile
@@ -28,6 +28,7 @@ obj-y += humidity/
 obj-y += imu/
 obj-y += light/
 obj-y += magnetometer/
+obj-y += multiplexer/
 obj-y += orientation/
 obj-y += potentiometer/
 obj-y += potentiostat/
diff --git a/drivers/iio/multiplexer/Kconfig b/drivers/iio/multiplexer/Kconfig
new file mode 100644
index 000000000000..70a044510686
--- /dev/null
+++ b/drivers/iio/multiplexer/Kconfig
@@ -0,0 +1,18 @@
+#
+# Multiplexer drivers
+#
+# When adding new entries keep the list in alphabetical order
+
+menu "Multiplexers"
+
+config IIO_MUX
+	tristate "IIO multiplexer driver"
+	select MULTIPLEXER
+	depends on OF
+	help
+	  Say yes here to build support for the IIO multiplexer.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called iio-mux.
+
+endmenu
diff --git a/drivers/iio/multiplexer/Makefile b/drivers/iio/multiplexer/Makefile
new file mode 100644
index 000000000000..68be3c4abd07
--- /dev/null
+++ b/drivers/iio/multiplexer/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for industrial I/O multiplexer drivers
+#
+
+# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_IIO_MUX) += iio-mux.o
diff --git a/drivers/iio/multiplexer/iio-mux.c b/drivers/iio/multiplexer/iio-mux.c
new file mode 100644
index 000000000000..8ad001c248f9
--- /dev/null
+++ b/drivers/iio/multiplexer/iio-mux.c
@@ -0,0 +1,457 @@
+/*
+ * IIO multiplexer driver
+ *
+ * Copyright (C) 2016 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda@axentia.se>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/err.h>
+#include <linux/iio/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mux.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+struct mux_ext_info_cache {
+	char *data;
+	size_t size;
+};
+
+struct mux_child {
+	struct mux_ext_info_cache *ext_info_cache;
+};
+
+struct mux {
+	int cached_state;
+	struct mux_control *control;
+	struct iio_channel *parent;
+	struct iio_dev *indio_dev;
+	struct iio_chan_spec *chan;
+	struct iio_chan_spec_ext_info *ext_info;
+	struct mux_child *child;
+};
+
+static int iio_mux_select(struct mux *mux, int idx)
+{
+	struct mux_child *child = &mux->child[idx];
+	struct iio_chan_spec const *chan = &mux->chan[idx];
+	int ret;
+	int i;
+
+	ret = mux_control_select(mux->control, chan->channel);
+	if (ret < 0) {
+		mux->cached_state = -1;
+		return ret;
+	}
+
+	if (mux->cached_state == chan->channel)
+		return 0;
+
+	if (chan->ext_info) {
+		for (i = 0; chan->ext_info[i].name; ++i) {
+			const char *attr = chan->ext_info[i].name;
+			struct mux_ext_info_cache *cache;
+
+			cache = &child->ext_info_cache[i];
+
+			if (cache->size < 0)
+				continue;
+
+			ret = iio_write_channel_ext_info(mux->parent, attr,
+							 cache->data,
+							 cache->size);
+
+			if (ret < 0) {
+				mux_control_deselect(mux->control);
+				mux->cached_state = -1;
+				return ret;
+			}
+		}
+	}
+	mux->cached_state = chan->channel;
+
+	return 0;
+}
+
+static void iio_mux_deselect(struct mux *mux)
+{
+	mux_control_deselect(mux->control);
+}
+
+static int mux_read_raw(struct iio_dev *indio_dev,
+			struct iio_chan_spec const *chan,
+			int *val, int *val2, long mask)
+{
+	struct mux *mux = iio_priv(indio_dev);
+	int idx = chan - mux->chan;
+	int ret;
+
+	ret = iio_mux_select(mux, idx);
+	if (ret < 0)
+		return ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = iio_read_channel_raw(mux->parent, val);
+		break;
+
+	case IIO_CHAN_INFO_SCALE:
+		ret = iio_read_channel_scale(mux->parent, val, val2);
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	iio_mux_deselect(mux);
+
+	return ret;
+}
+
+static int mux_read_avail(struct iio_dev *indio_dev,
+			  struct iio_chan_spec const *chan,
+			  const int **vals, int *type, int *length,
+			  long mask)
+{
+	struct mux *mux = iio_priv(indio_dev);
+	int idx = chan - mux->chan;
+	int ret;
+
+	ret = iio_mux_select(mux, idx);
+	if (ret < 0)
+		return ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		*type = IIO_VAL_INT;
+		ret = iio_read_avail_channel_raw(mux->parent, vals, length);
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	iio_mux_deselect(mux);
+
+	return ret;
+}
+
+static int mux_write_raw(struct iio_dev *indio_dev,
+			 struct iio_chan_spec const *chan,
+			 int val, int val2, long mask)
+{
+	struct mux *mux = iio_priv(indio_dev);
+	int idx = chan - mux->chan;
+	int ret;
+
+	ret = iio_mux_select(mux, idx);
+	if (ret < 0)
+		return ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = iio_write_channel_raw(mux->parent, val);
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	iio_mux_deselect(mux);
+
+	return ret;
+}
+
+static const struct iio_info mux_info = {
+	.read_raw = mux_read_raw,
+	.read_avail = mux_read_avail,
+	.write_raw = mux_write_raw,
+	.driver_module = THIS_MODULE,
+};
+
+static ssize_t mux_read_ext_info(struct iio_dev *indio_dev, uintptr_t private,
+				 struct iio_chan_spec const *chan, char *buf)
+{
+	struct mux *mux = iio_priv(indio_dev);
+	int idx = chan - mux->chan;
+	ssize_t ret;
+
+	ret = iio_mux_select(mux, idx);
+	if (ret < 0)
+		return ret;
+
+	ret = iio_read_channel_ext_info(mux->parent,
+					mux->ext_info[private].name,
+					buf);
+
+	iio_mux_deselect(mux);
+
+	return ret;
+}
+
+static ssize_t mux_write_ext_info(struct iio_dev *indio_dev, uintptr_t private,
+				  struct iio_chan_spec const *chan,
+				  const char *buf, size_t len)
+{
+	struct device *dev = indio_dev->dev.parent;
+	struct mux *mux = iio_priv(indio_dev);
+	int idx = chan - mux->chan;
+	char *new;
+	ssize_t ret;
+
+	ret = iio_mux_select(mux, idx);
+	if (ret < 0)
+		return ret;
+
+	new = devm_kmemdup(dev, buf, len + 1, GFP_KERNEL);
+	if (!new) {
+		iio_mux_deselect(mux);
+		return -ENOMEM;
+	}
+
+	new[len] = 0;
+
+	ret = iio_write_channel_ext_info(mux->parent,
+					 mux->ext_info[private].name,
+					 buf, len);
+	if (ret < 0) {
+		iio_mux_deselect(mux);
+		devm_kfree(dev, new);
+		return ret;
+	}
+
+	devm_kfree(dev, mux->child[idx].ext_info_cache[private].data);
+	mux->child[idx].ext_info_cache[private].data = new;
+	mux->child[idx].ext_info_cache[private].size = len;
+
+	iio_mux_deselect(mux);
+
+	return ret;
+}
+
+static int mux_configure_channel(struct device *dev, struct mux *mux,
+				 struct device_node *child_np, int idx)
+{
+	struct mux_child *child = &mux->child[idx];
+	struct iio_chan_spec *chan = &mux->chan[idx];
+	struct iio_chan_spec const *pchan = mux->parent->channel;
+	u32 state;
+	char *page = NULL;
+	int num_ext_info;
+	int i;
+	int ret;
+
+	chan->indexed = 1;
+	chan->output = pchan->output;
+	chan->datasheet_name = child_np->name;
+	chan->ext_info = mux->ext_info;
+
+	ret = iio_get_channel_type(mux->parent, &chan->type);
+	if (ret < 0) {
+		dev_err(dev, "failed to get parent channel type\n");
+		return ret;
+	}
+
+	if (iio_channel_has_info(pchan, IIO_CHAN_INFO_RAW))
+		chan->info_mask_separate |= BIT(IIO_CHAN_INFO_RAW);
+	if (iio_channel_has_info(pchan, IIO_CHAN_INFO_SCALE))
+		chan->info_mask_separate |= BIT(IIO_CHAN_INFO_SCALE);
+
+	if (iio_channel_has_available(pchan, IIO_CHAN_INFO_RAW))
+		chan->info_mask_separate_available |= BIT(IIO_CHAN_INFO_RAW);
+
+	ret = of_property_read_u32(child_np, "reg", &state);
+	if (ret < 0) {
+		dev_err(dev, "no reg property for node '%s'\n", child_np->name);
+		return ret;
+	}
+
+	if (state >= mux->control->states) {
+		dev_err(dev, "invalid reg %u\n", state);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < idx; ++i) {
+		if (mux->chan[i].channel == state) {
+			dev_err(dev, "double use of reg %u\n", state);
+			return -EINVAL;
+		}
+	}
+
+	chan->channel = state;
+
+	num_ext_info = iio_get_channel_ext_info_count(mux->parent);
+	if (num_ext_info) {
+		page = devm_kzalloc(dev, PAGE_SIZE, GFP_KERNEL);
+		if (!page)
+			return -ENOMEM;
+	}
+	child->ext_info_cache = devm_kzalloc(dev,
+					     sizeof(*child->ext_info_cache) *
+					     num_ext_info, GFP_KERNEL);
+	for (i = 0; i < num_ext_info; ++i) {
+		child->ext_info_cache[i].size = -1;
+
+		if (!pchan->ext_info[i].write)
+			continue;
+		if (!pchan->ext_info[i].read)
+			continue;
+
+		ret = iio_read_channel_ext_info(mux->parent,
+						mux->ext_info[i].name,
+						page);
+		if (ret < 0) {
+			dev_err(dev, "failed to get ext_info '%s'\n",
+				pchan->ext_info[i].name);
+			return ret;
+		}
+		if (ret >= PAGE_SIZE) {
+			dev_err(dev, "too large ext_info '%s'\n",
+				pchan->ext_info[i].name);
+			return -EINVAL;
+		}
+
+		child->ext_info_cache[i].data = devm_kmemdup(dev, page, ret + 1,
+							     GFP_KERNEL);
+		child->ext_info_cache[i].data[ret] = 0;
+		child->ext_info_cache[i].size = ret;
+	}
+
+	if (page)
+		devm_kfree(dev, page);
+
+	return 0;
+}
+
+static int mux_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *child_np;
+	struct iio_dev *indio_dev;
+	struct iio_channel *parent;
+	struct mux *mux;
+	int sizeof_ext_info;
+	int children;
+	int sizeof_priv;
+	int i;
+	int ret;
+
+	if (!np)
+		return -ENODEV;
+
+	parent = devm_iio_channel_get(dev, "parent");
+	if (IS_ERR(parent)) {
+		if (PTR_ERR(parent) != -EPROBE_DEFER)
+			dev_err(dev, "failed to get parent channel\n");
+		return PTR_ERR(parent);
+	}
+
+	sizeof_ext_info = iio_get_channel_ext_info_count(parent);
+	if (sizeof_ext_info) {
+		sizeof_ext_info += 1; /* one extra entry for the sentinel */
+		sizeof_ext_info *= sizeof(*mux->ext_info);
+	}
+
+	children = 0;
+	for_each_child_of_node(np, child_np) {
+		if (of_get_property(child_np, "reg", NULL))
+			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);
+	if (IS_ERR(mux->control)) {
+		if (PTR_ERR(mux->control) != -EPROBE_DEFER)
+			dev_err(dev, "failed to get control-mux\n");
+		return PTR_ERR(mux->control);
+	}
+
+	i = 0;
+	for_each_child_of_node(np, child_np) {
+		if (!of_get_property(child_np, "reg", NULL))
+			continue;
+
+		ret = mux_configure_channel(dev, mux, child_np, i);
+		if (ret < 0)
+			return ret;
+		i++;
+	}
+
+	ret = devm_iio_device_register(dev, indio_dev);
+	if (ret) {
+		dev_err(dev, "failed to register iio device\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct of_device_id mux_match[] = {
+	{ .compatible = "iio-mux" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mux_match);
+
+static struct platform_driver mux_driver = {
+	.probe = mux_probe,
+	.driver = {
+		.name = "iio-mux",
+		.of_match_table = mux_match,
+	},
+};
+module_platform_driver(mux_driver);
+
+MODULE_DESCRIPTION("IIO multiplexer driver");
+MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
+MODULE_LICENSE("GPL v2");
-- 
2.1.4

^ permalink raw reply related

* [PATCH v4 4/7] dt-bindings: iio: iio-mux: document iio-mux bindings
From: Peter Rosin @ 2016-11-24 15:18 UTC (permalink / raw)
  To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
  Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
	Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, Jonathan Corbet, Arnd Bergmann,
	Greg Kroah-Hartman, linux-i2c-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-iio-u79uwXL29TY76Z2rM5mHXA,
	linux-doc-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1480000687-5630-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>

Signed-off-by: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
 .../bindings/iio/multiplexer/iio-mux.txt           | 47 ++++++++++++++++++++++
 MAINTAINERS                                        |  6 +++
 2 files changed, 53 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt

diff --git a/Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt b/Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt
new file mode 100644
index 000000000000..f5eccdfbed40
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt
@@ -0,0 +1,47 @@
+IIO 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 : "iio-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>;
+
+Required properties for iio-mux child nodes:
+- reg : The multiplexer state as described in ../misc/mux-controller.txt
+
+For each iio-mux child, an iio channel will be created whose number will
+match the mux controller state.
+
+Example:
+	adc-mux {
+		compatible = "iio-mux";
+		io-channels = <&adc 0>;
+		io-channel-names = "parent";
+
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		mux-controller {
+			compatible = "mux-gpio";
+
+			mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
+				    <&pioA 1 GPIO_ACTIVE_HIGH>;
+		};
+
+		sync@0 {
+			reg = <0>;
+		};
+
+		in@1 {
+			reg = <1>;
+		};
+
+		system-regulator@2 {
+			reg = <2>;
+		};
+	};
diff --git a/MAINTAINERS b/MAINTAINERS
index 9635c9708a13..7f02f58dfc37 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6233,6 +6233,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 v4 3/7] iio: inkern: api for manipulating ext_info of iio channels
From: Peter Rosin @ 2016-11-24 15:18 UTC (permalink / raw)
  To: linux-kernel
  Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
	Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, Jonathan Corbet, Arnd Bergmann,
	Greg Kroah-Hartman, linux-i2c, devicetree, linux-iio, linux-doc
In-Reply-To: <1480000687-5630-1-git-send-email-peda@axentia.se>

Extend the inkern api with functions for reading and writing ext_info
of iio channels.

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 b0f4630a163f..4848b8129e6c 100644
--- a/drivers/iio/inkern.c
+++ b/drivers/iio/inkern.c
@@ -863,3 +863,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 v4 2/7] misc: minimal mux subsystem and gpio-based mux controller
From: Peter Rosin @ 2016-11-24 15:18 UTC (permalink / raw)
  To: linux-kernel
  Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
	Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, Jonathan Corbet, Arnd Bergmann,
	Greg Kroah-Hartman, linux-i2c, devicetree, linux-iio, linux-doc
In-Reply-To: <1480000687-5630-1-git-send-email-peda@axentia.se>

Add a new minimalistic subsystem that handles multiplexer controllers.
When multiplexers are used in various places in the kernel, and the
same multiplexer controller can be used for several independent things,
there should be one place to implement support for said multiplexer
controller.

A single multiplexer controller can also be used to control several
parallel multiplexers, that are in turn used by different subsystems
in the kernel, leading to a need to coordinate multiplexer accesses.
The multiplexer subsystem handles this coordination.

This new mux controller subsystem comes with a single backend driver
that controls gpio based multiplexers.

Signed-off-by: Peter Rosin <peda@axentia.se>
---
 Documentation/driver-model/devres.txt |   6 +-
 MAINTAINERS                           |   2 +
 drivers/misc/Kconfig                  |  23 +++
 drivers/misc/Makefile                 |   2 +
 drivers/misc/mux-core.c               | 311 ++++++++++++++++++++++++++++++++++
 drivers/misc/mux-gpio.c               | 124 ++++++++++++++
 include/linux/mux.h                   | 160 +++++++++++++++++
 7 files changed, 627 insertions(+), 1 deletion(-)
 create mode 100644 drivers/misc/mux-core.c
 create mode 100644 drivers/misc/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 ca9d1eb46bc0..d64ede85b61b 100644
--- a/Documentation/driver-model/devres.txt
+++ b/Documentation/driver-model/devres.txt
@@ -330,7 +330,11 @@ MEM
   devm_kzalloc()
 
 MFD
- devm_mfd_add_devices()
+  devm_mfd_add_devices()
+
+MUX
+  devm_mux_control_get()
+  devm_mux_control_put()
 
 PER-CPU MEM
   devm_alloc_percpu()
diff --git a/MAINTAINERS b/MAINTAINERS
index 35869ed0a50e..9635c9708a13 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8406,6 +8406,8 @@ MULTIPLEXER SUBSYSTEM
 M:	Peter Rosin <peda@axentia.se>
 S:	Maintained
 F:	Documentation/devicetree/bindings/misc/mux-*
+F:	include/linux/mux.h
+F:	drivers/misc/mux-*
 
 MULTISOUND SOUND DRIVER
 M:	Andrew Veliath <andrewtv@usa.net>
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 64971baf11fa..a3ca79e082c7 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -766,6 +766,29 @@ config PANEL_BOOT_MESSAGE
 	  An empty message will only clear the display at driver init time. Any other
 	  printf()-formatted message is valid with newline and escape codes.
 
+config 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.
+
+if MULTIPLEXER
+
+config MUX_GPIO
+	tristate "GPIO-controlled MUX controller"
+	depends on OF && GPIOLIB
+	help
+	  GPIO-controlled MUX controller.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called mux-gpio.
+
+endif
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 31983366090a..0befa2bba762 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -53,6 +53,8 @@ obj-$(CONFIG_ECHO)		+= echo/
 obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
 obj-$(CONFIG_CXL_BASE)		+= cxl/
 obj-$(CONFIG_PANEL)             += panel.o
+obj-$(CONFIG_MULTIPLEXER)      	+= mux-core.o
+obj-$(CONFIG_MUX_GPIO)		+= mux-gpio.o
 
 lkdtm-$(CONFIG_LKDTM)		+= lkdtm_core.o
 lkdtm-$(CONFIG_LKDTM)		+= lkdtm_bugs.o
diff --git a/drivers/misc/mux-core.c b/drivers/misc/mux-core.c
new file mode 100644
index 000000000000..6617fc78c072
--- /dev/null
+++ b/drivers/misc/mux-core.c
@@ -0,0 +1,311 @@
+/*
+ * Multiplexer subsystem
+ *
+ * Copyright (C) 2016 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda@axentia.se>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) "mux-core: " fmt
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/idr.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+
+static struct class mux_class = {
+	.name = "mux",
+	.owner = THIS_MODULE,
+};
+
+static int __init mux_init(void)
+{
+	return class_register(&mux_class);
+}
+
+static void __exit mux_exit(void)
+{
+	class_unregister(&mux_class);
+}
+
+static DEFINE_IDA(mux_ida);
+
+static void mux_control_release(struct device *dev)
+{
+	struct mux_control *mux = to_mux_control(dev);
+
+	ida_simple_remove(&mux_ida, mux->id);
+	kfree(mux);
+}
+
+static struct device_type mux_type = {
+	.name = "mux-control",
+	.release = mux_control_release,
+};
+
+struct mux_control *mux_control_alloc(struct device *dev, size_t sizeof_priv)
+{
+	struct mux_control *mux;
+
+	mux = kzalloc(sizeof(*mux) + sizeof_priv, GFP_KERNEL);
+	if (!mux)
+		return NULL;
+
+	mux->dev.class = &mux_class;
+	mux->dev.type = &mux_type;
+	mux->dev.parent = dev;
+	mux->dev.of_node = dev->of_node;
+	dev_set_drvdata(&mux->dev, mux);
+
+	mux->id = ida_simple_get(&mux_ida, 0, 0, GFP_KERNEL);
+	if (mux->id < 0) {
+		pr_err("muxX failed to get a device id\n");
+		kfree(mux);
+		return NULL;
+	}
+	dev_set_name(&mux->dev, "mux%d", mux->id);
+
+	init_rwsem(&mux->lock);
+	mux->cached_state = -1;
+	mux->idle_state = -1;
+
+	device_initialize(&mux->dev);
+
+	return mux;
+}
+EXPORT_SYMBOL_GPL(mux_control_alloc);
+
+int mux_control_register(struct mux_control *mux)
+{
+	int ret;
+
+	ret = device_add(&mux->dev);
+	if (ret < 0)
+		return ret;
+
+	if (mux->drv_pdev)
+		return ret;
+
+	ret = of_platform_populate(mux->dev.of_node, NULL, NULL, &mux->dev);
+	if (ret < 0)
+		device_del(&mux->dev);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(mux_control_register);
+
+void mux_control_unregister(struct mux_control *mux)
+{
+	if (!mux->drv_pdev)
+		of_platform_depopulate(&mux->dev);
+
+	device_del(&mux->dev);
+}
+EXPORT_SYMBOL_GPL(mux_control_unregister);
+
+void mux_control_put(struct mux_control *mux)
+{
+	struct platform_device *drv_pdev;
+
+	if (!mux)
+		return;
+	put_device(&mux->dev);
+
+	if (!mux->drv_pdev)
+		return;
+
+	if (atomic_read(&mux->dev.kobj.kref.refcount) != 1)
+		return;
+
+	/*
+	 * Only one ref left, and the mux core created the driver
+	 * that presumably holds it. Time to release the driver so
+	 * that it can let go of the final ref.
+	 */
+	drv_pdev = mux->drv_pdev;
+	mux->drv_pdev = NULL;
+	platform_device_unregister(drv_pdev);
+}
+EXPORT_SYMBOL_GPL(mux_control_put);
+
+static int mux_control_set(struct mux_control *mux, int state)
+{
+	int ret = mux->ops->set(mux, state);
+
+	mux->cached_state = ret < 0 ? -1 : state;
+
+	return ret;
+}
+
+int mux_control_select(struct mux_control *mux, int state)
+{
+	int ret;
+
+	if (down_read_trylock(&mux->lock)) {
+		if (mux->cached_state == state)
+			return 0;
+
+		/* Sigh, the mux needs updating... */
+		up_read(&mux->lock);
+	}
+
+	/* ...or it's just contended. */
+	down_write(&mux->lock);
+
+	if (mux->cached_state == state) {
+		/*
+		 * Hmmm, someone else changed the mux to my liking.
+		 * That makes me wonder how long I waited for nothing?
+		 */
+		downgrade_write(&mux->lock);
+		return 0;
+	}
+
+	ret = mux_control_set(mux, state);
+	if (ret < 0) {
+		if (mux->idle_state != -1)
+			mux_control_set(mux, mux->idle_state);
+
+		up_write(&mux->lock);
+		return ret;
+	}
+
+	downgrade_write(&mux->lock);
+
+	return 1;
+}
+EXPORT_SYMBOL_GPL(mux_control_select);
+
+int mux_control_deselect(struct mux_control *mux)
+{
+	int ret = 0;
+
+	if (mux->idle_state != -1 && mux->cached_state != mux->idle_state)
+		ret = mux_control_set(mux, mux->idle_state);
+
+	up_read(&mux->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(mux_control_deselect);
+
+static int of_dev_node_match(struct device *dev, const void *data)
+{
+	return dev->of_node == data;
+}
+
+static struct mux_control *of_find_mux_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_control(dev) : NULL;
+}
+
+struct mux_control *mux_control_get(struct device *dev)
+{
+	struct device_node *mux_np;
+	struct platform_device *drv_pdev;
+	struct mux_control *mux;
+
+	if (!dev->of_node)
+		return ERR_PTR(-ENODEV);
+
+	mux_np = of_get_child_by_name(dev->of_node, "mux-controller");
+	if (!mux_np) {
+		mux = of_find_mux_by_node(dev->of_node->parent);
+		if (!mux)
+			return ERR_PTR(-EPROBE_DEFER);
+
+		return mux;
+	}
+
+	drv_pdev = of_platform_device_create(mux_np, "mux-controller", dev);
+	of_node_put(mux_np);
+
+	if (!drv_pdev)
+		return ERR_PTR(-EPROBE_DEFER);
+
+	mux = of_find_mux_by_node(mux_np);
+	if (!mux) {
+		platform_device_unregister(drv_pdev);
+		return ERR_PTR(-ENODEV);
+	}
+
+	/*
+	 * Aiee, holding a reference to the driver that holds a
+	 * reference back. Circular deps, and refcounts never
+	 * hit zero -> leak.
+	 * So, watch for the mux-controller refcount to hit one
+	 * and release the driver-ref then, knowing that the
+	 * driver will (probably) not let go of its back-ref as
+	 * long as the mux core holds a ref to it.
+	 */
+
+	mux->drv_pdev = drv_pdev;
+	return mux;
+}
+EXPORT_SYMBOL_GPL(mux_control_get);
+
+static void devm_mux_control_free(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)
+{
+	struct mux_control **ptr, *mux;
+
+	ptr = devres_alloc(devm_mux_control_free, sizeof(*ptr), GFP_KERNEL);
+	if (!ptr)
+		return ERR_PTR(-ENOMEM);
+
+	mux = mux_control_get(dev);
+	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 (!r || !*r) {
+		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_free,
+			       devm_mux_control_match, mux));
+}
+EXPORT_SYMBOL_GPL(devm_mux_control_put);
+
+subsys_initcall(mux_init);
+module_exit(mux_exit);
+
+MODULE_AUTHOR("Peter Rosin <peda@axentia.se");
+MODULE_DESCRIPTION("MUX subsystem");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/misc/mux-gpio.c b/drivers/misc/mux-gpio.c
new file mode 100644
index 000000000000..66798d920e17
--- /dev/null
+++ b/drivers/misc/mux-gpio.c
@@ -0,0 +1,124 @@
+/*
+ * GPIO-controlled multiplexer driver
+ *
+ * Copyright (C) 2016 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda@axentia.se>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+struct mux_gpio {
+	struct gpio_descs *gpios;
+};
+
+static int mux_gpio_set(struct mux_control *mux, int state)
+{
+	struct mux_gpio *mux_gpio = mux_control_priv(mux);
+	int values[mux_gpio->gpios->ndescs];
+	int i;
+
+	for (i = 0; i < mux_gpio->gpios->ndescs; i++)
+		values[i] = (state >> i) & 1;
+
+	gpiod_set_array_value_cansleep(mux_gpio->gpios->ndescs,
+				       mux_gpio->gpios->desc,
+				       values);
+
+	return 0;
+}
+
+static const struct mux_control_ops mux_gpio_ops = {
+	.set = mux_gpio_set,
+};
+
+static const struct of_device_id mux_gpio_dt_ids[] = {
+	{ .compatible = "mux-gpio", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mux_gpio_dt_ids);
+
+static int mux_gpio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = pdev->dev.of_node;
+	struct mux_control *mux;
+	struct mux_gpio *mux_gpio;
+	u32 idle_state;
+	int ret;
+
+	if (!np)
+		return -ENODEV;
+
+	mux = mux_control_alloc(dev, sizeof(*mux_gpio));
+	if (!mux)
+		return -ENOMEM;
+	mux_gpio = mux_control_priv(mux);
+	mux->ops = &mux_gpio_ops;
+
+	platform_set_drvdata(pdev, mux);
+
+	mux_gpio->gpios = devm_gpiod_get_array(dev, "mux", GPIOD_OUT_LOW);
+	if (IS_ERR(mux_gpio->gpios)) {
+		if (PTR_ERR(mux_gpio->gpios) != -EPROBE_DEFER)
+			dev_err(dev, "failed to get gpios\n");
+		mux_control_put(mux);
+		return PTR_ERR(mux_gpio->gpios);
+	}
+	mux->states = 1 << mux_gpio->gpios->ndescs;
+
+	ret = of_property_read_u32(np, "idle-state", &idle_state);
+	if (ret >= 0) {
+		if (idle_state >= mux->states) {
+			dev_err(dev, "invalid idle-state %u\n", idle_state);
+			return -EINVAL;
+		}
+		mux->idle_state = idle_state;
+	}
+
+	ret = mux_control_register(mux);
+	if (ret < 0) {
+		dev_err(dev, "failed to register mux_control\n");
+		mux_control_put(mux);
+		return ret;
+	}
+
+	dev_info(dev, "%u-way mux-controller registered\n", mux->states);
+
+	return 0;
+}
+
+static int mux_gpio_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mux_control *mux = to_mux_control(dev);
+
+	mux_control_unregister(mux);
+	mux_control_put(mux);
+
+	return 0;
+}
+
+static struct platform_driver mux_gpio_driver = {
+	.driver = {
+		.name = "mux-gpio",
+		.of_match_table	= of_match_ptr(mux_gpio_dt_ids),
+	},
+	.probe = mux_gpio_probe,
+	.remove = mux_gpio_remove,
+};
+module_platform_driver(mux_gpio_driver);
+
+MODULE_AUTHOR("Peter Rosin <peda@axentia.se");
+MODULE_DESCRIPTION("GPIO-controlled multiplexer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mux.h b/include/linux/mux.h
new file mode 100644
index 000000000000..be7c138f9228
--- /dev/null
+++ b/include/linux/mux.h
@@ -0,0 +1,160 @@
+/*
+ * mux.h - definitions for the multiplexer interface
+ *
+ * Copyright (C) 2016 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda@axentia.se>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _LINUX_MUX_H
+#define _LINUX_MUX_H
+
+#include <linux/device.h>
+#include <linux/rwsem.h>
+
+struct mux_control;
+struct platform_device;
+
+struct mux_control_ops {
+	int (*set)(struct mux_control *mux, int state);
+};
+
+/**
+ * struct mux_control - Represents a mux controller.
+ * @lock:		Protects the mux controller state.
+ * @dev:		Device structure.
+ * @id:			Used to identify the device internally.
+ * @states:		The number of mux controller states.
+ * @cached_state:	The current mux controller state, or -1 if none.
+ * @idle_state:		The mux controller state to use when inactive, or -1
+ *			for none.
+ * @ops:		Mux controller operations.
+ */
+struct mux_control {
+	struct rw_semaphore lock; /* protects the state of the mux */
+
+	struct device dev;
+	int id;
+	struct platform_device *drv_pdev;
+
+	unsigned int states;
+	int cached_state;
+	int idle_state;
+
+	const struct mux_control_ops *ops;
+};
+
+#define to_mux_control(x) container_of((x), struct mux_control, dev)
+
+/**
+ * mux_control_priv() - Get the extra memory reserved by mux_control_alloc().
+ * @mux: The mux-control to get the extra memory from.
+ *
+ * Return: Pointer to the private memory requested by the allocator.
+ */
+static inline void *mux_control_priv(struct mux_control *mux)
+{
+	return mux + 1;
+}
+
+/**
+ * mux_control_alloc() - Allocate a mux-control.
+ * @dev: The device implementing the mux interface.
+ * @sizeof_priv: Size of extra memory area for private use by the caller.
+ *
+ * Return: A pointer to the new mux-control, NULL on failure.
+ */
+struct mux_control *mux_control_alloc(struct device *dev, size_t sizeof_priv);
+
+/**
+ * mux_control_register() - Register a mux-control, thus readying it for use.
+ * @mux: The mux-control to register.
+ *
+ * Do not retry registration of the same mux-control on failure. You should
+ * instead put it away with mux_control_put() 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_control_register(struct mux_control *mux);
+
+/**
+ * mux_control_unregister() - Take the mux-control off-line.
+ * @mux: The mux-control to unregister.
+ *
+ * mux_control_unregister() reverses the effects of mux_control_register().
+ * But not completely, you should not try to call mux_control_register()
+ * on a mux-control that has been registered before.
+ */
+void mux_control_unregister(struct mux_control *mux);
+
+/**
+ * mux_control_put() - Put away the mux-control for good.
+ * @mux: The mux-control to put away.
+ *
+ * mux_control_put() reverses the effects of either mux_control_alloc() or
+ * mux_control_get().
+ */
+void mux_control_put(struct mux_control *mux);
+
+/**
+ * mux_control_select() - Select the given multiplexer state.
+ * @mux: The mux-control to request a change of state from.
+ * @state: The new requested state.
+ *
+ * Make sure to call mux_control_deselect() when the operation is complete and
+ * the mux-control is free for others to use, but do not call
+ * mux_control_deselect() if mux_control_select() fails.
+ *
+ * Return: 0 if the requested state was already active, or 1 it the
+ * mux-control state was changed to the requested state. Or a negavive
+ * errno on error.
+ *
+ * Note that the difference in return value of zero or one is of
+ * questionable value; especially if the mux-control has several independent
+ * consumers, which is something the consumers should not be making
+ * assumptions about.
+ */
+int mux_control_select(struct mux_control *mux, int state);
+
+/**
+ * mux_control_deselect() - Deselect the previously selected multiplexer state.
+ * @mux: The mux-control to deselect.
+ *
+ * Return: 0 on success and a negative errno on error. An error can only
+ * occur if the mux has an idle state. Note that even if an error occurs, the
+ * mux-control is unlocked for others to access.
+ */
+int mux_control_deselect(struct mux_control *mux);
+
+/**
+ * mux_control_get() - Get the mux-control for a device.
+ * @dev: The device that needs a 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);
+
+/**
+ * devm_mux_control_get() - Get the mux-control for a device, with resource
+ *			    management.
+ * @dev: The device that needs a 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);
+
+/**
+ * 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


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox