linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/5] RTL8231 GPIO expander support
@ 2021-05-11 12:25 Sander Vanheule
  2021-05-11 12:25 ` [PATCH 1/5] dt-bindings: leds: Binding for RTL8231 scan matrix Sander Vanheule
                   ` (8 more replies)
  0 siblings, 9 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-11 12:25 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij, linux-leds,
	devicetree, linux-gpio
  Cc: linux-kernel, Sander Vanheule

The RTL8231 GPIO and LED expander can be configured for use as an MDIO or SMI
bus device. Currently only the MDIO mode is supported, although SMI mode
support should be fairly straightforward, once an SMI bus driver is available.

Provided features by the RTL8231:
  - Up to 37 GPIOs
    - Configurable drive strength: 8mA or 4mA (currently unsupported)
    - Input debouncing on high GPIOs (currently unsupported)
  - Up to 88 LEDs in multiple scan matrix groups
    - On, off, or one of six toggling intervals
    - "single-color mode": 2×36 single color LEDs + 8 bi-color LEDs
    - "bi-color mode": (12 + 2×6) bi-color LEDs + 24 single color LEDs
  - Up to one PWM output (currently unsupported)
    - Fixed duty cycle, 8 selectable frequencies (1.2kHz - 4.8kHz)

There remain some log warnings when probing the device, possibly due to the way
I'm using the MFD subsystem. Would it be possible to avoid these?
[    2.602242] rtl8231-pinctrl: Failed to locate of_node [id: -2]
[    2.609380] rtl8231-pinctrl rtl8231-pinctrl.0.auto: no of_node; not parsing pinctrl DT

When no 'leds' sub-node is specified:
[    2.922262] rtl8231-leds: Failed to locate of_node [id: -2]
[    2.967149] rtl8231-leds rtl8231-leds.1.auto: no of_node; not parsing pinctrl DT
[    2.975673] rtl8231-leds rtl8231-leds.1.auto: scan mode missing or invalid
[    2.983531] rtl8231-leds: probe of rtl8231-leds.1.auto failed with error -22

Changes since RFC:
  - Dropped MDIO regmap interface. I was unable to resolve the Kconfig
    dependency issue, so have reverted to using regmap_config.reg_read/write.
  - Added pinctrl support
  - Added LED support
  - Changed root device to MFD, with pinctrl and leds child devices. Root
    device is now an mdio_device driver.

Sander Vanheule (5):
  dt-bindings: leds: Binding for RTL8231 scan matrix
  dt-bindings: mfd: Binding for RTL8231
  mfd: Add RTL8231 core device
  pinctrl: Add RTL8231 pin control and GPIO support
  leds: Add support for RTL8231 LED scan matrix

 .../bindings/leds/realtek,rtl8231-leds.yaml   | 159 ++++++
 .../bindings/mfd/realtek,rtl8231.yaml         | 202 +++++++
 drivers/leds/Kconfig                          |  10 +
 drivers/leds/Makefile                         |   1 +
 drivers/leds/leds-rtl8231.c                   | 281 ++++++++++
 drivers/mfd/Kconfig                           |   9 +
 drivers/mfd/Makefile                          |   1 +
 drivers/mfd/rtl8231.c                         | 163 ++++++
 drivers/pinctrl/Kconfig                       |  10 +
 drivers/pinctrl/Makefile                      |   1 +
 drivers/pinctrl/pinctrl-rtl8231.c             | 497 ++++++++++++++++++
 include/linux/mfd/rtl8231.h                   |  49 ++
 12 files changed, 1383 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
 create mode 100644 Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
 create mode 100644 drivers/leds/leds-rtl8231.c
 create mode 100644 drivers/mfd/rtl8231.c
 create mode 100644 drivers/pinctrl/pinctrl-rtl8231.c
 create mode 100644 include/linux/mfd/rtl8231.h

-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH 1/5] dt-bindings: leds: Binding for RTL8231 scan matrix
  2021-05-11 12:25 [PATCH 0/5] RTL8231 GPIO expander support Sander Vanheule
@ 2021-05-11 12:25 ` Sander Vanheule
  2021-05-17 22:31   ` Rob Herring
  2021-05-11 12:25 ` [PATCH 2/5] dt-bindings: mfd: Binding for RTL8231 Sander Vanheule
                   ` (7 subsequent siblings)
  8 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-11 12:25 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij, linux-leds,
	devicetree, linux-gpio
  Cc: linux-kernel, Sander Vanheule

Add a binding description for the Realtek RTL8231's LED support, which
consists of up to 88 LEDs arranged in a number of scanning matrices.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 .../bindings/leds/realtek,rtl8231-leds.yaml   | 159 ++++++++++++++++++
 1 file changed, 159 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml

diff --git a/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml b/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
new file mode 100644
index 000000000000..aba2b55fb9c9
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
@@ -0,0 +1,159 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/realtek,rtl8231-leds.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Realtek RTL8231 LED scan matrix.
+
+maintainers:
+  - Sander Vanheule <sander@svanheule.net>
+
+description: |
+  The RTL8231 has support for driving a number of LED matrices, by scanning
+  over the LEDs pins, alternatingly lighting different columns and/or rows.
+
+  In single color scan mode, 88 LEDs are supported. These are grouped into
+  three output matrices:
+    - Group A of 6×6 single color LEDs. Rows and columns are driven by GPIO
+      pins 0-11.
+               L0[n]    L1[n]    L2[n]    L0[n+6]  L1[n+6]  L2[n+6]
+                |        |        |        |        |        |
+       P0/P6  --<--------<--------<--------<--------<--------< (3)
+                |        |        |        |        |        |
+       P1/P7  --<--------<--------<--------<--------<--------< (4)
+                |        |        |        |        |        |
+       P2/P8  --<--------<--------<--------<--------<--------< (5)
+                |        |        |        |        |        |
+       P3/P9  --<--------<--------<--------<--------<--------< (6)
+                |        |        |        |        |        |
+       P4/P10 --<--------<--------<--------<--------<--------< (7)
+                |        |        |        |        |        |
+       P5/P11 --<--------<--------<--------<--------<--------< (8)
+               (0)      (1)      (2)      (9)     (10)     (11)
+    - Group B of 6×6 single color LEDs. Rows and columns are driven by GPIO
+      pins 12-23.
+               L0[n]    L1[n]    L2[n]    L0[n+6]  L1[n+6]  L2[n+6]
+                |        |        |        |        |        |
+      P12/P18 --<--------<--------<--------<--------<--------< (15)
+                |        |        |        |        |        |
+      P13/P19 --<--------<--------<--------<--------<--------< (16)
+                |        |        |        |        |        |
+      P14/P20 --<--------<--------<--------<--------<--------< (17)
+                |        |        |        |        |        |
+      P15/P21 --<--------<--------<--------<--------<--------< (18)
+                |        |        |        |        |        |
+      P16/P22 --<--------<--------<--------<--------<--------< (19)
+                |        |        |        |        |        |
+      P17/P23 --<--------<--------<--------<--------<--------< (20)
+              (12)     (13)     (14)    (21)      (22)     (23)
+    - Group C of 8 pairs of anti-parallel (or bi-color) LEDs. LED selection is
+      provided by GPIO pins 24-27 and 29-32, polarity selection by GPIO 28.
+               P24     P25  ...  P30     P31
+                |       |         |       |
+      LED POL --X-------X---/\/---X-------X (28)
+              (24)    (25)  ... (31)    (32)
+
+  In bi-color scan mode, 72 LEDs are supported. These are grouped into four
+  output matrices:
+    - Group A of 12 pairs of anti-parallel LEDs. LED selection is provided
+      by GPIO pins 0-11, polarity selection by GPIO 12.
+    - Group B of 6 pairs of anti-parallel LEDs. LED selection is provided
+      by GPIO pins 23-28, polarity selection by GPIO 21.
+    - Group C of 6 pairs of anti-parallel LEDs. LED selection is provided
+      by GPIO pins 29-34, polarity selection by GPIO 22.
+    - Group of 4×6 single color LEDs. Rows are driven by GPIO pins 15-20,
+      columns by GPIO pins 13-14 and 21-22 (shared with groups B and C).
+          P[n]     P[n+6]   P[n+12]  P[n+18]
+            |        |        |        |
+       +0 --<--------<--------<--------< (15)
+            |        |        |        |
+       +1 --<--------<--------<--------< (16)
+            |        |        |        |
+       +2 --<--------<--------<--------< (17)
+            |        |        |        |
+       +3 --<--------<--------<--------< (18)
+            |        |        |        |
+       +4 --<--------<--------<--------< (19)
+            |        |        |        |
+       +6 --<--------<--------<--------< (20)
+          (13)     (14)     (21)     (22)
+
+  This node must always be a child of a 'realtek,rtl8231' node.
+
+properties:
+  $nodename:
+    const: leds
+
+  compatible:
+    const: realtek,rtl8231-leds
+
+  "#address-cells":
+    const: 2
+
+  "#size-cells":
+    const: 0
+
+  realtek,led-scan-mode:
+    $ref: /schemas/types.yaml#/definitions/string
+    description: |
+      Specify the scanning mode the chip should run in. See general description
+      for how the scanning matrices are wired up.
+    enum: ["single-color", "bi-color"]
+
+patternProperties:
+  "^led@[0-9]+,[0-2]$":
+    description: |
+      LEDs are addressed by their port index and led index. Ports 0-23 always
+      support three LEDs. Additionally, but only when used in single color scan
+      mode, ports 24-31 support two LEDs.
+    type: object
+
+    properties:
+      reg:
+        maxItems: 1
+
+    allOf:
+      - $ref: ../leds/common.yaml#
+
+    required:
+      - reg
+
+required:
+  - compatible
+  - "#address-cells"
+  - "#size-cells"
+  - realtek,led-scan-mode
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/leds/common.h>
+    leds {
+        compatible = "realtek,rtl8231-leds";
+        #address-cells = <2>;
+        #size-cells = <0>;
+
+        realtek,led-scan-mode = "single-color";
+
+        led@0,0 {
+            reg = <0 0>;
+            color = <LED_COLOR_ID_GREEN>;
+            function = LED_FUNCTION_LAN;
+            function-enumerator = <0>;
+        };
+
+        led@0,1 {
+            reg = <0 1>;
+            color = <LED_COLOR_ID_AMBER>;
+            function = LED_FUNCTION_LAN;
+            function-enumerator = <0>;
+        };
+
+        led@0,2 {
+            reg = <0 2>;
+            color = <LED_COLOR_ID_GREEN>;
+            function = LED_FUNCTION_STATUS;
+        };
+    };
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH 2/5] dt-bindings: mfd: Binding for RTL8231
  2021-05-11 12:25 [PATCH 0/5] RTL8231 GPIO expander support Sander Vanheule
  2021-05-11 12:25 ` [PATCH 1/5] dt-bindings: leds: Binding for RTL8231 scan matrix Sander Vanheule
@ 2021-05-11 12:25 ` Sander Vanheule
  2021-05-17 22:38   ` Rob Herring
  2021-05-11 12:25 ` [PATCH 3/5] mfd: Add RTL8231 core device Sander Vanheule
                   ` (6 subsequent siblings)
  8 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-11 12:25 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij, linux-leds,
	devicetree, linux-gpio
  Cc: linux-kernel, Sander Vanheule

Add a binding description for the Realtek RTL8231, a GPIO and LED
expander chip commonly used in ethernet switches based on a Realtek
switch SoC. These chips can be addressed via an MDIO or SMI bus, or used
as a plain 36-bit shift register.

This binding only describes the feature set provided by the MDIO/SMI
configuration, and covers the GPIO, PWM, and pin control properties. The
LED properties are defined in a separate binding.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 .../bindings/mfd/realtek,rtl8231.yaml         | 202 ++++++++++++++++++
 1 file changed, 202 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml

diff --git a/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml b/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
new file mode 100644
index 000000000000..2023cfa887a3
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
@@ -0,0 +1,202 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/realtek,rtl8231.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Realtek RTL8231 GPIO and LED expander.
+
+maintainers:
+  - Sander Vanheule <sander@svanheule.net>
+
+description: |
+  The RTL8231 is a GPIO and LED expander chip, providing up to 37 GPIOs, up to
+  88 LEDs, and up to one PWM output. This device is frequently used alongside
+  Realtek switch SoCs, to provide additional I/O capabilities.
+
+  To manage the RTL8231's features, its strapping pins can be used to configure
+  it in one of three modes: shift register, MDIO device, or SMI device. The
+  shift register mode does not need special support. In MDIO or SMI mode, most
+  pins can be configured as a GPIO output, LED matrix scan line/column, or as a
+  PWM output.
+
+  The GPIO and pin control are part of the main node. PWM and LED support are
+  configured as sub-nodes.
+
+properties:
+  compatible:
+    const: realtek,rtl8231
+
+  reg:
+    description: MDIO or SMI device address.
+    maxItems: 1
+
+  # GPIO support
+  gpio-controller: true
+
+  "#gpio-cells":
+    const: 2
+    description: |
+      The first cell is the pin number and the second cell is used to specify
+      the gpio active state.
+
+  gpio-ranges:
+    description: |
+      Must reference itself, and provide a zero-based mapping for 37 pins.
+    maxItems: 1
+
+  # Pin muxing and configuration
+  realtek,drive-strength:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: |
+      Common drive strength used for all GPIO output pins, must be 4mA or 8mA.
+      On reset, this value will default to 8mA.
+    enum: [4, 8]
+
+  # LED scanning matrix
+  leds:
+    $ref: ../leds/realtek,rtl8231-leds.yaml#
+
+  # PWM output
+  pwm:
+    type: object
+    description: |
+      Subnode describing the PWM peripheral. To use the PWM output, gpio35 must
+      be muxed to its 'pwm' function. Valid frequency values for consumers are
+      1200, 1600, 2000, 2400, 2800, 3200, 4000, and 4800.
+
+    properties:
+      "#pwm-cells":
+        description: |
+          Twos cells with PWM index (must be 0) and PWM frequency in Hz.
+        const: 2
+
+    required:
+      - "#pwm-cells"
+
+patternProperties:
+  "-pins$":
+    type: object
+    $ref: ../pinctrl/pinmux-node.yaml#
+
+    properties:
+      pins:
+        items:
+          oneOf:
+            - enum: ["gpio0", "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6",
+                     "gpio7", "gpio8", "gpio9", "gpio10", "gpio11", "gpio12", "gpio13",
+                     "gpio14", "gpio15", "gpio16", "gpio17", "gpio18", "gpio19", "gpio20",
+                     "gpio21", "gpio22", "gpio23", "gpio24", "gpio25", "gpio26", "gpio27",
+                     "gpio28", "gpio29", "gpio30", "gpio31", "gpio32", "gpio33", "gpio34",
+                     "gpio35", "gpio36"]
+        minItems: 1
+        maxItems: 37
+      function:
+        description: |
+          Select which function to use. "gpio" is supported for all pins, "led" is supported
+          for pins 0-34, "pwm" is supported for for pin 35.
+        enum: ["gpio", "led", "pwm"]
+
+    required:
+      - pins
+      - function
+
+required:
+  - compatible
+  - reg
+  - gpio-controller
+  - "#gpio-cells"
+  - gpio-ranges
+
+additionalProperties: false
+
+examples:
+  - |
+    // Minimal example
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        expander0: expander@0 {
+            compatible = "realtek,rtl8231";
+            reg = <0>;
+
+            gpio-controller;
+            #gpio-cells = <2>;
+            gpio-ranges = <&expander0 0 0 37>;
+        };
+    };
+  - |
+    // All bells and whistles included
+    #include <dt-bindings/leds/common.h>
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        expander1: expander@1 {
+            compatible = "realtek,rtl8231";
+            reg = <1>;
+
+            gpio-controller;
+            #gpio-cells = <2>;
+            gpio-ranges = <&expander1 0 0 37>;
+
+            realtek,drive-strength = <4>;
+
+            button-pins {
+                pins = "gpio36";
+                function = "gpio";
+                input-debounce = "100000";
+            };
+
+            pwm-pins {
+                pins = "gpio35";
+                function = "pwm";
+            };
+
+            led-pins {
+                pins = "gpio0", "gpio1", "gpio3", "gpio4";
+                function = "led";
+            };
+
+            pwm {
+                #pwm-cells = <2>;
+            };
+
+            leds {
+                compatible = "realtek,rtl8231-leds";
+                #address-cells = <2>;
+                #size-cells = <0>;
+
+                realtek,led-scan-mode = "single-color";
+
+                led@0,0 {
+                    reg = <0 0>;
+                    color = <LED_COLOR_ID_GREEN>;
+                    function = LED_FUNCTION_LAN;
+                    function-enumerator = <0>;
+                };
+
+                led@0,1 {
+                    reg = <0 1>;
+                    color = <LED_COLOR_ID_AMBER>;
+                    function = LED_FUNCTION_LAN;
+                    function-enumerator = <0>;
+                };
+
+                led@1,0 {
+                    reg = <1 0>;
+                    color = <LED_COLOR_ID_GREEN>;
+                    function = LED_FUNCTION_LAN;
+                    function-enumerator = <1>;
+                };
+
+                led@1,1 {
+                    reg = <1 1>;
+                    color = <LED_COLOR_ID_AMBER>;
+                    function = LED_FUNCTION_LAN;
+                    function-enumerator = <1>;
+                };
+            };
+        };
+    };
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH 3/5] mfd: Add RTL8231 core device
  2021-05-11 12:25 [PATCH 0/5] RTL8231 GPIO expander support Sander Vanheule
  2021-05-11 12:25 ` [PATCH 1/5] dt-bindings: leds: Binding for RTL8231 scan matrix Sander Vanheule
  2021-05-11 12:25 ` [PATCH 2/5] dt-bindings: mfd: Binding for RTL8231 Sander Vanheule
@ 2021-05-11 12:25 ` Sander Vanheule
  2021-05-12 12:29   ` kernel test robot
  2021-05-12 13:13   ` kernel test robot
  2021-05-11 12:25 ` [PATCH 4/5] pinctrl: Add RTL8231 pin control and GPIO support Sander Vanheule
                   ` (5 subsequent siblings)
  8 siblings, 2 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-11 12:25 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij, linux-leds,
	devicetree, linux-gpio
  Cc: linux-kernel, Sander Vanheule

The RTL8231 is implemented as an MDIO device, and provides a regmap
interface for register access by the core and child devices.

The chip can also be a device on an SMI bus, a proprietary I2C-like bus
by Realtek. Since kernel support for SMI is limited, and no real-world
SMI implementations have been encountered for this device, this is
currently unimplemented. The use of the regmap interface should make any
future support relatively straightforward.

After reset, all pins are muxed to GPIO inputs before the pin drivers
are enabled. This is done to prevent accidental system resets, when a
pin is connected to the parent SoC's reset line.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 drivers/mfd/Kconfig         |   9 ++
 drivers/mfd/Makefile        |   1 +
 drivers/mfd/rtl8231.c       | 163 ++++++++++++++++++++++++++++++++++++
 include/linux/mfd/rtl8231.h |  49 +++++++++++
 4 files changed, 222 insertions(+)
 create mode 100644 drivers/mfd/rtl8231.c
 create mode 100644 include/linux/mfd/rtl8231.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 5c7f2b100191..0b0cf296aac4 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1076,6 +1076,15 @@ config MFD_RDC321X
 	  southbridge which provides access to GPIOs and Watchdog using the
 	  southbridge PCI device configuration space.
 
+config MFD_RTL8231
+	tristate "Realtek RTL8231 GPIO and LED expander"
+	select MFD_CORE
+	select REGMAP
+	help
+	  Support for the Realtek RTL8231 GPIO and LED expander.
+	  Provides up to 37 GPIOs, 88 LEDs, and one PWM output.
+	  When built as a module, this module will be named rtl8231_expander.
+
 config MFD_RT5033
 	tristate "Richtek RT5033 Power Management IC"
 	depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 4f6d2b8a5f76..4b27c2486ccc 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -234,6 +234,7 @@ obj-$(CONFIG_MFD_MENF21BMC)	+= menf21bmc.o
 obj-$(CONFIG_MFD_HI6421_PMIC)	+= hi6421-pmic-core.o
 obj-$(CONFIG_MFD_HI655X_PMIC)   += hi655x-pmic.o
 obj-$(CONFIG_MFD_DLN2)		+= dln2.o
+obj-$(CONFIG_MFD_RTL8231)	+= rtl8231.o
 obj-$(CONFIG_MFD_RT5033)	+= rt5033.o
 obj-$(CONFIG_MFD_SKY81452)	+= sky81452.o
 
diff --git a/drivers/mfd/rtl8231.c b/drivers/mfd/rtl8231.c
new file mode 100644
index 000000000000..4db3ad23d822
--- /dev/null
+++ b/drivers/mfd/rtl8231.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/rtl8231.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+static const struct reg_field RTL8231_FIELD_LED_START = REG_FIELD(RTL8231_REG_FUNC0, 1, 1);
+static const struct reg_field RTL8231_FIELD_READY_CODE = REG_FIELD(RTL8231_REG_FUNC1, 4, 9);
+static const struct reg_field RTL8231_FIELD_SOFT_RESET = REG_FIELD(RTL8231_REG_PIN_HI_CFG, 15, 15);
+
+static const struct mfd_cell rtl8231_cells[] = {
+	{
+		.name = "rtl8231-pinctrl",
+		.of_compatible = "realtek,rtl8231-pinctrl",
+	},
+	{
+		.name = "rtl8231-leds",
+		.of_compatible = "realtek,rtl8231-leds",
+	},
+};
+
+static int rtl8231_init(struct device *dev, struct regmap *map)
+{
+	struct regmap_field *field_ready_code;
+	struct regmap_field *field_soft_reset;
+	unsigned int v;
+	int err = 0;
+
+	field_ready_code = regmap_field_alloc(map, RTL8231_FIELD_READY_CODE);
+	if (IS_ERR(field_ready_code))
+		return PTR_ERR(field_ready_code);
+
+	field_soft_reset = regmap_field_alloc(map, RTL8231_FIELD_SOFT_RESET);
+	if (IS_ERR(field_soft_reset)) {
+		err = PTR_ERR(field_soft_reset);
+		goto init_out;
+	}
+
+	err = regmap_field_read(field_ready_code, &v);
+
+	if (err) {
+		dev_err(dev, "failed to read READY_CODE\n");
+		goto init_out;
+	} else if (v != RTL8231_FUNC1_READY_CODE_VALUE) {
+		dev_err(dev, "RTL8231 not present or ready 0x%x != 0x%x\n",
+			v, RTL8231_FUNC1_READY_CODE_VALUE);
+		err = -ENODEV;
+		goto init_out;
+	}
+
+	// TODO Implement reset-gpios
+	regmap_field_write(field_soft_reset, 1);
+	usleep_range(1000, 10000);
+
+	/* Do not write LED_START before configuring pins */
+	/* Select GPIO functionality for all pins and set to input */
+	regmap_write(map, RTL8231_REG_PIN_MODE0, 0xffff);
+	regmap_write(map, RTL8231_REG_GPIO_DIR0, 0xffff);
+	regmap_write(map, RTL8231_REG_PIN_MODE1, 0xffff);
+	regmap_write(map, RTL8231_REG_GPIO_DIR1, 0xffff);
+	regmap_write(map, RTL8231_REG_PIN_HI_CFG, GENMASK(4, 0) | GENMASK(9, 5));
+
+init_out:
+	regmap_field_free(field_ready_code);
+	regmap_field_free(field_soft_reset);
+	return err;
+}
+
+static int rtl8231_mdio_reg_read(void *ctx, unsigned int reg, unsigned int *val)
+{
+	struct mdio_device *mdiodev = ctx;
+	int ret;
+
+	ret = mdiobus_read(mdiodev->bus, mdiodev->addr, reg);
+	if (ret < 0)
+		return ret;
+
+	*val = ret & 0xffff;
+	return 0;
+}
+
+static int rtl8231_mdio_reg_write(void *ctx, unsigned int reg, unsigned int val)
+{
+	struct mdio_device *mdiodev = ctx;
+
+	return mdiobus_write(mdiodev->bus, mdiodev->addr, reg, val);
+}
+
+static const struct regmap_config rtl8231_regmap_config = {
+	.val_bits = 16,
+	.reg_bits = 5,
+	.max_register = RTL8231_REG_COUNT - 1,
+	.use_single_read = true,
+	.use_single_write = true,
+	.reg_format_endian = REGMAP_ENDIAN_BIG,
+	.val_format_endian = REGMAP_ENDIAN_BIG,
+	.reg_read = rtl8231_mdio_reg_read,
+	.reg_write = rtl8231_mdio_reg_write,
+};
+
+static int rtl8231_mdio_probe(struct mdio_device *mdiodev)
+{
+	struct device *dev = &mdiodev->dev;
+	struct regmap_field *led_start;
+	struct regmap *map;
+	int err;
+
+	map = devm_regmap_init(dev, NULL, mdiodev, &rtl8231_regmap_config);
+
+	if (IS_ERR(map)) {
+		dev_err(dev, "failed to init regmap\n");
+		return PTR_ERR(map);
+	}
+
+	led_start = devm_regmap_field_alloc(dev, map, RTL8231_FIELD_LED_START);
+	if (IS_ERR(led_start))
+		return PTR_ERR(led_start);
+
+	dev_set_drvdata(dev, led_start);
+
+	err = rtl8231_init(dev, map);
+	if (err)
+		return err;
+
+	/* LED_START enables power to output pins, and starts the LED engine */
+	regmap_field_write(led_start, 1);
+
+	return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, rtl8231_cells,
+		ARRAY_SIZE(rtl8231_cells), NULL, 0, NULL);
+}
+
+static void rtl8231_mdio_remove(struct mdio_device *mdiodev)
+{
+	struct regmap_field *led_start;
+
+	led_start = dev_get_drvdata(&mdiodev->dev);
+	regmap_field_write(led_start, 0);
+}
+
+static const struct of_device_id rtl8231_of_match[] = {
+	{ .compatible = "realtek,rtl8231" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, rtl8231_of_match);
+
+static struct mdio_driver rtl8231_mdio_driver = {
+	.mdiodrv.driver = {
+		.name = "rtl8231-expander",
+		.of_match_table	= rtl8231_of_match,
+	},
+	.probe = rtl8231_mdio_probe,
+	.remove = rtl8231_mdio_remove,
+};
+mdio_module_driver(rtl8231_mdio_driver);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek RTL8231 GPIO and LED expander");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/rtl8231.h b/include/linux/mfd/rtl8231.h
new file mode 100644
index 000000000000..5af312adbf0b
--- /dev/null
+++ b/include/linux/mfd/rtl8231.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Register definitions the RTL8231 GPIO and LED expander chip
+ */
+
+#ifndef __LINUX_MFD_RTL8231_H
+#define __LINUX_MFD_RTL8231_H
+
+/* Chip control */
+#define RTL8231_REG_FUNC0		0x00
+#define RTL8231_FUNC0_SCAN_MODE		BIT(0)
+#define RTL8231_FUNC0_SCAN_SINGLE	0
+#define RTL8231_FUNC0_SCAN_BICOLOR	BIT(0)
+
+#define RTL8231_REG_FUNC1		0x01
+#define RTL8231_FUNC1_READY_CODE_VALUE	0x37
+
+/* Pin control */
+#define RTL8231_REG_PIN_MODE0		0x02
+#define RTL8231_REG_PIN_MODE1		0x03
+
+#define RTL8231_PIN_MODE_LED		0
+#define RTL8231_PIN_MODE_GPIO		1
+
+/* Pin high config: pin and GPIO control for pins 32-26 */
+#define RTL8231_REG_PIN_HI_CFG		0x04
+
+/* GPIO control registers */
+#define RTL8231_REG_GPIO_DIR0		0x05
+#define RTL8231_REG_GPIO_DIR1		0x06
+#define RTL8231_REG_GPIO_INVERT0	0x07
+#define RTL8231_REG_GPIO_INVERT1	0x08
+
+#define RTL8231_GPIO_DIR_IN		1
+#define RTL8231_GPIO_DIR_OUT		0
+
+/* GPIO data registers */
+#define RTL8231_REG_GPIO_DATA0		0x1c
+#define RTL8231_REG_GPIO_DATA1		0x1d
+#define RTL8231_REG_GPIO_DATA2		0x1e
+
+/* LED control base registers */
+#define RTL8231_REG_LED0_BASE		0x09
+#define RTL8231_REG_LED1_BASE		0x10
+#define RTL8231_REG_LED2_BASE		0x17
+
+#define RTL8231_REG_COUNT		0x1f
+
+#endif /* __LINUX_MFD_RTL8231_H */
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH 4/5] pinctrl: Add RTL8231 pin control and GPIO support
  2021-05-11 12:25 [PATCH 0/5] RTL8231 GPIO expander support Sander Vanheule
                   ` (2 preceding siblings ...)
  2021-05-11 12:25 ` [PATCH 3/5] mfd: Add RTL8231 core device Sander Vanheule
@ 2021-05-11 12:25 ` Sander Vanheule
  2021-05-11 12:25 ` [PATCH 5/5] leds: Add support for RTL8231 LED scan matrix Sander Vanheule
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-11 12:25 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij, linux-leds,
	devicetree, linux-gpio
  Cc: linux-kernel, Sander Vanheule

This driver implements the GPIO and pin muxing features provided by the
RTL8231. The device should be instantiated as an MFD child, where the
parent device has already configured the regmap used for register
access.

Although described in the bindings, pin debouncing and drive strength
selection are currently not implemented. Debouncing is only available
for the six highest GPIOs, and must be emulated when other pins are used
for (button) inputs anyway.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 drivers/pinctrl/Kconfig           |  10 +
 drivers/pinctrl/Makefile          |   1 +
 drivers/pinctrl/pinctrl-rtl8231.c | 497 ++++++++++++++++++++++++++++++
 3 files changed, 508 insertions(+)
 create mode 100644 drivers/pinctrl/pinctrl-rtl8231.c

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index c2c7e7963ed0..5fce23126ae3 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -221,6 +221,16 @@ config PINCTRL_ROCKCHIP
 	help
           This support pinctrl and gpio driver for Rockchip SoCs.
 
+config PINCTRL_RTL8231
+	tristate "Realtek RTL8231 GPIO expander's pin controller"
+	depends on MFD_RTL8231
+	default MFD_RTL8231
+	select GENERIC_PINCONF
+	select PINMUX
+	help
+	  Support for RTL8231 expander's GPIOs and pin controller.
+	  When built as a module, the module will be called rtl8231_pinctrl.
+
 config PINCTRL_SINGLE
 	tristate "One-register-per-pin type device tree based pinctrl driver"
 	depends on OF
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 5ef5334a797f..239603efb317 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_PINCTRL_PALMAS)	+= pinctrl-palmas.o
 obj-$(CONFIG_PINCTRL_PIC32)	+= pinctrl-pic32.o
 obj-$(CONFIG_PINCTRL_PISTACHIO)	+= pinctrl-pistachio.o
 obj-$(CONFIG_PINCTRL_ROCKCHIP)	+= pinctrl-rockchip.o
+obj-$(CONFIG_PINCTRL_RTL8231)	+= pinctrl-rtl8231.o
 obj-$(CONFIG_PINCTRL_SINGLE)	+= pinctrl-single.o
 obj-$(CONFIG_PINCTRL_SX150X)	+= pinctrl-sx150x.o
 obj-$(CONFIG_ARCH_TEGRA)	+= tegra/
diff --git a/drivers/pinctrl/pinctrl-rtl8231.c b/drivers/pinctrl/pinctrl-rtl8231.c
new file mode 100644
index 000000000000..bbfb407e790d
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-rtl8231.c
@@ -0,0 +1,497 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bitops.h>
+#include <linux/gpio/driver.h>
+#include <linux/mfd/rtl8231.h>
+#include <linux/module.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define RTL8231_NUM_GPIOS	37
+
+enum rtl8231_gpio_regfield {
+	RTL8231_FIELD_GPIO_DIR0,
+	RTL8231_FIELD_GPIO_DIR1,
+	RTL8231_FIELD_GPIO_DIR2,
+	RTL8231_FIELD_GPIO_DATA0,
+	RTL8231_FIELD_GPIO_DATA1,
+	RTL8231_FIELD_GPIO_DATA2,
+	RTL8231_FIELD_GPIO_MAX
+};
+
+static struct reg_field rtl8231_gpio_fields[RTL8231_FIELD_GPIO_MAX] = {
+	[RTL8231_FIELD_GPIO_DIR0] = REG_FIELD(RTL8231_REG_GPIO_DIR0, 0, 15),
+	[RTL8231_FIELD_GPIO_DIR1] = REG_FIELD(RTL8231_REG_GPIO_DIR1, 0, 15),
+	[RTL8231_FIELD_GPIO_DIR2] = REG_FIELD(RTL8231_REG_PIN_HI_CFG, 5, 9),
+	[RTL8231_FIELD_GPIO_DATA0] = REG_FIELD(RTL8231_REG_GPIO_DATA0, 0, 15),
+	[RTL8231_FIELD_GPIO_DATA1] = REG_FIELD(RTL8231_REG_GPIO_DATA1, 0, 15),
+	[RTL8231_FIELD_GPIO_DATA2] = REG_FIELD(RTL8231_REG_GPIO_DATA2, 0, 4),
+};
+
+struct rtl8231_function {
+	const char *name;
+	unsigned int ngroups;
+	const char **groups;
+};
+
+struct rtl8231_pin_ctrl {
+	/* Pin controller */
+	struct pinctrl_desc pctl_desc;
+	unsigned int nfunctions;
+	struct rtl8231_function *functions;
+	struct regmap *map;
+	/* GPIO controller */
+	struct gpio_chip gc;
+	struct regmap_field *fields[RTL8231_FIELD_GPIO_MAX];
+};
+
+/*
+ * Pin controller functionality
+ */
+static const char * const rtl8231_pin_function_names[] = {
+	"gpio",
+	"led",
+	"pwm",
+};
+
+enum rtl8231_pin_function {
+	RTL8231_PIN_FUNCTION_GPIO = BIT(0),
+	RTL8231_PIN_FUNCTION_LED = BIT(1),
+	RTL8231_PIN_FUNCTION_PWM = BIT(2),
+};
+
+struct rtl8231_pin_desc {
+	unsigned int number;
+	const char *name;
+	enum rtl8231_pin_function functions;
+	u8 reg;
+	u8 offset;
+	u8 gpio_function_value;
+};
+
+#define RTL8231_PIN(_num, _func, _reg, _fld, _val)		\
+	{							\
+		.number = _num,					\
+		.name = "gpio" #_num,				\
+		.functions = RTL8231_PIN_FUNCTION_GPIO | _func,	\
+		.reg = _reg,					\
+		.offset = _fld,					\
+		.gpio_function_value = _val,			\
+	}
+#define RTL8231_GPIO_PIN(_num)					\
+	RTL8231_PIN(_num, 0, 0, 0, 0)
+#define RTL8231_LED_PIN(_num, _reg, _fld)			\
+	RTL8231_PIN(_num, RTL8231_PIN_FUNCTION_LED, _reg, _fld, RTL8231_PIN_MODE_GPIO)
+#define RTL8231_PWM_PIN(_num, _reg, _fld)			\
+	RTL8231_PIN(_num, RTL8231_PIN_FUNCTION_PWM, _reg, _fld, 0)
+
+/* Pins always support GPIO, and may support one alternate function */
+static const struct rtl8231_pin_desc rtl8231_pins[RTL8231_NUM_GPIOS] = {
+	RTL8231_LED_PIN(0, RTL8231_REG_PIN_MODE0, 0),
+	RTL8231_LED_PIN(1, RTL8231_REG_PIN_MODE0, 1),
+	RTL8231_LED_PIN(2, RTL8231_REG_PIN_MODE0, 2),
+	RTL8231_LED_PIN(3, RTL8231_REG_PIN_MODE0, 3),
+	RTL8231_LED_PIN(4, RTL8231_REG_PIN_MODE0, 4),
+	RTL8231_LED_PIN(5, RTL8231_REG_PIN_MODE0, 5),
+	RTL8231_LED_PIN(6, RTL8231_REG_PIN_MODE0, 6),
+	RTL8231_LED_PIN(7, RTL8231_REG_PIN_MODE0, 7),
+	RTL8231_LED_PIN(8, RTL8231_REG_PIN_MODE0, 8),
+	RTL8231_LED_PIN(9, RTL8231_REG_PIN_MODE0, 9),
+	RTL8231_LED_PIN(10, RTL8231_REG_PIN_MODE0, 10),
+	RTL8231_LED_PIN(11, RTL8231_REG_PIN_MODE0, 11),
+	RTL8231_LED_PIN(12, RTL8231_REG_PIN_MODE0, 12),
+	RTL8231_LED_PIN(13, RTL8231_REG_PIN_MODE0, 13),
+	RTL8231_LED_PIN(14, RTL8231_REG_PIN_MODE0, 14),
+	RTL8231_LED_PIN(15, RTL8231_REG_PIN_MODE0, 15),
+	RTL8231_LED_PIN(16, RTL8231_REG_PIN_MODE1, 0),
+	RTL8231_LED_PIN(17, RTL8231_REG_PIN_MODE1, 1),
+	RTL8231_LED_PIN(18, RTL8231_REG_PIN_MODE1, 2),
+	RTL8231_LED_PIN(19, RTL8231_REG_PIN_MODE1, 3),
+	RTL8231_LED_PIN(20, RTL8231_REG_PIN_MODE1, 4),
+	RTL8231_LED_PIN(21, RTL8231_REG_PIN_MODE1, 5),
+	RTL8231_LED_PIN(22, RTL8231_REG_PIN_MODE1, 6),
+	RTL8231_LED_PIN(23, RTL8231_REG_PIN_MODE1, 7),
+	RTL8231_LED_PIN(24, RTL8231_REG_PIN_MODE1, 8),
+	RTL8231_LED_PIN(25, RTL8231_REG_PIN_MODE1, 9),
+	RTL8231_LED_PIN(26, RTL8231_REG_PIN_MODE1, 10),
+	RTL8231_LED_PIN(27, RTL8231_REG_PIN_MODE1, 11),
+	RTL8231_LED_PIN(28, RTL8231_REG_PIN_MODE1, 12),
+	RTL8231_LED_PIN(29, RTL8231_REG_PIN_MODE1, 13),
+	RTL8231_LED_PIN(30, RTL8231_REG_PIN_MODE1, 14),
+	RTL8231_LED_PIN(31, RTL8231_REG_PIN_MODE1, 15),
+	RTL8231_LED_PIN(32, RTL8231_REG_PIN_HI_CFG, 0),
+	RTL8231_LED_PIN(33, RTL8231_REG_PIN_HI_CFG, 1),
+	RTL8231_LED_PIN(34, RTL8231_REG_PIN_HI_CFG, 2),
+	RTL8231_PWM_PIN(35, RTL8231_REG_FUNC1, 3),
+	RTL8231_GPIO_PIN(36),
+};
+
+static int rtl8231_get_groups_count(struct pinctrl_dev *pctldev)
+{
+	return ARRAY_SIZE(rtl8231_pins);
+}
+
+static const char *rtl8231_get_group_name(struct pinctrl_dev *pctldev, unsigned int selector)
+{
+	return rtl8231_pins[selector].name;
+}
+
+static int rtl8231_get_group_pins(struct pinctrl_dev *pctldev, unsigned int selector,
+	const unsigned int **pins, unsigned int *num_pins)
+{
+	if (selector < ARRAY_SIZE(rtl8231_pins)) {
+		*pins = &rtl8231_pins[selector].number;
+		*num_pins = 1;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static const struct pinctrl_ops rtl8231_pinctrl_ops = {
+	.get_groups_count = rtl8231_get_groups_count,
+	.get_group_name = rtl8231_get_group_name,
+	.get_group_pins = rtl8231_get_group_pins,
+	.dt_node_to_map = pinconf_generic_dt_node_to_map_all,
+	.dt_free_map = pinconf_generic_dt_free_map,
+};
+
+static int rtl8231_get_functions_count(struct pinctrl_dev *pctldev)
+{
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+
+	return ctrl->nfunctions;
+}
+
+static const char *rtl8231_get_function_name(struct pinctrl_dev *pctldev, unsigned int selector)
+{
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+
+	return ctrl->functions[selector].name;
+}
+
+static int rtl8231_get_function_groups(struct pinctrl_dev *pctldev, unsigned int selector,
+	const char * const **groups, unsigned int *num_groups)
+{
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+
+	*groups = ctrl->functions[selector].groups;
+	*num_groups = ctrl->functions[selector].ngroups;
+	return 0;
+}
+
+static int rtl8231_set_mux(struct pinctrl_dev *pctldev, unsigned int func_selector,
+	unsigned int group_selector)
+{
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+	const struct rtl8231_pin_desc *desc = &rtl8231_pins[group_selector];
+	unsigned int func_flag = BIT(func_selector);
+	unsigned int function_mask;
+	unsigned int gpio_function;
+	int err = 0;
+
+	if (!(desc->functions & func_flag))
+		return -EINVAL;
+
+	function_mask = BIT(desc->offset);
+	gpio_function = desc->gpio_function_value << desc->offset;
+
+	switch (func_flag) {
+	case RTL8231_PIN_FUNCTION_LED:
+	case RTL8231_PIN_FUNCTION_PWM:
+		err = regmap_update_bits(ctrl->map, desc->reg, function_mask, ~gpio_function);
+		break;
+	case RTL8231_PIN_FUNCTION_GPIO:
+		err = regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return err;
+}
+
+static int rtl8231_gpio_request_enable(struct pinctrl_dev *pctldev,
+	struct pinctrl_gpio_range *range, unsigned int offset)
+{
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+	const struct rtl8231_pin_desc *desc = &rtl8231_pins[offset];
+	unsigned int function_mask;
+	unsigned int gpio_function;
+
+	function_mask = BIT(desc->offset);
+	gpio_function = desc->gpio_function_value << desc->offset;
+
+	return regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
+}
+
+static const struct pinmux_ops rtl8231_pinmux_ops = {
+	.set_mux = rtl8231_set_mux,
+	.get_functions_count = rtl8231_get_functions_count,
+	.get_function_name = rtl8231_get_function_name,
+	.get_function_groups = rtl8231_get_function_groups,
+	.gpio_request_enable = rtl8231_gpio_request_enable,
+	.strict = true
+};
+
+
+static int rtl8231_pinctrl_init_functions(struct device *dev, struct rtl8231_pin_ctrl *ctrl)
+{
+	struct rtl8231_function *function;
+	const char **group_name;
+	unsigned int f_idx;
+	unsigned int pin;
+
+	ctrl->nfunctions = ARRAY_SIZE(rtl8231_pin_function_names);
+	ctrl->functions = devm_kcalloc(dev, ctrl->nfunctions, sizeof(*ctrl->functions), GFP_KERNEL);
+	if (IS_ERR(ctrl->functions)) {
+		dev_err(dev, "failed to allocate pin function descriptors\n");
+		return PTR_ERR(ctrl->functions);
+	}
+
+	for (f_idx = 0; f_idx < ctrl->nfunctions; f_idx++) {
+		function = &ctrl->functions[f_idx];
+		function->name = rtl8231_pin_function_names[f_idx];
+
+		for (pin = 0; pin < ctrl->pctl_desc.npins; pin++)
+			if (rtl8231_pins[pin].functions & BIT(f_idx))
+				function->ngroups++;
+
+		function->groups = devm_kcalloc(dev, function->ngroups,
+			sizeof(*function->groups), GFP_KERNEL);
+		if (IS_ERR(function->groups)) {
+			dev_err(dev, "failed to allocate pin function group names\n");
+			return PTR_ERR(function->groups);
+		}
+
+		group_name = function->groups;
+		for (pin = 0; pin < ctrl->pctl_desc.npins; pin++)
+			if (rtl8231_pins[pin].functions & BIT(f_idx))
+				*group_name++ = rtl8231_pins[pin].name;
+	}
+
+	return 0;
+}
+
+static int rtl8231_pinctrl_init(struct device *dev, struct rtl8231_pin_ctrl *ctrl)
+{
+	struct pinctrl_dev *pctl;
+	struct pinctrl_pin_desc *pins;
+	unsigned int pin;
+	int err = 0;
+
+	ctrl->pctl_desc.name = "rtl8231-pinctrl",
+	ctrl->pctl_desc.owner = THIS_MODULE,
+	ctrl->pctl_desc.pctlops = &rtl8231_pinctrl_ops,
+	ctrl->pctl_desc.pmxops = &rtl8231_pinmux_ops,
+
+	ctrl->pctl_desc.npins = ARRAY_SIZE(rtl8231_pins);
+	pins = devm_kcalloc(dev, ctrl->pctl_desc.npins, sizeof(*pins), GFP_KERNEL);
+	if (IS_ERR(pins)) {
+		dev_err(dev, "failed to allocate pin descriptors\n");
+		return PTR_ERR(pins);
+	}
+	ctrl->pctl_desc.pins = pins;
+
+	for (pin = 0; pin < ctrl->pctl_desc.npins; pin++) {
+		pins[pin].number = rtl8231_pins[pin].number;
+		pins[pin].name = rtl8231_pins[pin].name;
+	}
+
+	err = rtl8231_pinctrl_init_functions(dev, ctrl);
+	if (err)
+		return err;
+
+	err = devm_pinctrl_register_and_init(dev->parent, &ctrl->pctl_desc, ctrl, &pctl);
+	if (err) {
+		dev_err(dev, "failed to register pin controller\n");
+		return err;
+	}
+
+	err = pinctrl_enable(pctl);
+	if (err)
+		dev_err(dev, "failed to enable pin controller\n");
+
+	return err;
+}
+
+/*
+ * GPIO controller functionality
+ */
+static int rtl8231_pin_read(struct rtl8231_pin_ctrl *ctrl, int base, int offset)
+{
+	int field = base + offset / 16;
+	int bit = offset % 16;
+	unsigned int v;
+	int err;
+
+	err = regmap_field_read(ctrl->fields[field], &v);
+	if (err)
+		return err;
+
+	return !!(v & BIT(bit));
+}
+
+static int rtl8231_pin_write(struct rtl8231_pin_ctrl *ctrl, int base, int offset, int val)
+{
+	int field = base + offset / 16;
+	int bit = offset % 16;
+
+	return regmap_field_update_bits(ctrl->fields[field], BIT(bit), val << bit);
+}
+
+static int rtl8231_direction_input(struct gpio_chip *gc, unsigned int offset)
+{
+	struct rtl8231_pin_ctrl *ctrl = gpiochip_get_data(gc);
+
+	return rtl8231_pin_write(ctrl, RTL8231_FIELD_GPIO_DIR0, offset, RTL8231_GPIO_DIR_IN);
+}
+
+static int rtl8231_direction_output(struct gpio_chip *gc, unsigned int offset, int value)
+{
+	struct rtl8231_pin_ctrl *ctrl = gpiochip_get_data(gc);
+	int err;
+
+	err = rtl8231_pin_write(ctrl, RTL8231_FIELD_GPIO_DIR0, offset, RTL8231_GPIO_DIR_OUT);
+	if (err)
+		return err;
+
+	return rtl8231_pin_write(ctrl, RTL8231_FIELD_GPIO_DATA0, offset, value);
+}
+
+static int rtl8231_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+	struct rtl8231_pin_ctrl *ctrl = gpiochip_get_data(gc);
+
+	return rtl8231_pin_read(ctrl, RTL8231_FIELD_GPIO_DIR0, offset);
+}
+
+static int rtl8231_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+	struct rtl8231_pin_ctrl *ctrl = gpiochip_get_data(gc);
+
+	return rtl8231_pin_read(ctrl, RTL8231_FIELD_GPIO_DATA0, offset);
+}
+
+static void rtl8231_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+	struct rtl8231_pin_ctrl *ctrl = gpiochip_get_data(gc);
+
+	rtl8231_pin_write(ctrl, RTL8231_FIELD_GPIO_DATA0, offset, value);
+}
+
+static int rtl8231_gpio_get_multiple(struct gpio_chip *gc,
+	unsigned long *mask, unsigned long *bits)
+{
+	struct rtl8231_pin_ctrl *ctrl = gpiochip_get_data(gc);
+	unsigned long sub_mask, bit_value;
+	struct regmap_field **field;
+	unsigned int reg_value;
+	int offset, shift;
+	int read;
+	int err;
+
+	field = &ctrl->fields[RTL8231_FIELD_GPIO_DATA0];
+
+	for (read = 0; read < gc->ngpio; field++, read += 16) {
+		shift = read % BITS_PER_TYPE(*bits);
+		offset = read / BITS_PER_TYPE(*bits);
+		sub_mask = mask[offset] & (0xffffUL << shift);
+		if (sub_mask) {
+			err = regmap_field_read(*field, &reg_value);
+			if (err)
+				return err;
+			bit_value = ((unsigned long) reg_value) << shift;
+			bits[offset] = (bits[offset] & ~sub_mask) | (bit_value & sub_mask);
+		}
+	}
+
+	return err;
+}
+
+static void rtl8231_gpio_set_multiple(struct gpio_chip *gc,
+	unsigned long *mask, unsigned long *bits)
+{
+	struct rtl8231_pin_ctrl *ctrl = gpiochip_get_data(gc);
+	unsigned long sub_mask, value;
+	struct regmap_field **field;
+	int offset, shift;
+	int read;
+
+	field = &ctrl->fields[RTL8231_FIELD_GPIO_DATA0];
+
+	for (read = 0; read < gc->ngpio; field++, read += 16) {
+		shift = read % BITS_PER_TYPE(*bits);
+		offset = read / BITS_PER_TYPE(*bits);
+		sub_mask = (mask[offset] >> shift) & 0xffff;
+		if (sub_mask) {
+			value = bits[offset] >> shift;
+			regmap_field_update_bits(*field, sub_mask, value);
+		}
+	}
+}
+
+static int rtl8231_pinctrl_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rtl8231_pin_ctrl *ctrl;
+	int err;
+
+	ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+	if (!ctrl)
+		return -ENOMEM;
+
+	ctrl->map = dev_get_regmap(dev->parent, NULL);
+	if (IS_ERR_OR_NULL(ctrl->map)) {
+		dev_err(dev, "failed to retrieve regmap\n");
+		if (!ctrl->map)
+			return -ENODEV;
+		else
+			return PTR_ERR(ctrl->map);
+	}
+
+	err = devm_regmap_field_bulk_alloc(dev, ctrl->map, ctrl->fields, rtl8231_gpio_fields,
+		ARRAY_SIZE(ctrl->fields));
+	if (err) {
+		dev_err(dev, "unable to allocate gpio regmap fields\n");
+		return err;
+	}
+
+	err = rtl8231_pinctrl_init(dev, ctrl);
+	if (err)
+		return err;
+
+	ctrl->gc.base = -1;
+	ctrl->gc.ngpio = RTL8231_NUM_GPIOS;
+	ctrl->gc.label = pdev->name;
+	ctrl->gc.owner = THIS_MODULE;
+	ctrl->gc.can_sleep = true;
+	ctrl->gc.parent = dev->parent;
+
+	ctrl->gc.set = rtl8231_gpio_set;
+	ctrl->gc.set_multiple = rtl8231_gpio_set_multiple;
+	ctrl->gc.get = rtl8231_gpio_get;
+	ctrl->gc.get_multiple = rtl8231_gpio_get_multiple;
+	ctrl->gc.direction_input = rtl8231_direction_input;
+	ctrl->gc.direction_output = rtl8231_direction_output;
+	ctrl->gc.get_direction = rtl8231_get_direction;
+	ctrl->gc.request = gpiochip_generic_request;
+	ctrl->gc.free = gpiochip_generic_free;
+
+	return devm_gpiochip_add_data(dev, &ctrl->gc, ctrl);
+}
+
+static struct platform_driver rtl8231_pinctrl_driver = {
+	.driver = {
+		.name = "rtl8231-pinctrl",
+	},
+	.probe = rtl8231_pinctrl_probe,
+};
+module_platform_driver(rtl8231_pinctrl_driver);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek RTL8231 pin control and GPIO support");
+MODULE_LICENSE("GPL v2");
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH 5/5] leds: Add support for RTL8231 LED scan matrix
  2021-05-11 12:25 [PATCH 0/5] RTL8231 GPIO expander support Sander Vanheule
                   ` (3 preceding siblings ...)
  2021-05-11 12:25 ` [PATCH 4/5] pinctrl: Add RTL8231 pin control and GPIO support Sander Vanheule
@ 2021-05-11 12:25 ` Sander Vanheule
       [not found] ` <CAHp75VffoKyyPJbdtKMLx575c9LT0S8+EHOk7Mw36j=aTL6Q4Q@mail.gmail.com>
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-11 12:25 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij, linux-leds,
	devicetree, linux-gpio
  Cc: linux-kernel, Sander Vanheule

Both single and bi-color scanning modes are supported. The driver will
verify that the addresses are valid for the current mode, before
registering the LEDs.

LEDs can be turned on, off, or toggled at one of six predefined rates
from 40ms to 1280ms.

Implements a platform device for use as child device with RTL8231 MFD,
and uses the parent regmap to access the required registers.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 drivers/leds/Kconfig        |  10 ++
 drivers/leds/Makefile       |   1 +
 drivers/leds/leds-rtl8231.c | 281 ++++++++++++++++++++++++++++++++++++
 3 files changed, 292 insertions(+)
 create mode 100644 drivers/leds/leds-rtl8231.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 49d99cb084db..e5ff6150800c 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -593,6 +593,16 @@ config LEDS_REGULATOR
 	help
 	  This option enables support for regulator driven LEDs.
 
+config LEDS_RTL8231
+	tristate "RTL8231 LED matrix support"
+	depends on LEDS_CLASS
+	depends on MFD_RTL8231
+	default MFD_RTL8231
+	help
+	  This options enables support for using the LED scanning matrix output
+	  of the RTL8231 GPIO and LED expander chip.
+	  When built as a module, this module will be named rtl8231_leds.
+
 config LEDS_BD2802
 	tristate "LED driver for BD2802 RGB LED"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 7e604d3028c8..ce0f44a87dee 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -80,6 +80,7 @@ obj-$(CONFIG_LEDS_PM8058)		+= leds-pm8058.o
 obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
 obj-$(CONFIG_LEDS_PWM)			+= leds-pwm.o
 obj-$(CONFIG_LEDS_REGULATOR)		+= leds-regulator.o
+obj-$(CONFIG_LEDS_RTL8231)		+= leds-rtl8231.o
 obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o
 obj-$(CONFIG_LEDS_SC27XX_BLTC)		+= leds-sc27xx-bltc.o
 obj-$(CONFIG_LEDS_SGM3140)		+= leds-sgm3140.o
diff --git a/drivers/leds/leds-rtl8231.c b/drivers/leds/leds-rtl8231.c
new file mode 100644
index 000000000000..fc39eb0d950e
--- /dev/null
+++ b/drivers/leds/leds-rtl8231.c
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/device.h>
+#include <linux/leds.h>
+#include <linux/mfd/rtl8231.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/property.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+struct led_toggle_rate {
+	u16 interval; /* Toggle interval in ms */
+	u8 mode; /* Register value */
+};
+
+struct led_modes {
+	/* Array of toggle rates, sorted by interval */
+	const struct led_toggle_rate *toggle_rates;
+	unsigned int num_toggle_rates;
+	u8 off;
+	u8 on;
+};
+
+struct rtl8231_led {
+	struct led_classdev led;
+	const struct led_modes *modes;
+	struct regmap_field *reg_field;
+};
+#define to_rtl8231_led(_cdev) container_of(_cdev, struct rtl8231_led, led)
+
+#define RTL8231_NUM_LEDS	3
+#define RTL8231_LED_PER_REG	5
+#define RTL8231_BITS_PER_LED	3
+
+static const unsigned int rtl8231_led_port_count_single[RTL8231_NUM_LEDS] = {32, 32, 24};
+static const unsigned int rtl8231_led_port_count_bicolor[RTL8231_NUM_LEDS] = {24, 24, 24};
+
+static const unsigned int rtl8231_led_base[RTL8231_NUM_LEDS] = {
+	RTL8231_REG_LED0_BASE,
+	RTL8231_REG_LED1_BASE,
+	RTL8231_REG_LED2_BASE,
+};
+
+static const struct led_toggle_rate rtl8231_toggle_rates[] = {
+	{  40, 1},
+	{  80, 2},
+	{ 160, 3},
+	{ 320, 4},
+	{ 640, 5},
+	{1280, 6},
+};
+
+static const struct led_modes rtl8231_led_modes = {
+	.off = 0,
+	.on = 7,
+	.num_toggle_rates = ARRAY_SIZE(rtl8231_toggle_rates),
+	.toggle_rates = rtl8231_toggle_rates,
+};
+
+static void rtl8231_led_brightness_set(struct led_classdev *led_cdev,
+	enum led_brightness brightness)
+{
+	struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
+
+	if (brightness)
+		regmap_field_write(pled->reg_field, pled->modes->on);
+	else
+		regmap_field_write(pled->reg_field, pled->modes->off);
+}
+
+static enum led_brightness rtl8231_led_brightness_get(struct led_classdev *led_cdev)
+{
+	struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
+	u32 current_mode = pled->modes->off;
+
+	regmap_field_read(pled->reg_field, &current_mode);
+
+	if (current_mode == pled->modes->off)
+		return LED_OFF;
+	else
+		return LED_ON;
+}
+
+static unsigned int rtl8231_led_current_interval(struct rtl8231_led *pled)
+{
+	unsigned int mode;
+	unsigned int i = 0;
+
+	if (regmap_field_read(pled->reg_field, &mode))
+		return 0;
+
+	while (i < pled->modes->num_toggle_rates && mode != pled->modes->toggle_rates[i].mode)
+		i++;
+
+	if (i < pled->modes->num_toggle_rates)
+		return pled->modes->toggle_rates[i].interval;
+	else
+		return 0;
+}
+
+static int rtl8231_led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
+	unsigned long *delay_off)
+{
+	struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
+	const struct led_modes *modes = pled->modes;
+	unsigned int interval;
+	unsigned int i = 0;
+	int err;
+
+	if (*delay_on == 0 && *delay_off == 0) {
+		/* Choose 500ms as default interval */
+		interval = 500;
+	} else {
+		/*
+		 * If the current mode is blinking, choose the delay that (likely) changed.
+		 * Otherwise, choose the interval that would have the same total delay.
+		 */
+		interval = rtl8231_led_current_interval(pled);
+
+		if (interval > 0 && interval == *delay_off)
+			interval = *delay_on;
+		else if (interval > 0 && interval == *delay_on)
+			interval = *delay_off;
+		else
+			interval = (*delay_on + *delay_off) / 2;
+	}
+
+	/* Find clamped toggle interval */
+	while (i < (modes->num_toggle_rates - 1) && interval > modes->toggle_rates[i].interval)
+		i++;
+
+	interval = modes->toggle_rates[i].interval;
+
+	err = regmap_field_write(pled->reg_field, modes->toggle_rates[i].mode);
+	if (err)
+		return err;
+
+	*delay_on = interval;
+	*delay_off = interval;
+
+	return 0;
+}
+
+static int rtl8231_led_read_address(struct device_node *np, unsigned int *addr_port,
+	unsigned int *addr_led)
+{
+	const __be32 *addr;
+
+	if (of_n_addr_cells(np) != 2 || of_n_size_cells(np) != 0)
+		return -ENODEV;
+
+	addr = of_get_address(np, 0, NULL, NULL);
+	if (!addr)
+		return -ENODEV;
+
+	*addr_port = of_read_number(addr, 1);
+	*addr_led = of_read_number(addr + 1, 1);
+
+	return 0;
+}
+
+static struct reg_field rtl8231_led_get_field(unsigned int port_index, unsigned int led_index)
+{
+	unsigned int offset, shift;
+	struct reg_field field;
+
+	offset = port_index / RTL8231_LED_PER_REG;
+	shift = (port_index % RTL8231_LED_PER_REG) * RTL8231_BITS_PER_LED;
+
+	field.reg = rtl8231_led_base[led_index] + offset;
+	field.lsb = shift;
+	field.msb = shift + RTL8231_BITS_PER_LED - 1;
+
+	return field;
+}
+
+static int rtl8231_led_probe_single(struct device *dev, struct regmap *map,
+	const unsigned int *port_count, struct device_node *np)
+{
+	struct rtl8231_led *pled;
+	unsigned int port_index;
+	unsigned int led_index;
+	struct reg_field field;
+	struct led_init_data init_data = {};
+	int err;
+
+	pled = devm_kzalloc(dev, sizeof(*pled), GFP_KERNEL);
+	if (IS_ERR(pled))
+		return PTR_ERR(pled);
+
+	err = rtl8231_led_read_address(np, &port_index, &led_index);
+
+	if (err) {
+		dev_err(dev, "LED address invalid\n");
+		return err;
+	} else if (led_index >= RTL8231_NUM_LEDS || port_index >= port_count[led_index]) {
+		dev_err(dev, "LED address (%d.%d) invalid\n", port_index, led_index);
+		return -ENODEV;
+	}
+
+	field = rtl8231_led_get_field(port_index, led_index);
+	pled->reg_field = devm_regmap_field_alloc(dev, map, field);
+	if (IS_ERR(pled->reg_field))
+		return PTR_ERR(pled->reg_field);
+
+	pled->modes = &rtl8231_led_modes;
+
+	pled->led.max_brightness = 1;
+	pled->led.brightness_get = rtl8231_led_brightness_get;
+	pled->led.brightness_set = rtl8231_led_brightness_set;
+	pled->led.blink_set = rtl8231_led_blink_set;
+
+	init_data.fwnode = of_fwnode_handle(np);
+
+	return devm_led_classdev_register_ext(dev, &pled->led, &init_data);
+}
+
+static int rtl8231_led_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const unsigned int *port_count;
+	struct device_node *child;
+	struct regmap *map;
+	int err;
+
+	map = dev_get_regmap(dev->parent, NULL);
+	if (IS_ERR_OR_NULL(map)) {
+		dev_err(dev, "failed to retrieve regmap\n");
+		if (!map)
+			return -ENODEV;
+		else
+			return PTR_ERR(map);
+	}
+
+	if (!device_property_match_string(dev, "realtek,led-scan-mode", "single-color")) {
+		port_count = rtl8231_led_port_count_single;
+		regmap_update_bits(map, RTL8231_REG_FUNC0,
+			RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_SINGLE);
+	} else if (!device_property_match_string(dev, "realtek,led-scan-mode", "bi-color")) {
+		port_count = rtl8231_led_port_count_bicolor;
+		regmap_update_bits(map, RTL8231_REG_FUNC0,
+			RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_BICOLOR);
+	} else {
+		dev_err(dev, "scan mode missing or invalid\n");
+		return -EINVAL;
+	}
+
+	for_each_child_of_node(dev->of_node, child) {
+		if (of_node_name_prefix(child, "led")) {
+			err = rtl8231_led_probe_single(dev, map, port_count, child);
+			if (err)
+				dev_warn(dev, "failed to register %pOF\n", child);
+			continue;
+		}
+
+		dev_dbg(dev, "skipping unsupported node %pOF\n", child);
+	}
+
+	return 0;
+}
+
+static const struct of_device_id of_rtl8231_led_match[] = {
+	{ .compatible = "realtek,rtl8231-leds" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, of_rtl8231_led_match);
+
+static struct platform_driver rtl8231_led_driver = {
+	.driver = {
+		.name = "rtl8231-leds",
+		.of_match_table = of_rtl8231_led_match,
+	},
+	.probe = rtl8231_led_probe,
+};
+module_platform_driver(rtl8231_led_driver);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek RTL8231 LED support");
+MODULE_LICENSE("GPL v2");
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 3/5] mfd: Add RTL8231 core device
  2021-05-11 12:25 ` [PATCH 3/5] mfd: Add RTL8231 core device Sander Vanheule
@ 2021-05-12 12:29   ` kernel test robot
  2021-05-12 13:13   ` kernel test robot
  1 sibling, 0 replies; 114+ messages in thread
From: kernel test robot @ 2021-05-12 12:29 UTC (permalink / raw)
  To: Sander Vanheule, Pavel Machek, Rob Herring, Lee Jones,
	Linus Walleij, linux-leds, devicetree, linux-gpio
  Cc: kbuild-all, linux-kernel, Sander Vanheule

[-- Attachment #1: Type: text/plain, Size: 2166 bytes --]

Hi Sander,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on pavel-linux-leds/for-next]
[also build test ERROR on lee-mfd/for-mfd-next pinctrl/devel v5.13-rc1]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Sander-Vanheule/RTL8231-GPIO-expander-support/20210511-202618
base:   git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds.git for-next
config: microblaze-randconfig-r023-20210512 (attached as .config)
compiler: microblaze-linux-gcc (GCC) 9.3.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/0day-ci/linux/commit/e031cc2da2c2948230bacd1ca56cfe9990e1aefd
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Sander-Vanheule/RTL8231-GPIO-expander-support/20210511-202618
        git checkout e031cc2da2c2948230bacd1ca56cfe9990e1aefd
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross W=1 ARCH=microblaze 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

   microblaze-linux-ld: drivers/mfd/rtl8231.o: in function `rtl8231_mdio_reg_write':
>> (.text+0x50): undefined reference to `mdiobus_write'
   microblaze-linux-ld: drivers/mfd/rtl8231.o: in function `rtl8231_mdio_reg_read':
>> (.text+0x80): undefined reference to `mdiobus_read'
   microblaze-linux-ld: drivers/mfd/rtl8231.o: in function `mdio_module_init':
>> (.init.text+0x10): undefined reference to `mdio_driver_register'
   microblaze-linux-ld: drivers/mfd/rtl8231.o: in function `mdio_module_exit':
>> (.exit.text+0x10): undefined reference to `mdio_driver_unregister'

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org

[-- Attachment #2: .config.gz --]
[-- Type: application/gzip, Size: 33447 bytes --]

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 3/5] mfd: Add RTL8231 core device
  2021-05-11 12:25 ` [PATCH 3/5] mfd: Add RTL8231 core device Sander Vanheule
  2021-05-12 12:29   ` kernel test robot
@ 2021-05-12 13:13   ` kernel test robot
  2021-05-19 14:58     ` Lee Jones
  1 sibling, 1 reply; 114+ messages in thread
From: kernel test robot @ 2021-05-12 13:13 UTC (permalink / raw)
  To: Sander Vanheule, Pavel Machek, Rob Herring, Lee Jones,
	Linus Walleij, linux-leds, devicetree, linux-gpio
  Cc: kbuild-all, linux-kernel, Sander Vanheule

[-- Attachment #1: Type: text/plain, Size: 2016 bytes --]

Hi Sander,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on pavel-linux-leds/for-next]
[also build test ERROR on lee-mfd/for-mfd-next pinctrl/devel v5.13-rc1]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Sander-Vanheule/RTL8231-GPIO-expander-support/20210511-202618
base:   git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds.git for-next
config: h8300-randconfig-r012-20210512 (attached as .config)
compiler: h8300-linux-gcc (GCC) 9.3.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/0day-ci/linux/commit/e031cc2da2c2948230bacd1ca56cfe9990e1aefd
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Sander-Vanheule/RTL8231-GPIO-expander-support/20210511-202618
        git checkout e031cc2da2c2948230bacd1ca56cfe9990e1aefd
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross W=1 ARCH=h8300 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

   h8300-linux-ld: drivers/mfd/rtl8231.o: in function `rtl8231_mdio_reg_write':
>> rtl8231.c:(.text+0x4f): undefined reference to `mdiobus_write'
   h8300-linux-ld: drivers/mfd/rtl8231.o: in function `rtl8231_mdio_reg_read':
>> rtl8231.c:(.text+0x75): undefined reference to `mdiobus_read'
   h8300-linux-ld: drivers/mfd/rtl8231.o: in function `mdio_module_init':
>> rtl8231.c:(.init.text+0xd): undefined reference to `mdio_driver_register'

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org

[-- Attachment #2: .config.gz --]
[-- Type: application/gzip, Size: 18783 bytes --]

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 0/5] RTL8231 GPIO expander support
       [not found] ` <CAHp75VffoKyyPJbdtKMLx575c9LT0S8+EHOk7Mw36j=aTL6Q4Q@mail.gmail.com>
@ 2021-05-16 21:40   ` Sander Vanheule
  2021-05-17  8:13     ` Andy Shevchenko
  2021-05-17 19:32     ` Sander Vanheule
  0 siblings, 2 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-16 21:40 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij, linux-leds,
	devicetree, linux-gpio, linux-kernel

On Wed, 2021-05-12 at 18:29 +0300, Andy Shevchenko wrote:
> 
> 
> On Tuesday, May 11, 2021, Sander Vanheule <sander@svanheule.net> wrote:
> > The RTL8231 GPIO and LED expander can be configured for use as an MDIO or
> > SMI
> > bus device. Currently only the MDIO mode is supported, although SMI mode
> > support should be fairly straightforward, once an SMI bus driver is
> > available.
> > 
> > Provided features by the RTL8231:
> >   - Up to 37 GPIOs
> >     - Configurable drive strength: 8mA or 4mA (currently unsupported)
> >     - Input debouncing on high GPIOs (currently unsupported)
> >   - Up to 88 LEDs in multiple scan matrix groups
> >     - On, off, or one of six toggling intervals
> >     - "single-color mode": 2×36 single color LEDs + 8 bi-color LEDs
> >     - "bi-color mode": (12 + 2×6) bi-color LEDs + 24 single color LEDs
> >   - Up to one PWM output (currently unsupported)
> >     - Fixed duty cycle, 8 selectable frequencies (1.2kHz - 4.8kHz)
> > 
> > There remain some log warnings when probing the device, possibly due to the
> > way
> > I'm using the MFD subsystem. Would it be possible to avoid these?
> > [    2.602242] rtl8231-pinctrl: Failed to locate of_node [id: -2]
> > [    2.609380] rtl8231-pinctrl rtl8231-pinctrl.0.auto: no of_node; not
> > parsing pinctrl DT
> > 
> > When no 'leds' sub-node is specified:
> > [    2.922262] rtl8231-leds: Failed to locate of_node [id: -2]
> > [    2.967149] rtl8231-leds rtl8231-leds.1.auto: no of_node; not parsing
> > pinctrl DT
> > [    2.975673] rtl8231-leds rtl8231-leds.1.auto: scan mode missing or
> > invalid
> > [    2.983531] rtl8231-leds: probe of rtl8231-leds.1.auto failed with error
> > -22
> > 
> > 
> 
> 
> I have several comments to the series, but I may give them next week.
> 
> Just couple here:
> 1. If subsystem provides a regmap API I would suggest to use it, I.o.w. try
> again to understand what is wrong with MDIO case.

Are you referring to the MDIO regmap interface, or the GPIO regmap interface?

For the MDIO regmap interface, I have been able to resolve the Kconfig
dependency issue. So I can reintroduce that, if that's preferred over the
solution in this v1.

With an extra patch, I was able to use the gpio-regmap interface, dropping most
of the GPIO code. The current gpio-regmap implementation makes the assumption
that an output value can be set while a pin is configured as an input. That
assumption is invalid for this chip, so I had to provide an extra flag for
gpio_regmap_config, similar to how this is handled in gpio-mmio.


> 2. Please, switch to fwnode API in LED driver

Since you had the same comment on my previous patch set, I had already tried to
this this into account as much as possible.

There's a few things I couldn't find the fwnode-equivalent for:
 * I use of_node_name_prefix to enforce the naming required by the binding. I
   could just walk over all (available) child nodes, which would be mostly
   equivalent.
 * To get the address of an LED child node, I use of_get_address, since this
   appeared to provide what I want to do: get the address of the node. I know
   next to nothing about ACPI. Does the equivalent exist there? Or am I taking
   the wrong approach?


I have updated patches ready, if you would rather just review a v2.


Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 0/5] RTL8231 GPIO expander support
  2021-05-16 21:40   ` [PATCH 0/5] RTL8231 GPIO expander support Sander Vanheule
@ 2021-05-17  8:13     ` Andy Shevchenko
  2021-05-17  8:50       ` Sander Vanheule
  2021-05-17 19:32     ` Sander Vanheule
  1 sibling, 1 reply; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-17  8:13 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij, linux-leds,
	devicetree, linux-gpio, linux-kernel

On Mon, May 17, 2021 at 12:40 AM Sander Vanheule <sander@svanheule.net> wrote:
> On Wed, 2021-05-12 at 18:29 +0300, Andy Shevchenko wrote:
> > On Tuesday, May 11, 2021, Sander Vanheule <sander@svanheule.net> wrote:

...

> > I have several comments to the series, but I may give them next week.
> >
> > Just couple here:
> > 1. If subsystem provides a regmap API I would suggest to use it, I.o.w. try
> > again to understand what is wrong with MDIO case.
>
> Are you referring to the MDIO regmap interface, or the GPIO regmap interface?

MDIO

> For the MDIO regmap interface, I have been able to resolve the Kconfig
> dependency issue. So I can reintroduce that, if that's preferred over the
> solution in this v1.
>
> With an extra patch, I was able to use the gpio-regmap interface, dropping most
> of the GPIO code. The current gpio-regmap implementation makes the assumption
> that an output value can be set while a pin is configured as an input. That
> assumption is invalid for this chip, so I had to provide an extra flag for
> gpio_regmap_config, similar to how this is handled in gpio-mmio.
>
>
> > 2. Please, switch to fwnode API in LED driver
>
> Since you had the same comment on my previous patch set, I had already tried to
> this this into account as much as possible.
>
> There's a few things I couldn't find the fwnode-equivalent for:
>  * I use of_node_name_prefix to enforce the naming required by the binding. I
>    could just walk over all (available) child nodes, which would be mostly
>    equivalent.

AFAIU the LED traditional bindings is that you define LED compatible
nodes and all child nodes of it are the one-per-LED ones, there
shouldn't be others.

>  * To get the address of an LED child node, I use of_get_address, since this
>    appeared to provide what I want to do: get the address of the node. I know
>    next to nothing about ACPI. Does the equivalent exist there? Or am I taking
>    the wrong approach?

What are the means of an address in this case?

> I have updated patches ready, if you would rather just review a v2.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 0/5] RTL8231 GPIO expander support
  2021-05-17  8:13     ` Andy Shevchenko
@ 2021-05-17  8:50       ` Sander Vanheule
  0 siblings, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-17  8:50 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij, linux-leds,
	devicetree, linux-gpio, linux-kernel

On Mon, 2021-05-17 at 11:13 +0300, Andy Shevchenko wrote:
> On Mon, May 17, 2021 at 12:40 AM Sander Vanheule <sander@svanheule.net> wrote:
> > On Wed, 2021-05-12 at 18:29 +0300, Andy Shevchenko wrote:
> > > On Tuesday, May 11, 2021, Sander Vanheule <sander@svanheule.net> wrote:
> 
> ...
> 
> > >  * > > > I have several comments to the series, but I may give them next
> > >    week.
> > > 
> > > Just couple here:
> > > 1. If subsystem provides a regmap API I would suggest to use it, I.o.w.
> > > try
> > > again to understand what is wrong with MDIO case.
> > 
> > Are you referring to the MDIO regmap interface, or the GPIO regmap
> > interface?
> 
> MDIO
> 
> > For the MDIO regmap interface, I have been able to resolve the Kconfig
> > dependency issue. So I can reintroduce that, if that's preferred over the
> > solution in this v1.
> > 
> > With an extra patch, I was able to use the gpio-regmap interface, dropping
> > most
> > of the GPIO code. The current gpio-regmap implementation makes the
> > assumption
> > that an output value can be set while a pin is configured as an input. That
> > assumption is invalid for this chip, so I had to provide an extra flag for
> > gpio_regmap_config, similar to how this is handled in gpio-mmio.
> > 
> > 
> > > 2. Please, switch to fwnode API in LED driver
> > 
> > Since you had the same comment on my previous patch set, I had already tried
> > to
> > this this into account as much as possible.
> > 
> > There's a few things I couldn't find the fwnode-equivalent for:
> >  * I use of_node_name_prefix to enforce the naming required by the binding.
> > I
> >    could just walk over all (available) child nodes, which would be mostly
> >    equivalent.
> 
> AFAIU the LED traditional bindings is that you define LED compatible
> nodes and all child nodes of it are the one-per-LED ones, there
> shouldn't be others.

OK, then I can just iterate over all child fwnodes.


> >  * To get the address of an LED child node, I use of_get_address, since this
> >    appeared to provide what I want to do: get the address of the node. I
> > know
> >    next to nothing about ACPI. Does the equivalent exist there? Or am I
> > taking
> >    the wrong approach?
> 
> What are the means of an address in this case?

The chip appears to be intended for use with ethernet switches. The registers
are organised to into a few groups, to provide 2 or 3 status LEDs per switch
port:

 * "LED0" group for 32 ports,
 * "LED1" group for 32 ports,
 * "LED2" group for 24 ports

The number of LEDs that can be used depends on the output mode, so I use a two-
part <#PORT #LED> address, resembling how this is defined by Realtek.

A single linear LED address space would get awkward gaps in bi-color mode (where
only the lower 24 ports can be used), but would still require addresses to be
able to specify which LED is where. For example in case the user want to link
them to a phy trigger for a specific switch port.

Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v2 0/7] RTL8231 GPIO expander support
  2021-05-11 12:25 [PATCH 0/5] RTL8231 GPIO expander support Sander Vanheule
                   ` (5 preceding siblings ...)
       [not found] ` <CAHp75VffoKyyPJbdtKMLx575c9LT0S8+EHOk7Mw36j=aTL6Q4Q@mail.gmail.com>
@ 2021-05-17 19:28 ` Sander Vanheule
  2021-05-17 19:28   ` [PATCH v2 1/7] regmap: Add MDIO bus support Sander Vanheule
                     ` (7 more replies)
  2021-05-23 22:33 ` [PATCH v3 0/6] " Sander Vanheule
  2021-06-03 10:00 ` [PATCH v4 0/5] " Sander Vanheule
  8 siblings, 8 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-17 19:28 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio
  Cc: Andrew Lunn, Andy Shevchenko, linux-kernel, Sander Vanheule

The RTL8231 GPIO and LED expander can be configured for use as an MDIO or SMI
bus device. Currently only the MDIO mode is supported, although SMI mode
support should be fairly straightforward, once an SMI bus driver is available.

Provided features by the RTL8231:
  - Up to 37 GPIOs
    - Configurable drive strength: 8mA or 4mA (currently unsupported)
    - Input debouncing on high GPIOs (currently unsupported)
  - Up to 88 LEDs in multiple scan matrix groups
    - On, off, or one of six toggling intervals
    - "single-color mode": 2×36 single color LEDs + 8 bi-color LEDs
    - "bi-color mode": (12 + 2×6) bi-color LEDs + 24 single color LEDs
  - Up to one PWM output (currently unsupported)
    - Fixed duty cycle, 8 selectable frequencies (1.2kHz - 4.8kHz)

Register access is provided through a new MDIO regmap provider. The GPIO
controller uses gpio-regmap, although a patch is required to support a
limitation of the chip.

There remain some log warnings when probing the device, possibly due to the way
I'm using the MFD subsystem. Would it be possible to avoid these?
[    2.602242] rtl8231-pinctrl: Failed to locate of_node [id: -2]
[    2.609380] rtl8231-pinctrl rtl8231-pinctrl.0.auto: no of_node; not parsing pinctrl DT

When no 'leds' sub-node is specified:
[    2.922262] rtl8231-leds: Failed to locate of_node [id: -2]
[    2.967149] rtl8231-leds rtl8231-leds.1.auto: no of_node; not parsing pinctrl DT
[    2.975673] rtl8231-leds rtl8231-leds.1.auto: scan mode missing or invalid
[    2.983531] rtl8231-leds: probe of rtl8231-leds.1.auto failed with error -22

Changes since v1:
  - Reintroduce MDIO regmap, with fixed Kconfig dependencies
  - Add configurable dir/value order for gpio-regmap direction_out call
  - Drop allocations for regmap fields that are used only on init
  - Move some definitions to MFD header
  - Add PM ops to replace driver remove for MFD
  - Change pinctrl driver to (modified) gpio-regmap
  - Change leds driver to use fwnode
Link: https://lore.kernel.org/lkml/cover.1620735871.git.sander@svanheule.net/

Changes since RFC:
  - Dropped MDIO regmap interface. I was unable to resolve the Kconfig
    dependency issue, so have reverted to using regmap_config.reg_read/write.
  - Added pinctrl support
  - Added LED support
  - Changed root device to MFD, with pinctrl and leds child devices. Root
    device is now an mdio_device driver.
Link: https://lore.kernel.org/linux-gpio/cover.1617914861.git.sander@svanheule.net/

Sander Vanheule (7):
  regmap: Add MDIO bus support
  gpio: regmap: Add configurable dir/value order
  dt-bindings: leds: Binding for RTL8231 scan matrix
  dt-bindings: mfd: Binding for RTL8231
  mfd: Add RTL8231 core device
  pinctrl: Add RTL8231 pin control and GPIO support
  leds: Add support for RTL8231 LED scan matrix

 .../bindings/leds/realtek,rtl8231-leds.yaml   | 159 ++++++++
 .../bindings/mfd/realtek,rtl8231.yaml         | 202 ++++++++++
 drivers/base/regmap/Kconfig                   |   6 +-
 drivers/base/regmap/Makefile                  |   1 +
 drivers/base/regmap/regmap-mdio.c             |  57 +++
 drivers/gpio/gpio-regmap.c                    |  20 +-
 drivers/leds/Kconfig                          |  10 +
 drivers/leds/Makefile                         |   1 +
 drivers/leds/leds-rtl8231.c                   | 293 ++++++++++++++
 drivers/mfd/Kconfig                           |   9 +
 drivers/mfd/Makefile                          |   1 +
 drivers/mfd/rtl8231.c                         | 153 +++++++
 drivers/pinctrl/Kconfig                       |  11 +
 drivers/pinctrl/Makefile                      |   1 +
 drivers/pinctrl/pinctrl-rtl8231.c             | 377 ++++++++++++++++++
 include/linux/gpio/regmap.h                   |   3 +
 include/linux/mfd/rtl8231.h                   |  57 +++
 include/linux/regmap.h                        |  36 ++
 18 files changed, 1393 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
 create mode 100644 Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
 create mode 100644 drivers/base/regmap/regmap-mdio.c
 create mode 100644 drivers/leds/leds-rtl8231.c
 create mode 100644 drivers/mfd/rtl8231.c
 create mode 100644 drivers/pinctrl/pinctrl-rtl8231.c
 create mode 100644 include/linux/mfd/rtl8231.h

-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v2 1/7] regmap: Add MDIO bus support
  2021-05-17 19:28 ` [PATCH v2 0/7] " Sander Vanheule
@ 2021-05-17 19:28   ` Sander Vanheule
  2021-05-19 16:12     ` Mark Brown
  2021-05-17 19:28   ` [PATCH v2 2/7] gpio: regmap: Add configurable dir/value order Sander Vanheule
                     ` (6 subsequent siblings)
  7 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-17 19:28 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio
  Cc: Andrew Lunn, Andy Shevchenko, linux-kernel, Sander Vanheule

Basic support for MDIO bus access. Support only includes clause-22
register access, with 5-bit addresses, and 16-bit wide registers.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 drivers/base/regmap/Kconfig       |  6 +++-
 drivers/base/regmap/Makefile      |  1 +
 drivers/base/regmap/regmap-mdio.c | 57 +++++++++++++++++++++++++++++++
 include/linux/regmap.h            | 36 +++++++++++++++++++
 4 files changed, 99 insertions(+), 1 deletion(-)
 create mode 100644 drivers/base/regmap/regmap-mdio.c

diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig
index 50b1e2d06a25..159bac6c5046 100644
--- a/drivers/base/regmap/Kconfig
+++ b/drivers/base/regmap/Kconfig
@@ -4,8 +4,9 @@
 # subsystems should select the appropriate symbols.
 
 config REGMAP
-	default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_SPMI || REGMAP_W1 || REGMAP_AC97 || REGMAP_MMIO || REGMAP_IRQ || REGMAP_SOUNDWIRE || REGMAP_SOUNDWIRE_MBQ || REGMAP_SCCB || REGMAP_I3C || REGMAP_SPI_AVMM)
+	default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_SPMI || REGMAP_W1 || REGMAP_AC97 || REGMAP_MMIO || REGMAP_IRQ || REGMAP_SOUNDWIRE || REGMAP_SOUNDWIRE_MBQ || REGMAP_SCCB || REGMAP_I3C || REGMAP_SPI_AVMM || REGMAP_MDIO)
 	select IRQ_DOMAIN if REGMAP_IRQ
+	select MDIO_BUS if REGMAP_MDIO
 	bool
 
 config REGCACHE_COMPRESSED
@@ -36,6 +37,9 @@ config REGMAP_W1
 	tristate
 	depends on W1
 
+config REGMAP_MDIO
+	tristate
+
 config REGMAP_MMIO
 	tristate
 
diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile
index 33f63adb5b3d..11facb32a027 100644
--- a/drivers/base/regmap/Makefile
+++ b/drivers/base/regmap/Makefile
@@ -19,3 +19,4 @@ obj-$(CONFIG_REGMAP_SOUNDWIRE_MBQ) += regmap-sdw-mbq.o
 obj-$(CONFIG_REGMAP_SCCB) += regmap-sccb.o
 obj-$(CONFIG_REGMAP_I3C) += regmap-i3c.o
 obj-$(CONFIG_REGMAP_SPI_AVMM) += regmap-spi-avmm.o
+obj-$(CONFIG_REGMAP_MDIO) += regmap-mdio.o
diff --git a/drivers/base/regmap/regmap-mdio.c b/drivers/base/regmap/regmap-mdio.c
new file mode 100644
index 000000000000..5f18fe409f56
--- /dev/null
+++ b/drivers/base/regmap/regmap-mdio.c
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/errno.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+static int regmap_mdio_read(void *context, unsigned int reg, unsigned int *val)
+{
+	struct mdio_device *mdio_dev = context;
+	int ret;
+
+	ret = mdiobus_read(mdio_dev->bus, mdio_dev->addr, reg);
+	*val = ret & 0xffff;
+
+	return ret < 0 ? ret : 0;
+}
+
+static int regmap_mdio_write(void *context, unsigned int reg, unsigned int val)
+{
+	struct mdio_device *mdio_dev = context;
+
+	return mdiobus_write(mdio_dev->bus, mdio_dev->addr, reg, val);
+}
+
+static const struct regmap_bus regmap_mdio_bus = {
+	.reg_write = regmap_mdio_write,
+	.reg_read = regmap_mdio_read,
+};
+
+struct regmap *__regmap_init_mdio(struct mdio_device *mdio_dev,
+	const struct regmap_config *config, struct lock_class_key *lock_key,
+	const char *lock_name)
+{
+	if (config->reg_bits != 5 || config->val_bits != 16)
+		return ERR_PTR(-EOPNOTSUPP);
+
+	return __regmap_init(&mdio_dev->dev, &regmap_mdio_bus, mdio_dev, config,
+		lock_key, lock_name);
+}
+EXPORT_SYMBOL_GPL(__regmap_init_mdio);
+
+struct regmap *__devm_regmap_init_mdio(struct mdio_device *mdio_dev,
+	const struct regmap_config *config, struct lock_class_key *lock_key,
+	const char *lock_name)
+{
+	if (config->reg_bits != 5 || config->val_bits != 16)
+		return ERR_PTR(-EOPNOTSUPP);
+
+	return __devm_regmap_init(&mdio_dev->dev, &regmap_mdio_bus, mdio_dev,
+		config, lock_key, lock_name);
+}
+EXPORT_SYMBOL_GPL(__devm_regmap_init_mdio);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Regmap MDIO Module");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/regmap.h b/include/linux/regmap.h
index f87a11a5cc4a..e97dd05f7cdb 100644
--- a/include/linux/regmap.h
+++ b/include/linux/regmap.h
@@ -27,6 +27,7 @@ struct device_node;
 struct i2c_client;
 struct i3c_device;
 struct irq_domain;
+struct mdio_device;
 struct slim_device;
 struct spi_device;
 struct spmi_device;
@@ -538,6 +539,10 @@ struct regmap *__regmap_init_i2c(struct i2c_client *i2c,
 				 const struct regmap_config *config,
 				 struct lock_class_key *lock_key,
 				 const char *lock_name);
+struct regmap *__regmap_init_mdio(struct mdio_device *mdio_dev,
+				 const struct regmap_config *config,
+				 struct lock_class_key *lock_key,
+				 const char *lock_name);
 struct regmap *__regmap_init_sccb(struct i2c_client *i2c,
 				  const struct regmap_config *config,
 				  struct lock_class_key *lock_key,
@@ -594,6 +599,10 @@ struct regmap *__devm_regmap_init_i2c(struct i2c_client *i2c,
 				      const struct regmap_config *config,
 				      struct lock_class_key *lock_key,
 				      const char *lock_name);
+struct regmap *__devm_regmap_init_mdio(struct mdio_device *mdio_dev,
+				      const struct regmap_config *config,
+				      struct lock_class_key *lock_key,
+				      const char *lock_name);
 struct regmap *__devm_regmap_init_sccb(struct i2c_client *i2c,
 				       const struct regmap_config *config,
 				       struct lock_class_key *lock_key,
@@ -697,6 +706,19 @@ int regmap_attach_dev(struct device *dev, struct regmap *map,
 	__regmap_lockdep_wrapper(__regmap_init_i2c, #config,		\
 				i2c, config)
 
+/**
+ * regmap_init_mdio() - Initialise register map
+ *
+ * @mdio_dev: Device that will be interacted with
+ * @config: Configuration for register map
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer to
+ * a struct regmap.
+ */
+#define regmap_init_mdio(mdio_dev, config)				\
+	__regmap_lockdep_wrapper(__regmap_init_mdio, #config,		\
+				mdio_dev, config)
+
 /**
  * regmap_init_sccb() - Initialise register map
  *
@@ -888,6 +910,20 @@ bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg);
 	__regmap_lockdep_wrapper(__devm_regmap_init_i2c, #config,	\
 				i2c, config)
 
+/**
+ * devm_regmap_init_mdio() - Initialise managed register map
+ *
+ * @mdio_dev: Device that will be interacted with
+ * @config: Configuration for register map
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer
+ * to a struct regmap.  The regmap will be automatically freed by the
+ * device management code.
+ */
+#define devm_regmap_init_mdio(mdio_dev, config)				\
+	__regmap_lockdep_wrapper(__devm_regmap_init_mdio, #config,	\
+				mdio_dev, config)
+
 /**
  * devm_regmap_init_sccb() - Initialise managed register map
  *
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v2 2/7] gpio: regmap: Add configurable dir/value order
  2021-05-17 19:28 ` [PATCH v2 0/7] " Sander Vanheule
  2021-05-17 19:28   ` [PATCH v2 1/7] regmap: Add MDIO bus support Sander Vanheule
@ 2021-05-17 19:28   ` Sander Vanheule
  2021-05-17 21:06     ` Andy Shevchenko
                       ` (2 more replies)
  2021-05-17 19:28   ` [PATCH v2 3/7] dt-bindings: leds: Binding for RTL8231 scan matrix Sander Vanheule
                     ` (5 subsequent siblings)
  7 siblings, 3 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-17 19:28 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio
  Cc: Andrew Lunn, Andy Shevchenko, linux-kernel, Sander Vanheule

GPIO chips may not support setting the output value when a pin is
configured as an input, although the current implementation assumes this
is always possible.

Add support for setting pin direction before value. The order defaults
to setting the value first, but this can be reversed by setting the
regmap_config.no_set_on_input flag, similar to the corresponding flag in
the gpio-mmio driver.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 drivers/gpio/gpio-regmap.c  | 20 +++++++++++++++++---
 include/linux/gpio/regmap.h |  3 +++
 2 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c
index 134cedf151a7..1cdb20f8f8b4 100644
--- a/drivers/gpio/gpio-regmap.c
+++ b/drivers/gpio/gpio-regmap.c
@@ -170,14 +170,25 @@ static int gpio_regmap_direction_input(struct gpio_chip *chip,
 	return gpio_regmap_set_direction(chip, offset, false);
 }
 
-static int gpio_regmap_direction_output(struct gpio_chip *chip,
-					unsigned int offset, int value)
+static int gpio_regmap_dir_out_val_first(struct gpio_chip *chip,
+					 unsigned int offset, int value)
 {
 	gpio_regmap_set(chip, offset, value);
 
 	return gpio_regmap_set_direction(chip, offset, true);
 }
 
+static int gpio_regmap_dir_out_dir_first(struct gpio_chip *chip,
+					 unsigned int offset, int value)
+{
+	int err;
+
+	err = gpio_regmap_set_direction(chip, offset, true);
+	gpio_regmap_set(chip, offset, value);
+
+	return err;
+}
+
 void gpio_regmap_set_drvdata(struct gpio_regmap *gpio, void *data)
 {
 	gpio->driver_data = data;
@@ -277,7 +288,10 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
 	if (gpio->reg_dir_in_base || gpio->reg_dir_out_base) {
 		chip->get_direction = gpio_regmap_get_direction;
 		chip->direction_input = gpio_regmap_direction_input;
-		chip->direction_output = gpio_regmap_direction_output;
+		if (config->no_set_on_input)
+			chip->direction_output = gpio_regmap_dir_out_dir_first;
+		else
+			chip->direction_output = gpio_regmap_dir_out_val_first;
 	}
 
 	ret = gpiochip_add_data(chip, gpio);
diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h
index 334dd928042b..2a732f8f23be 100644
--- a/include/linux/gpio/regmap.h
+++ b/include/linux/gpio/regmap.h
@@ -30,6 +30,8 @@ struct regmap;
  * @reg_dir_out_base:	(Optional) out setting register base address
  * @reg_stride:		(Optional) May be set if the registers (of the
  *			same type, dat, set, etc) are not consecutive.
+ * @no_set_on_input:	Set if output value can only be set when the direction
+ *			is configured as output.
  * @ngpio_per_reg:	Number of GPIOs per register
  * @irq_domain:		(Optional) IRQ domain if the controller is
  *			interrupt-capable
@@ -73,6 +75,7 @@ struct gpio_regmap_config {
 	unsigned int reg_dir_out_base;
 	int reg_stride;
 	int ngpio_per_reg;
+	bool no_set_on_input;
 	struct irq_domain *irq_domain;
 
 	int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base,
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v2 3/7] dt-bindings: leds: Binding for RTL8231 scan matrix
  2021-05-17 19:28 ` [PATCH v2 0/7] " Sander Vanheule
  2021-05-17 19:28   ` [PATCH v2 1/7] regmap: Add MDIO bus support Sander Vanheule
  2021-05-17 19:28   ` [PATCH v2 2/7] gpio: regmap: Add configurable dir/value order Sander Vanheule
@ 2021-05-17 19:28   ` Sander Vanheule
  2021-05-17 19:28   ` [PATCH v2 4/7] dt-bindings: mfd: Binding for RTL8231 Sander Vanheule
                     ` (4 subsequent siblings)
  7 siblings, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-17 19:28 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio
  Cc: Andrew Lunn, Andy Shevchenko, linux-kernel, Sander Vanheule

Add a binding description for the Realtek RTL8231's LED support, which
consists of up to 88 LEDs arranged in a number of scanning matrices.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 .../bindings/leds/realtek,rtl8231-leds.yaml   | 159 ++++++++++++++++++
 1 file changed, 159 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml

diff --git a/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml b/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
new file mode 100644
index 000000000000..aba2b55fb9c9
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
@@ -0,0 +1,159 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/realtek,rtl8231-leds.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Realtek RTL8231 LED scan matrix.
+
+maintainers:
+  - Sander Vanheule <sander@svanheule.net>
+
+description: |
+  The RTL8231 has support for driving a number of LED matrices, by scanning
+  over the LEDs pins, alternatingly lighting different columns and/or rows.
+
+  In single color scan mode, 88 LEDs are supported. These are grouped into
+  three output matrices:
+    - Group A of 6×6 single color LEDs. Rows and columns are driven by GPIO
+      pins 0-11.
+               L0[n]    L1[n]    L2[n]    L0[n+6]  L1[n+6]  L2[n+6]
+                |        |        |        |        |        |
+       P0/P6  --<--------<--------<--------<--------<--------< (3)
+                |        |        |        |        |        |
+       P1/P7  --<--------<--------<--------<--------<--------< (4)
+                |        |        |        |        |        |
+       P2/P8  --<--------<--------<--------<--------<--------< (5)
+                |        |        |        |        |        |
+       P3/P9  --<--------<--------<--------<--------<--------< (6)
+                |        |        |        |        |        |
+       P4/P10 --<--------<--------<--------<--------<--------< (7)
+                |        |        |        |        |        |
+       P5/P11 --<--------<--------<--------<--------<--------< (8)
+               (0)      (1)      (2)      (9)     (10)     (11)
+    - Group B of 6×6 single color LEDs. Rows and columns are driven by GPIO
+      pins 12-23.
+               L0[n]    L1[n]    L2[n]    L0[n+6]  L1[n+6]  L2[n+6]
+                |        |        |        |        |        |
+      P12/P18 --<--------<--------<--------<--------<--------< (15)
+                |        |        |        |        |        |
+      P13/P19 --<--------<--------<--------<--------<--------< (16)
+                |        |        |        |        |        |
+      P14/P20 --<--------<--------<--------<--------<--------< (17)
+                |        |        |        |        |        |
+      P15/P21 --<--------<--------<--------<--------<--------< (18)
+                |        |        |        |        |        |
+      P16/P22 --<--------<--------<--------<--------<--------< (19)
+                |        |        |        |        |        |
+      P17/P23 --<--------<--------<--------<--------<--------< (20)
+              (12)     (13)     (14)    (21)      (22)     (23)
+    - Group C of 8 pairs of anti-parallel (or bi-color) LEDs. LED selection is
+      provided by GPIO pins 24-27 and 29-32, polarity selection by GPIO 28.
+               P24     P25  ...  P30     P31
+                |       |         |       |
+      LED POL --X-------X---/\/---X-------X (28)
+              (24)    (25)  ... (31)    (32)
+
+  In bi-color scan mode, 72 LEDs are supported. These are grouped into four
+  output matrices:
+    - Group A of 12 pairs of anti-parallel LEDs. LED selection is provided
+      by GPIO pins 0-11, polarity selection by GPIO 12.
+    - Group B of 6 pairs of anti-parallel LEDs. LED selection is provided
+      by GPIO pins 23-28, polarity selection by GPIO 21.
+    - Group C of 6 pairs of anti-parallel LEDs. LED selection is provided
+      by GPIO pins 29-34, polarity selection by GPIO 22.
+    - Group of 4×6 single color LEDs. Rows are driven by GPIO pins 15-20,
+      columns by GPIO pins 13-14 and 21-22 (shared with groups B and C).
+          P[n]     P[n+6]   P[n+12]  P[n+18]
+            |        |        |        |
+       +0 --<--------<--------<--------< (15)
+            |        |        |        |
+       +1 --<--------<--------<--------< (16)
+            |        |        |        |
+       +2 --<--------<--------<--------< (17)
+            |        |        |        |
+       +3 --<--------<--------<--------< (18)
+            |        |        |        |
+       +4 --<--------<--------<--------< (19)
+            |        |        |        |
+       +6 --<--------<--------<--------< (20)
+          (13)     (14)     (21)     (22)
+
+  This node must always be a child of a 'realtek,rtl8231' node.
+
+properties:
+  $nodename:
+    const: leds
+
+  compatible:
+    const: realtek,rtl8231-leds
+
+  "#address-cells":
+    const: 2
+
+  "#size-cells":
+    const: 0
+
+  realtek,led-scan-mode:
+    $ref: /schemas/types.yaml#/definitions/string
+    description: |
+      Specify the scanning mode the chip should run in. See general description
+      for how the scanning matrices are wired up.
+    enum: ["single-color", "bi-color"]
+
+patternProperties:
+  "^led@[0-9]+,[0-2]$":
+    description: |
+      LEDs are addressed by their port index and led index. Ports 0-23 always
+      support three LEDs. Additionally, but only when used in single color scan
+      mode, ports 24-31 support two LEDs.
+    type: object
+
+    properties:
+      reg:
+        maxItems: 1
+
+    allOf:
+      - $ref: ../leds/common.yaml#
+
+    required:
+      - reg
+
+required:
+  - compatible
+  - "#address-cells"
+  - "#size-cells"
+  - realtek,led-scan-mode
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/leds/common.h>
+    leds {
+        compatible = "realtek,rtl8231-leds";
+        #address-cells = <2>;
+        #size-cells = <0>;
+
+        realtek,led-scan-mode = "single-color";
+
+        led@0,0 {
+            reg = <0 0>;
+            color = <LED_COLOR_ID_GREEN>;
+            function = LED_FUNCTION_LAN;
+            function-enumerator = <0>;
+        };
+
+        led@0,1 {
+            reg = <0 1>;
+            color = <LED_COLOR_ID_AMBER>;
+            function = LED_FUNCTION_LAN;
+            function-enumerator = <0>;
+        };
+
+        led@0,2 {
+            reg = <0 2>;
+            color = <LED_COLOR_ID_GREEN>;
+            function = LED_FUNCTION_STATUS;
+        };
+    };
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v2 4/7] dt-bindings: mfd: Binding for RTL8231
  2021-05-17 19:28 ` [PATCH v2 0/7] " Sander Vanheule
                     ` (2 preceding siblings ...)
  2021-05-17 19:28   ` [PATCH v2 3/7] dt-bindings: leds: Binding for RTL8231 scan matrix Sander Vanheule
@ 2021-05-17 19:28   ` Sander Vanheule
  2021-05-18 22:02     ` Linus Walleij
  2021-05-17 19:28   ` [PATCH v2 5/7] mfd: Add RTL8231 core device Sander Vanheule
                     ` (3 subsequent siblings)
  7 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-17 19:28 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio
  Cc: Andrew Lunn, Andy Shevchenko, linux-kernel, Sander Vanheule

Add a binding description for the Realtek RTL8231, a GPIO and LED
expander chip commonly used in ethernet switches based on a Realtek
switch SoC. These chips can be addressed via an MDIO or SMI bus, or used
as a plain 36-bit shift register.

This binding only describes the feature set provided by the MDIO/SMI
configuration, and covers the GPIO, PWM, and pin control properties. The
LED properties are defined in a separate binding.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 .../bindings/mfd/realtek,rtl8231.yaml         | 202 ++++++++++++++++++
 1 file changed, 202 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml

diff --git a/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml b/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
new file mode 100644
index 000000000000..24ab7344c0c4
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
@@ -0,0 +1,202 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/realtek,rtl8231.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Realtek RTL8231 GPIO and LED expander.
+
+maintainers:
+  - Sander Vanheule <sander@svanheule.net>
+
+description: |
+  The RTL8231 is a GPIO and LED expander chip, providing up to 37 GPIOs, up to
+  88 LEDs, and up to one PWM output. This device is frequently used alongside
+  Realtek switch SoCs, to provide additional I/O capabilities.
+
+  To manage the RTL8231's features, its strapping pins can be used to configure
+  it in one of three modes: shift register, MDIO device, or SMI device. The
+  shift register mode does not need special support. In MDIO or SMI mode, most
+  pins can be configured as a GPIO output, LED matrix scan line/column, or as a
+  PWM output.
+
+  The GPIO and pin control are part of the main node. PWM and LED support are
+  configured as sub-nodes.
+
+properties:
+  compatible:
+    const: realtek,rtl8231
+
+  reg:
+    description: MDIO or SMI device address.
+    maxItems: 1
+
+  # GPIO support
+  gpio-controller: true
+
+  "#gpio-cells":
+    const: 2
+    description: |
+      The first cell is the pin number and the second cell is used to specify
+      the gpio active state.
+
+  gpio-ranges:
+    description: |
+      Must reference itself, and provide a zero-based mapping for 37 pins.
+    maxItems: 1
+
+  # Pin muxing and configuration
+  realtek,drive-strength:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: |
+      Common drive strength used for all GPIO output pins, must be 4mA or 8mA.
+      On reset, this value will default to 8mA.
+    enum: [4, 8]
+
+  # LED scanning matrix
+  leds:
+    $ref: ../leds/realtek,rtl8231-leds.yaml#
+
+  # PWM output
+  pwm:
+    type: object
+    description: |
+      Subnode describing the PWM peripheral. To use the PWM output, gpio35 must
+      be muxed to its 'pwm' function. Valid frequency values for consumers are
+      1200, 1600, 2000, 2400, 2800, 3200, 4000, and 4800.
+
+    properties:
+      "#pwm-cells":
+        description: |
+          Twos cells with PWM index (must be 0) and PWM frequency in Hz.
+        const: 2
+
+    required:
+      - "#pwm-cells"
+
+patternProperties:
+  "-pins$":
+    type: object
+    $ref: ../pinctrl/pinmux-node.yaml#
+
+    properties:
+      pins:
+        items:
+          oneOf:
+            - enum: ["gpio0", "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6",
+                     "gpio7", "gpio8", "gpio9", "gpio10", "gpio11", "gpio12", "gpio13",
+                     "gpio14", "gpio15", "gpio16", "gpio17", "gpio18", "gpio19", "gpio20",
+                     "gpio21", "gpio22", "gpio23", "gpio24", "gpio25", "gpio26", "gpio27",
+                     "gpio28", "gpio29", "gpio30", "gpio31", "gpio32", "gpio33", "gpio34",
+                     "gpio35", "gpio36"]
+        minItems: 1
+        maxItems: 37
+      function:
+        description: |
+          Select which function to use. "gpio" is supported for all pins, "led" is supported
+          for pins 0-34, "pwm" is supported for pin 35.
+        enum: ["gpio", "led", "pwm"]
+
+    required:
+      - pins
+      - function
+
+required:
+  - compatible
+  - reg
+  - gpio-controller
+  - "#gpio-cells"
+  - gpio-ranges
+
+additionalProperties: false
+
+examples:
+  - |
+    // Minimal example
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        expander0: expander@0 {
+            compatible = "realtek,rtl8231";
+            reg = <0>;
+
+            gpio-controller;
+            #gpio-cells = <2>;
+            gpio-ranges = <&expander0 0 0 37>;
+        };
+    };
+  - |
+    // All bells and whistles included
+    #include <dt-bindings/leds/common.h>
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        expander1: expander@1 {
+            compatible = "realtek,rtl8231";
+            reg = <1>;
+
+            gpio-controller;
+            #gpio-cells = <2>;
+            gpio-ranges = <&expander1 0 0 37>;
+
+            realtek,drive-strength = <4>;
+
+            button-pins {
+                pins = "gpio36";
+                function = "gpio";
+                input-debounce = "100000";
+            };
+
+            pwm-pins {
+                pins = "gpio35";
+                function = "pwm";
+            };
+
+            led-pins {
+                pins = "gpio0", "gpio1", "gpio3", "gpio4";
+                function = "led";
+            };
+
+            pwm {
+                #pwm-cells = <2>;
+            };
+
+            leds {
+                compatible = "realtek,rtl8231-leds";
+                #address-cells = <2>;
+                #size-cells = <0>;
+
+                realtek,led-scan-mode = "single-color";
+
+                led@0,0 {
+                    reg = <0 0>;
+                    color = <LED_COLOR_ID_GREEN>;
+                    function = LED_FUNCTION_LAN;
+                    function-enumerator = <0>;
+                };
+
+                led@0,1 {
+                    reg = <0 1>;
+                    color = <LED_COLOR_ID_AMBER>;
+                    function = LED_FUNCTION_LAN;
+                    function-enumerator = <0>;
+                };
+
+                led@1,0 {
+                    reg = <1 0>;
+                    color = <LED_COLOR_ID_GREEN>;
+                    function = LED_FUNCTION_LAN;
+                    function-enumerator = <1>;
+                };
+
+                led@1,1 {
+                    reg = <1 1>;
+                    color = <LED_COLOR_ID_AMBER>;
+                    function = LED_FUNCTION_LAN;
+                    function-enumerator = <1>;
+                };
+            };
+        };
+    };
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v2 5/7] mfd: Add RTL8231 core device
  2021-05-17 19:28 ` [PATCH v2 0/7] " Sander Vanheule
                     ` (3 preceding siblings ...)
  2021-05-17 19:28   ` [PATCH v2 4/7] dt-bindings: mfd: Binding for RTL8231 Sander Vanheule
@ 2021-05-17 19:28   ` Sander Vanheule
  2021-05-17 21:18     ` Andy Shevchenko
  2021-05-17 19:28   ` [PATCH v2 6/7] pinctrl: Add RTL8231 pin control and GPIO support Sander Vanheule
                     ` (2 subsequent siblings)
  7 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-17 19:28 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio
  Cc: Andrew Lunn, Andy Shevchenko, linux-kernel, Sander Vanheule,
	kernel test robot

The RTL8231 is implemented as an MDIO device, and provides a regmap
interface for register access by the core and child devices.

The chip can also be a device on an SMI bus, an I2C-like bus by Realtek.
Since kernel support for SMI is limited, and no real-world SMI
implementations have been encountered for this device, this is currently
unimplemented. The use of the regmap interface should make any future
support relatively straightforward.

After reset, all pins are muxed to GPIO inputs before the pin drivers
are enabled. This is done to prevent accidental system resets, when a
pin is connected to the parent SoC's reset line.

[missing MDIO_BUS dependency, provided via REGMAP_MDIO]
Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 drivers/mfd/Kconfig         |   9 +++
 drivers/mfd/Makefile        |   1 +
 drivers/mfd/rtl8231.c       | 153 ++++++++++++++++++++++++++++++++++++
 include/linux/mfd/rtl8231.h |  57 ++++++++++++++
 4 files changed, 220 insertions(+)
 create mode 100644 drivers/mfd/rtl8231.c
 create mode 100644 include/linux/mfd/rtl8231.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 5c7f2b100191..bdeeaba88116 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1076,6 +1076,15 @@ config MFD_RDC321X
 	  southbridge which provides access to GPIOs and Watchdog using the
 	  southbridge PCI device configuration space.
 
+config MFD_RTL8231
+	tristate "Realtek RTL8231 GPIO and LED expander"
+	select MFD_CORE
+	select REGMAP_MDIO
+	help
+	  Support for the Realtek RTL8231 GPIO and LED expander.
+	  Provides up to 37 GPIOs, 88 LEDs, and one PWM output.
+	  When built as a module, this module will be named rtl8231_expander.
+
 config MFD_RT5033
 	tristate "Richtek RT5033 Power Management IC"
 	depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 4f6d2b8a5f76..4b27c2486ccc 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -234,6 +234,7 @@ obj-$(CONFIG_MFD_MENF21BMC)	+= menf21bmc.o
 obj-$(CONFIG_MFD_HI6421_PMIC)	+= hi6421-pmic-core.o
 obj-$(CONFIG_MFD_HI655X_PMIC)   += hi655x-pmic.o
 obj-$(CONFIG_MFD_DLN2)		+= dln2.o
+obj-$(CONFIG_MFD_RTL8231)	+= rtl8231.o
 obj-$(CONFIG_MFD_RT5033)	+= rt5033.o
 obj-$(CONFIG_MFD_SKY81452)	+= sky81452.o
 
diff --git a/drivers/mfd/rtl8231.c b/drivers/mfd/rtl8231.c
new file mode 100644
index 000000000000..204d6c4f64b2
--- /dev/null
+++ b/drivers/mfd/rtl8231.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mfd/core.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/rtl8231.h>
+
+static const struct reg_field RTL8231_FIELD_LED_START = REG_FIELD(RTL8231_REG_FUNC0, 1, 1);
+
+static const struct mfd_cell rtl8231_cells[] = {
+	{
+		.name = "rtl8231-pinctrl",
+		.of_compatible = "realtek,rtl8231-pinctrl",
+	},
+	{
+		.name = "rtl8231-leds",
+		.of_compatible = "realtek,rtl8231-leds",
+	},
+};
+
+static int rtl8231_init(struct device *dev, struct regmap *map)
+{
+	unsigned int ready_code;
+	unsigned int v;
+	int err = 0;
+
+	err = regmap_read(map, RTL8231_REG_FUNC1, &v);
+	ready_code = FIELD_GET(RTL8231_FUNC1_READY_CODE_MASK, v);
+
+	if (err) {
+		dev_err(dev, "failed to read READY_CODE\n");
+		return err;
+	} else if (ready_code != RTL8231_FUNC1_READY_CODE_VALUE) {
+		dev_err(dev, "RTL8231 not present or ready 0x%x != 0x%x\n",
+			ready_code, RTL8231_FUNC1_READY_CODE_VALUE);
+		return -ENODEV;
+	}
+
+	/* SOFT_RESET bit self-clears when done */
+	regmap_update_bits(map, RTL8231_REG_PIN_HI_CFG,
+		RTL8231_PIN_HI_CFG_SOFT_RESET, RTL8231_PIN_HI_CFG_SOFT_RESET);
+	usleep_range(1000, 10000);
+
+	/*
+	 * Chip reset results in a pin configuration that is a mix of LED and GPIO outputs.
+	 * Select GPI functionality for all pins before enabling pin outputs.
+	 */
+	regmap_write(map, RTL8231_REG_PIN_MODE0, 0xffff);
+	regmap_write(map, RTL8231_REG_GPIO_DIR0, 0xffff);
+	regmap_write(map, RTL8231_REG_PIN_MODE1, 0xffff);
+	regmap_write(map, RTL8231_REG_GPIO_DIR1, 0xffff);
+	regmap_write(map, RTL8231_REG_PIN_HI_CFG,
+		RTL8231_PIN_HI_CFG_MODE_MASK | RTL8231_PIN_HI_CFG_DIR_MASK);
+
+	return err;
+}
+
+static const struct regmap_config rtl8231_mdio_regmap_config = {
+	.val_bits = RTL8231_BITS_VAL,
+	.reg_bits = 5,
+	.max_register = RTL8231_REG_COUNT - 1,
+	.use_single_read = true,
+	.use_single_write = true,
+	.reg_format_endian = REGMAP_ENDIAN_BIG,
+	.val_format_endian = REGMAP_ENDIAN_BIG,
+};
+
+static int rtl8231_mdio_probe(struct mdio_device *mdiodev)
+{
+	struct device *dev = &mdiodev->dev;
+	struct regmap_field *led_start;
+	struct regmap *map;
+	int err;
+
+	map = devm_regmap_init_mdio(mdiodev, &rtl8231_mdio_regmap_config);
+
+	if (IS_ERR(map)) {
+		dev_err(dev, "failed to init regmap\n");
+		return PTR_ERR(map);
+	}
+
+	led_start = devm_regmap_field_alloc(dev, map, RTL8231_FIELD_LED_START);
+	if (IS_ERR(led_start))
+		return PTR_ERR(led_start);
+
+	dev_set_drvdata(dev, led_start);
+
+	mdiodev->reset_gpio = gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+	device_property_read_u32(dev, "reset-assert-delay", &mdiodev->reset_assert_delay);
+	device_property_read_u32(dev, "reset-deassert-delay", &mdiodev->reset_deassert_delay);
+
+	err = rtl8231_init(dev, map);
+	if (err)
+		return err;
+
+	/* LED_START enables power to output pins, and starts the LED engine */
+	regmap_field_write(led_start, 1);
+
+	return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, rtl8231_cells,
+		ARRAY_SIZE(rtl8231_cells), NULL, 0, NULL);
+}
+
+#ifdef CONFIG_PM
+static int rtl8231_suspend(struct device *dev)
+{
+	struct regmap_field *led_start = dev_get_drvdata(dev);
+
+	return regmap_field_write(led_start, 0);
+}
+
+static int rtl8231_resume(struct device *dev)
+{
+	struct regmap_field *led_start = dev_get_drvdata(dev);
+
+	return regmap_field_write(led_start, 1);
+}
+
+static const struct dev_pm_ops rtl8231_pm_ops = {
+	.suspend = rtl8231_suspend,
+	.resume = rtl8231_resume,
+};
+#define RTL8231_PM_OPS (&rtl8231_pm_ops)
+#else
+#define RTL8231_PM_OPS NULL
+#endif /* CONFIG_PM */
+
+static const struct of_device_id rtl8231_of_match[] = {
+	{ .compatible = "realtek,rtl8231" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, rtl8231_of_match);
+
+static struct mdio_driver rtl8231_mdio_driver = {
+	.mdiodrv.driver = {
+		.name = "rtl8231-expander",
+		.of_match_table	= rtl8231_of_match,
+		.pm = RTL8231_PM_OPS,
+	},
+	.probe = rtl8231_mdio_probe,
+};
+mdio_module_driver(rtl8231_mdio_driver);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek RTL8231 GPIO and LED expander");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/rtl8231.h b/include/linux/mfd/rtl8231.h
new file mode 100644
index 000000000000..7f1df92a9d36
--- /dev/null
+++ b/include/linux/mfd/rtl8231.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Register definitions the RTL8231 GPIO and LED expander chip
+ */
+
+#ifndef __LINUX_MFD_RTL8231_H
+#define __LINUX_MFD_RTL8231_H
+
+#include <linux/bits.h>
+
+#define RTL8231_BITS_VAL		16
+
+/* Chip control */
+#define RTL8231_REG_FUNC0		0x00
+#define RTL8231_FUNC0_SCAN_MODE		BIT(0)
+#define RTL8231_FUNC0_SCAN_SINGLE	0
+#define RTL8231_FUNC0_SCAN_BICOLOR	BIT(0)
+
+#define RTL8231_REG_FUNC1		0x01
+#define RTL8231_FUNC1_READY_CODE_VALUE	0x37
+#define RTL8231_FUNC1_READY_CODE_MASK	GENMASK(9, 4)
+
+/* Pin control */
+#define RTL8231_REG_PIN_MODE0		0x02
+#define RTL8231_REG_PIN_MODE1		0x03
+
+#define RTL8231_PIN_MODE_LED		0
+#define RTL8231_PIN_MODE_GPIO		1
+
+/* Pin high config: pin and GPIO control for pins 32-26 */
+#define RTL8231_REG_PIN_HI_CFG		0x04
+#define RTL8231_PIN_HI_CFG_MODE_MASK	GENMASK(4, 0)
+#define RTL8231_PIN_HI_CFG_DIR_MASK	GENMASK(9, 5)
+#define RTL8231_PIN_HI_CFG_SOFT_RESET	BIT(15)
+
+/* GPIO control registers */
+#define RTL8231_REG_GPIO_DIR0		0x05
+#define RTL8231_REG_GPIO_DIR1		0x06
+#define RTL8231_REG_GPIO_INVERT0	0x07
+#define RTL8231_REG_GPIO_INVERT1	0x08
+
+#define RTL8231_GPIO_DIR_IN		1
+#define RTL8231_GPIO_DIR_OUT		0
+
+/* GPIO data registers */
+#define RTL8231_REG_GPIO_DATA0		0x1c
+#define RTL8231_REG_GPIO_DATA1		0x1d
+#define RTL8231_REG_GPIO_DATA2		0x1e
+
+/* LED control base registers */
+#define RTL8231_REG_LED0_BASE		0x09
+#define RTL8231_REG_LED1_BASE		0x10
+#define RTL8231_REG_LED2_BASE		0x17
+
+#define RTL8231_REG_COUNT		0x1f
+
+#endif /* __LINUX_MFD_RTL8231_H */
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v2 6/7] pinctrl: Add RTL8231 pin control and GPIO support
  2021-05-17 19:28 ` [PATCH v2 0/7] " Sander Vanheule
                     ` (4 preceding siblings ...)
  2021-05-17 19:28   ` [PATCH v2 5/7] mfd: Add RTL8231 core device Sander Vanheule
@ 2021-05-17 19:28   ` Sander Vanheule
  2021-05-17 21:42     ` Andy Shevchenko
  2021-05-17 19:28   ` [PATCH v2 7/7] leds: Add support for RTL8231 LED scan matrix Sander Vanheule
  2021-05-19 16:10   ` (subset) [PATCH v2 0/7] RTL8231 GPIO expander support Mark Brown
  7 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-17 19:28 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio
  Cc: Andrew Lunn, Andy Shevchenko, linux-kernel, Sander Vanheule

This driver implements the GPIO and pin muxing features provided by the
RTL8231. The device should be instantiated as an MFD child, where the
parent device has already configured the regmap used for register
access.

Although described in the bindings, pin debouncing and drive strength
selection are currently not implemented. Debouncing is only available
for the six highest GPIOs, and must be emulated when other pins are used
for (button) inputs anyway.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 drivers/pinctrl/Kconfig           |  11 +
 drivers/pinctrl/Makefile          |   1 +
 drivers/pinctrl/pinctrl-rtl8231.c | 377 ++++++++++++++++++++++++++++++
 3 files changed, 389 insertions(+)
 create mode 100644 drivers/pinctrl/pinctrl-rtl8231.c

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index c2c7e7963ed0..462df82c5133 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -221,6 +221,17 @@ config PINCTRL_ROCKCHIP
 	help
           This support pinctrl and gpio driver for Rockchip SoCs.
 
+config PINCTRL_RTL8231
+	tristate "Realtek RTL8231 GPIO expander's pin controller"
+	depends on MFD_RTL8231
+	default MFD_RTL8231
+	select GENERIC_PINCONF
+	select GPIO_REGMAP
+	select PINMUX
+	help
+	  Support for RTL8231 expander's GPIOs and pin controller.
+	  When built as a module, the module will be called rtl8231_pinctrl.
+
 config PINCTRL_SINGLE
 	tristate "One-register-per-pin type device tree based pinctrl driver"
 	depends on OF
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 5ef5334a797f..239603efb317 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_PINCTRL_PALMAS)	+= pinctrl-palmas.o
 obj-$(CONFIG_PINCTRL_PIC32)	+= pinctrl-pic32.o
 obj-$(CONFIG_PINCTRL_PISTACHIO)	+= pinctrl-pistachio.o
 obj-$(CONFIG_PINCTRL_ROCKCHIP)	+= pinctrl-rockchip.o
+obj-$(CONFIG_PINCTRL_RTL8231)	+= pinctrl-rtl8231.o
 obj-$(CONFIG_PINCTRL_SINGLE)	+= pinctrl-single.o
 obj-$(CONFIG_PINCTRL_SX150X)	+= pinctrl-sx150x.o
 obj-$(CONFIG_ARCH_TEGRA)	+= tegra/
diff --git a/drivers/pinctrl/pinctrl-rtl8231.c b/drivers/pinctrl/pinctrl-rtl8231.c
new file mode 100644
index 000000000000..44f08ba39f14
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-rtl8231.c
@@ -0,0 +1,377 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bitfield.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/regmap.h>
+#include <linux/module.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/rtl8231.h>
+
+#define RTL8231_NUM_GPIOS	37
+
+struct rtl8231_function {
+	const char *name;
+	unsigned int ngroups;
+	const char **groups;
+};
+
+struct rtl8231_pin_ctrl {
+	struct pinctrl_desc pctl_desc;
+	unsigned int nfunctions;
+	struct rtl8231_function *functions;
+	struct regmap *map;
+};
+
+/*
+ * Pin controller functionality
+ */
+static const char * const rtl8231_pin_function_names[] = {
+	"gpio",
+	"led",
+	"pwm",
+};
+
+enum rtl8231_pin_function {
+	RTL8231_PIN_FUNCTION_GPIO = BIT(0),
+	RTL8231_PIN_FUNCTION_LED = BIT(1),
+	RTL8231_PIN_FUNCTION_PWM = BIT(2),
+};
+
+struct rtl8231_pin_desc {
+	unsigned int number;
+	const char *name;
+	enum rtl8231_pin_function functions;
+	u8 reg;
+	u8 offset;
+	u8 gpio_function_value;
+};
+
+#define RTL8231_PIN(_num, _func, _reg, _fld, _val)		\
+	{							\
+		.number = _num,					\
+		.name = "gpio" #_num,				\
+		.functions = RTL8231_PIN_FUNCTION_GPIO | _func,	\
+		.reg = _reg,					\
+		.offset = _fld,					\
+		.gpio_function_value = _val,			\
+	}
+#define RTL8231_GPIO_PIN(_num)					\
+	RTL8231_PIN(_num, 0, 0, 0, 0)
+#define RTL8231_LED_PIN(_num, _reg, _fld)			\
+	RTL8231_PIN(_num, RTL8231_PIN_FUNCTION_LED, _reg, _fld, RTL8231_PIN_MODE_GPIO)
+#define RTL8231_PWM_PIN(_num, _reg, _fld)			\
+	RTL8231_PIN(_num, RTL8231_PIN_FUNCTION_PWM, _reg, _fld, 0)
+
+/* Pins always support GPIO, and may support one alternate function */
+static const struct rtl8231_pin_desc rtl8231_pins[RTL8231_NUM_GPIOS] = {
+	RTL8231_LED_PIN(0, RTL8231_REG_PIN_MODE0, 0),
+	RTL8231_LED_PIN(1, RTL8231_REG_PIN_MODE0, 1),
+	RTL8231_LED_PIN(2, RTL8231_REG_PIN_MODE0, 2),
+	RTL8231_LED_PIN(3, RTL8231_REG_PIN_MODE0, 3),
+	RTL8231_LED_PIN(4, RTL8231_REG_PIN_MODE0, 4),
+	RTL8231_LED_PIN(5, RTL8231_REG_PIN_MODE0, 5),
+	RTL8231_LED_PIN(6, RTL8231_REG_PIN_MODE0, 6),
+	RTL8231_LED_PIN(7, RTL8231_REG_PIN_MODE0, 7),
+	RTL8231_LED_PIN(8, RTL8231_REG_PIN_MODE0, 8),
+	RTL8231_LED_PIN(9, RTL8231_REG_PIN_MODE0, 9),
+	RTL8231_LED_PIN(10, RTL8231_REG_PIN_MODE0, 10),
+	RTL8231_LED_PIN(11, RTL8231_REG_PIN_MODE0, 11),
+	RTL8231_LED_PIN(12, RTL8231_REG_PIN_MODE0, 12),
+	RTL8231_LED_PIN(13, RTL8231_REG_PIN_MODE0, 13),
+	RTL8231_LED_PIN(14, RTL8231_REG_PIN_MODE0, 14),
+	RTL8231_LED_PIN(15, RTL8231_REG_PIN_MODE0, 15),
+	RTL8231_LED_PIN(16, RTL8231_REG_PIN_MODE1, 0),
+	RTL8231_LED_PIN(17, RTL8231_REG_PIN_MODE1, 1),
+	RTL8231_LED_PIN(18, RTL8231_REG_PIN_MODE1, 2),
+	RTL8231_LED_PIN(19, RTL8231_REG_PIN_MODE1, 3),
+	RTL8231_LED_PIN(20, RTL8231_REG_PIN_MODE1, 4),
+	RTL8231_LED_PIN(21, RTL8231_REG_PIN_MODE1, 5),
+	RTL8231_LED_PIN(22, RTL8231_REG_PIN_MODE1, 6),
+	RTL8231_LED_PIN(23, RTL8231_REG_PIN_MODE1, 7),
+	RTL8231_LED_PIN(24, RTL8231_REG_PIN_MODE1, 8),
+	RTL8231_LED_PIN(25, RTL8231_REG_PIN_MODE1, 9),
+	RTL8231_LED_PIN(26, RTL8231_REG_PIN_MODE1, 10),
+	RTL8231_LED_PIN(27, RTL8231_REG_PIN_MODE1, 11),
+	RTL8231_LED_PIN(28, RTL8231_REG_PIN_MODE1, 12),
+	RTL8231_LED_PIN(29, RTL8231_REG_PIN_MODE1, 13),
+	RTL8231_LED_PIN(30, RTL8231_REG_PIN_MODE1, 14),
+	RTL8231_LED_PIN(31, RTL8231_REG_PIN_MODE1, 15),
+	RTL8231_LED_PIN(32, RTL8231_REG_PIN_HI_CFG, 0),
+	RTL8231_LED_PIN(33, RTL8231_REG_PIN_HI_CFG, 1),
+	RTL8231_LED_PIN(34, RTL8231_REG_PIN_HI_CFG, 2),
+	RTL8231_PWM_PIN(35, RTL8231_REG_FUNC1, 3),
+	RTL8231_GPIO_PIN(36),
+};
+
+static int rtl8231_get_groups_count(struct pinctrl_dev *pctldev)
+{
+	return ARRAY_SIZE(rtl8231_pins);
+}
+
+static const char *rtl8231_get_group_name(struct pinctrl_dev *pctldev, unsigned int selector)
+{
+	return rtl8231_pins[selector].name;
+}
+
+static int rtl8231_get_group_pins(struct pinctrl_dev *pctldev, unsigned int selector,
+	const unsigned int **pins, unsigned int *num_pins)
+{
+	if (selector < ARRAY_SIZE(rtl8231_pins)) {
+		*pins = &rtl8231_pins[selector].number;
+		*num_pins = 1;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static const struct pinctrl_ops rtl8231_pinctrl_ops = {
+	.get_groups_count = rtl8231_get_groups_count,
+	.get_group_name = rtl8231_get_group_name,
+	.get_group_pins = rtl8231_get_group_pins,
+	.dt_node_to_map = pinconf_generic_dt_node_to_map_all,
+	.dt_free_map = pinconf_generic_dt_free_map,
+};
+
+static int rtl8231_get_functions_count(struct pinctrl_dev *pctldev)
+{
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+
+	return ctrl->nfunctions;
+}
+
+static const char *rtl8231_get_function_name(struct pinctrl_dev *pctldev, unsigned int selector)
+{
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+
+	return ctrl->functions[selector].name;
+}
+
+static int rtl8231_get_function_groups(struct pinctrl_dev *pctldev, unsigned int selector,
+	const char * const **groups, unsigned int *num_groups)
+{
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+
+	*groups = ctrl->functions[selector].groups;
+	*num_groups = ctrl->functions[selector].ngroups;
+	return 0;
+}
+
+static int rtl8231_set_mux(struct pinctrl_dev *pctldev, unsigned int func_selector,
+	unsigned int group_selector)
+{
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+	const struct rtl8231_pin_desc *desc = &rtl8231_pins[group_selector];
+	unsigned int func_flag = BIT(func_selector);
+	unsigned int function_mask;
+	unsigned int gpio_function;
+	int err = 0;
+
+	if (!(desc->functions & func_flag))
+		return -EINVAL;
+
+	function_mask = BIT(desc->offset);
+	gpio_function = desc->gpio_function_value << desc->offset;
+
+	switch (func_flag) {
+	case RTL8231_PIN_FUNCTION_LED:
+	case RTL8231_PIN_FUNCTION_PWM:
+		err = regmap_update_bits(ctrl->map, desc->reg, function_mask, ~gpio_function);
+		break;
+	case RTL8231_PIN_FUNCTION_GPIO:
+		err = regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return err;
+}
+
+static int rtl8231_gpio_request_enable(struct pinctrl_dev *pctldev,
+	struct pinctrl_gpio_range *range, unsigned int offset)
+{
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+	const struct rtl8231_pin_desc *desc = &rtl8231_pins[offset];
+	unsigned int function_mask;
+	unsigned int gpio_function;
+
+	function_mask = BIT(desc->offset);
+	gpio_function = desc->gpio_function_value << desc->offset;
+
+	return regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
+}
+
+static const struct pinmux_ops rtl8231_pinmux_ops = {
+	.set_mux = rtl8231_set_mux,
+	.get_functions_count = rtl8231_get_functions_count,
+	.get_function_name = rtl8231_get_function_name,
+	.get_function_groups = rtl8231_get_function_groups,
+	.gpio_request_enable = rtl8231_gpio_request_enable,
+	.strict = true
+};
+
+
+static int rtl8231_pinctrl_init_functions(struct device *dev, struct rtl8231_pin_ctrl *ctrl)
+{
+	struct rtl8231_function *function;
+	const char **group_name;
+	unsigned int f_idx;
+	unsigned int pin;
+
+	ctrl->nfunctions = ARRAY_SIZE(rtl8231_pin_function_names);
+	ctrl->functions = devm_kcalloc(dev, ctrl->nfunctions, sizeof(*ctrl->functions), GFP_KERNEL);
+	if (IS_ERR(ctrl->functions)) {
+		dev_err(dev, "failed to allocate pin function descriptors\n");
+		return PTR_ERR(ctrl->functions);
+	}
+
+	for (f_idx = 0; f_idx < ctrl->nfunctions; f_idx++) {
+		function = &ctrl->functions[f_idx];
+		function->name = rtl8231_pin_function_names[f_idx];
+
+		for (pin = 0; pin < ctrl->pctl_desc.npins; pin++)
+			if (rtl8231_pins[pin].functions & BIT(f_idx))
+				function->ngroups++;
+
+		function->groups = devm_kcalloc(dev, function->ngroups,
+			sizeof(*function->groups), GFP_KERNEL);
+		if (IS_ERR(function->groups)) {
+			dev_err(dev, "failed to allocate pin function group names\n");
+			return PTR_ERR(function->groups);
+		}
+
+		group_name = function->groups;
+		for (pin = 0; pin < ctrl->pctl_desc.npins; pin++)
+			if (rtl8231_pins[pin].functions & BIT(f_idx))
+				*group_name++ = rtl8231_pins[pin].name;
+	}
+
+	return 0;
+}
+
+static int rtl8231_pinctrl_init(struct device *dev, struct rtl8231_pin_ctrl *ctrl)
+{
+	struct pinctrl_dev *pctl;
+	struct pinctrl_pin_desc *pins;
+	unsigned int pin;
+	int err = 0;
+
+	ctrl->pctl_desc.name = "rtl8231-pinctrl",
+	ctrl->pctl_desc.owner = THIS_MODULE,
+	ctrl->pctl_desc.pctlops = &rtl8231_pinctrl_ops,
+	ctrl->pctl_desc.pmxops = &rtl8231_pinmux_ops,
+
+	ctrl->pctl_desc.npins = ARRAY_SIZE(rtl8231_pins);
+	pins = devm_kcalloc(dev, ctrl->pctl_desc.npins, sizeof(*pins), GFP_KERNEL);
+	if (IS_ERR(pins)) {
+		dev_err(dev, "failed to allocate pin descriptors\n");
+		return PTR_ERR(pins);
+	}
+	ctrl->pctl_desc.pins = pins;
+
+	for (pin = 0; pin < ctrl->pctl_desc.npins; pin++) {
+		pins[pin].number = rtl8231_pins[pin].number;
+		pins[pin].name = rtl8231_pins[pin].name;
+	}
+
+	err = rtl8231_pinctrl_init_functions(dev, ctrl);
+	if (err)
+		return err;
+
+	err = devm_pinctrl_register_and_init(dev->parent, &ctrl->pctl_desc, ctrl, &pctl);
+	if (err) {
+		dev_err(dev, "failed to register pin controller\n");
+		return err;
+	}
+
+	err = pinctrl_enable(pctl);
+	if (err)
+		dev_err(dev, "failed to enable pin controller\n");
+
+	return err;
+}
+
+/*
+ * GPIO controller functionality
+ */
+static int rtl8231_gpio_reg_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
+	unsigned int offset, unsigned int *reg, unsigned int *mask)
+{
+	unsigned int pin_mask = BIT(offset % RTL8231_BITS_VAL);
+
+	if (base == RTL8231_REG_GPIO_DATA0 || offset < 32) {
+		*reg = base + offset / RTL8231_BITS_VAL;
+		*mask = pin_mask;
+	} else if (base == RTL8231_REG_GPIO_DIR0) {
+		*reg = RTL8231_REG_PIN_HI_CFG;
+		*mask = FIELD_PREP(RTL8231_PIN_HI_CFG_DIR_MASK, pin_mask);
+	} else {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rtl8231_pinctrl_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rtl8231_pin_ctrl *ctrl;
+	struct gpio_regmap_config gpio_cfg = {};
+	struct gpio_regmap *gr;
+	int err;
+
+	ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+	if (!ctrl)
+		return -ENOMEM;
+
+	ctrl->map = dev_get_regmap(dev->parent, NULL);
+	if (IS_ERR_OR_NULL(ctrl->map)) {
+		dev_err(dev, "failed to retrieve regmap\n");
+		if (!ctrl->map)
+			return -ENODEV;
+		else
+			return PTR_ERR(ctrl->map);
+	}
+
+	err = rtl8231_pinctrl_init(dev, ctrl);
+	if (err)
+		return err;
+
+	gpio_cfg.regmap = ctrl->map;
+	gpio_cfg.parent = dev->parent;
+	gpio_cfg.ngpio = RTL8231_NUM_GPIOS;
+	gpio_cfg.ngpio_per_reg = RTL8231_BITS_VAL;
+
+	gpio_cfg.reg_dat_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
+	gpio_cfg.reg_set_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
+	gpio_cfg.reg_dir_in_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DIR0);
+	gpio_cfg.no_set_on_input = true;
+
+	gpio_cfg.reg_mask_xlate = rtl8231_gpio_reg_mask_xlate;
+
+	gr = devm_gpio_regmap_register(dev, &gpio_cfg);
+	if (IS_ERR(gr)) {
+		dev_err(dev, "failed to register gpio controller\n");
+		return PTR_ERR(gr);
+	}
+
+	return 0;
+}
+
+static struct platform_driver rtl8231_pinctrl_driver = {
+	.driver = {
+		.name = "rtl8231-pinctrl",
+	},
+	.probe = rtl8231_pinctrl_probe,
+};
+module_platform_driver(rtl8231_pinctrl_driver);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek RTL8231 pin control and GPIO support");
+MODULE_LICENSE("GPL v2");
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v2 7/7] leds: Add support for RTL8231 LED scan matrix
  2021-05-17 19:28 ` [PATCH v2 0/7] " Sander Vanheule
                     ` (5 preceding siblings ...)
  2021-05-17 19:28   ` [PATCH v2 6/7] pinctrl: Add RTL8231 pin control and GPIO support Sander Vanheule
@ 2021-05-17 19:28   ` Sander Vanheule
  2021-05-17 22:00     ` Andy Shevchenko
  2021-05-19 16:10   ` (subset) [PATCH v2 0/7] RTL8231 GPIO expander support Mark Brown
  7 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-17 19:28 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio
  Cc: Andrew Lunn, Andy Shevchenko, linux-kernel, Sander Vanheule

Both single and bi-color scanning modes are supported. The driver will
verify that the addresses are valid for the current mode, before
registering the LEDs. LEDs can be turned on, off, or toggled at one of
six predefined rates from 40ms to 1280ms.

Implements a platform device for use as child device with RTL8231 MFD,
and uses the parent regmap to access the required registers.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 drivers/leds/Kconfig        |  10 ++
 drivers/leds/Makefile       |   1 +
 drivers/leds/leds-rtl8231.c | 293 ++++++++++++++++++++++++++++++++++++
 3 files changed, 304 insertions(+)
 create mode 100644 drivers/leds/leds-rtl8231.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 49d99cb084db..e5ff6150800c 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -593,6 +593,16 @@ config LEDS_REGULATOR
 	help
 	  This option enables support for regulator driven LEDs.
 
+config LEDS_RTL8231
+	tristate "RTL8231 LED matrix support"
+	depends on LEDS_CLASS
+	depends on MFD_RTL8231
+	default MFD_RTL8231
+	help
+	  This options enables support for using the LED scanning matrix output
+	  of the RTL8231 GPIO and LED expander chip.
+	  When built as a module, this module will be named rtl8231_leds.
+
 config LEDS_BD2802
 	tristate "LED driver for BD2802 RGB LED"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 7e604d3028c8..ce0f44a87dee 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -80,6 +80,7 @@ obj-$(CONFIG_LEDS_PM8058)		+= leds-pm8058.o
 obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
 obj-$(CONFIG_LEDS_PWM)			+= leds-pwm.o
 obj-$(CONFIG_LEDS_REGULATOR)		+= leds-regulator.o
+obj-$(CONFIG_LEDS_RTL8231)		+= leds-rtl8231.o
 obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o
 obj-$(CONFIG_LEDS_SC27XX_BLTC)		+= leds-sc27xx-bltc.o
 obj-$(CONFIG_LEDS_SGM3140)		+= leds-sgm3140.o
diff --git a/drivers/leds/leds-rtl8231.c b/drivers/leds/leds-rtl8231.c
new file mode 100644
index 000000000000..7712bbaaf7c1
--- /dev/null
+++ b/drivers/leds/leds-rtl8231.c
@@ -0,0 +1,293 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/device.h>
+#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/rtl8231.h>
+
+/**
+ * struct led_toggle_rate - description of an LED blinking mode
+ * @interval:	LED toggle rate in ms
+ * @mode:	Register field value used to active this mode
+ *
+ * For LED hardware accelerated blinking, with equal on and off delay.
+ * Both delays are given by @interval, so the interval at which the LED blinks
+ * (i.e. turn on and off once) is double this value.
+ */
+struct led_toggle_rate {
+	u16 interval;
+	u8 mode;
+};
+
+/**
+ * struct led_modes - description of all LED modes
+ * @toggle_rates:	Array of led_toggle_rate values, sorted by ascending interval
+ * @num_toggle_rates:	Number of elements in @led_toggle_rate
+ * @off:		Register field value to turn LED off
+ * @on:			Register field value to turn LED on
+ */
+struct led_modes {
+	const struct led_toggle_rate *toggle_rates;
+	unsigned int num_toggle_rates;
+	u8 off;
+	u8 on;
+};
+
+struct rtl8231_led {
+	struct led_classdev led;
+	const struct led_modes *modes;
+	struct regmap_field *reg_field;
+};
+#define to_rtl8231_led(_cdev) container_of(_cdev, struct rtl8231_led, led)
+
+#define RTL8231_NUM_LEDS	3
+#define RTL8231_LED_PER_REG	5
+#define RTL8231_BITS_PER_LED	3
+
+static const unsigned int rtl8231_led_port_counts_single[RTL8231_NUM_LEDS] = {32, 32, 24};
+static const unsigned int rtl8231_led_port_counts_bicolor[RTL8231_NUM_LEDS] = {24, 24, 24};
+
+static const unsigned int rtl8231_led_base[RTL8231_NUM_LEDS] = {
+	RTL8231_REG_LED0_BASE,
+	RTL8231_REG_LED1_BASE,
+	RTL8231_REG_LED2_BASE,
+};
+
+static const struct led_toggle_rate rtl8231_toggle_rates[] = {
+	{  40, 1},
+	{  80, 2},
+	{ 160, 3},
+	{ 320, 4},
+	{ 640, 5},
+	{1280, 6},
+};
+
+static const struct led_modes rtl8231_led_modes = {
+	.off = 0,
+	.on = 7,
+	.num_toggle_rates = ARRAY_SIZE(rtl8231_toggle_rates),
+	.toggle_rates = rtl8231_toggle_rates,
+};
+
+static void rtl8231_led_brightness_set(struct led_classdev *led_cdev,
+	enum led_brightness brightness)
+{
+	struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
+
+	if (brightness)
+		regmap_field_write(pled->reg_field, pled->modes->on);
+	else
+		regmap_field_write(pled->reg_field, pled->modes->off);
+}
+
+static enum led_brightness rtl8231_led_brightness_get(struct led_classdev *led_cdev)
+{
+	struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
+	u32 current_mode = pled->modes->off;
+
+	regmap_field_read(pled->reg_field, &current_mode);
+
+	if (current_mode == pled->modes->off)
+		return LED_OFF;
+	else
+		return LED_ON;
+}
+
+static unsigned int rtl8231_led_current_interval(struct rtl8231_led *pled)
+{
+	unsigned int mode;
+	unsigned int i = 0;
+
+	if (regmap_field_read(pled->reg_field, &mode))
+		return 0;
+
+	while (i < pled->modes->num_toggle_rates && mode != pled->modes->toggle_rates[i].mode)
+		i++;
+
+	if (i < pled->modes->num_toggle_rates)
+		return pled->modes->toggle_rates[i].interval;
+	else
+		return 0;
+}
+
+static int rtl8231_led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
+	unsigned long *delay_off)
+{
+	struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
+	const struct led_modes *modes = pled->modes;
+	unsigned int interval;
+	unsigned int i = 0;
+	int err;
+
+	if (*delay_on == 0 && *delay_off == 0) {
+		/* Choose 500ms as default interval */
+		interval = 500;
+	} else {
+		/*
+		 * If the current mode is blinking, choose the delay that (likely) changed.
+		 * Otherwise, choose the interval that would have the same total delay.
+		 */
+		interval = rtl8231_led_current_interval(pled);
+
+		if (interval > 0 && interval == *delay_off)
+			interval = *delay_on;
+		else if (interval > 0 && interval == *delay_on)
+			interval = *delay_off;
+		else
+			interval = (*delay_on + *delay_off) / 2;
+	}
+
+	/* Find clamped toggle interval */
+	while (i < (modes->num_toggle_rates - 1) && interval > modes->toggle_rates[i].interval)
+		i++;
+
+	interval = modes->toggle_rates[i].interval;
+
+	err = regmap_field_write(pled->reg_field, modes->toggle_rates[i].mode);
+	if (err)
+		return err;
+
+	*delay_on = interval;
+	*delay_off = interval;
+
+	return 0;
+}
+
+static int rtl8231_led_read_address(struct fwnode_handle *fwnode, unsigned int *addr_port,
+	unsigned int *addr_led)
+{
+	u32 addr[2];
+	int err;
+
+	if (!fwnode_property_count_u32(fwnode, "reg"))
+		return -ENODEV;
+
+	err = fwnode_property_read_u32_array(fwnode, "reg", addr, ARRAY_SIZE(addr));
+	if (err)
+		return err;
+
+	*addr_port = addr[0];
+	*addr_led = addr[1];
+
+	return 0;
+}
+
+static struct reg_field rtl8231_led_get_field(unsigned int port_index, unsigned int led_index)
+{
+	unsigned int offset, shift;
+	struct reg_field field;
+
+	offset = port_index / RTL8231_LED_PER_REG;
+	shift = (port_index % RTL8231_LED_PER_REG) * RTL8231_BITS_PER_LED;
+
+	field.reg = rtl8231_led_base[led_index] + offset;
+	field.lsb = shift;
+	field.msb = shift + RTL8231_BITS_PER_LED - 1;
+
+	return field;
+}
+
+static int rtl8231_led_probe_single(struct device *dev, struct regmap *map,
+	const unsigned int *port_counts, struct fwnode_handle *fwnode)
+{
+	struct led_init_data init_data = {};
+	struct rtl8231_led *pled;
+	unsigned int port_index;
+	unsigned int led_index;
+	struct reg_field field;
+	int err;
+
+	pled = devm_kzalloc(dev, sizeof(*pled), GFP_KERNEL);
+	if (IS_ERR(pled))
+		return PTR_ERR(pled);
+
+	err = rtl8231_led_read_address(fwnode, &port_index, &led_index);
+
+	if (err) {
+		dev_err(dev, "LED address invalid\n");
+		return err;
+	} else if (led_index >= RTL8231_NUM_LEDS || port_index >= port_counts[led_index]) {
+		dev_err(dev, "LED address (%d.%d) invalid\n", port_index, led_index);
+		return -ENODEV;
+	}
+
+	field = rtl8231_led_get_field(port_index, led_index);
+	pled->reg_field = devm_regmap_field_alloc(dev, map, field);
+	if (IS_ERR(pled->reg_field))
+		return PTR_ERR(pled->reg_field);
+
+	pled->modes = &rtl8231_led_modes;
+
+	pled->led.max_brightness = 1;
+	pled->led.brightness_get = rtl8231_led_brightness_get;
+	pled->led.brightness_set = rtl8231_led_brightness_set;
+	pled->led.blink_set = rtl8231_led_blink_set;
+
+	init_data.fwnode = fwnode;
+
+	return devm_led_classdev_register_ext(dev, &pled->led, &init_data);
+}
+
+static int rtl8231_led_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const unsigned int *port_counts;
+	struct fwnode_handle *child;
+	struct regmap *map;
+	int err;
+
+	map = dev_get_regmap(dev->parent, NULL);
+	if (IS_ERR_OR_NULL(map)) {
+		dev_err(dev, "failed to retrieve regmap\n");
+		if (!map)
+			return -ENODEV;
+		else
+			return PTR_ERR(map);
+	}
+
+	if (!device_property_match_string(dev, "realtek,led-scan-mode", "single-color")) {
+		port_counts = rtl8231_led_port_counts_single;
+		regmap_update_bits(map, RTL8231_REG_FUNC0,
+			RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_SINGLE);
+	} else if (!device_property_match_string(dev, "realtek,led-scan-mode", "bi-color")) {
+		port_counts = rtl8231_led_port_counts_bicolor;
+		regmap_update_bits(map, RTL8231_REG_FUNC0,
+			RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_BICOLOR);
+	} else {
+		dev_err(dev, "scan mode missing or invalid\n");
+		return -EINVAL;
+	}
+
+	fwnode_for_each_available_child_node(dev->fwnode, child) {
+		err = rtl8231_led_probe_single(dev, map, port_counts, child);
+		if (err)
+			dev_warn(dev, "failed to register LED %pfwP\n", child);
+		continue;
+	}
+
+	return 0;
+}
+
+static const struct of_device_id of_rtl8231_led_match[] = {
+	{ .compatible = "realtek,rtl8231-leds" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, of_rtl8231_led_match);
+
+static struct platform_driver rtl8231_led_driver = {
+	.driver = {
+		.name = "rtl8231-leds",
+		.of_match_table = of_rtl8231_led_match,
+	},
+	.probe = rtl8231_led_probe,
+};
+module_platform_driver(rtl8231_led_driver);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek RTL8231 LED support");
+MODULE_LICENSE("GPL v2");
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 0/5] RTL8231 GPIO expander support
  2021-05-16 21:40   ` [PATCH 0/5] RTL8231 GPIO expander support Sander Vanheule
  2021-05-17  8:13     ` Andy Shevchenko
@ 2021-05-17 19:32     ` Sander Vanheule
  1 sibling, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-17 19:32 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij, linux-leds,
	devicetree, linux-gpio, linux-kernel

On Sun, 2021-05-16 at 23:40 +0200, Sander Vanheule wrote:
> > 2. Please, switch to fwnode API in LED driver
> 
> Since you had the same comment on my previous patch set, I had already tried to
> this this into account as much as possible.
> 
> There's a few things I couldn't find the fwnode-equivalent for:
>  * I use of_node_name_prefix to enforce the naming required by the binding. I
>    could just walk over all (available) child nodes, which would be mostly
>    equivalent.
>  * To get the address of an LED child node, I use of_get_address, since this
>    appeared to provide what I want to do: get the address of the node. I know
>    next to nothing about ACPI. Does the equivalent exist there? Or am I taking
>    the wrong approach?

Hi Andy,

I found this:
https://www.kernel.org/doc/html/latest/firmware-guide/acpi/dsd/leds.html

So instead of of_address, I now just read the "reg" property. The v2 I just sent
should be fwnode-only.

Best,
Sander



^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 2/7] gpio: regmap: Add configurable dir/value order
  2021-05-17 19:28   ` [PATCH v2 2/7] gpio: regmap: Add configurable dir/value order Sander Vanheule
@ 2021-05-17 21:06     ` Andy Shevchenko
  2021-05-18  1:40     ` Andrew Lunn
  2021-05-18  8:39     ` Michael Walle
  2 siblings, 0 replies; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-17 21:06 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio, Andrew Lunn, linux-kernel

On Mon, May 17, 2021 at 10:28 PM Sander Vanheule <sander@svanheule.net> wrote:
>
> GPIO chips may not support setting the output value when a pin is
> configured as an input, although the current implementation assumes this
> is always possible.

But it's broken hardware.
Can it be rather marked as a quirk?

> Add support for setting pin direction before value. The order defaults
> to setting the value first, but this can be reversed by setting the
> regmap_config.no_set_on_input flag, similar to the corresponding flag in
> the gpio-mmio driver.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 5/7] mfd: Add RTL8231 core device
  2021-05-17 19:28   ` [PATCH v2 5/7] mfd: Add RTL8231 core device Sander Vanheule
@ 2021-05-17 21:18     ` Andy Shevchenko
  2021-05-23 21:28       ` Sander Vanheule
  2021-05-24  7:50       ` Sander Vanheule
  0 siblings, 2 replies; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-17 21:18 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List, kernel test robot

On Mon, May 17, 2021 at 10:28 PM Sander Vanheule <sander@svanheule.net> wrote:
>
> The RTL8231 is implemented as an MDIO device, and provides a regmap
> interface for register access by the core and child devices.
>
> The chip can also be a device on an SMI bus, an I2C-like bus by Realtek.
> Since kernel support for SMI is limited, and no real-world SMI
> implementations have been encountered for this device, this is currently
> unimplemented. The use of the regmap interface should make any future
> support relatively straightforward.
>
> After reset, all pins are muxed to GPIO inputs before the pin drivers
> are enabled. This is done to prevent accidental system resets, when a
> pin is connected to the parent SoC's reset line.

> [missing MDIO_BUS dependency, provided via REGMAP_MDIO]
> Reported-by: kernel test robot <lkp@intel.com>

What is the culprit? Shouldn't this have a Fixes tag?

...

> +         Support for the Realtek RTL8231 GPIO and LED expander.
> +         Provides up to 37 GPIOs, 88 LEDs, and one PWM output.

> +         When built as a module, this module will be named rtl8231_expander.

The name is not the one it will be according to Makefile.

> +obj-$(CONFIG_MFD_RTL8231)      += rtl8231.o

...

> +#include <linux/bits.h>
> +#include <linux/bitfield.h>
> +#include <linux/delay.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mdio.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>

...

> +static int rtl8231_init(struct device *dev, struct regmap *map)
> +{
> +       unsigned int ready_code;
> +       unsigned int v;

> +       int err = 0;

Redundant assignment.

> +       err = regmap_read(map, RTL8231_REG_FUNC1, &v);

> +       ready_code = FIELD_GET(RTL8231_FUNC1_READY_CODE_MASK, v);

If we got an error why we need a read_core, what for?

> +       if (err) {
> +               dev_err(dev, "failed to read READY_CODE\n");
> +               return err;

> +       } else if (ready_code != RTL8231_FUNC1_READY_CODE_VALUE) {

Redundant 'else'.

> +               dev_err(dev, "RTL8231 not present or ready 0x%x != 0x%x\n",
> +                       ready_code, RTL8231_FUNC1_READY_CODE_VALUE);
> +               return -ENODEV;
> +       }

...

> +       return err;

Is it somehow different to 0?

> +}

...

> +static int rtl8231_mdio_probe(struct mdio_device *mdiodev)
> +{
> +       struct device *dev = &mdiodev->dev;
> +       struct regmap_field *led_start;
> +       struct regmap *map;
> +       int err;
> +
> +       map = devm_regmap_init_mdio(mdiodev, &rtl8231_mdio_regmap_config);

> +

Redundant blank line.

> +       if (IS_ERR(map)) {
> +               dev_err(dev, "failed to init regmap\n");
> +               return PTR_ERR(map);
> +       }
> +
> +       led_start = devm_regmap_field_alloc(dev, map, RTL8231_FIELD_LED_START);
> +       if (IS_ERR(led_start))
> +               return PTR_ERR(led_start);
> +
> +       dev_set_drvdata(dev, led_start);
> +
> +       mdiodev->reset_gpio = gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
> +       device_property_read_u32(dev, "reset-assert-delay", &mdiodev->reset_assert_delay);
> +       device_property_read_u32(dev, "reset-deassert-delay", &mdiodev->reset_deassert_delay);
> +
> +       err = rtl8231_init(dev, map);
> +       if (err)

Resource leakage.

> +               return err;
> +
> +       /* LED_START enables power to output pins, and starts the LED engine */
> +       regmap_field_write(led_start, 1);

> +       return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, rtl8231_cells,
> +               ARRAY_SIZE(rtl8231_cells), NULL, 0, NULL);

Ditto.

> +}

...

> +#ifdef CONFIG_PM

Replace this with __maybe_unused attribute.


> +#define RTL8231_PM_OPS (&rtl8231_pm_ops)

> +#else
> +#define RTL8231_PM_OPS NULL
> +#endif /* CONFIG_PM */

...

> +static const struct of_device_id rtl8231_of_match[] = {
> +       { .compatible = "realtek,rtl8231" },
> +       {},

No need to have a comma for the terminator line.

> +};

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 6/7] pinctrl: Add RTL8231 pin control and GPIO support
  2021-05-17 19:28   ` [PATCH v2 6/7] pinctrl: Add RTL8231 pin control and GPIO support Sander Vanheule
@ 2021-05-17 21:42     ` Andy Shevchenko
  2021-05-17 21:46       ` Andy Shevchenko
  2021-05-23 21:42       ` Sander Vanheule
  0 siblings, 2 replies; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-17 21:42 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List

On Mon, May 17, 2021 at 10:28 PM Sander Vanheule <sander@svanheule.net> wrote:
>
> This driver implements the GPIO and pin muxing features provided by the
> RTL8231. The device should be instantiated as an MFD child, where the
> parent device has already configured the regmap used for register
> access.
>
> Although described in the bindings, pin debouncing and drive strength
> selection are currently not implemented. Debouncing is only available
> for the six highest GPIOs, and must be emulated when other pins are used
> for (button) inputs anyway.

...

> +struct rtl8231_pin_desc {
> +       unsigned int number;
> +       const char *name;
> +       enum rtl8231_pin_function functions;
> +       u8 reg;
> +       u8 offset;
> +       u8 gpio_function_value;
> +};

I would see rather

sturct pinctrl_pin_desc desc;

Where drv_data describes the rest of the data for pin

...

> +static int rtl8231_get_group_pins(struct pinctrl_dev *pctldev, unsigned int selector,
> +       const unsigned int **pins, unsigned int *num_pins)
> +{

> +       if (selector < ARRAY_SIZE(rtl8231_pins)) {

Can we use traditional pattern, i.e.

  if (... >= ARRAY_SIZE(...))
    return -EINVAL;

  ...
  return 0;

?

> +               *pins = &rtl8231_pins[selector].number;
> +               *num_pins = 1;
> +               return 0;
> +       }
> +
> +       return -EINVAL;
> +}

...

> +static int rtl8231_set_mux(struct pinctrl_dev *pctldev, unsigned int func_selector,
> +       unsigned int group_selector)
> +{

> +       int err = 0;

Redundant variable.

> +       switch (func_flag) {
> +       case RTL8231_PIN_FUNCTION_LED:
> +       case RTL8231_PIN_FUNCTION_PWM:
> +               err = regmap_update_bits(ctrl->map, desc->reg, function_mask, ~gpio_function);
> +               break;
> +       case RTL8231_PIN_FUNCTION_GPIO:
> +               err = regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return err;
> +}

...

> +static const struct pinmux_ops rtl8231_pinmux_ops = {
> +       .set_mux = rtl8231_set_mux,
> +       .get_functions_count = rtl8231_get_functions_count,
> +       .get_function_name = rtl8231_get_function_name,
> +       .get_function_groups = rtl8231_get_function_groups,
> +       .gpio_request_enable = rtl8231_gpio_request_enable,

> +       .strict = true

Leave comma for non-terminator entries.

> +};
> +
> +

One blank line is enough.

...

> +static int rtl8231_pinctrl_init_functions(struct device *dev, struct rtl8231_pin_ctrl *ctrl)
> +{
> +       struct rtl8231_function *function;
> +       const char **group_name;
> +       unsigned int f_idx;
> +       unsigned int pin;
> +
> +       ctrl->nfunctions = ARRAY_SIZE(rtl8231_pin_function_names);
> +       ctrl->functions = devm_kcalloc(dev, ctrl->nfunctions, sizeof(*ctrl->functions), GFP_KERNEL);
> +       if (IS_ERR(ctrl->functions)) {

Wrong.


> +               dev_err(dev, "failed to allocate pin function descriptors\n");

Noisy message, user space will print the similar.

> +               return PTR_ERR(ctrl->functions);
> +       }
> +
> +       for (f_idx = 0; f_idx < ctrl->nfunctions; f_idx++) {
> +               function = &ctrl->functions[f_idx];
> +               function->name = rtl8231_pin_function_names[f_idx];
> +
> +               for (pin = 0; pin < ctrl->pctl_desc.npins; pin++)
> +                       if (rtl8231_pins[pin].functions & BIT(f_idx))
> +                               function->ngroups++;
> +
> +               function->groups = devm_kcalloc(dev, function->ngroups,
> +                       sizeof(*function->groups), GFP_KERNEL);

> +               if (IS_ERR(function->groups)) {
> +                       dev_err(dev, "failed to allocate pin function group names\n");
> +                       return PTR_ERR(function->groups);
> +               }

Ditto.

> +               group_name = function->groups;
> +               for (pin = 0; pin < ctrl->pctl_desc.npins; pin++)
> +                       if (rtl8231_pins[pin].functions & BIT(f_idx))
> +                               *group_name++ = rtl8231_pins[pin].name;
> +       }
> +
> +       return 0;
> +}
> +
> +static int rtl8231_pinctrl_init(struct device *dev, struct rtl8231_pin_ctrl *ctrl)
> +{
> +       struct pinctrl_dev *pctl;
> +       struct pinctrl_pin_desc *pins;
> +       unsigned int pin;

> +       int err = 0;

Check entire series for unnecessary assignments.They

> +
> +       ctrl->pctl_desc.name = "rtl8231-pinctrl",
> +       ctrl->pctl_desc.owner = THIS_MODULE,
> +       ctrl->pctl_desc.pctlops = &rtl8231_pinctrl_ops,
> +       ctrl->pctl_desc.pmxops = &rtl8231_pinmux_ops,
> +
> +       ctrl->pctl_desc.npins = ARRAY_SIZE(rtl8231_pins);
> +       pins = devm_kcalloc(dev, ctrl->pctl_desc.npins, sizeof(*pins), GFP_KERNEL);
> +       if (IS_ERR(pins)) {
> +               dev_err(dev, "failed to allocate pin descriptors\n");
> +               return PTR_ERR(pins);
> +       }

Ditto.

> +       ctrl->pctl_desc.pins = pins;
> +
> +       for (pin = 0; pin < ctrl->pctl_desc.npins; pin++) {
> +               pins[pin].number = rtl8231_pins[pin].number;
> +               pins[pin].name = rtl8231_pins[pin].name;
> +       }
> +
> +       err = rtl8231_pinctrl_init_functions(dev, ctrl);
> +       if (err)
> +               return err;
> +
> +       err = devm_pinctrl_register_and_init(dev->parent, &ctrl->pctl_desc, ctrl, &pctl);
> +       if (err) {
> +               dev_err(dev, "failed to register pin controller\n");
> +               return err;
> +       }
> +
> +       err = pinctrl_enable(pctl);
> +       if (err)
> +               dev_err(dev, "failed to enable pin controller\n");
> +
> +       return err;
> +}
> +
> +/*
> + * GPIO controller functionality
> + */
> +static int rtl8231_gpio_reg_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
> +       unsigned int offset, unsigned int *reg, unsigned int *mask)
> +{
> +       unsigned int pin_mask = BIT(offset % RTL8231_BITS_VAL);
> +
> +       if (base == RTL8231_REG_GPIO_DATA0 || offset < 32) {
> +               *reg = base + offset / RTL8231_BITS_VAL;
> +               *mask = pin_mask;
> +       } else if (base == RTL8231_REG_GPIO_DIR0) {
> +               *reg = RTL8231_REG_PIN_HI_CFG;
> +               *mask = FIELD_PREP(RTL8231_PIN_HI_CFG_DIR_MASK, pin_mask);
> +       } else {
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +
> +static int rtl8231_pinctrl_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct rtl8231_pin_ctrl *ctrl;
> +       struct gpio_regmap_config gpio_cfg = {};
> +       struct gpio_regmap *gr;
> +       int err;
> +
> +       ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
> +       if (!ctrl)
> +               return -ENOMEM;
> +
> +       ctrl->map = dev_get_regmap(dev->parent, NULL);
> +       if (IS_ERR_OR_NULL(ctrl->map)) {
> +               dev_err(dev, "failed to retrieve regmap\n");
> +               if (!ctrl->map)
> +                       return -ENODEV;
> +               else
> +                       return PTR_ERR(ctrl->map);
> +       }
> +
> +       err = rtl8231_pinctrl_init(dev, ctrl);
> +       if (err)
> +               return err;
> +
> +       gpio_cfg.regmap = ctrl->map;
> +       gpio_cfg.parent = dev->parent;
> +       gpio_cfg.ngpio = RTL8231_NUM_GPIOS;
> +       gpio_cfg.ngpio_per_reg = RTL8231_BITS_VAL;
> +
> +       gpio_cfg.reg_dat_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
> +       gpio_cfg.reg_set_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
> +       gpio_cfg.reg_dir_in_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DIR0);
> +       gpio_cfg.no_set_on_input = true;
> +
> +       gpio_cfg.reg_mask_xlate = rtl8231_gpio_reg_mask_xlate;
> +
> +       gr = devm_gpio_regmap_register(dev, &gpio_cfg);
> +       if (IS_ERR(gr)) {
> +               dev_err(dev, "failed to register gpio controller\n");
> +               return PTR_ERR(gr);
> +       }
> +
> +       return 0;
> +}
> +
> +static struct platform_driver rtl8231_pinctrl_driver = {
> +       .driver = {
> +               .name = "rtl8231-pinctrl",
> +       },
> +       .probe = rtl8231_pinctrl_probe,
> +};
> +module_platform_driver(rtl8231_pinctrl_driver);
> +
> +MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
> +MODULE_DESCRIPTION("Realtek RTL8231 pin control and GPIO support");
> +MODULE_LICENSE("GPL v2");
> --
> 2.31.1
>


-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 6/7] pinctrl: Add RTL8231 pin control and GPIO support
  2021-05-17 21:42     ` Andy Shevchenko
@ 2021-05-17 21:46       ` Andy Shevchenko
  2021-05-23 21:42       ` Sander Vanheule
  1 sibling, 0 replies; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-17 21:46 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List

On Tue, May 18, 2021 at 12:42 AM Andy Shevchenko
<andy.shevchenko@gmail.com> wrote:
> On Mon, May 17, 2021 at 10:28 PM Sander Vanheule <sander@svanheule.net> wrote:

The rest of the review is here (hit the send before finished).

...

> > +       int err = 0;
>
> Check entire series for unnecessary assignments.They

They even may hide a mistake.

...


> > +static int rtl8231_pinctrl_probe(struct platform_device *pdev)
> > +{

> > +       ctrl->map = dev_get_regmap(dev->parent, NULL);
> > +       if (IS_ERR_OR_NULL(ctrl->map)) {
> > +               dev_err(dev, "failed to retrieve regmap\n");
> > +               if (!ctrl->map)
> > +                       return -ENODEV;
> > +               else
> > +                       return PTR_ERR(ctrl->map);
> > +       }

Simply split the outer conditional to two.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 7/7] leds: Add support for RTL8231 LED scan matrix
  2021-05-17 19:28   ` [PATCH v2 7/7] leds: Add support for RTL8231 LED scan matrix Sander Vanheule
@ 2021-05-17 22:00     ` Andy Shevchenko
  2021-05-23 21:53       ` Sander Vanheule
  0 siblings, 1 reply; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-17 22:00 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List

On Mon, May 17, 2021 at 10:28 PM Sander Vanheule <sander@svanheule.net> wrote:
>
> Both single and bi-color scanning modes are supported. The driver will
> verify that the addresses are valid for the current mode, before
> registering the LEDs. LEDs can be turned on, off, or toggled at one of
> six predefined rates from 40ms to 1280ms.
>
> Implements a platform device for use as child device with RTL8231 MFD,

as a child

> and uses the parent regmap to access the required registers.

...

> +         When built as a module, this module will be named rtl8231_leds.

Again, what it's written here is not what is in Makefile.

> +obj-$(CONFIG_LEDS_RTL8231)             += leds-rtl8231.o

...

> +/**
> + * struct led_toggle_rate - description of an LED blinking mode
> + * @interval:  LED toggle rate in ms
> + * @mode:      Register field value used to active this mode

activate

> + *
> + * For LED hardware accelerated blinking, with equal on and off delay.
> + * Both delays are given by @interval, so the interval at which the LED blinks
> + * (i.e. turn on and off once) is double this value.
> + */

...

> +static unsigned int rtl8231_led_current_interval(struct rtl8231_led *pled)
> +{
> +       unsigned int mode;

> +       unsigned int i = 0;

This...

> +       if (regmap_field_read(pled->reg_field, &mode))
> +               return 0;
> +
> +       while (i < pled->modes->num_toggle_rates && mode != pled->modes->toggle_rates[i].mode)
> +               i++;

...and this will be better as a for-loop.

> +       if (i < pled->modes->num_toggle_rates)
> +               return pled->modes->toggle_rates[i].interval;

> +       else

Redundant.

> +               return 0;
> +}

...

> +       unsigned int i = 0;

As per above.

...

> +               interval = 500;

interval_ms

> +               /*
> +                * If the current mode is blinking, choose the delay that (likely) changed.
> +                * Otherwise, choose the interval that would have the same total delay.
> +                */
> +               interval = rtl8231_led_current_interval(pled);

> +

Redundant blank line.

> +               if (interval > 0 && interval == *delay_off)
> +                       interval = *delay_on;
> +               else if (interval > 0 && interval == *delay_on)
> +                       interval = *delay_off;
> +               else
> +                       interval = (*delay_on + *delay_off) / 2;
> +       }

...

> +       u32 addr[2];
> +       int err;
> +

> +       if (!fwnode_property_count_u32(fwnode, "reg"))

err = fwnode_property_count_u32(...);
if (err < 0)
  return err;
if (err == 0)
  return -ENODEV;

> +               return -ENODEV;
> +
> +       err = fwnode_property_read_u32_array(fwnode, "reg", addr, ARRAY_SIZE(addr));

If count returns 1? What's the point of counting if you always want two?

> +       if (err)
> +               return err;
> +
> +       *addr_port = addr[0];
> +       *addr_led = addr[1];
> +
> +       return 0;
> +}

...

> +       pled = devm_kzalloc(dev, sizeof(*pled), GFP_KERNEL);
> +       if (IS_ERR(pled))

Wrong.

> +               return PTR_ERR(pled);

...

> +       err = rtl8231_led_read_address(fwnode, &port_index, &led_index);
> +

Redundant blank line.

> +       if (err) {
> +               dev_err(dev, "LED address invalid\n");
> +               return err;

> +       } else if (led_index >= RTL8231_NUM_LEDS || port_index >= port_counts[led_index]) {

Redundant 'else'

> +               dev_err(dev, "LED address (%d.%d) invalid\n", port_index, led_index);
> +               return -ENODEV;
> +       }

...

> +       map = dev_get_regmap(dev->parent, NULL);
> +       if (IS_ERR_OR_NULL(map)) {

Split it into two conditionals.

> +               dev_err(dev, "failed to retrieve regmap\n");
> +               if (!map)
> +                       return -ENODEV;
> +               else
> +                       return PTR_ERR(map);
> +       }

...

> +       if (!device_property_match_string(dev, "realtek,led-scan-mode", "single-color")) {

It seems that device_property_match_string() and accompanying
functions have wrong description of returned codes, i.e. it returns
the index of the matched string. It's possible that some APIs are
broken (but I believe that the former is the case).

That said, I think the proper comparison should be >= 0.

> +               port_counts = rtl8231_led_port_counts_single;
> +               regmap_update_bits(map, RTL8231_REG_FUNC0,
> +                       RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_SINGLE);
> +       } else if (!device_property_match_string(dev, "realtek,led-scan-mode", "bi-color")) {

Ditto.

> +               port_counts = rtl8231_led_port_counts_bicolor;
> +               regmap_update_bits(map, RTL8231_REG_FUNC0,
> +                       RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_BICOLOR);
> +       } else {
> +               dev_err(dev, "scan mode missing or invalid\n");
> +               return -EINVAL;
> +       }

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 1/5] dt-bindings: leds: Binding for RTL8231 scan matrix
  2021-05-11 12:25 ` [PATCH 1/5] dt-bindings: leds: Binding for RTL8231 scan matrix Sander Vanheule
@ 2021-05-17 22:31   ` Rob Herring
  2021-05-19 16:39     ` Sander Vanheule
  0 siblings, 1 reply; 114+ messages in thread
From: Rob Herring @ 2021-05-17 22:31 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Lee Jones, Linus Walleij, linux-leds, devicetree,
	linux-gpio, linux-kernel

On Tue, May 11, 2021 at 02:25:19PM +0200, Sander Vanheule wrote:
> Add a binding description for the Realtek RTL8231's LED support, which
> consists of up to 88 LEDs arranged in a number of scanning matrices.
> 
> Signed-off-by: Sander Vanheule <sander@svanheule.net>
> ---
>  .../bindings/leds/realtek,rtl8231-leds.yaml   | 159 ++++++++++++++++++
>  1 file changed, 159 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
> 
> diff --git a/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml b/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
> new file mode 100644
> index 000000000000..aba2b55fb9c9
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
> @@ -0,0 +1,159 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/leds/realtek,rtl8231-leds.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Realtek RTL8231 LED scan matrix.
> +
> +maintainers:
> +  - Sander Vanheule <sander@svanheule.net>
> +
> +description: |
> +  The RTL8231 has support for driving a number of LED matrices, by scanning
> +  over the LEDs pins, alternatingly lighting different columns and/or rows.
> +
> +  In single color scan mode, 88 LEDs are supported. These are grouped into
> +  three output matrices:
> +    - Group A of 6×6 single color LEDs. Rows and columns are driven by GPIO
> +      pins 0-11.
> +               L0[n]    L1[n]    L2[n]    L0[n+6]  L1[n+6]  L2[n+6]
> +                |        |        |        |        |        |
> +       P0/P6  --<--------<--------<--------<--------<--------< (3)
> +                |        |        |        |        |        |
> +       P1/P7  --<--------<--------<--------<--------<--------< (4)
> +                |        |        |        |        |        |
> +       P2/P8  --<--------<--------<--------<--------<--------< (5)
> +                |        |        |        |        |        |
> +       P3/P9  --<--------<--------<--------<--------<--------< (6)
> +                |        |        |        |        |        |
> +       P4/P10 --<--------<--------<--------<--------<--------< (7)
> +                |        |        |        |        |        |
> +       P5/P11 --<--------<--------<--------<--------<--------< (8)
> +               (0)      (1)      (2)      (9)     (10)     (11)
> +    - Group B of 6×6 single color LEDs. Rows and columns are driven by GPIO
> +      pins 12-23.
> +               L0[n]    L1[n]    L2[n]    L0[n+6]  L1[n+6]  L2[n+6]
> +                |        |        |        |        |        |
> +      P12/P18 --<--------<--------<--------<--------<--------< (15)
> +                |        |        |        |        |        |
> +      P13/P19 --<--------<--------<--------<--------<--------< (16)
> +                |        |        |        |        |        |
> +      P14/P20 --<--------<--------<--------<--------<--------< (17)
> +                |        |        |        |        |        |
> +      P15/P21 --<--------<--------<--------<--------<--------< (18)
> +                |        |        |        |        |        |
> +      P16/P22 --<--------<--------<--------<--------<--------< (19)
> +                |        |        |        |        |        |
> +      P17/P23 --<--------<--------<--------<--------<--------< (20)
> +              (12)     (13)     (14)    (21)      (22)     (23)
> +    - Group C of 8 pairs of anti-parallel (or bi-color) LEDs. LED selection is
> +      provided by GPIO pins 24-27 and 29-32, polarity selection by GPIO 28.
> +               P24     P25  ...  P30     P31
> +                |       |         |       |
> +      LED POL --X-------X---/\/---X-------X (28)
> +              (24)    (25)  ... (31)    (32)
> +
> +  In bi-color scan mode, 72 LEDs are supported. These are grouped into four
> +  output matrices:
> +    - Group A of 12 pairs of anti-parallel LEDs. LED selection is provided
> +      by GPIO pins 0-11, polarity selection by GPIO 12.
> +    - Group B of 6 pairs of anti-parallel LEDs. LED selection is provided
> +      by GPIO pins 23-28, polarity selection by GPIO 21.
> +    - Group C of 6 pairs of anti-parallel LEDs. LED selection is provided
> +      by GPIO pins 29-34, polarity selection by GPIO 22.
> +    - Group of 4×6 single color LEDs. Rows are driven by GPIO pins 15-20,
> +      columns by GPIO pins 13-14 and 21-22 (shared with groups B and C).
> +          P[n]     P[n+6]   P[n+12]  P[n+18]
> +            |        |        |        |
> +       +0 --<--------<--------<--------< (15)
> +            |        |        |        |
> +       +1 --<--------<--------<--------< (16)
> +            |        |        |        |
> +       +2 --<--------<--------<--------< (17)
> +            |        |        |        |
> +       +3 --<--------<--------<--------< (18)
> +            |        |        |        |
> +       +4 --<--------<--------<--------< (19)
> +            |        |        |        |
> +       +6 --<--------<--------<--------< (20)
> +          (13)     (14)     (21)     (22)
> +
> +  This node must always be a child of a 'realtek,rtl8231' node.
> +
> +properties:
> +  $nodename:
> +    const: leds

led-controller

> +
> +  compatible:
> +    const: realtek,rtl8231-leds

How is this device controlled?

> +
> +  "#address-cells":
> +    const: 2
> +
> +  "#size-cells":
> +    const: 0
> +
> +  realtek,led-scan-mode:
> +    $ref: /schemas/types.yaml#/definitions/string
> +    description: |
> +      Specify the scanning mode the chip should run in. See general description
> +      for how the scanning matrices are wired up.
> +    enum: ["single-color", "bi-color"]
> +
> +patternProperties:
> +  "^led@[0-9]+,[0-2]$":
> +    description: |
> +      LEDs are addressed by their port index and led index. Ports 0-23 always
> +      support three LEDs. Additionally, but only when used in single color scan
> +      mode, ports 24-31 support two LEDs.

Normally unit-addresses are hex values.

> +    type: object
> +
> +    properties:
> +      reg:
> +        maxItems: 1

This should have more constraints:

reg:
  items: 
    - items:
        - description: port index
          maximum: 31
        - description: led index
          maximum: 2

> +
> +    allOf:
> +      - $ref: ../leds/common.yaml#
> +
> +    required:
> +      - reg
> +
> +required:
> +  - compatible
> +  - "#address-cells"
> +  - "#size-cells"
> +  - realtek,led-scan-mode
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/leds/common.h>
> +    leds {
> +        compatible = "realtek,rtl8231-leds";
> +        #address-cells = <2>;
> +        #size-cells = <0>;
> +
> +        realtek,led-scan-mode = "single-color";
> +
> +        led@0,0 {
> +            reg = <0 0>;
> +            color = <LED_COLOR_ID_GREEN>;
> +            function = LED_FUNCTION_LAN;
> +            function-enumerator = <0>;
> +        };
> +
> +        led@0,1 {
> +            reg = <0 1>;
> +            color = <LED_COLOR_ID_AMBER>;
> +            function = LED_FUNCTION_LAN;
> +            function-enumerator = <0>;
> +        };
> +
> +        led@0,2 {
> +            reg = <0 2>;
> +            color = <LED_COLOR_ID_GREEN>;
> +            function = LED_FUNCTION_STATUS;
> +        };
> +    };
> -- 
> 2.31.1
> 

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 2/5] dt-bindings: mfd: Binding for RTL8231
  2021-05-11 12:25 ` [PATCH 2/5] dt-bindings: mfd: Binding for RTL8231 Sander Vanheule
@ 2021-05-17 22:38   ` Rob Herring
  2021-05-19 16:53     ` Sander Vanheule
  0 siblings, 1 reply; 114+ messages in thread
From: Rob Herring @ 2021-05-17 22:38 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Lee Jones, Linus Walleij, linux-leds, devicetree,
	linux-gpio, linux-kernel

On Tue, May 11, 2021 at 02:25:20PM +0200, Sander Vanheule wrote:
> Add a binding description for the Realtek RTL8231, a GPIO and LED
> expander chip commonly used in ethernet switches based on a Realtek
> switch SoC. These chips can be addressed via an MDIO or SMI bus, or used
> as a plain 36-bit shift register.
> 
> This binding only describes the feature set provided by the MDIO/SMI
> configuration, and covers the GPIO, PWM, and pin control properties. The
> LED properties are defined in a separate binding.
> 
> Signed-off-by: Sander Vanheule <sander@svanheule.net>
> ---
>  .../bindings/mfd/realtek,rtl8231.yaml         | 202 ++++++++++++++++++
>  1 file changed, 202 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
> 
> diff --git a/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml b/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
> new file mode 100644
> index 000000000000..2023cfa887a3
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
> @@ -0,0 +1,202 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/mfd/realtek,rtl8231.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Realtek RTL8231 GPIO and LED expander.
> +
> +maintainers:
> +  - Sander Vanheule <sander@svanheule.net>
> +
> +description: |
> +  The RTL8231 is a GPIO and LED expander chip, providing up to 37 GPIOs, up to
> +  88 LEDs, and up to one PWM output. This device is frequently used alongside
> +  Realtek switch SoCs, to provide additional I/O capabilities.
> +
> +  To manage the RTL8231's features, its strapping pins can be used to configure
> +  it in one of three modes: shift register, MDIO device, or SMI device. The
> +  shift register mode does not need special support. In MDIO or SMI mode, most
> +  pins can be configured as a GPIO output, LED matrix scan line/column, or as a
> +  PWM output.
> +
> +  The GPIO and pin control are part of the main node. PWM and LED support are
> +  configured as sub-nodes.
> +
> +properties:
> +  compatible:
> +    const: realtek,rtl8231
> +
> +  reg:
> +    description: MDIO or SMI device address.
> +    maxItems: 1
> +
> +  # GPIO support
> +  gpio-controller: true
> +
> +  "#gpio-cells":
> +    const: 2
> +    description: |
> +      The first cell is the pin number and the second cell is used to specify
> +      the gpio active state.
> +
> +  gpio-ranges:
> +    description: |
> +      Must reference itself, and provide a zero-based mapping for 37 pins.
> +    maxItems: 1
> +
> +  # Pin muxing and configuration
> +  realtek,drive-strength:
> +    $ref: /schemas/types.yaml#/definitions/uint32

Use the standard 'drive-strength' property.

> +    description: |
> +      Common drive strength used for all GPIO output pins, must be 4mA or 8mA.
> +      On reset, this value will default to 8mA.
> +    enum: [4, 8]
> +
> +  # LED scanning matrix
> +  leds:
> +    $ref: ../leds/realtek,rtl8231-leds.yaml#
> +
> +  # PWM output
> +  pwm:
> +    type: object
> +    description: |
> +      Subnode describing the PWM peripheral. To use the PWM output, gpio35 must
> +      be muxed to its 'pwm' function. Valid frequency values for consumers are
> +      1200, 1600, 2000, 2400, 2800, 3200, 4000, and 4800.
> +
> +    properties:
> +      "#pwm-cells":
> +        description: |
> +          Twos cells with PWM index (must be 0) and PWM frequency in Hz.
> +        const: 2
> +
> +    required:
> +      - "#pwm-cells"

Just move this to the parent node. No reason for a child node or that 1 
node can't be 2 providers.

> +
> +patternProperties:
> +  "-pins$":
> +    type: object
> +    $ref: ../pinctrl/pinmux-node.yaml#
> +
> +    properties:
> +      pins:
> +        items:
> +          oneOf:

No need for oneOf when there's only 1 entry.

> +            - enum: ["gpio0", "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6",
> +                     "gpio7", "gpio8", "gpio9", "gpio10", "gpio11", "gpio12", "gpio13",
> +                     "gpio14", "gpio15", "gpio16", "gpio17", "gpio18", "gpio19", "gpio20",
> +                     "gpio21", "gpio22", "gpio23", "gpio24", "gpio25", "gpio26", "gpio27",
> +                     "gpio28", "gpio29", "gpio30", "gpio31", "gpio32", "gpio33", "gpio34",
> +                     "gpio35", "gpio36"]
> +        minItems: 1
> +        maxItems: 37
> +      function:
> +        description: |
> +          Select which function to use. "gpio" is supported for all pins, "led" is supported
> +          for pins 0-34, "pwm" is supported for for pin 35.
> +        enum: ["gpio", "led", "pwm"]
> +
> +    required:
> +      - pins
> +      - function
> +
> +required:
> +  - compatible
> +  - reg
> +  - gpio-controller
> +  - "#gpio-cells"
> +  - gpio-ranges
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    // Minimal example
> +    mdio {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        expander0: expander@0 {
> +            compatible = "realtek,rtl8231";
> +            reg = <0>;
> +
> +            gpio-controller;
> +            #gpio-cells = <2>;
> +            gpio-ranges = <&expander0 0 0 37>;
> +        };
> +    };
> +  - |
> +    // All bells and whistles included
> +    #include <dt-bindings/leds/common.h>
> +    mdio {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        expander1: expander@1 {
> +            compatible = "realtek,rtl8231";
> +            reg = <1>;
> +
> +            gpio-controller;
> +            #gpio-cells = <2>;
> +            gpio-ranges = <&expander1 0 0 37>;
> +
> +            realtek,drive-strength = <4>;
> +
> +            button-pins {
> +                pins = "gpio36";
> +                function = "gpio";
> +                input-debounce = "100000";
> +            };
> +
> +            pwm-pins {
> +                pins = "gpio35";
> +                function = "pwm";
> +            };
> +
> +            led-pins {
> +                pins = "gpio0", "gpio1", "gpio3", "gpio4";
> +                function = "led";
> +            };
> +
> +            pwm {
> +                #pwm-cells = <2>;
> +            };
> +
> +            leds {
> +                compatible = "realtek,rtl8231-leds";
> +                #address-cells = <2>;
> +                #size-cells = <0>;
> +
> +                realtek,led-scan-mode = "single-color";
> +
> +                led@0,0 {
> +                    reg = <0 0>;
> +                    color = <LED_COLOR_ID_GREEN>;
> +                    function = LED_FUNCTION_LAN;
> +                    function-enumerator = <0>;
> +                };
> +
> +                led@0,1 {
> +                    reg = <0 1>;
> +                    color = <LED_COLOR_ID_AMBER>;
> +                    function = LED_FUNCTION_LAN;
> +                    function-enumerator = <0>;
> +                };
> +
> +                led@1,0 {
> +                    reg = <1 0>;
> +                    color = <LED_COLOR_ID_GREEN>;
> +                    function = LED_FUNCTION_LAN;
> +                    function-enumerator = <1>;
> +                };
> +
> +                led@1,1 {
> +                    reg = <1 1>;
> +                    color = <LED_COLOR_ID_AMBER>;
> +                    function = LED_FUNCTION_LAN;
> +                    function-enumerator = <1>;
> +                };
> +            };
> +        };
> +    };
> -- 
> 2.31.1
> 

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 2/7] gpio: regmap: Add configurable dir/value order
  2021-05-17 19:28   ` [PATCH v2 2/7] gpio: regmap: Add configurable dir/value order Sander Vanheule
  2021-05-17 21:06     ` Andy Shevchenko
@ 2021-05-18  1:40     ` Andrew Lunn
  2021-05-18 11:39       ` Sander Vanheule
  2021-05-23 22:21       ` Sander Vanheule
  2021-05-18  8:39     ` Michael Walle
  2 siblings, 2 replies; 114+ messages in thread
From: Andrew Lunn @ 2021-05-18  1:40 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio, Andy Shevchenko, linux-kernel

On Mon, May 17, 2021 at 09:28:04PM +0200, Sander Vanheule wrote:
> GPIO chips may not support setting the output value when a pin is
> configured as an input

Could you describe what happens with the hardware you are playing
with. Not being able to do this means you will get glitches when
enabling the output so you should not use these GPIOs with bit banging
busses like i2c.

       Andrew

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 2/7] gpio: regmap: Add configurable dir/value order
  2021-05-17 19:28   ` [PATCH v2 2/7] gpio: regmap: Add configurable dir/value order Sander Vanheule
  2021-05-17 21:06     ` Andy Shevchenko
  2021-05-18  1:40     ` Andrew Lunn
@ 2021-05-18  8:39     ` Michael Walle
  2021-05-18 10:39       ` Andy Shevchenko
  2021-05-23 21:19       ` Sander Vanheule
  2 siblings, 2 replies; 114+ messages in thread
From: Michael Walle @ 2021-05-18  8:39 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Linus Walleij,
	Bartosz Golaszewski, linux-leds, devicetree, linux-gpio,
	Andrew Lunn, Andy Shevchenko, linux-kernel

Hi,

Am 2021-05-17 21:28, schrieb Sander Vanheule:
> GPIO chips may not support setting the output value when a pin is
> configured as an input, although the current implementation assumes 
> this
> is always possible.
> 
> Add support for setting pin direction before value. The order defaults
> to setting the value first, but this can be reversed by setting the
> regmap_config.no_set_on_input flag, similar to the corresponding flag 
> in
> the gpio-mmio driver.
> 
> Signed-off-by: Sander Vanheule <sander@svanheule.net>
> ---
>  drivers/gpio/gpio-regmap.c  | 20 +++++++++++++++++---
>  include/linux/gpio/regmap.h |  3 +++
>  2 files changed, 20 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c
> index 134cedf151a7..1cdb20f8f8b4 100644
> --- a/drivers/gpio/gpio-regmap.c
> +++ b/drivers/gpio/gpio-regmap.c
> @@ -170,14 +170,25 @@ static int gpio_regmap_direction_input(struct
> gpio_chip *chip,
>  	return gpio_regmap_set_direction(chip, offset, false);
>  }
> 
> -static int gpio_regmap_direction_output(struct gpio_chip *chip,
> -					unsigned int offset, int value)
> +static int gpio_regmap_dir_out_val_first(struct gpio_chip *chip,
> +					 unsigned int offset, int value)

Can we leave the name as is? TBH I find these two similar names
super confusing. Maybe its just me, though.

>  {
>  	gpio_regmap_set(chip, offset, value);
> 
>  	return gpio_regmap_set_direction(chip, offset, true);
>  }
> 
> +static int gpio_regmap_dir_out_dir_first(struct gpio_chip *chip,
> +					 unsigned int offset, int value)
> +{
> +	int err;

use ret for consistency here

> +
> +	err = gpio_regmap_set_direction(chip, offset, true);
> +	gpio_regmap_set(chip, offset, value);
> +
> +	return err;
> +}
> +

Instead of adding a new one, we can also just check no_set_on_input
in gpio_regmap_direction_output(), which I'd prefer.

static int gpio_regmap_direction_output(struct gpio_chip *chip,
					unsigned int offset, int value)
{
	struct gpio_regmap *gpio = gpiochip_get_data(chip);
	int ret;

	if (gpio->no_set_on_input) {
		/* some smart comment here, also mention gliches */
		ret = gpio_regmap_set_direction(chip, offset, true);
		gpio_regmap_set(chip, offset, value);
	} else {
		gpio_regmap_set(chip, offset, value);
		ret = gpio_regmap_set_direction(chip, offset, true);
	}

	return ret;
}

>  void gpio_regmap_set_drvdata(struct gpio_regmap *gpio, void *data)
>  {
>  	gpio->driver_data = data;
> @@ -277,7 +288,10 @@ struct gpio_regmap *gpio_regmap_register(const
> struct gpio_regmap_config *config
>  	if (gpio->reg_dir_in_base || gpio->reg_dir_out_base) {
>  		chip->get_direction = gio_regmap_get_direction;
>  		chip->direction_input = gpio_regmap_direction_input;
> -		chip->direction_output = gpio_regmap_direction_output;
> +		if (config->no_set_on_input)
> +			chip->direction_output = gpio_regmap_dir_out_dir_first;
> +		else
> +			chip->direction_output = gpio_regmap_dir_out_val_first;
>  	}
> 
>  	ret = gpiochip_add_data(chip, gpio);
> diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h
> index 334dd928042b..2a732f8f23be 100644
> --- a/include/linux/gpio/regmap.h
> +++ b/include/linux/gpio/regmap.h
> @@ -30,6 +30,8 @@ struct regmap;
>   * @reg_dir_out_base:	(Optional) out setting register base address
>   * @reg_stride:		(Optional) May be set if the registers (of the
>   *			same type, dat, set, etc) are not consecutive.
> + * @no_set_on_input:	Set if output value can only be set when the 
> direction
> + *			is configured as output.

set_direction_first ?

>   * @ngpio_per_reg:	Number of GPIOs per register
>   * @irq_domain:		(Optional) IRQ domain if the controller is
>   *			interrupt-capable
> @@ -73,6 +75,7 @@ struct gpio_regmap_config {
>  	unsigned int reg_dir_out_base;
>  	int reg_stride;
>  	int ngpio_per_reg;
> +	bool no_set_on_input;
>  	struct irq_domain *irq_domain;
> 
>  	int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base,

-michael

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 2/7] gpio: regmap: Add configurable dir/value order
  2021-05-18  8:39     ` Michael Walle
@ 2021-05-18 10:39       ` Andy Shevchenko
  2021-05-23 21:19       ` Sander Vanheule
  1 sibling, 0 replies; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-18 10:39 UTC (permalink / raw)
  To: Michael Walle, Matti Vaittinen
  Cc: Sander Vanheule, Pavel Machek, Rob Herring, Lee Jones,
	Mark Brown, Greg Kroah-Hartman, Rafael J . Wysocki,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List

+Matti

On Tue, May 18, 2021 at 11:39 AM Michael Walle <michael@walle.cc> wrote:
> Am 2021-05-17 21:28, schrieb Sander Vanheule:

...

> Instead of adding a new one, we can also just check no_set_on_input
> in gpio_regmap_direction_output(), which I'd prefer.

+! here.

> static int gpio_regmap_direction_output(struct gpio_chip *chip,
>                                         unsigned int offset, int value)
> {
>         struct gpio_regmap *gpio = gpiochip_get_data(chip);
>         int ret;
>
>         if (gpio->no_set_on_input) {
>                 /* some smart comment here, also mention gliches */
>                 ret = gpio_regmap_set_direction(chip, offset, true);
>                 gpio_regmap_set(chip, offset, value);
>         } else {
>                 gpio_regmap_set(chip, offset, value);
>                 ret = gpio_regmap_set_direction(chip, offset, true);
>         }
>
>         return ret;
> }

...

> > + * @no_set_on_input: Set if output value can only be set when the
> > direction
> > + *                   is configured as output.
>
> set_direction_first ?

Perhaps we need to establish rather something like

/* Broken hardware can't set value on input pin, we have to set it to
output first */
#define GPIO_REGMAP_QUIRK_...  BIT(0)

unsigned int quirks;

?

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 2/7] gpio: regmap: Add configurable dir/value order
  2021-05-18  1:40     ` Andrew Lunn
@ 2021-05-18 11:39       ` Sander Vanheule
  2021-05-23 22:21       ` Sander Vanheule
  1 sibling, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-18 11:39 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio, Andy Shevchenko, linux-kernel, Matti Vaittinen

Hi Andrew,

On Tue, 2021-05-18 at 03:40 +0200, Andrew Lunn wrote:
> On Mon, May 17, 2021 at 09:28:04PM +0200, Sander Vanheule wrote:
> > GPIO chips may not support setting the output value when a pin is
> > configured as an input
> 
> Could you describe what happens with the hardware you are playing
> with. Not being able to do this means you will get glitches when
> enabling the output so you should not use these GPIOs with bit banging
> busses like i2c.

As I was testing this driver, I noticed that output settings for GPIO LEDs,
connected to the RTL8231, weren't being properly set. The actual LED brightness
didn't correspond to the one reported by sysfs. Changing the operation order
fixed this.

However, the vendor code uses I2C bitbanging quite extensively on these chips,
so I decided to have another look.

From u-boot on my device, I can manipulate the RTL8231 registeres relatively
easily. I performed the following short tests:
 * Set pin to input, pull pin high, write output low, change direction: pin
   output changes to low value
 * Set pin to input pull pin low, write output high, change direction: pin
   output changes to high value

Which seems to indicate that I _can_ set output values on input pins... I'll
need to look into this in more detail when I have a bit more time, later this
week.


Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 4/7] dt-bindings: mfd: Binding for RTL8231
  2021-05-17 19:28   ` [PATCH v2 4/7] dt-bindings: mfd: Binding for RTL8231 Sander Vanheule
@ 2021-05-18 22:02     ` Linus Walleij
  0 siblings, 0 replies; 114+ messages in thread
From: Linus Walleij @ 2021-05-18 22:02 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Bartosz Golaszewski, Linux LED Subsystem,
	open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
	open list:GPIO SUBSYSTEM, Andrew Lunn, Andy Shevchenko,
	linux-kernel

On Mon, May 17, 2021 at 9:28 PM Sander Vanheule <sander@svanheule.net> wrote:

> Add a binding description for the Realtek RTL8231, a GPIO and LED
> expander chip commonly used in ethernet switches based on a Realtek
> switch SoC. These chips can be addressed via an MDIO or SMI bus, or used
> as a plain 36-bit shift register.
>
> This binding only describes the feature set provided by the MDIO/SMI
> configuration, and covers the GPIO, PWM, and pin control properties. The
> LED properties are defined in a separate binding.
>
> Signed-off-by: Sander Vanheule <sander@svanheule.net>

This looks correct from the GPIO side of things:
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 3/5] mfd: Add RTL8231 core device
  2021-05-12 13:13   ` kernel test robot
@ 2021-05-19 14:58     ` Lee Jones
  2021-05-19 15:11       ` Sander Vanheule
  0 siblings, 1 reply; 114+ messages in thread
From: Lee Jones @ 2021-05-19 14:58 UTC (permalink / raw)
  To: kernel test robot
  Cc: Sander Vanheule, Pavel Machek, Rob Herring, Linus Walleij,
	linux-leds, devicetree, linux-gpio, kbuild-all, linux-kernel

On Wed, 12 May 2021, kernel test robot wrote:

> Hi Sander,
> 
> Thank you for the patch! Yet something to improve:
> 
> [auto build test ERROR on pavel-linux-leds/for-next]
> [also build test ERROR on lee-mfd/for-mfd-next pinctrl/devel v5.13-rc1]
> [If your patch is applied to the wrong git tree, kindly drop us a note.
> And when submitting patch, we suggest to use '--base' as documented in
> https://git-scm.com/docs/git-format-patch]
> 
> url:    https://github.com/0day-ci/linux/commits/Sander-Vanheule/RTL8231-GPIO-expander-support/20210511-202618
> base:   git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds.git for-next
> config: h8300-randconfig-r012-20210512 (attached as .config)
> compiler: h8300-linux-gcc (GCC) 9.3.0
> reproduce (this is a W=1 build):
>         wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
>         chmod +x ~/bin/make.cross
>         # https://github.com/0day-ci/linux/commit/e031cc2da2c2948230bacd1ca56cfe9990e1aefd
>         git remote add linux-review https://github.com/0day-ci/linux
>         git fetch --no-tags linux-review Sander-Vanheule/RTL8231-GPIO-expander-support/20210511-202618
>         git checkout e031cc2da2c2948230bacd1ca56cfe9990e1aefd
>         # save the attached .config to linux build tree
>         COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross W=1 ARCH=h8300 
> 
> If you fix the issue, kindly add following tag as appropriate
> Reported-by: kernel test robot <lkp@intel.com>
> 
> All errors (new ones prefixed by >>):
> 
>    h8300-linux-ld: drivers/mfd/rtl8231.o: in function `rtl8231_mdio_reg_write':
> >> rtl8231.c:(.text+0x4f): undefined reference to `mdiobus_write'
>    h8300-linux-ld: drivers/mfd/rtl8231.o: in function `rtl8231_mdio_reg_read':
> >> rtl8231.c:(.text+0x75): undefined reference to `mdiobus_read'
>    h8300-linux-ld: drivers/mfd/rtl8231.o: in function `mdio_module_init':
> >> rtl8231.c:(.init.text+0xd): undefined reference to `mdio_driver_register'
> 
> ---
> 0-DAY CI Kernel Test Service, Intel Corporation
> https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org

Please could you take a look at these failures.

Either fix them up or report a false positive.

-- 
Lee Jones [李琼斯]
Senior Technical Lead - Developer Services
Linaro.org │ Open source software for Arm SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 3/5] mfd: Add RTL8231 core device
  2021-05-19 14:58     ` Lee Jones
@ 2021-05-19 15:11       ` Sander Vanheule
  0 siblings, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-19 15:11 UTC (permalink / raw)
  To: Lee Jones, kernel test robot
  Cc: Pavel Machek, Rob Herring, Linus Walleij, linux-leds, devicetree,
	linux-gpio, kbuild-all, linux-kernel

On Wed, 2021-05-19 at 15:58 +0100, Lee Jones wrote:
> On Wed, 12 May 2021, kernel test robot wrote:
> 
> > Hi Sander,
> > 
> > Thank you for the patch! Yet something to improve:
> > 
> > [auto build test ERROR on pavel-linux-leds/for-next]
> > [also build test ERROR on lee-mfd/for-mfd-next pinctrl/devel v5.13-rc1]
> > [If your patch is applied to the wrong git tree, kindly drop us a note.
> > And when submitting patch, we suggest to use '--base' as documented in
> > https://git-scm.com/docs/git-format-patch]
> > 
> > url:   
> > https://github.com/0day-ci/linux/commits/Sander-Vanheule/RTL8231-GPIO-expander-support/20210511-202618
> > base:   git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds.git
> > for-next
> > config: h8300-randconfig-r012-20210512 (attached as .config)
> > compiler: h8300-linux-gcc (GCC) 9.3.0
> > reproduce (this is a W=1 build):
> >         wget
> > https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O
> > ~/bin/make.cross
> >         chmod +x ~/bin/make.cross
> >         #
> > https://github.com/0day-ci/linux/commit/e031cc2da2c2948230bacd1ca56cfe9990e1aefd
> >         git remote add linux-review https://github.com/0day-ci/linux
> >         git fetch --no-tags linux-review Sander-Vanheule/RTL8231-GPIO-
> > expander-support/20210511-202618
> >         git checkout e031cc2da2c2948230bacd1ca56cfe9990e1aefd
> >         # save the attached .config to linux build tree
> >         COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross W=1
> > ARCH=h8300 
> > 
> > If you fix the issue, kindly add following tag as appropriate
> > Reported-by: kernel test robot <lkp@intel.com>
> > 
> > All errors (new ones prefixed by >>):
> > 
> >    h8300-linux-ld: drivers/mfd/rtl8231.o: in function
> > `rtl8231_mdio_reg_write':
> > > > rtl8231.c:(.text+0x4f): undefined reference to `mdiobus_write'
> >    h8300-linux-ld: drivers/mfd/rtl8231.o: in function `rtl8231_mdio_reg_read':
> > > > rtl8231.c:(.text+0x75): undefined reference to `mdiobus_read'
> >    h8300-linux-ld: drivers/mfd/rtl8231.o: in function `mdio_module_init':
> > > > rtl8231.c:(.init.text+0xd): undefined reference to `mdio_driver_register'
> > 
> > ---
> > 0-DAY CI Kernel Test Service, Intel Corporation
> > https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
> 
> Please could you take a look at these failures.
> 
> Either fix them up or report a false positive.

Hi Lee,

These were caused by a missing dependency on MDIO_BUS. This should be resolved
in the v2 series.

https://lore.kernel.org/lkml/f1ca940216c0accfc804afee2dbe46d260d890ae.1621279162.git.sander@svanheule.net/

I wasn't sure how to attribute this to the test bot, since the depedency was
partly resolved through another patch.

Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: (subset) [PATCH v2 0/7] RTL8231 GPIO expander support
  2021-05-17 19:28 ` [PATCH v2 0/7] " Sander Vanheule
                     ` (6 preceding siblings ...)
  2021-05-17 19:28   ` [PATCH v2 7/7] leds: Add support for RTL8231 LED scan matrix Sander Vanheule
@ 2021-05-19 16:10   ` Mark Brown
  7 siblings, 0 replies; 114+ messages in thread
From: Mark Brown @ 2021-05-19 16:10 UTC (permalink / raw)
  To: Rob Herring, Sander Vanheule, Bartosz Golaszewski, devicetree,
	Pavel Machek, Lee Jones, Linus Walleij, linux-gpio,
	Michael Walle, linux-leds, Rafael J . Wysocki,
	Greg Kroah-Hartman
  Cc: Mark Brown, linux-kernel, Andy Shevchenko, Andrew Lunn

On Mon, 17 May 2021 21:28:02 +0200, Sander Vanheule wrote:
> The RTL8231 GPIO and LED expander can be configured for use as an MDIO or SMI
> bus device. Currently only the MDIO mode is supported, although SMI mode
> support should be fairly straightforward, once an SMI bus driver is available.
> 
> Provided features by the RTL8231:
>   - Up to 37 GPIOs
>     - Configurable drive strength: 8mA or 4mA (currently unsupported)
>     - Input debouncing on high GPIOs (currently unsupported)
>   - Up to 88 LEDs in multiple scan matrix groups
>     - On, off, or one of six toggling intervals
>     - "single-color mode": 2×36 single color LEDs + 8 bi-color LEDs
>     - "bi-color mode": (12 + 2×6) bi-color LEDs + 24 single color LEDs
>   - Up to one PWM output (currently unsupported)
>     - Fixed duty cycle, 8 selectable frequencies (1.2kHz - 4.8kHz)
> 
> [...]

Applied to

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap.git for-next

Thanks!

[1/7] regmap: Add MDIO bus support
      commit: 1f89d2fe16072a74b34bdb895160910091427891

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 1/7] regmap: Add MDIO bus support
  2021-05-17 19:28   ` [PATCH v2 1/7] regmap: Add MDIO bus support Sander Vanheule
@ 2021-05-19 16:12     ` Mark Brown
  0 siblings, 0 replies; 114+ messages in thread
From: Mark Brown @ 2021-05-19 16:12 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Greg Kroah-Hartman,
	Rafael J . Wysocki, Michael Walle, Linus Walleij,
	Bartosz Golaszewski, linux-leds, devicetree, linux-gpio,
	Andrew Lunn, Andy Shevchenko, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 1144 bytes --]

On Mon, May 17, 2021 at 09:28:03PM +0200, Sander Vanheule wrote:
> Basic support for MDIO bus access. Support only includes clause-22
> register access, with 5-bit addresses, and 16-bit wide registers.

The following changes since commit 6efb943b8616ec53a5e444193dccf1af9ad627b5:

  Linux 5.13-rc1 (2021-05-09 14:17:44 -0700)

are available in the Git repository at:

  https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap.git tags/regmap-mdio

for you to fetch changes up to 1f89d2fe16072a74b34bdb895160910091427891:

  regmap: Add MDIO bus support (2021-05-19 14:19:10 +0100)

----------------------------------------------------------------
regmap: Add MDIO bus support

----------------------------------------------------------------
Sander Vanheule (1):
      regmap: Add MDIO bus support

 drivers/base/regmap/Kconfig       |  6 ++++-
 drivers/base/regmap/Makefile      |  1 +
 drivers/base/regmap/regmap-mdio.c | 57 +++++++++++++++++++++++++++++++++++++++
 include/linux/regmap.h            | 36 +++++++++++++++++++++++++
 4 files changed, 99 insertions(+), 1 deletion(-)
 create mode 100644 drivers/base/regmap/regmap-mdio.c

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 1/5] dt-bindings: leds: Binding for RTL8231 scan matrix
  2021-05-17 22:31   ` Rob Herring
@ 2021-05-19 16:39     ` Sander Vanheule
  0 siblings, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-19 16:39 UTC (permalink / raw)
  To: Rob Herring
  Cc: Pavel Machek, Lee Jones, Linus Walleij, linux-leds, devicetree,
	linux-gpio, linux-kernel

On Mon, 2021-05-17 at 17:31 -0500, Rob Herring wrote:
> On Tue, May 11, 2021 at 02:25:19PM +0200, Sander Vanheule wrote:
> > Add a binding description for the Realtek RTL8231's LED support, which
> > consists of up to 88 LEDs arranged in a number of scanning matrices.
> > 
> > Signed-off-by: Sander Vanheule <sander@svanheule.net>
> > ---
> >  .../bindings/leds/realtek,rtl8231-leds.yaml   | 159 ++++++++++++++++++
> >  1 file changed, 159 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/leds/realtek,rtl8231-
> > leds.yaml
> > 
> > diff --git a/Documentation/devicetree/bindings/leds/realtek,rtl8231-
> > leds.yaml b/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
> > new file mode 100644
> > index 000000000000..aba2b55fb9c9
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
> > @@ -0,0 +1,159 @@
> > +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/leds/realtek,rtl8231-leds.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Realtek RTL8231 LED scan matrix.
> > +
> > +maintainers:
> > +  - Sander Vanheule <sander@svanheule.net>
> > +
> > +description: |
> > +  The RTL8231 has support for driving a number of LED matrices, by scanning
> > +  over the LEDs pins, alternatingly lighting different columns and/or rows.
> > +
> > +  In single color scan mode, 88 LEDs are supported. These are grouped into
> > +  three output matrices:
> > +    - Group A of 6×6 single color LEDs. Rows and columns are driven by GPIO
> > +      pins 0-11.
> > +               L0[n]    L1[n]    L2[n]    L0[n+6]  L1[n+6]  L2[n+6]
> > +                |        |        |        |        |        |
> > +       P0/P6  --<--------<--------<--------<--------<--------< (3)
> > +                |        |        |        |        |        |
> > +       P1/P7  --<--------<--------<--------<--------<--------< (4)
> > +                |        |        |        |        |        |
> > +       P2/P8  --<--------<--------<--------<--------<--------< (5)
> > +                |        |        |        |        |        |
> > +       P3/P9  --<--------<--------<--------<--------<--------< (6)
> > +                |        |        |        |        |        |
> > +       P4/P10 --<--------<--------<--------<--------<--------< (7)
> > +                |        |        |        |        |        |
> > +       P5/P11 --<--------<--------<--------<--------<--------< (8)
> > +               (0)      (1)      (2)      (9)     (10)     (11)
> > +    - Group B of 6×6 single color LEDs. Rows and columns are driven by GPIO
> > +      pins 12-23.
> > +               L0[n]    L1[n]    L2[n]    L0[n+6]  L1[n+6]  L2[n+6]
> > +                |        |        |        |        |        |
> > +      P12/P18 --<--------<--------<--------<--------<--------< (15)
> > +                |        |        |        |        |        |
> > +      P13/P19 --<--------<--------<--------<--------<--------< (16)
> > +                |        |        |        |        |        |
> > +      P14/P20 --<--------<--------<--------<--------<--------< (17)
> > +                |        |        |        |        |        |
> > +      P15/P21 --<--------<--------<--------<--------<--------< (18)
> > +                |        |        |        |        |        |
> > +      P16/P22 --<--------<--------<--------<--------<--------< (19)
> > +                |        |        |        |        |        |
> > +      P17/P23 --<--------<--------<--------<--------<--------< (20)
> > +              (12)     (13)     (14)    (21)      (22)     (23)
> > +    - Group C of 8 pairs of anti-parallel (or bi-color) LEDs. LED selection
> > is
> > +      provided by GPIO pins 24-27 and 29-32, polarity selection by GPIO 28.
> > +               P24     P25  ...  P30     P31
> > +                |       |         |       |
> > +      LED POL --X-------X---/\/---X-------X (28)
> > +              (24)    (25)  ... (31)    (32)
> > +
> > +  In bi-color scan mode, 72 LEDs are supported. These are grouped into four
> > +  output matrices:
> > +    - Group A of 12 pairs of anti-parallel LEDs. LED selection is provided
> > +      by GPIO pins 0-11, polarity selection by GPIO 12.
> > +    - Group B of 6 pairs of anti-parallel LEDs. LED selection is provided
> > +      by GPIO pins 23-28, polarity selection by GPIO 21.
> > +    - Group C of 6 pairs of anti-parallel LEDs. LED selection is provided
> > +      by GPIO pins 29-34, polarity selection by GPIO 22.
> > +    - Group of 4×6 single color LEDs. Rows are driven by GPIO pins 15-20,
> > +      columns by GPIO pins 13-14 and 21-22 (shared with groups B and C).
> > +          P[n]     P[n+6]   P[n+12]  P[n+18]
> > +            |        |        |        |
> > +       +0 --<--------<--------<--------< (15)
> > +            |        |        |        |
> > +       +1 --<--------<--------<--------< (16)
> > +            |        |        |        |
> > +       +2 --<--------<--------<--------< (17)
> > +            |        |        |        |
> > +       +3 --<--------<--------<--------< (18)
> > +            |        |        |        |
> > +       +4 --<--------<--------<--------< (19)
> > +            |        |        |        |
> > +       +6 --<--------<--------<--------< (20)
> > +          (13)     (14)     (21)     (22)
> > +
> > +  This node must always be a child of a 'realtek,rtl8231' node.
> > +
> > +properties:
> > +  $nodename:
> > +    const: leds
> 
> led-controller

Will update.

> > +
> > +  compatible:
> > +    const: realtek,rtl8231-leds
> 
> How is this device controlled?
> 

This device represents a part of the functionality of the RTL8231 GPIO/LED
expander, which sits on an MDIO bus or Realtek SMI bus. I'll add this bit to the
main description.

Does this answer your question, or were you looking for other details?

> > 
> > +patternProperties:
> > +  "^led@[0-9]+,[0-2]$":
> > +    description: |
> > +      LEDs are addressed by their port index and led index. Ports 0-23
> > always
> > +      support three LEDs. Additionally, but only when used in single color
> > scan
> > +      mode, ports 24-31 support two LEDs.
> 
> Normally unit-addresses are hex values.
> > 
> > +    type: object
> > +
> > +    properties:
> > +      reg:
> > +        maxItems: 1
> 
> This should have more constraints:
> 
> reg:
>   items: 
>     - items:
>         - description: port index
>           maximum: 31
>         - description: led index
>           maximum: 2
> 

Thanks for the suggestion, I was wondering what the best approach was here. I
will change the pattern to "^led@" as well, so the address restrictions are only
defined by the reg property.
> 

Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 2/5] dt-bindings: mfd: Binding for RTL8231
  2021-05-17 22:38   ` Rob Herring
@ 2021-05-19 16:53     ` Sander Vanheule
  0 siblings, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-19 16:53 UTC (permalink / raw)
  To: Rob Herring
  Cc: Pavel Machek, Lee Jones, Linus Walleij, linux-leds, devicetree,
	linux-gpio, linux-kernel

On Mon, 2021-05-17 at 17:38 -0500, Rob Herring wrote:
> On Tue, May 11, 2021 at 02:25:20PM +0200, Sander Vanheule wrote:
> > Add a binding description for the Realtek RTL8231, a GPIO and LED
> > expander chip commonly used in ethernet switches based on a Realtek
> > switch SoC. These chips can be addressed via an MDIO or SMI bus, or used
> > as a plain 36-bit shift register.
> > 
> > This binding only describes the feature set provided by the MDIO/SMI
> > configuration, and covers the GPIO, PWM, and pin control properties. The
> > LED properties are defined in a separate binding.
> > 
> > Signed-off-by: Sander Vanheule <sander@svanheule.net>
> > ---
> >  .../bindings/mfd/realtek,rtl8231.yaml         | 202 ++++++++++++++++++
> >  1 file changed, 202 insertions(+)
> >  create mode 100644
> > Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
> > 
> > diff --git a/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
> > b/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
> > new file mode 100644
> > index 000000000000..2023cfa887a3
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
> > @@ -0,0 +1,202 @@
> > +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/mfd/realtek,rtl8231.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Realtek RTL8231 GPIO and LED expander.
> > +
> > +maintainers:
> > +  - Sander Vanheule <sander@svanheule.net>
> > +
> > +description: |
> > +  The RTL8231 is a GPIO and LED expander chip, providing up to 37 GPIOs, up
> > to
> > +  88 LEDs, and up to one PWM output. This device is frequently used
> > alongside
> > +  Realtek switch SoCs, to provide additional I/O capabilities.
> > +
> > +  To manage the RTL8231's features, its strapping pins can be used to
> > configure
> > +  it in one of three modes: shift register, MDIO device, or SMI device. The
> > +  shift register mode does not need special support. In MDIO or SMI mode,
> > most
> > +  pins can be configured as a GPIO output, LED matrix scan line/column, or
> > as a
> > +  PWM output.
> > +
> > +  The GPIO and pin control are part of the main node. PWM and LED support
> > are
> > +  configured as sub-nodes.
> > +
> > +properties:
> > +  compatible:
> > +    const: realtek,rtl8231
> > +
> > +  reg:
> > +    description: MDIO or SMI device address.
> > +    maxItems: 1
> > +
> > +  # GPIO support
> > +  gpio-controller: true
> > +
> > +  "#gpio-cells":
> > +    const: 2
> > +    description: |
> > +      The first cell is the pin number and the second cell is used to
> > specify
> > +      the gpio active state.
> > +
> > +  gpio-ranges:
> > +    description: |
> > +      Must reference itself, and provide a zero-based mapping for 37 pins.
> > +    maxItems: 1
> > +
> > +  # Pin muxing and configuration
> > +  realtek,drive-strength:
> > +    $ref: /schemas/types.yaml#/definitions/uint32
> 
> Use the standard 'drive-strength' property.

Ok, I wasn't sure I could do this, since it's normally used in a pin config, not
a pin controller config. I'll update this, as well as the suggested changes
below.

Best,
Sander


> 
> > +    description: |
> > +      Common drive strength used for all GPIO output pins, must be 4mA or
> > 8mA.
> > +      On reset, this value will default to 8mA.
> > +    enum: [4, 8]
> > +
> > +  # LED scanning matrix
> > +  leds:
> > +    $ref: ../leds/realtek,rtl8231-leds.yaml#
> > +
> > +  # PWM output
> > +  pwm:
> > +    type: object
> > +    description: |
> > +      Subnode describing the PWM peripheral. To use the PWM output, gpio35
> > must
> > +      be muxed to its 'pwm' function. Valid frequency values for consumers
> > are
> > +      1200, 1600, 2000, 2400, 2800, 3200, 4000, and 4800.
> > +
> > +    properties:
> > +      "#pwm-cells":
> > +        description: |
> > +          Twos cells with PWM index (must be 0) and PWM frequency in Hz.
> > +        const: 2
> > +
> > +    required:
> > +      - "#pwm-cells"
> 
> Just move this to the parent node. No reason for a child node or that 1 
> node can't be 2 providers.
> 
> > +
> > +patternProperties:
> > +  "-pins$":
> > +    type: object
> > +    $ref: ../pinctrl/pinmux-node.yaml#
> > +
> > +    properties:
> > +      pins:
> > +        items:
> > +          oneOf:
> 
> No need for oneOf when there's only 1 entry.
> 
> > +            - enum: ["gpio0", "gpio1", "gpio2", "gpio3", "gpio4", "gpio5",
> > "gpio6",
> > +                     "gpio7", "gpio8", "gpio9", "gpio10", "gpio11",
> > "gpio12", "gpio13",
> > +                     "gpio14", "gpio15", "gpio16", "gpio17", "gpio18",
> > "gpio19", "gpio20",
> > +                     "gpio21", "gpio22", "gpio23", "gpio24", "gpio25",
> > "gpio26", "gpio27",
> > +                     "gpio28", "gpio29", "gpio30", "gpio31", "gpio32",
> > "gpio33", "gpio34",
> > +                     "gpio35", "gpio36"]
> > +        minItems: 1
> > +        maxItems: 37
> > +      function:
> > +        description: |
> > +          Select which function to use. "gpio" is supported for all pins,
> > "led" is supported
> > +          for pins 0-34, "pwm" is supported for for pin 35.
> > +        enum: ["gpio", "led", "pwm"]
> > +
> > +    required:
> > +      - pins
> > +      - function
> > +
> > +required:
> > +  - compatible
> > +  - reg
> > +  - gpio-controller
> > +  - "#gpio-cells"
> > +  - gpio-ranges
> > +
> > +additionalProperties: false
> > +
> > +examples:
> > +  - |
> > +    // Minimal example
> > +    mdio {
> > +        #address-cells = <1>;
> > +        #size-cells = <0>;
> > +
> > +        expander0: expander@0 {
> > +            compatible = "realtek,rtl8231";
> > +            reg = <0>;
> > +
> > +            gpio-controller;
> > +            #gpio-cells = <2>;
> > +            gpio-ranges = <&expander0 0 0 37>;
> > +        };
> > +    };
> > +  - |
> > +    // All bells and whistles included
> > +    #include <dt-bindings/leds/common.h>
> > +    mdio {
> > +        #address-cells = <1>;
> > +        #size-cells = <0>;
> > +
> > +        expander1: expander@1 {
> > +            compatible = "realtek,rtl8231";
> > +            reg = <1>;
> > +
> > +            gpio-controller;
> > +            #gpio-cells = <2>;
> > +            gpio-ranges = <&expander1 0 0 37>;
> > +
> > +            realtek,drive-strength = <4>;
> > +
> > +            button-pins {
> > +                pins = "gpio36";
> > +                function = "gpio";
> > +                input-debounce = "100000";
> > +            };
> > +
> > +            pwm-pins {
> > +                pins = "gpio35";
> > +                function = "pwm";
> > +            };
> > +
> > +            led-pins {
> > +                pins = "gpio0", "gpio1", "gpio3", "gpio4";
> > +                function = "led";
> > +            };
> > +
> > +            pwm {
> > +                #pwm-cells = <2>;
> > +            };
> > +
> > +            leds {
> > +                compatible = "realtek,rtl8231-leds";
> > +                #address-cells = <2>;
> > +                #size-cells = <0>;
> > +
> > +                realtek,led-scan-mode = "single-color";
> > +
> > +                led@0,0 {
> > +                    reg = <0 0>;
> > +                    color = <LED_COLOR_ID_GREEN>;
> > +                    function = LED_FUNCTION_LAN;
> > +                    function-enumerator = <0>;
> > +                };
> > +
> > +                led@0,1 {
> > +                    reg = <0 1>;
> > +                    color = <LED_COLOR_ID_AMBER>;
> > +                    function = LED_FUNCTION_LAN;
> > +                    function-enumerator = <0>;
> > +                };
> > +
> > +                led@1,0 {
> > +                    reg = <1 0>;
> > +                    color = <LED_COLOR_ID_GREEN>;
> > +                    function = LED_FUNCTION_LAN;
> > +                    function-enumerator = <1>;
> > +                };
> > +
> > +                led@1,1 {
> > +                    reg = <1 1>;
> > +                    color = <LED_COLOR_ID_AMBER>;
> > +                    function = LED_FUNCTION_LAN;
> > +                    function-enumerator = <1>;
> > +                };
> > +            };
> > +        };
> > +    };
> > -- 
> > 2.31.1
> > 



^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 2/7] gpio: regmap: Add configurable dir/value order
  2021-05-18  8:39     ` Michael Walle
  2021-05-18 10:39       ` Andy Shevchenko
@ 2021-05-23 21:19       ` Sander Vanheule
  1 sibling, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-23 21:19 UTC (permalink / raw)
  To: Michael Walle
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Linus Walleij,
	Bartosz Golaszewski, linux-leds, devicetree, linux-gpio,
	Andrew Lunn, Andy Shevchenko, linux-kernel

Hi Michael,

On Tue, 2021-05-18 at 10:39 +0200, Michael Walle wrote:
> Hi,
> 
> Am 2021-05-17 21:28, schrieb Sander Vanheule:
> > GPIO chips may not support setting the output value when a pin is
> > configured as an input, although the current implementation assumes 
> > this
> > is always possible.
> > 
> > Add support for setting pin direction before value. The order defaults
> > to setting the value first, but this can be reversed by setting the
> > regmap_config.no_set_on_input flag, similar to the corresponding flag 
> > in
> > the gpio-mmio driver.
> > 
> > Signed-off-by: Sander Vanheule <sander@svanheule.net>
> > ---
> >  drivers/gpio/gpio-regmap.c  | 20 +++++++++++++++++---
> >  include/linux/gpio/regmap.h |  3 +++
> >  2 files changed, 20 insertions(+), 3 deletions(-)
> > 
> > diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c
> > index 134cedf151a7..1cdb20f8f8b4 100644
> > --- a/drivers/gpio/gpio-regmap.c
> > +++ b/drivers/gpio/gpio-regmap.c
> > @@ -170,14 +170,25 @@ static int gpio_regmap_direction_input(struct
> > gpio_chip *chip,
> >         return gpio_regmap_set_direction(chip, offset, false);
> >  }
> > 
> > -static int gpio_regmap_direction_output(struct gpio_chip *chip,
> > -                                       unsigned int offset, int value)
> > +static int gpio_regmap_dir_out_val_first(struct gpio_chip *chip,
> > +                                        unsigned int offset, int value)
> 
> Can we leave the name as is? TBH I find these two similar names
> super confusing. Maybe its just me, though.

Sure. This is the implementation used in gpio-mmio.c to provide the same
functionality, so I had used that for consistenty between the two drivers.

> >  {
> >         gpio_regmap_set(chip, offset, value);
> > 
> >         return gpio_regmap_set_direction(chip, offset, true);
> >  }
> > 
> > +static int gpio_regmap_dir_out_dir_first(struct gpio_chip *chip,
> > +                                        unsigned int offset, int value)
> > +{
> > +       int err;
> 
> use ret for consistency here
> 
> > +
> > +       err = gpio_regmap_set_direction(chip, offset, true);
> > +       gpio_regmap_set(chip, offset, value);
> > +
> > +       return err;
> > +}
> > +
> 
> Instead of adding a new one, we can also just check no_set_on_input
> in gpio_regmap_direction_output(), which I'd prefer.
> 
> static int gpio_regmap_direction_output(struct gpio_chip *chip,
>                                         unsigned int offset, int value)
> {
>         struct gpio_regmap *gpio = gpiochip_get_data(chip);
>         int ret;
> 
>         if (gpio->no_set_on_input) {
>                 /* some smart comment here, also mention gliches */
>                 ret = gpio_regmap_set_direction(chip, offset, true);
>                 gpio_regmap_set(chip, offset, value);
>         } else {
>                 gpio_regmap_set(chip, offset, value);
>                 ret = gpio_regmap_set_direction(chip, offset, true);
>         }
> 
>         return ret;
> }
> 

This would certainly make the code a bit easier to follow when you're not
familiar with it :-)
I also see the other functions do checks on static values too, so I'll bring
this function in line with that style.


> >  void gpio_regmap_set_drvdata(struct gpio_regmap *gpio, void *data)
> >  {
> >         gpio->driver_data = data;
> > @@ -277,7 +288,10 @@ struct gpio_regmap *gpio_regmap_register(const
> > struct gpio_regmap_config *config
> >         if (gpio->reg_dir_in_base || gpio->reg_dir_out_base) {
> >                 chip->get_direction = gio_regmap_get_direction;
> >                 chip->direction_input = gpio_regmap_direction_input;
> > -               chip->direction_output = gpio_regmap_direction_output;
> > +               if (config->no_set_on_input)
> > +                       chip->direction_output =
> > gpio_regmap_dir_out_dir_first;
> > +               else
> > +                       chip->direction_output =
> > gpio_regmap_dir_out_val_first;
> >         }
> > 
> >         ret = gpiochip_add_data(chip, gpio);
> > diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h
> > index 334dd928042b..2a732f8f23be 100644
> > --- a/include/linux/gpio/regmap.h
> > +++ b/include/linux/gpio/regmap.h
> > @@ -30,6 +30,8 @@ struct regmap;
> >   * @reg_dir_out_base:  (Optional) out setting register base address
> >   * @reg_stride:                (Optional) May be set if the registers (of
> > the
> >   *                     same type, dat, set, etc) are not consecutive.
> > + * @no_set_on_input:   Set if output value can only be set when the 
> > direction
> > + *                     is configured as output.
> 
> set_direction_first ?

This negation can indeed be a bit confusing, I'll change this. As Andy
suggested, I just went for a 'quirks' field, with currently only one defined
flag.

Best,
Sander



^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 5/7] mfd: Add RTL8231 core device
  2021-05-17 21:18     ` Andy Shevchenko
@ 2021-05-23 21:28       ` Sander Vanheule
  2021-05-24  7:49         ` Andy Shevchenko
  2021-05-24  7:50       ` Sander Vanheule
  1 sibling, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-23 21:28 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List, kernel test robot

Hi Andy,

I've implemented the minor remarks (redundant assignments, if/else code
structure...). Some extra details below.

On Tue, 2021-05-18 at 00:18 +0300, Andy Shevchenko wrote:
> On Mon, May 17, 2021 at 10:28 PM Sander Vanheule <sander@svanheule.net> wrote:
> > 
> > The RTL8231 is implemented as an MDIO device, and provides a regmap
> > interface for register access by the core and child devices.
> > 
> > The chip can also be a device on an SMI bus, an I2C-like bus by Realtek.
> > Since kernel support for SMI is limited, and no real-world SMI
> > implementations have been encountered for this device, this is currently
> > unimplemented. The use of the regmap interface should make any future
> > support relatively straightforward.
> > 
> > After reset, all pins are muxed to GPIO inputs before the pin drivers
> > are enabled. This is done to prevent accidental system resets, when a
> > pin is connected to the parent SoC's reset line.
> 
> > [missing MDIO_BUS dependency, provided via REGMAP_MDIO]
> > Reported-by: kernel test robot <lkp@intel.com>
> 
> What is the culprit? Shouldn't this have a Fixes tag?

But it doesn't actually fix an issue created by an existing commit, just
something that was wrong in the first version of the patch.  This patch is not
dedicated to fixing that single issue though, it's just a part of it. Hence the
note above the Reported-by tag.

> > 
> > +       mdiodev->reset_gpio = gpiod_get_optional(dev, "reset",
> > GPIOD_OUT_LOW);
> > +       device_property_read_u32(dev, "reset-assert-delay", &mdiodev-
> > >reset_assert_delay);
> > +       device_property_read_u32(dev, "reset-deassert-delay", &mdiodev-
> > >reset_deassert_delay);
> > +
> > +       err = rtl8231_init(dev, map);
> > +       if (err)
> 
> Resource leakage.

Replaced gpiod_get_optional by devm_gpiod_get_optional.

> 
> > +               return err;
> > +
> > +       /* LED_START enables power to output pins, and starts the LED engine
> > */
> > +       regmap_field_write(led_start, 1);
> 
> > +       return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, rtl8231_cells,
> > +               ARRAY_SIZE(rtl8231_cells), NULL, 0, NULL);
> 
> Ditto.
> 
> > +}
> 
> ...
> 
> > +#ifdef CONFIG_PM
> 
> Replace this with __maybe_unused attribute.

Done. I've also used a few extra macros from PM header to clean this part up a
bit more.



Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 6/7] pinctrl: Add RTL8231 pin control and GPIO support
  2021-05-17 21:42     ` Andy Shevchenko
  2021-05-17 21:46       ` Andy Shevchenko
@ 2021-05-23 21:42       ` Sander Vanheule
  1 sibling, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-23 21:42 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List

Hi Andy,

On Tue, 2021-05-18 at 00:42 +0300, Andy Shevchenko wrote:
> On Mon, May 17, 2021 at 10:28 PM Sander Vanheule <sander@svanheule.net> wrote:
> > 
> > This driver implements the GPIO and pin muxing features provided by the
> > RTL8231. The device should be instantiated as an MFD child, where the
> > parent device has already configured the regmap used for register
> > access.
> > 
> > Although described in the bindings, pin debouncing and drive strength
> > selection are currently not implemented. Debouncing is only available
> > for the six highest GPIOs, and must be emulated when other pins are used
> > for (button) inputs anyway.
> 
> ...
> 
> > +struct rtl8231_pin_desc {
> > +       unsigned int number;
> > +       const char *name;
> > +       enum rtl8231_pin_function functions;
> > +       u8 reg;
> > +       u8 offset;
> > +       u8 gpio_function_value;
> > +};
> 
> I would see rather
> 
> sturct pinctrl_pin_desc desc;
> 
> Where drv_data describes the rest of the data for pin
> 

I've split up the definitions into two parts:
 * pinctrl_pin_desc with the standard info, which has drv_data pointing to...
 * a device-specific rtl8231_pin_desc, with the register field info and
   alternate function

So the pin descriptions are now entirely static, and only the pin functions are
assembled at runtime.

> 
> > +static int rtl8231_get_group_pins(struct pinctrl_dev *pctldev, unsigned int
> > selector,
> > +       const unsigned int **pins, unsigned int *num_pins)
> > +{
> 
> > +       if (selector < ARRAY_SIZE(rtl8231_pins)) {
> 
> Can we use traditional pattern, i.e.
> 
>   if (... >= ARRAY_SIZE(...))
>     return -EINVAL;
> 
>   ...
>   return 0;
> 
> ?

Sure. Will be implemented in v3.

> 
> > +               *pins = &rtl8231_pins[selector].number;
> > +               *num_pins = 1;
> > +               return 0;
> > +       }
> > +
> > +       return -EINVAL;
> > +}
> 
> ...
> 
> > +static int rtl8231_set_mux(struct pinctrl_dev *pctldev, unsigned int
> > func_selector,
> > +       unsigned int group_selector)
> > +{
> 
> > +       int err = 0;
> 
> Redundant variable.
> 
> > +       switch (func_flag) {
> > +       case RTL8231_PIN_FUNCTION_LED:
> > +       case RTL8231_PIN_FUNCTION_PWM:
> > +               err = regmap_update_bits(ctrl->map, desc->reg,
> > function_mask, ~gpio_function);
> > +               break;
> > +       case RTL8231_PIN_FUNCTION_GPIO:
> > +               err = regmap_update_bits(ctrl->map, desc->reg,
> > function_mask, gpio_function);
> > +               break;
> > +       default:
> > +               return -EINVAL;
> > +       }
> > +
> > +       return err;
> > +}
> 

I've reworked this whole section a bit. since a pin is either (only) GPIO, or
some alternative function, this could be done with a simpler if/else.

> 
> > +static const struct pinmux_ops rtl8231_pinmux_ops = {
> > +       .set_mux = rtl8231_set_mux,
> > +       .get_functions_count = rtl8231_get_functions_count,
> > +       .get_function_name = rtl8231_get_function_name,
> > +       .get_function_groups = rtl8231_get_function_groups,
> > +       .gpio_request_enable = rtl8231_gpio_request_enable,
> 
> > +       .strict = true
> 
> Leave comma for non-terminator entries.
> 
> > +};
> > +
> > +
> 
> One blank line is enough.
> 
> ...
> 
> > +static int rtl8231_pinctrl_init_functions(struct device *dev, struct
> > rtl8231_pin_ctrl *ctrl)
> > +{
> > +       struct rtl8231_function *function;
> > +       const char **group_name;
> > +       unsigned int f_idx;
> > +       unsigned int pin;
> > +
> > +       ctrl->nfunctions = ARRAY_SIZE(rtl8231_pin_function_names);
> > +       ctrl->functions = devm_kcalloc(dev, ctrl->nfunctions, sizeof(*ctrl-
> > >functions), GFP_KERNEL);
> > +       if (IS_ERR(ctrl->functions)) {
> 
> Wrong.

I was somehow thinking that this would either return an error value or a valid
point. Don't know where I got that, but should be fixed here and for the other
kallocs.

Best,
Sander



^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 7/7] leds: Add support for RTL8231 LED scan matrix
  2021-05-17 22:00     ` Andy Shevchenko
@ 2021-05-23 21:53       ` Sander Vanheule
  0 siblings, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-23 21:53 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List

Hi Andy,

Also here I've tried to address your remarks for v3, some extra details below.

On Tue, 2021-05-18 at 01:00 +0300, Andy Shevchenko wrote:
> On Mon, May 17, 2021 at 10:28 PM Sander Vanheule <sander@svanheule.net> wrote:
> > +static unsigned int rtl8231_led_current_interval(struct rtl8231_led *pled)
> > +{
> > +       unsigned int mode;
> 
> > +       unsigned int i = 0;
> 
> This...
> 
> > +       if (regmap_field_read(pled->reg_field, &mode))
> > +               return 0;
> > +
> > +       while (i < pled->modes->num_toggle_rates && mode != pled->modes-
> > >toggle_rates[i].mode)
> > +               i++;
> 
> ...and this will be better as a for-loop.
> 
> > +       if (i < pled->modes->num_toggle_rates)
> > +               return pled->modes->toggle_rates[i].interval;
> 
> > +       else
> 
> Redundant.
> 
> > +               return 0;
> > +}

Shrunk down to "for (...) if (...) return ...;" in v3.


> 
> > +               interval = 500;
> 
> interval_ms

Good suggestion, thanks. Don't need those comments in the code then.


> 
> > +       u32 addr[2];
> > +       int err;
> > +
> 
> > +       if (!fwnode_property_count_u32(fwnode, "reg"))
> 
> err = fwnode_property_count_u32(...);
> if (err < 0)
>   return err;
> if (err == 0)
>   return -ENODEV;
> 
> > +               return -ENODEV;
> > +
> > +       err = fwnode_property_read_u32_array(fwnode, "reg", addr,
> > ARRAY_SIZE(addr));
> 
> If count returns 1? What's the point of counting if you always want two?

If count returns 1, or more than 2, that's an error. But this check was missing
in v2, so I added it in v3.


> 
> > +       if (!device_property_match_string(dev, "realtek,led-scan-mode",
> > "single-color")) {
> 
> It seems that device_property_match_string() and accompanying
> functions have wrong description of returned codes, i.e. it returns
> the index of the matched string. It's possible that some APIs are
> broken (but I believe that the former is the case).
> 
> That said, I think the proper comparison should be >= 0.

Thanks, fixed.


Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 2/7] gpio: regmap: Add configurable dir/value order
  2021-05-18  1:40     ` Andrew Lunn
  2021-05-18 11:39       ` Sander Vanheule
@ 2021-05-23 22:21       ` Sander Vanheule
  1 sibling, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-23 22:21 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio, Andy Shevchenko, linux-kernel

Hi Adrew,

On Tue, 2021-05-18 at 03:40 +0200, Andrew Lunn wrote:
> On Mon, May 17, 2021 at 09:28:04PM +0200, Sander Vanheule wrote:
> > GPIO chips may not support setting the output value when a pin is
> > configured as an input
> 
> Could you describe what happens with the hardware you are playing
> with. Not being able to do this means you will get glitches when
> enabling the output so you should not use these GPIOs with bit banging
> busses like i2c.

As I reported earlier, using value-before-direction breaks the GPIO driven LEDs
on one of my devices.

I've tried to use another device to test if I could reproduce this. Using the
gpioset and gpioget utilities, I can't seem to reproduce this however. Whether I
enable the (new) quirk or not, doesn't seem to make any difference.

The documentation we have on this chip is quite scarce, so I'm unaware of
different chip revisions, or how I would distinguish between revisions. As far
as I can see, Realtek's code (present in the GPL archives we got from some
vendors) set the pin direction before setting the value.

For now, I've made the implementation so that the quirk is always applied. Like
the behaviour that is implicit in the origal code. If prefered, I could also
supply a separate devicetree compatible or extra devictree flag.

Regarding bit banged I2C, glitches may not actually be an issue. If a pull-up
resistor is used for HIGH values, and an open drain for LOW values, the GPIO pin
doesn't actually have to change value, only direction (IN for HIGH, OUT for
LOW). A configuration like this would perhaps glitch once on the initial OUT-LOW
configuration. Like I mentioned, bit banged I2C is frequently used in ethernet
switches with these chips to talk to SFP modules.


Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-11 12:25 [PATCH 0/5] RTL8231 GPIO expander support Sander Vanheule
                   ` (6 preceding siblings ...)
  2021-05-17 19:28 ` [PATCH v2 0/7] " Sander Vanheule
@ 2021-05-23 22:33 ` Sander Vanheule
  2021-05-23 22:33   ` [PATCH v3 1/6] gpio: regmap: Add quirk for output data register Sander Vanheule
                     ` (6 more replies)
  2021-06-03 10:00 ` [PATCH v4 0/5] " Sander Vanheule
  8 siblings, 7 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-23 22:33 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio
  Cc: Andrew Lunn, Andy Shevchenko, linux-kernel, Sander Vanheule

The RTL8231 GPIO and LED expander can be configured for use as an MDIO or SMI
bus device. Currently only the MDIO mode is supported, although SMI mode
support should be fairly straightforward, once an SMI bus driver is available.

Provided features by the RTL8231:
  - Up to 37 GPIOs
    - Configurable drive strength: 8mA or 4mA (currently unsupported)
    - Input debouncing on high GPIOs (currently unsupported)
  - Up to 88 LEDs in multiple scan matrix groups
    - On, off, or one of six toggling intervals
    - "single-color mode": 2×36 single color LEDs + 8 bi-color LEDs
    - "bi-color mode": (12 + 2×6) bi-color LEDs + 24 single color LEDs
  - Up to one PWM output (currently unsupported)
    - Fixed duty cycle, 8 selectable frequencies (1.2kHz - 4.8kHz)

Register access is provided through a new MDIO regmap provider. The GPIO
controller uses gpio-regmap, although a patch is required to support a
limitation of the chip.

Changes since v2:
  - MDIO regmap support was merged, so patch is dropped here
  - Implement feedback for DT bindings
  - Use correct module names in Kconfigs
  - Fix k*alloc return value checks
  - Introduce GPIO regmap quirks to set output direction first
  - pinctrl: Use static pin descriptions for pin controller
  - pinctrl: Fix gpio consumer resource leak
  - mfd: Replace CONFIG_PM-ifdef'ery
  - leds: Rename interval to interval_ms

Changes since v1:
  - Reintroduce MDIO regmap, with fixed Kconfig dependencies
  - Add configurable dir/value order for gpio-regmap direction_out call
  - Drop allocations for regmap fields that are used only on init
  - Move some definitions to MFD header
  - Add PM ops to replace driver remove for MFD
  - Change pinctrl driver to (modified) gpio-regmap
  - Change leds driver to use fwnode

Changes since RFC:
  - Dropped MDIO regmap interface. I was unable to resolve the Kconfig
    dependency issue, so have reverted to using regmap_config.reg_read/write.
  - Added pinctrl support
  - Added LED support
  - Changed root device to MFD, with pinctrl and leds child devices. Root
    device is now an mdio_device driver.

Sander Vanheule (6):
  gpio: regmap: Add quirk for output data register
  dt-bindings: leds: Binding for RTL8231 scan matrix
  dt-bindings: mfd: Binding for RTL8231
  mfd: Add RTL8231 core device
  pinctrl: Add RTL8231 pin control and GPIO support
  leds: Add support for RTL8231 LED scan matrix

 .../bindings/leds/realtek,rtl8231-leds.yaml   | 166 ++++++++
 .../bindings/mfd/realtek,rtl8231.yaml         | 190 +++++++++
 drivers/gpio/gpio-regmap.c                    |  15 +-
 drivers/leds/Kconfig                          |  10 +
 drivers/leds/Makefile                         |   1 +
 drivers/leds/leds-rtl8231.c                   | 291 +++++++++++++
 drivers/mfd/Kconfig                           |   9 +
 drivers/mfd/Makefile                          |   1 +
 drivers/mfd/rtl8231.c                         | 143 +++++++
 drivers/pinctrl/Kconfig                       |  11 +
 drivers/pinctrl/Makefile                      |   1 +
 drivers/pinctrl/pinctrl-rtl8231.c             | 398 ++++++++++++++++++
 include/linux/gpio/regmap.h                   |  13 +
 include/linux/mfd/rtl8231.h                   |  57 +++
 14 files changed, 1304 insertions(+), 2 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
 create mode 100644 Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
 create mode 100644 drivers/leds/leds-rtl8231.c
 create mode 100644 drivers/mfd/rtl8231.c
 create mode 100644 drivers/pinctrl/pinctrl-rtl8231.c
 create mode 100644 include/linux/mfd/rtl8231.h

-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v3 1/6] gpio: regmap: Add quirk for output data register
  2021-05-23 22:33 ` [PATCH v3 0/6] " Sander Vanheule
@ 2021-05-23 22:33   ` Sander Vanheule
  2021-05-28  6:40     ` Michael Walle
  2021-05-31  7:25     ` Bartosz Golaszewski
  2021-05-23 22:34   ` [PATCH v3 2/6] dt-bindings: leds: Binding for RTL8231 scan matrix Sander Vanheule
                     ` (5 subsequent siblings)
  6 siblings, 2 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-23 22:33 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio
  Cc: Andrew Lunn, Andy Shevchenko, linux-kernel, Sander Vanheule

GPIO chips may not support setting the output value when a pin is
configured as an input, although the current implementation assumes this
is always possible.

Add support for setting pin direction before value. The order defaults
to setting the value first, but this can be reversed by setting the
GPIO_REGMAP_QUIRK_SET_DIRECTION_FIRST flag in regmap_config.quirks.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 drivers/gpio/gpio-regmap.c  | 15 +++++++++++++--
 include/linux/gpio/regmap.h | 13 +++++++++++++
 2 files changed, 26 insertions(+), 2 deletions(-)

diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c
index 134cedf151a7..95553734e169 100644
--- a/drivers/gpio/gpio-regmap.c
+++ b/drivers/gpio/gpio-regmap.c
@@ -15,6 +15,7 @@ struct gpio_regmap {
 	struct device *parent;
 	struct regmap *regmap;
 	struct gpio_chip gpio_chip;
+	unsigned int quirks;
 
 	int reg_stride;
 	int ngpio_per_reg;
@@ -173,9 +174,18 @@ static int gpio_regmap_direction_input(struct gpio_chip *chip,
 static int gpio_regmap_direction_output(struct gpio_chip *chip,
 					unsigned int offset, int value)
 {
-	gpio_regmap_set(chip, offset, value);
+	struct gpio_regmap *gpio = gpiochip_get_data(chip);
+	int ret;
+
+	if (gpio->quirks & GPIO_REGMAP_QUIRK_SET_DIRECTION_FIRST) {
+		ret = gpio_regmap_set_direction(chip, offset, true);
+		gpio_regmap_set(chip, offset, value);
+	} else {
+		gpio_regmap_set(chip, offset, value);
+		ret = gpio_regmap_set_direction(chip, offset, true);
+	}
 
-	return gpio_regmap_set_direction(chip, offset, true);
+	return ret;
 }
 
 void gpio_regmap_set_drvdata(struct gpio_regmap *gpio, void *data)
@@ -227,6 +237,7 @@ struct gpio_regmap *gpio_regmap_register(const struct gpio_regmap_config *config
 
 	gpio->parent = config->parent;
 	gpio->regmap = config->regmap;
+	gpio->quirks = config->quirks;
 	gpio->ngpio_per_reg = config->ngpio_per_reg;
 	gpio->reg_stride = config->reg_stride;
 	gpio->reg_mask_xlate = config->reg_mask_xlate;
diff --git a/include/linux/gpio/regmap.h b/include/linux/gpio/regmap.h
index 334dd928042b..cb609489903e 100644
--- a/include/linux/gpio/regmap.h
+++ b/include/linux/gpio/regmap.h
@@ -12,6 +12,17 @@ struct regmap;
 #define GPIO_REGMAP_ADDR_ZERO ((unsigned int)(-1))
 #define GPIO_REGMAP_ADDR(addr) ((addr) ? : GPIO_REGMAP_ADDR_ZERO)
 
+enum gpio_regmap_quirk {
+	/*
+	 * For hardware where the pin output value cannot be set while the pin
+	 * is configured as an input. Resolve by setting the direction to
+	 * output first, and the new value second. Because the previous output
+	 * value is used immediately after the direction change, this may result
+	 * in glitches.
+	 */
+	GPIO_REGMAP_QUIRK_SET_DIRECTION_FIRST = BIT(0),
+};
+
 /**
  * struct gpio_regmap_config - Description of a generic regmap gpio_chip.
  * @parent:		The parent device
@@ -31,6 +42,7 @@ struct regmap;
  * @reg_stride:		(Optional) May be set if the registers (of the
  *			same type, dat, set, etc) are not consecutive.
  * @ngpio_per_reg:	Number of GPIOs per register
+ * @quirks:		Flags indicating GPIO chip hardware issues
  * @irq_domain:		(Optional) IRQ domain if the controller is
  *			interrupt-capable
  * @reg_mask_xlate:     (Optional) Translates base address and GPIO
@@ -73,6 +85,7 @@ struct gpio_regmap_config {
 	unsigned int reg_dir_out_base;
 	int reg_stride;
 	int ngpio_per_reg;
+	unsigned int quirks;
 	struct irq_domain *irq_domain;
 
 	int (*reg_mask_xlate)(struct gpio_regmap *gpio, unsigned int base,
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v3 2/6] dt-bindings: leds: Binding for RTL8231 scan matrix
  2021-05-23 22:33 ` [PATCH v3 0/6] " Sander Vanheule
  2021-05-23 22:33   ` [PATCH v3 1/6] gpio: regmap: Add quirk for output data register Sander Vanheule
@ 2021-05-23 22:34   ` Sander Vanheule
  2021-06-02 18:58     ` Rob Herring
  2021-05-23 22:34   ` [PATCH v3 3/6] dt-bindings: mfd: Binding for RTL8231 Sander Vanheule
                     ` (4 subsequent siblings)
  6 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-23 22:34 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio
  Cc: Andrew Lunn, Andy Shevchenko, linux-kernel, Sander Vanheule

Add a binding description for the Realtek RTL8231's LED support, which
consists of up to 88 LEDs arranged in a number of scanning matrices.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 .../bindings/leds/realtek,rtl8231-leds.yaml   | 166 ++++++++++++++++++
 1 file changed, 166 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml

diff --git a/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml b/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
new file mode 100644
index 000000000000..560249cd7e8c
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
@@ -0,0 +1,166 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/realtek,rtl8231-leds.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Realtek RTL8231 LED scan matrix.
+
+maintainers:
+  - Sander Vanheule <sander@svanheule.net>
+
+description: |
+  The RTL8231 has support for driving a number of LED matrices, by scanning
+  over the LEDs pins, alternatingly lighting different columns and/or rows.
+
+  This functionality is available on an RTL8231, when it is configured for use
+  as an MDIO device, or SMI device.
+
+  In single color scan mode, 88 LEDs are supported. These are grouped into
+  three output matrices:
+    - Group A of 6×6 single color LEDs. Rows and columns are driven by GPIO
+      pins 0-11.
+               L0[n]    L1[n]    L2[n]    L0[n+6]  L1[n+6]  L2[n+6]
+                |        |        |        |        |        |
+       P0/P6  --<--------<--------<--------<--------<--------< (3)
+                |        |        |        |        |        |
+       P1/P7  --<--------<--------<--------<--------<--------< (4)
+                |        |        |        |        |        |
+       P2/P8  --<--------<--------<--------<--------<--------< (5)
+                |        |        |        |        |        |
+       P3/P9  --<--------<--------<--------<--------<--------< (6)
+                |        |        |        |        |        |
+       P4/P10 --<--------<--------<--------<--------<--------< (7)
+                |        |        |        |        |        |
+       P5/P11 --<--------<--------<--------<--------<--------< (8)
+               (0)      (1)      (2)      (9)     (10)     (11)
+    - Group B of 6×6 single color LEDs. Rows and columns are driven by GPIO
+      pins 12-23.
+               L0[n]    L1[n]    L2[n]    L0[n+6]  L1[n+6]  L2[n+6]
+                |        |        |        |        |        |
+      P12/P18 --<--------<--------<--------<--------<--------< (15)
+                |        |        |        |        |        |
+      P13/P19 --<--------<--------<--------<--------<--------< (16)
+                |        |        |        |        |        |
+      P14/P20 --<--------<--------<--------<--------<--------< (17)
+                |        |        |        |        |        |
+      P15/P21 --<--------<--------<--------<--------<--------< (18)
+                |        |        |        |        |        |
+      P16/P22 --<--------<--------<--------<--------<--------< (19)
+                |        |        |        |        |        |
+      P17/P23 --<--------<--------<--------<--------<--------< (20)
+              (12)     (13)     (14)    (21)      (22)     (23)
+    - Group C of 8 pairs of anti-parallel (or bi-color) LEDs. LED selection is
+      provided by GPIO pins 24-27 and 29-32, polarity selection by GPIO 28.
+               P24     P25  ...  P30     P31
+                |       |         |       |
+      LED POL --X-------X---/\/---X-------X (28)
+              (24)    (25)  ... (31)    (32)
+
+  In bi-color scan mode, 72 LEDs are supported. These are grouped into four
+  output matrices:
+    - Group A of 12 pairs of anti-parallel LEDs. LED selection is provided
+      by GPIO pins 0-11, polarity selection by GPIO 12.
+    - Group B of 6 pairs of anti-parallel LEDs. LED selection is provided
+      by GPIO pins 23-28, polarity selection by GPIO 21.
+    - Group C of 6 pairs of anti-parallel LEDs. LED selection is provided
+      by GPIO pins 29-34, polarity selection by GPIO 22.
+    - Group of 4×6 single color LEDs. Rows are driven by GPIO pins 15-20,
+      columns by GPIO pins 13-14 and 21-22 (shared with groups B and C).
+           L2[n]    L2[n+6]   L2[n+12]  L2[n+18]
+            |        |         |         |
+       +0 --<--------<---------<---------< (15)
+            |        |         |         |
+       +1 --<--------<---------<---------< (16)
+            |        |         |         |
+       +2 --<--------<---------<---------< (17)
+            |        |         |         |
+       +3 --<--------<---------<---------< (18)
+            |        |         |         |
+       +4 --<--------<---------<---------< (19)
+            |        |         |         |
+       +6 --<--------<---------<---------< (20)
+          (13)     (14)      (21)      (22)
+
+  This node must always be a child of a 'realtek,rtl8231' node.
+
+properties:
+  $nodename:
+    const: led-controller
+
+  compatible:
+    const: realtek,rtl8231-leds
+
+  "#address-cells":
+    const: 2
+
+  "#size-cells":
+    const: 0
+
+  realtek,led-scan-mode:
+    $ref: /schemas/types.yaml#/definitions/string
+    description: |
+      Specify the scanning mode the chip should run in. See general description
+      for how the scanning matrices are wired up.
+    enum: ["single-color", "bi-color"]
+
+patternProperties:
+  "^led@":
+    description: |
+      LEDs are addressed by their port index and led index. Ports 0-23 always
+      support three LEDs. Additionally, but only when used in single color scan
+      mode, ports 24-31 support two LEDs.
+    type: object
+
+    properties:
+      reg:
+        items:
+          - description: port index
+            maximum: 31
+          - description: led index
+            maximum: 2
+
+    allOf:
+      - $ref: ../leds/common.yaml#
+
+    required:
+      - reg
+
+required:
+  - compatible
+  - "#address-cells"
+  - "#size-cells"
+  - realtek,led-scan-mode
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/leds/common.h>
+    led-controller {
+        compatible = "realtek,rtl8231-leds";
+        #address-cells = <2>;
+        #size-cells = <0>;
+
+        realtek,led-scan-mode = "single-color";
+
+        led@0,0 {
+            reg = <0 0>;
+            color = <LED_COLOR_ID_GREEN>;
+            function = LED_FUNCTION_LAN;
+            function-enumerator = <0>;
+        };
+
+        led@0,1 {
+            reg = <0 1>;
+            color = <LED_COLOR_ID_AMBER>;
+            function = LED_FUNCTION_LAN;
+            function-enumerator = <0>;
+        };
+
+        led@0,2 {
+            reg = <0 2>;
+            color = <LED_COLOR_ID_GREEN>;
+            function = LED_FUNCTION_STATUS;
+        };
+    };
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v3 3/6] dt-bindings: mfd: Binding for RTL8231
  2021-05-23 22:33 ` [PATCH v3 0/6] " Sander Vanheule
  2021-05-23 22:33   ` [PATCH v3 1/6] gpio: regmap: Add quirk for output data register Sander Vanheule
  2021-05-23 22:34   ` [PATCH v3 2/6] dt-bindings: leds: Binding for RTL8231 scan matrix Sander Vanheule
@ 2021-05-23 22:34   ` Sander Vanheule
  2021-05-27 23:31     ` Linus Walleij
  2021-06-02 19:02     ` Rob Herring
  2021-05-23 22:34   ` [PATCH v3 4/6] mfd: Add RTL8231 core device Sander Vanheule
                     ` (3 subsequent siblings)
  6 siblings, 2 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-23 22:34 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio
  Cc: Andrew Lunn, Andy Shevchenko, linux-kernel, Sander Vanheule

Add a binding description for the Realtek RTL8231, a GPIO and LED
expander chip commonly used in ethernet switches based on a Realtek
switch SoC. These chips can be addressed via an MDIO or SMI bus, or used
as a plain 36-bit shift register.

This binding only describes the feature set provided by the MDIO/SMI
configuration, and covers the GPIO, PWM, and pin control properties. The
LED properties are defined in a separate binding.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 .../bindings/mfd/realtek,rtl8231.yaml         | 190 ++++++++++++++++++
 1 file changed, 190 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml

diff --git a/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml b/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
new file mode 100644
index 000000000000..4e2326401560
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
@@ -0,0 +1,190 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/realtek,rtl8231.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Realtek RTL8231 GPIO and LED expander.
+
+maintainers:
+  - Sander Vanheule <sander@svanheule.net>
+
+description: |
+  The RTL8231 is a GPIO and LED expander chip, providing up to 37 GPIOs, up to
+  88 LEDs, and up to one PWM output. This device is frequently used alongside
+  Realtek switch SoCs, to provide additional I/O capabilities.
+
+  To manage the RTL8231's features, its strapping pins can be used to configure
+  it in one of three modes: shift register, MDIO device, or SMI device. The
+  shift register mode does not need special support. In MDIO or SMI mode, most
+  pins can be configured as a GPIO output, LED matrix scan line/column, or as a
+  PWM output.
+
+  The GPIO, PWM, and pin control are part of the main node. LED support is
+  configured as a sub-node.
+
+properties:
+  compatible:
+    const: realtek,rtl8231
+
+  reg:
+    description: MDIO or SMI device address.
+    maxItems: 1
+
+  # GPIO support
+  gpio-controller: true
+
+  "#gpio-cells":
+    const: 2
+    description: |
+      The first cell is the pin number and the second cell is used to specify
+      the GPIO active state.
+
+  gpio-ranges:
+    description: |
+      Must reference itself, and provide a zero-based mapping for 37 pins.
+    maxItems: 1
+
+  # Pin muxing and configuration
+  drive-strength:
+    description: |
+      Common drive strength used for all GPIO output pins, must be 4mA or 8mA.
+      On reset, this value will default to 8mA.
+    enum: [4, 8]
+
+  # LED scanning matrix
+  led-controller:
+    $ref: ../leds/realtek,rtl8231-leds.yaml#
+
+  # PWM output
+  "#pwm-cells":
+    description: |
+      Twos cells with PWM index (must be 0) and PWM frequency in Hz. To use
+      the PWM output, gpio35 must be muxed to its 'pwm' function. Valid
+      frequency values for consumers are 1200, 1600, 2000, 2400, 2800, 3200,
+      4000, and 4800.
+    const: 2
+
+patternProperties:
+  "-pins$":
+    type: object
+    $ref: ../pinctrl/pinmux-node.yaml#
+
+    properties:
+      pins:
+        items:
+          enum: ["gpio0", "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6",
+                 "gpio7", "gpio8", "gpio9", "gpio10", "gpio11", "gpio12", "gpio13",
+                 "gpio14", "gpio15", "gpio16", "gpio17", "gpio18", "gpio19", "gpio20",
+                 "gpio21", "gpio22", "gpio23", "gpio24", "gpio25", "gpio26", "gpio27",
+                 "gpio28", "gpio29", "gpio30", "gpio31", "gpio32", "gpio33", "gpio34",
+                 "gpio35", "gpio36"]
+        minItems: 1
+        maxItems: 37
+      function:
+        description: |
+          Select which function to use. "gpio" is supported for all pins, "led" is supported
+          for pins 0-34, "pwm" is supported for pin 35.
+        enum: ["gpio", "led", "pwm"]
+
+    required:
+      - pins
+      - function
+
+required:
+  - compatible
+  - reg
+  - gpio-controller
+  - "#gpio-cells"
+  - gpio-ranges
+
+additionalProperties: false
+
+examples:
+  - |
+    // Minimal example
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        expander0: expander@0 {
+            compatible = "realtek,rtl8231";
+            reg = <0>;
+
+            gpio-controller;
+            #gpio-cells = <2>;
+            gpio-ranges = <&expander0 0 0 37>;
+        };
+    };
+  - |
+    // All bells and whistles included
+    #include <dt-bindings/leds/common.h>
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        expander1: expander@1 {
+            compatible = "realtek,rtl8231";
+            reg = <1>;
+
+            gpio-controller;
+            #gpio-cells = <2>;
+            gpio-ranges = <&expander1 0 0 37>;
+
+            #pwm-cells = <2>;
+
+            drive-strength = <4>;
+
+            button-pins {
+                pins = "gpio36";
+                function = "gpio";
+                input-debounce = "100000";
+            };
+
+            pwm-pins {
+                pins = "gpio35";
+                function = "pwm";
+            };
+
+            led-pins {
+                pins = "gpio0", "gpio1", "gpio3", "gpio4";
+                function = "led";
+            };
+
+            led-controller {
+                compatible = "realtek,rtl8231-leds";
+                #address-cells = <2>;
+                #size-cells = <0>;
+
+                realtek,led-scan-mode = "single-color";
+
+                led@0,0 {
+                    reg = <0 0>;
+                    color = <LED_COLOR_ID_GREEN>;
+                    function = LED_FUNCTION_LAN;
+                    function-enumerator = <0>;
+                };
+
+                led@0,1 {
+                    reg = <0 1>;
+                    color = <LED_COLOR_ID_AMBER>;
+                    function = LED_FUNCTION_LAN;
+                    function-enumerator = <0>;
+                };
+
+                led@1,0 {
+                    reg = <1 0>;
+                    color = <LED_COLOR_ID_GREEN>;
+                    function = LED_FUNCTION_LAN;
+                    function-enumerator = <1>;
+                };
+
+                led@1,1 {
+                    reg = <1 1>;
+                    color = <LED_COLOR_ID_AMBER>;
+                    function = LED_FUNCTION_LAN;
+                    function-enumerator = <1>;
+                };
+            };
+        };
+    };
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v3 4/6] mfd: Add RTL8231 core device
  2021-05-23 22:33 ` [PATCH v3 0/6] " Sander Vanheule
                     ` (2 preceding siblings ...)
  2021-05-23 22:34   ` [PATCH v3 3/6] dt-bindings: mfd: Binding for RTL8231 Sander Vanheule
@ 2021-05-23 22:34   ` Sander Vanheule
  2021-05-24  8:02     ` Andy Shevchenko
  2021-05-23 22:34   ` [PATCH v3 5/6] pinctrl: Add RTL8231 pin control and GPIO support Sander Vanheule
                     ` (2 subsequent siblings)
  6 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-23 22:34 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio
  Cc: Andrew Lunn, Andy Shevchenko, linux-kernel, Sander Vanheule,
	kernel test robot

The RTL8231 is implemented as an MDIO device, and provides a regmap
interface for register access by the core and child devices.

The chip can also be a device on an SMI bus, an I2C-like bus by Realtek.
Since kernel support for SMI is limited, and no real-world SMI
implementations have been encountered for this device, this is currently
unimplemented. The use of the regmap interface should make any future
support relatively straightforward.

After reset, all pins are muxed to GPIO inputs before the pin drivers
are enabled. This is done to prevent accidental system resets, when a
pin is connected to the parent SoC's reset line.

[missing MDIO_BUS dependency, provided via REGMAP_MDIO]
Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 drivers/mfd/Kconfig         |   9 +++
 drivers/mfd/Makefile        |   1 +
 drivers/mfd/rtl8231.c       | 143 ++++++++++++++++++++++++++++++++++++
 include/linux/mfd/rtl8231.h |  57 ++++++++++++++
 4 files changed, 210 insertions(+)
 create mode 100644 drivers/mfd/rtl8231.c
 create mode 100644 include/linux/mfd/rtl8231.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 5c7f2b100191..68f28a335b8c 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1076,6 +1076,15 @@ config MFD_RDC321X
 	  southbridge which provides access to GPIOs and Watchdog using the
 	  southbridge PCI device configuration space.
 
+config MFD_RTL8231
+	tristate "Realtek RTL8231 GPIO and LED expander"
+	select MFD_CORE
+	select REGMAP_MDIO
+	help
+	  Support for the Realtek RTL8231 GPIO and LED expander.
+	  Provides up to 37 GPIOs, 88 LEDs, and one PWM output.
+	  When built as a module, this module will be named rtl8231.
+
 config MFD_RT5033
 	tristate "Richtek RT5033 Power Management IC"
 	depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 4f6d2b8a5f76..4b27c2486ccc 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -234,6 +234,7 @@ obj-$(CONFIG_MFD_MENF21BMC)	+= menf21bmc.o
 obj-$(CONFIG_MFD_HI6421_PMIC)	+= hi6421-pmic-core.o
 obj-$(CONFIG_MFD_HI655X_PMIC)   += hi655x-pmic.o
 obj-$(CONFIG_MFD_DLN2)		+= dln2.o
+obj-$(CONFIG_MFD_RTL8231)	+= rtl8231.o
 obj-$(CONFIG_MFD_RT5033)	+= rt5033.o
 obj-$(CONFIG_MFD_SKY81452)	+= sky81452.o
 
diff --git a/drivers/mfd/rtl8231.c b/drivers/mfd/rtl8231.c
new file mode 100644
index 000000000000..559afc44a0c7
--- /dev/null
+++ b/drivers/mfd/rtl8231.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mfd/core.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/rtl8231.h>
+
+static const struct reg_field RTL8231_FIELD_LED_START = REG_FIELD(RTL8231_REG_FUNC0, 1, 1);
+
+static const struct mfd_cell rtl8231_cells[] = {
+	{
+		.name = "rtl8231-pinctrl",
+	},
+	{
+		.name = "rtl8231-leds",
+		.of_compatible = "realtek,rtl8231-leds",
+	},
+};
+
+static int rtl8231_init(struct device *dev, struct regmap *map)
+{
+	unsigned int val;
+	int err;
+
+	err = regmap_read(map, RTL8231_REG_FUNC1, &val);
+	if (err) {
+		dev_err(dev, "failed to read READY_CODE\n");
+		return err;
+	}
+
+	val = FIELD_GET(RTL8231_FUNC1_READY_CODE_MASK, val);
+	if (val != RTL8231_FUNC1_READY_CODE_VALUE) {
+		dev_err(dev, "RTL8231 not present or ready 0x%x != 0x%x\n",
+			val, RTL8231_FUNC1_READY_CODE_VALUE);
+		return -ENODEV;
+	}
+
+	/* SOFT_RESET bit self-clears when done */
+	regmap_update_bits(map, RTL8231_REG_PIN_HI_CFG,
+		RTL8231_PIN_HI_CFG_SOFT_RESET, RTL8231_PIN_HI_CFG_SOFT_RESET);
+	usleep_range(1000, 10000);
+
+	/*
+	 * Chip reset results in a pin configuration that is a mix of LED and GPIO outputs.
+	 * Select GPI functionality for all pins before enabling pin outputs.
+	 */
+	regmap_write(map, RTL8231_REG_PIN_MODE0, 0xffff);
+	regmap_write(map, RTL8231_REG_GPIO_DIR0, 0xffff);
+	regmap_write(map, RTL8231_REG_PIN_MODE1, 0xffff);
+	regmap_write(map, RTL8231_REG_GPIO_DIR1, 0xffff);
+	regmap_write(map, RTL8231_REG_PIN_HI_CFG,
+		RTL8231_PIN_HI_CFG_MODE_MASK | RTL8231_PIN_HI_CFG_DIR_MASK);
+
+	return 0;
+}
+
+static const struct regmap_config rtl8231_mdio_regmap_config = {
+	.val_bits = RTL8231_BITS_VAL,
+	.reg_bits = 5,
+	.max_register = RTL8231_REG_COUNT - 1,
+	.use_single_read = true,
+	.use_single_write = true,
+	.reg_format_endian = REGMAP_ENDIAN_BIG,
+	.val_format_endian = REGMAP_ENDIAN_BIG,
+};
+
+static int rtl8231_mdio_probe(struct mdio_device *mdiodev)
+{
+	struct device *dev = &mdiodev->dev;
+	struct regmap_field *led_start;
+	struct regmap *map;
+	int err;
+
+	map = devm_regmap_init_mdio(mdiodev, &rtl8231_mdio_regmap_config);
+	if (IS_ERR(map)) {
+		dev_err(dev, "failed to init regmap\n");
+		return PTR_ERR(map);
+	}
+
+	led_start = devm_regmap_field_alloc(dev, map, RTL8231_FIELD_LED_START);
+	if (IS_ERR(led_start))
+		return PTR_ERR(led_start);
+
+	dev_set_drvdata(dev, led_start);
+
+	mdiodev->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+	device_property_read_u32(dev, "reset-assert-delay", &mdiodev->reset_assert_delay);
+	device_property_read_u32(dev, "reset-deassert-delay", &mdiodev->reset_deassert_delay);
+
+	err = rtl8231_init(dev, map);
+	if (err)
+		return err;
+
+	/* LED_START enables power to output pins, and starts the LED engine */
+	regmap_field_write(led_start, 1);
+
+	return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, rtl8231_cells,
+		ARRAY_SIZE(rtl8231_cells), NULL, 0, NULL);
+}
+
+__maybe_unused static int rtl8231_suspend(struct device *dev)
+{
+	struct regmap_field *led_start = dev_get_drvdata(dev);
+
+	return regmap_field_write(led_start, 0);
+}
+
+__maybe_unused static int rtl8231_resume(struct device *dev)
+{
+	struct regmap_field *led_start = dev_get_drvdata(dev);
+
+	return regmap_field_write(led_start, 1);
+}
+
+static SIMPLE_DEV_PM_OPS(rtl8231_pm_ops, rtl8231_suspend, rtl8231_resume);
+
+static const struct of_device_id rtl8231_of_match[] = {
+	{ .compatible = "realtek,rtl8231" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, rtl8231_of_match);
+
+static struct mdio_driver rtl8231_mdio_driver = {
+	.mdiodrv.driver = {
+		.name = "rtl8231-expander",
+		.of_match_table	= rtl8231_of_match,
+		.pm = pm_ptr(&rtl8231_pm_ops),
+	},
+	.probe = rtl8231_mdio_probe,
+};
+mdio_module_driver(rtl8231_mdio_driver);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek RTL8231 GPIO and LED expander");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/rtl8231.h b/include/linux/mfd/rtl8231.h
new file mode 100644
index 000000000000..7f1df92a9d36
--- /dev/null
+++ b/include/linux/mfd/rtl8231.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Register definitions the RTL8231 GPIO and LED expander chip
+ */
+
+#ifndef __LINUX_MFD_RTL8231_H
+#define __LINUX_MFD_RTL8231_H
+
+#include <linux/bits.h>
+
+#define RTL8231_BITS_VAL		16
+
+/* Chip control */
+#define RTL8231_REG_FUNC0		0x00
+#define RTL8231_FUNC0_SCAN_MODE		BIT(0)
+#define RTL8231_FUNC0_SCAN_SINGLE	0
+#define RTL8231_FUNC0_SCAN_BICOLOR	BIT(0)
+
+#define RTL8231_REG_FUNC1		0x01
+#define RTL8231_FUNC1_READY_CODE_VALUE	0x37
+#define RTL8231_FUNC1_READY_CODE_MASK	GENMASK(9, 4)
+
+/* Pin control */
+#define RTL8231_REG_PIN_MODE0		0x02
+#define RTL8231_REG_PIN_MODE1		0x03
+
+#define RTL8231_PIN_MODE_LED		0
+#define RTL8231_PIN_MODE_GPIO		1
+
+/* Pin high config: pin and GPIO control for pins 32-26 */
+#define RTL8231_REG_PIN_HI_CFG		0x04
+#define RTL8231_PIN_HI_CFG_MODE_MASK	GENMASK(4, 0)
+#define RTL8231_PIN_HI_CFG_DIR_MASK	GENMASK(9, 5)
+#define RTL8231_PIN_HI_CFG_SOFT_RESET	BIT(15)
+
+/* GPIO control registers */
+#define RTL8231_REG_GPIO_DIR0		0x05
+#define RTL8231_REG_GPIO_DIR1		0x06
+#define RTL8231_REG_GPIO_INVERT0	0x07
+#define RTL8231_REG_GPIO_INVERT1	0x08
+
+#define RTL8231_GPIO_DIR_IN		1
+#define RTL8231_GPIO_DIR_OUT		0
+
+/* GPIO data registers */
+#define RTL8231_REG_GPIO_DATA0		0x1c
+#define RTL8231_REG_GPIO_DATA1		0x1d
+#define RTL8231_REG_GPIO_DATA2		0x1e
+
+/* LED control base registers */
+#define RTL8231_REG_LED0_BASE		0x09
+#define RTL8231_REG_LED1_BASE		0x10
+#define RTL8231_REG_LED2_BASE		0x17
+
+#define RTL8231_REG_COUNT		0x1f
+
+#endif /* __LINUX_MFD_RTL8231_H */
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v3 5/6] pinctrl: Add RTL8231 pin control and GPIO support
  2021-05-23 22:33 ` [PATCH v3 0/6] " Sander Vanheule
                     ` (3 preceding siblings ...)
  2021-05-23 22:34   ` [PATCH v3 4/6] mfd: Add RTL8231 core device Sander Vanheule
@ 2021-05-23 22:34   ` Sander Vanheule
  2021-05-28  6:29     ` Michael Walle
  2021-05-23 22:34   ` [PATCH v3 6/6] leds: Add support for RTL8231 LED scan matrix Sander Vanheule
  2021-05-24  1:10   ` [PATCH v3 0/6] RTL8231 GPIO expander support Andrew Lunn
  6 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-23 22:34 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio
  Cc: Andrew Lunn, Andy Shevchenko, linux-kernel, Sander Vanheule

This driver implements the GPIO and pin muxing features provided by the
RTL8231. The device should be instantiated as an MFD child, where the
parent device has already configured the regmap used for register
access.

Although described in the bindings, pin debouncing and drive strength
selection are currently not implemented. Debouncing is only available
for the six highest GPIOs, and must be emulated when other pins are used
for (button) inputs anyway.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 drivers/pinctrl/Kconfig           |  11 +
 drivers/pinctrl/Makefile          |   1 +
 drivers/pinctrl/pinctrl-rtl8231.c | 398 ++++++++++++++++++++++++++++++
 3 files changed, 410 insertions(+)
 create mode 100644 drivers/pinctrl/pinctrl-rtl8231.c

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index c2c7e7963ed0..e47d6f3550df 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -221,6 +221,17 @@ config PINCTRL_ROCKCHIP
 	help
           This support pinctrl and gpio driver for Rockchip SoCs.
 
+config PINCTRL_RTL8231
+	tristate "Realtek RTL8231 GPIO expander's pin controller"
+	depends on MFD_RTL8231
+	default MFD_RTL8231
+	select GENERIC_PINCONF
+	select GPIO_REGMAP
+	select PINMUX
+	help
+	  Support for RTL8231 expander's GPIOs and pin controller.
+	  When built as a module, the module will be called pinctrl-rtl8231.
+
 config PINCTRL_SINGLE
 	tristate "One-register-per-pin type device tree based pinctrl driver"
 	depends on OF
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 5ef5334a797f..239603efb317 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_PINCTRL_PALMAS)	+= pinctrl-palmas.o
 obj-$(CONFIG_PINCTRL_PIC32)	+= pinctrl-pic32.o
 obj-$(CONFIG_PINCTRL_PISTACHIO)	+= pinctrl-pistachio.o
 obj-$(CONFIG_PINCTRL_ROCKCHIP)	+= pinctrl-rockchip.o
+obj-$(CONFIG_PINCTRL_RTL8231)	+= pinctrl-rtl8231.o
 obj-$(CONFIG_PINCTRL_SINGLE)	+= pinctrl-single.o
 obj-$(CONFIG_PINCTRL_SX150X)	+= pinctrl-sx150x.o
 obj-$(CONFIG_ARCH_TEGRA)	+= tegra/
diff --git a/drivers/pinctrl/pinctrl-rtl8231.c b/drivers/pinctrl/pinctrl-rtl8231.c
new file mode 100644
index 000000000000..d0eea42a4bd3
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-rtl8231.c
@@ -0,0 +1,398 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bitfield.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/regmap.h>
+#include <linux/module.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/rtl8231.h>
+
+#define RTL8231_NUM_GPIOS	37
+
+struct rtl8231_function {
+	const char *name;
+	unsigned int ngroups;
+	const char **groups;
+};
+
+struct rtl8231_pin_ctrl {
+	struct pinctrl_desc pctl_desc;
+	unsigned int nfunctions;
+	struct rtl8231_function *functions;
+	struct regmap *map;
+};
+
+/*
+ * Pin controller functionality
+ */
+static const char * const rtl8231_pin_function_names[] = {
+	"gpio",
+	"led",
+	"pwm",
+};
+
+enum rtl8231_pin_function {
+	RTL8231_PIN_FUNCTION_GPIO = BIT(0),
+	RTL8231_PIN_FUNCTION_LED = BIT(1),
+	RTL8231_PIN_FUNCTION_PWM = BIT(2),
+};
+
+struct rtl8231_pin_desc {
+	const enum rtl8231_pin_function functions;
+	const u8 reg;
+	const u8 offset;
+	const u8 gpio_function_value;
+};
+
+#define RTL8231_PIN_DESC(_num, _func, _reg, _fld, _val)		\
+	[_num] = {						\
+		.functions = RTL8231_PIN_FUNCTION_GPIO | _func,	\
+		.reg = _reg,					\
+		.offset = _fld,					\
+		.gpio_function_value = _val,			\
+	}
+#define RTL8231_GPIO_PIN_DESC(_num, _reg, _fld)			\
+	RTL8231_PIN_DESC(_num, 0, _reg, _fld, RTL8231_PIN_MODE_GPIO)
+#define RTL8231_LED_PIN_DESC(_num, _reg, _fld)			\
+	RTL8231_PIN_DESC(_num, RTL8231_PIN_FUNCTION_LED, _reg, _fld, RTL8231_PIN_MODE_GPIO)
+#define RTL8231_PWM_PIN_DESC(_num, _reg, _fld)			\
+	RTL8231_PIN_DESC(_num, RTL8231_PIN_FUNCTION_PWM, _reg, _fld, 0)
+
+/*
+ * All pins have a GPIO/LED mux bit, but the bits for pins 35/36 are read-only. Use this bit
+ * for the GPIO-only pin instead of a placeholder, so the rest of the logic can stay generic.
+ */
+static struct rtl8231_pin_desc rtl8231_pin_data[RTL8231_NUM_GPIOS] = {
+	RTL8231_LED_PIN_DESC(0, RTL8231_REG_PIN_MODE0, 0),
+	RTL8231_LED_PIN_DESC(1, RTL8231_REG_PIN_MODE0, 1),
+	RTL8231_LED_PIN_DESC(2, RTL8231_REG_PIN_MODE0, 2),
+	RTL8231_LED_PIN_DESC(3, RTL8231_REG_PIN_MODE0, 3),
+	RTL8231_LED_PIN_DESC(4, RTL8231_REG_PIN_MODE0, 4),
+	RTL8231_LED_PIN_DESC(5, RTL8231_REG_PIN_MODE0, 5),
+	RTL8231_LED_PIN_DESC(6, RTL8231_REG_PIN_MODE0, 6),
+	RTL8231_LED_PIN_DESC(7, RTL8231_REG_PIN_MODE0, 7),
+	RTL8231_LED_PIN_DESC(8, RTL8231_REG_PIN_MODE0, 8),
+	RTL8231_LED_PIN_DESC(9, RTL8231_REG_PIN_MODE0, 9),
+	RTL8231_LED_PIN_DESC(10, RTL8231_REG_PIN_MODE0, 10),
+	RTL8231_LED_PIN_DESC(11, RTL8231_REG_PIN_MODE0, 11),
+	RTL8231_LED_PIN_DESC(12, RTL8231_REG_PIN_MODE0, 12),
+	RTL8231_LED_PIN_DESC(13, RTL8231_REG_PIN_MODE0, 13),
+	RTL8231_LED_PIN_DESC(14, RTL8231_REG_PIN_MODE0, 14),
+	RTL8231_LED_PIN_DESC(15, RTL8231_REG_PIN_MODE0, 15),
+	RTL8231_LED_PIN_DESC(16, RTL8231_REG_PIN_MODE1, 0),
+	RTL8231_LED_PIN_DESC(17, RTL8231_REG_PIN_MODE1, 1),
+	RTL8231_LED_PIN_DESC(18, RTL8231_REG_PIN_MODE1, 2),
+	RTL8231_LED_PIN_DESC(19, RTL8231_REG_PIN_MODE1, 3),
+	RTL8231_LED_PIN_DESC(20, RTL8231_REG_PIN_MODE1, 4),
+	RTL8231_LED_PIN_DESC(21, RTL8231_REG_PIN_MODE1, 5),
+	RTL8231_LED_PIN_DESC(22, RTL8231_REG_PIN_MODE1, 6),
+	RTL8231_LED_PIN_DESC(23, RTL8231_REG_PIN_MODE1, 7),
+	RTL8231_LED_PIN_DESC(24, RTL8231_REG_PIN_MODE1, 8),
+	RTL8231_LED_PIN_DESC(25, RTL8231_REG_PIN_MODE1, 9),
+	RTL8231_LED_PIN_DESC(26, RTL8231_REG_PIN_MODE1, 10),
+	RTL8231_LED_PIN_DESC(27, RTL8231_REG_PIN_MODE1, 11),
+	RTL8231_LED_PIN_DESC(28, RTL8231_REG_PIN_MODE1, 12),
+	RTL8231_LED_PIN_DESC(29, RTL8231_REG_PIN_MODE1, 13),
+	RTL8231_LED_PIN_DESC(30, RTL8231_REG_PIN_MODE1, 14),
+	RTL8231_LED_PIN_DESC(31, RTL8231_REG_PIN_MODE1, 15),
+	RTL8231_LED_PIN_DESC(32, RTL8231_REG_PIN_HI_CFG, 0),
+	RTL8231_LED_PIN_DESC(33, RTL8231_REG_PIN_HI_CFG, 1),
+	RTL8231_LED_PIN_DESC(34, RTL8231_REG_PIN_HI_CFG, 2),
+	RTL8231_PWM_PIN_DESC(35, RTL8231_REG_FUNC1, 3),
+	RTL8231_GPIO_PIN_DESC(36, RTL8231_REG_PIN_HI_CFG, 4),
+};
+
+#define RTL8231_PIN(_num)				\
+	{						\
+		.number = _num,				\
+		.name = "gpio" #_num,			\
+		.drv_data = &rtl8231_pin_data[_num]	\
+	}
+
+static const struct pinctrl_pin_desc rtl8231_pins[RTL8231_NUM_GPIOS] = {
+	RTL8231_PIN(0),
+	RTL8231_PIN(1),
+	RTL8231_PIN(2),
+	RTL8231_PIN(3),
+	RTL8231_PIN(4),
+	RTL8231_PIN(5),
+	RTL8231_PIN(6),
+	RTL8231_PIN(7),
+	RTL8231_PIN(8),
+	RTL8231_PIN(9),
+	RTL8231_PIN(10),
+	RTL8231_PIN(11),
+	RTL8231_PIN(12),
+	RTL8231_PIN(13),
+	RTL8231_PIN(14),
+	RTL8231_PIN(15),
+	RTL8231_PIN(16),
+	RTL8231_PIN(17),
+	RTL8231_PIN(18),
+	RTL8231_PIN(19),
+	RTL8231_PIN(20),
+	RTL8231_PIN(21),
+	RTL8231_PIN(22),
+	RTL8231_PIN(23),
+	RTL8231_PIN(24),
+	RTL8231_PIN(25),
+	RTL8231_PIN(26),
+	RTL8231_PIN(27),
+	RTL8231_PIN(28),
+	RTL8231_PIN(29),
+	RTL8231_PIN(30),
+	RTL8231_PIN(31),
+	RTL8231_PIN(32),
+	RTL8231_PIN(33),
+	RTL8231_PIN(34),
+	RTL8231_PIN(35),
+	RTL8231_PIN(36),
+};
+
+static int rtl8231_get_groups_count(struct pinctrl_dev *pctldev)
+{
+	return ARRAY_SIZE(rtl8231_pins);
+}
+
+static const char *rtl8231_get_group_name(struct pinctrl_dev *pctldev, unsigned int selector)
+{
+	return rtl8231_pins[selector].name;
+}
+
+static int rtl8231_get_group_pins(struct pinctrl_dev *pctldev, unsigned int selector,
+	const unsigned int **pins, unsigned int *num_pins)
+{
+	if (selector >= ARRAY_SIZE(rtl8231_pins))
+		return -EINVAL;
+
+	*pins = &rtl8231_pins[selector].number;
+	*num_pins = 1;
+
+	return 0;
+}
+
+static const struct pinctrl_ops rtl8231_pinctrl_ops = {
+	.get_groups_count = rtl8231_get_groups_count,
+	.get_group_name = rtl8231_get_group_name,
+	.get_group_pins = rtl8231_get_group_pins,
+	.dt_node_to_map = pinconf_generic_dt_node_to_map_all,
+	.dt_free_map = pinconf_generic_dt_free_map,
+};
+
+static int rtl8231_get_functions_count(struct pinctrl_dev *pctldev)
+{
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+
+	return ctrl->nfunctions;
+}
+
+static const char *rtl8231_get_function_name(struct pinctrl_dev *pctldev, unsigned int selector)
+{
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+
+	return ctrl->functions[selector].name;
+}
+
+static int rtl8231_get_function_groups(struct pinctrl_dev *pctldev, unsigned int selector,
+	const char * const **groups, unsigned int *num_groups)
+{
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+
+	*groups = ctrl->functions[selector].groups;
+	*num_groups = ctrl->functions[selector].ngroups;
+
+	return 0;
+}
+
+static int rtl8231_set_mux(struct pinctrl_dev *pctldev, unsigned int func_selector,
+	unsigned int group_selector)
+{
+	const struct rtl8231_pin_desc *desc =
+		(struct rtl8231_pin_desc *) &rtl8231_pins[group_selector].drv_data;
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+	unsigned int func_flag = BIT(func_selector);
+	unsigned int function_mask;
+	unsigned int gpio_function;
+
+	if (!(desc->functions & func_flag))
+		return -EINVAL;
+
+	function_mask = BIT(desc->offset);
+	gpio_function = desc->gpio_function_value << desc->offset;
+
+	if (func_flag == RTL8231_PIN_FUNCTION_GPIO)
+		return regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
+	else
+		return regmap_update_bits(ctrl->map, desc->reg, function_mask, ~gpio_function);
+}
+
+static int rtl8231_gpio_request_enable(struct pinctrl_dev *pctldev,
+	struct pinctrl_gpio_range *range, unsigned int offset)
+{
+	struct rtl8231_pin_desc *desc =
+		(struct rtl8231_pin_desc *) &rtl8231_pins[offset].drv_data;
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+	unsigned int function_mask;
+	unsigned int gpio_function;
+
+	function_mask = BIT(desc->offset);
+	gpio_function = desc->gpio_function_value << desc->offset;
+
+	return regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
+}
+
+static const struct pinmux_ops rtl8231_pinmux_ops = {
+	.set_mux = rtl8231_set_mux,
+	.get_functions_count = rtl8231_get_functions_count,
+	.get_function_name = rtl8231_get_function_name,
+	.get_function_groups = rtl8231_get_function_groups,
+	.gpio_request_enable = rtl8231_gpio_request_enable,
+	.strict = true,
+};
+
+static int rtl8231_pinctrl_init_functions(struct device *dev, struct rtl8231_pin_ctrl *ctrl)
+{
+	struct rtl8231_function *function;
+	const char **group_name;
+	unsigned int f_idx;
+	unsigned int pin;
+
+	ctrl->nfunctions = ARRAY_SIZE(rtl8231_pin_function_names);
+	ctrl->functions = devm_kcalloc(dev, ctrl->nfunctions, sizeof(*ctrl->functions), GFP_KERNEL);
+	if (!ctrl->functions) {
+		dev_err(dev, "failed to allocate pin function descriptors\n");
+		return -ENOMEM;
+	}
+
+	for (f_idx = 0; f_idx < ctrl->nfunctions; f_idx++) {
+		function = &ctrl->functions[f_idx];
+		function->name = rtl8231_pin_function_names[f_idx];
+
+		for (pin = 0; pin < ctrl->pctl_desc.npins; pin++)
+			if (rtl8231_pin_data[pin].functions & BIT(f_idx))
+				function->ngroups++;
+
+		function->groups = devm_kcalloc(dev, function->ngroups,
+			sizeof(*function->groups), GFP_KERNEL);
+		if (!function->groups)
+			return -ENOMEM;
+
+		group_name = function->groups;
+		for (pin = 0; pin < ctrl->pctl_desc.npins; pin++)
+			if (rtl8231_pin_data[pin].functions & BIT(f_idx))
+				*group_name++ = rtl8231_pins[pin].name;
+	}
+
+	return 0;
+}
+
+static int rtl8231_pinctrl_init(struct device *dev, struct rtl8231_pin_ctrl *ctrl)
+{
+	struct pinctrl_dev *pctl;
+	int err;
+
+	ctrl->pctl_desc.name = "rtl8231-pinctrl";
+	ctrl->pctl_desc.owner = THIS_MODULE;
+	ctrl->pctl_desc.pctlops = &rtl8231_pinctrl_ops;
+	ctrl->pctl_desc.pmxops = &rtl8231_pinmux_ops;
+	ctrl->pctl_desc.npins = ARRAY_SIZE(rtl8231_pins);
+	ctrl->pctl_desc.pins = rtl8231_pins;
+
+	err = rtl8231_pinctrl_init_functions(dev, ctrl);
+	if (err)
+		return err;
+
+	err = devm_pinctrl_register_and_init(dev->parent, &ctrl->pctl_desc, ctrl, &pctl);
+	if (err) {
+		dev_err(dev, "failed to register pin controller\n");
+		return err;
+	}
+
+	err = pinctrl_enable(pctl);
+	if (err)
+		dev_err(dev, "failed to enable pin controller\n");
+
+	return err;
+}
+
+/*
+ * GPIO controller functionality
+ */
+static int rtl8231_gpio_reg_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
+	unsigned int offset, unsigned int *reg, unsigned int *mask)
+{
+	unsigned int pin_mask = BIT(offset % RTL8231_BITS_VAL);
+
+	if (base == RTL8231_REG_GPIO_DATA0 || offset < 32) {
+		*reg = base + offset / RTL8231_BITS_VAL;
+		*mask = pin_mask;
+	} else if (base == RTL8231_REG_GPIO_DIR0) {
+		*reg = RTL8231_REG_PIN_HI_CFG;
+		*mask = FIELD_PREP(RTL8231_PIN_HI_CFG_DIR_MASK, pin_mask);
+	} else {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rtl8231_pinctrl_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rtl8231_pin_ctrl *ctrl;
+	struct gpio_regmap_config gpio_cfg = {};
+	struct gpio_regmap *gr;
+	int err;
+
+	ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+	if (!ctrl)
+		return -ENOMEM;
+
+	ctrl->map = dev_get_regmap(dev->parent, NULL);
+	if (!ctrl->map)
+		return -ENODEV;
+
+	if (IS_ERR(ctrl->map))
+		return PTR_ERR(ctrl->map);
+
+	err = rtl8231_pinctrl_init(dev, ctrl);
+	if (err)
+		return err;
+
+	gpio_cfg.regmap = ctrl->map;
+	gpio_cfg.parent = dev->parent;
+	gpio_cfg.ngpio = RTL8231_NUM_GPIOS;
+	gpio_cfg.ngpio_per_reg = RTL8231_BITS_VAL;
+
+	gpio_cfg.reg_dat_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
+	gpio_cfg.reg_set_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
+	gpio_cfg.reg_dir_in_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DIR0);
+	gpio_cfg.quirks = GPIO_REGMAP_QUIRK_SET_DIRECTION_FIRST;
+
+	gpio_cfg.reg_mask_xlate = rtl8231_gpio_reg_mask_xlate;
+
+	gr = devm_gpio_regmap_register(dev, &gpio_cfg);
+	if (IS_ERR(gr)) {
+		dev_err(dev, "failed to register gpio controller\n");
+		return PTR_ERR(gr);
+	}
+
+	return 0;
+}
+
+static struct platform_driver rtl8231_pinctrl_driver = {
+	.driver = {
+		.name = "rtl8231-pinctrl",
+	},
+	.probe = rtl8231_pinctrl_probe,
+};
+module_platform_driver(rtl8231_pinctrl_driver);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek RTL8231 pin control and GPIO support");
+MODULE_LICENSE("GPL v2");
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v3 6/6] leds: Add support for RTL8231 LED scan matrix
  2021-05-23 22:33 ` [PATCH v3 0/6] " Sander Vanheule
                     ` (4 preceding siblings ...)
  2021-05-23 22:34   ` [PATCH v3 5/6] pinctrl: Add RTL8231 pin control and GPIO support Sander Vanheule
@ 2021-05-23 22:34   ` Sander Vanheule
  2021-05-24 10:24     ` Andy Shevchenko
  2021-05-24  1:10   ` [PATCH v3 0/6] RTL8231 GPIO expander support Andrew Lunn
  6 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-23 22:34 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio
  Cc: Andrew Lunn, Andy Shevchenko, linux-kernel, Sander Vanheule

Both single and bi-color scanning modes are supported. The driver will
verify that the addresses are valid for the current mode, before
registering the LEDs. LEDs can be turned on, off, or toggled at one of
six predefined rates from 40ms to 1280ms.

Implements a platform device for use as a child device with RTL8231 MFD,
and uses the parent regmap to access the required registers.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
---
 drivers/leds/Kconfig        |  10 ++
 drivers/leds/Makefile       |   1 +
 drivers/leds/leds-rtl8231.c | 291 ++++++++++++++++++++++++++++++++++++
 3 files changed, 302 insertions(+)
 create mode 100644 drivers/leds/leds-rtl8231.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 49d99cb084db..be723b6e9644 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -593,6 +593,16 @@ config LEDS_REGULATOR
 	help
 	  This option enables support for regulator driven LEDs.
 
+config LEDS_RTL8231
+	tristate "RTL8231 LED matrix support"
+	depends on LEDS_CLASS
+	depends on MFD_RTL8231
+	default MFD_RTL8231
+	help
+	  This options enables support for using the LED scanning matrix output
+	  of the RTL8231 GPIO and LED expander chip.
+	  When built as a module, this module will be named leds-rtl8231.
+
 config LEDS_BD2802
 	tristate "LED driver for BD2802 RGB LED"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 7e604d3028c8..ce0f44a87dee 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -80,6 +80,7 @@ obj-$(CONFIG_LEDS_PM8058)		+= leds-pm8058.o
 obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
 obj-$(CONFIG_LEDS_PWM)			+= leds-pwm.o
 obj-$(CONFIG_LEDS_REGULATOR)		+= leds-regulator.o
+obj-$(CONFIG_LEDS_RTL8231)		+= leds-rtl8231.o
 obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o
 obj-$(CONFIG_LEDS_SC27XX_BLTC)		+= leds-sc27xx-bltc.o
 obj-$(CONFIG_LEDS_SGM3140)		+= leds-sgm3140.o
diff --git a/drivers/leds/leds-rtl8231.c b/drivers/leds/leds-rtl8231.c
new file mode 100644
index 000000000000..4f3333a61a84
--- /dev/null
+++ b/drivers/leds/leds-rtl8231.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/device.h>
+#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/rtl8231.h>
+
+/**
+ * struct led_toggle_rate - description of an LED blinking mode
+ * @interval_ms:	LED toggle rate in milliseconds
+ * @mode:		Register field value used to activate this mode
+ *
+ * For LED hardware accelerated blinking, with equal on and off delay.
+ * Both delays are given by @interval, so the interval at which the LED blinks
+ * (i.e. turn on and off once) is double this value.
+ */
+struct led_toggle_rate {
+	u16 interval_ms;
+	u8 mode;
+};
+
+/**
+ * struct led_modes - description of all LED modes
+ * @toggle_rates:	Array of led_toggle_rate values, sorted by ascending interval
+ * @num_toggle_rates:	Number of elements in @led_toggle_rate
+ * @off:		Register field value to turn LED off
+ * @on:			Register field value to turn LED on
+ */
+struct led_modes {
+	const struct led_toggle_rate *toggle_rates;
+	unsigned int num_toggle_rates;
+	u8 off;
+	u8 on;
+};
+
+struct rtl8231_led {
+	struct led_classdev led;
+	const struct led_modes *modes;
+	struct regmap_field *reg_field;
+};
+#define to_rtl8231_led(_cdev) container_of(_cdev, struct rtl8231_led, led)
+
+#define RTL8231_NUM_LEDS	3
+#define RTL8231_LED_PER_REG	5
+#define RTL8231_BITS_PER_LED	3
+
+static const unsigned int rtl8231_led_port_counts_single[RTL8231_NUM_LEDS] = {32, 32, 24};
+static const unsigned int rtl8231_led_port_counts_bicolor[RTL8231_NUM_LEDS] = {24, 24, 24};
+
+static const unsigned int rtl8231_led_base[RTL8231_NUM_LEDS] = {
+	RTL8231_REG_LED0_BASE,
+	RTL8231_REG_LED1_BASE,
+	RTL8231_REG_LED2_BASE,
+};
+
+static const struct led_toggle_rate rtl8231_toggle_rates[] = {
+	{  40, 1},
+	{  80, 2},
+	{ 160, 3},
+	{ 320, 4},
+	{ 640, 5},
+	{1280, 6},
+};
+
+static const struct led_modes rtl8231_led_modes = {
+	.off = 0,
+	.on = 7,
+	.num_toggle_rates = ARRAY_SIZE(rtl8231_toggle_rates),
+	.toggle_rates = rtl8231_toggle_rates,
+};
+
+static void rtl8231_led_brightness_set(struct led_classdev *led_cdev,
+	enum led_brightness brightness)
+{
+	struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
+
+	if (brightness)
+		regmap_field_write(pled->reg_field, pled->modes->on);
+	else
+		regmap_field_write(pled->reg_field, pled->modes->off);
+}
+
+static enum led_brightness rtl8231_led_brightness_get(struct led_classdev *led_cdev)
+{
+	struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
+	u32 current_mode = pled->modes->off;
+
+	regmap_field_read(pled->reg_field, &current_mode);
+
+	if (current_mode == pled->modes->off)
+		return LED_OFF;
+	else
+		return LED_ON;
+}
+
+static unsigned int rtl8231_led_current_interval(struct rtl8231_led *pled)
+{
+	unsigned int mode;
+	unsigned int i;
+
+	if (regmap_field_read(pled->reg_field, &mode))
+		return 0;
+
+	for (i = 0; i < pled->modes->num_toggle_rates; i++)
+		if (mode == pled->modes->toggle_rates[i].mode)
+			return pled->modes->toggle_rates[i].interval_ms;
+
+	return 0;
+}
+
+static int rtl8231_led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
+	unsigned long *delay_off)
+{
+	struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
+	const struct led_toggle_rate *rates = pled->modes->toggle_rates;
+	unsigned int num_rates = pled->modes->num_toggle_rates;
+	unsigned int interval_ms;
+	unsigned int i;
+	int err;
+
+	if (*delay_on == 0 && *delay_off == 0) {
+		interval_ms = 500;
+	} else {
+		/*
+		 * If the current mode is blinking, choose the delay that (likely) changed.
+		 * Otherwise, choose the interval that would have the same total delay.
+		 */
+		interval_ms = rtl8231_led_current_interval(pled);
+		if (interval_ms > 0 && interval_ms == *delay_off)
+			interval_ms = *delay_on;
+		else if (interval_ms > 0 && interval_ms == *delay_on)
+			interval_ms = *delay_off;
+		else
+			interval_ms = (*delay_on + *delay_off) / 2;
+	}
+
+	/* Find clamped toggle interval */
+	for (i = 0; i < (num_rates - 1); i++)
+		if (interval_ms > rates[i].interval_ms)
+			break;
+
+	interval_ms = rates[i].interval_ms;
+
+	err = regmap_field_write(pled->reg_field, rates[i].mode);
+	if (err)
+		return err;
+
+	*delay_on = interval_ms;
+	*delay_off = interval_ms;
+
+	return 0;
+}
+
+static int rtl8231_led_read_address(struct fwnode_handle *fwnode, unsigned int *addr_port,
+	unsigned int *addr_led)
+{
+	u32 addr[2];
+	int ret;
+
+	ret = fwnode_property_count_u32(fwnode, "reg");
+	if (ret < 0)
+		return ret;
+	if (ret != 2)
+		return -ENODEV;
+
+	ret = fwnode_property_read_u32_array(fwnode, "reg", addr, ARRAY_SIZE(addr));
+	if (ret)
+		return ret;
+
+	*addr_port = addr[0];
+	*addr_led = addr[1];
+
+	return 0;
+}
+
+static struct reg_field rtl8231_led_get_field(unsigned int port_index, unsigned int led_index)
+{
+	unsigned int offset, shift;
+	struct reg_field field;
+
+	offset = port_index / RTL8231_LED_PER_REG;
+	shift = (port_index % RTL8231_LED_PER_REG) * RTL8231_BITS_PER_LED;
+
+	field.reg = rtl8231_led_base[led_index] + offset;
+	field.lsb = shift;
+	field.msb = shift + RTL8231_BITS_PER_LED - 1;
+
+	return field;
+}
+
+static int rtl8231_led_probe_single(struct device *dev, struct regmap *map,
+	const unsigned int *port_counts, struct fwnode_handle *fwnode)
+{
+	struct led_init_data init_data = {};
+	struct rtl8231_led *pled;
+	unsigned int port_index;
+	unsigned int led_index;
+	struct reg_field field;
+	int err;
+
+	pled = devm_kzalloc(dev, sizeof(*pled), GFP_KERNEL);
+	if (!pled)
+		return -ENOMEM;
+
+	err = rtl8231_led_read_address(fwnode, &port_index, &led_index);
+	if (err) {
+		dev_err(dev, "LED address invalid\n");
+		return err;
+	}
+
+	if (led_index >= RTL8231_NUM_LEDS || port_index >= port_counts[led_index]) {
+		dev_err(dev, "LED address (%d.%d) invalid\n", port_index, led_index);
+		return -ENODEV;
+	}
+
+	field = rtl8231_led_get_field(port_index, led_index);
+	pled->reg_field = devm_regmap_field_alloc(dev, map, field);
+	if (IS_ERR(pled->reg_field))
+		return PTR_ERR(pled->reg_field);
+
+	pled->modes = &rtl8231_led_modes;
+
+	pled->led.max_brightness = 1;
+	pled->led.brightness_get = rtl8231_led_brightness_get;
+	pled->led.brightness_set = rtl8231_led_brightness_set;
+	pled->led.blink_set = rtl8231_led_blink_set;
+
+	init_data.fwnode = fwnode;
+
+	return devm_led_classdev_register_ext(dev, &pled->led, &init_data);
+}
+
+static int rtl8231_led_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const unsigned int *port_counts;
+	struct fwnode_handle *child;
+	struct regmap *map;
+	int err;
+
+	map = dev_get_regmap(dev->parent, NULL);
+	if (!map)
+		return -ENODEV;
+	if (IS_ERR(map))
+		return PTR_ERR(map);
+
+	if (device_property_match_string(dev, "realtek,led-scan-mode", "single-color") >= 0) {
+		port_counts = rtl8231_led_port_counts_single;
+		regmap_update_bits(map, RTL8231_REG_FUNC0,
+			RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_SINGLE);
+	} else if (device_property_match_string(dev, "realtek,led-scan-mode", "bi-color") >= 0) {
+		port_counts = rtl8231_led_port_counts_bicolor;
+		regmap_update_bits(map, RTL8231_REG_FUNC0,
+			RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_BICOLOR);
+	} else {
+		dev_err(dev, "scan mode missing or invalid\n");
+		return -EINVAL;
+	}
+
+	fwnode_for_each_available_child_node(dev->fwnode, child) {
+		err = rtl8231_led_probe_single(dev, map, port_counts, child);
+		if (err)
+			dev_warn(dev, "failed to register LED %pfwP\n", child);
+	}
+
+	return 0;
+}
+
+static const struct of_device_id of_rtl8231_led_match[] = {
+	{ .compatible = "realtek,rtl8231-leds" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, of_rtl8231_led_match);
+
+static struct platform_driver rtl8231_led_driver = {
+	.driver = {
+		.name = "rtl8231-leds",
+		.of_match_table = of_rtl8231_led_match,
+	},
+	.probe = rtl8231_led_probe,
+};
+module_platform_driver(rtl8231_led_driver);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek RTL8231 LED support");
+MODULE_LICENSE("GPL v2");
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-23 22:33 ` [PATCH v3 0/6] " Sander Vanheule
                     ` (5 preceding siblings ...)
  2021-05-23 22:34   ` [PATCH v3 6/6] leds: Add support for RTL8231 LED scan matrix Sander Vanheule
@ 2021-05-24  1:10   ` Andrew Lunn
  2021-05-24  7:53     ` Andy Shevchenko
  6 siblings, 1 reply; 114+ messages in thread
From: Andrew Lunn @ 2021-05-24  1:10 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, linux-leds, devicetree,
	linux-gpio, Andy Shevchenko, linux-kernel

> Changes since v2:
>   - MDIO regmap support was merged, so patch is dropped here

Do you have any idea how this will get merged. It sounds like one of
the Maintainers will need a stable branch of regmap.

>   - Introduce GPIO regmap quirks to set output direction first

I thought you had determined it was possible to set output before
direction?

	Andrew

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 5/7] mfd: Add RTL8231 core device
  2021-05-23 21:28       ` Sander Vanheule
@ 2021-05-24  7:49         ` Andy Shevchenko
  0 siblings, 0 replies; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-24  7:49 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List, kernel test robot

On Mon, May 24, 2021 at 12:28 AM Sander Vanheule <sander@svanheule.net> wrote:
> On Tue, 2021-05-18 at 00:18 +0300, Andy Shevchenko wrote:
> > On Mon, May 17, 2021 at 10:28 PM Sander Vanheule <sander@svanheule.net> wrote:
> > >
> > > The RTL8231 is implemented as an MDIO device, and provides a regmap
> > > interface for register access by the core and child devices.
> > >
> > > The chip can also be a device on an SMI bus, an I2C-like bus by Realtek.
> > > Since kernel support for SMI is limited, and no real-world SMI
> > > implementations have been encountered for this device, this is currently
> > > unimplemented. The use of the regmap interface should make any future
> > > support relatively straightforward.
> > >
> > > After reset, all pins are muxed to GPIO inputs before the pin drivers
> > > are enabled. This is done to prevent accidental system resets, when a
> > > pin is connected to the parent SoC's reset line.
> >
> > > [missing MDIO_BUS dependency, provided via REGMAP_MDIO]
> > > Reported-by: kernel test robot <lkp@intel.com>
> >
> > What is the culprit? Shouldn't this have a Fixes tag?
>
> But it doesn't actually fix an issue created by an existing commit, just
> something that was wrong in the first version of the patch.

Then why is it in the tag block?
If you want to give a credit to LKP, do it in the comments block
(after '---' cutter line).

>  This patch is not
> dedicated to fixing that single issue though, it's just a part of it. Hence the
> note above the Reported-by tag.

--
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 5/7] mfd: Add RTL8231 core device
  2021-05-17 21:18     ` Andy Shevchenko
  2021-05-23 21:28       ` Sander Vanheule
@ 2021-05-24  7:50       ` Sander Vanheule
  2021-05-24  7:55         ` Andy Shevchenko
  1 sibling, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-24  7:50 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List, kernel test robot

Hi Andy,

I forgot to address one of your questions, so here's a short follow up.

On Tue, 2021-05-18 at 00:18 +0300, Andy Shevchenko wrote:
> On Mon, May 17, 2021 at 10:28 PM Sander Vanheule <sander@svanheule.net> wrote:
> > +       err = regmap_read(map, RTL8231_REG_FUNC1, &v);
> 
> > +       ready_code = FIELD_GET(RTL8231_FUNC1_READY_CODE_MASK, v);
> 
> If we got an error why we need a read_core, what for?

The chip has a static 5-bit field in register 0x01, called READY_CODE according
to the datasheet. If a device is present, and a read from register 0x01
succeeds, I still check that this field has the correct value. For the RTL8231,
it should return 0x37. If this isn't the case, I assume this isn't an RTL8231,
so the driver probe stops and returns an error value.

Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-24  1:10   ` [PATCH v3 0/6] RTL8231 GPIO expander support Andrew Lunn
@ 2021-05-24  7:53     ` Andy Shevchenko
  2021-05-24 11:41       ` Sander Vanheule
  0 siblings, 1 reply; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-24  7:53 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Sander Vanheule, Pavel Machek, Rob Herring, Lee Jones,
	Mark Brown, Greg Kroah-Hartman, Rafael J . Wysocki,
	Michael Walle, Linus Walleij, Bartosz Golaszewski,
	Linux LED Subsystem, devicetree, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

On Mon, May 24, 2021 at 4:11 AM Andrew Lunn <andrew@lunn.ch> wrote:
>
> > Changes since v2:
> >   - MDIO regmap support was merged, so patch is dropped here
>
> Do you have any idea how this will get merged. It sounds like one of
> the Maintainers will need a stable branch of regmap.

This is not a problem if Mark provides an immutable branch to pull from.

> >   - Introduce GPIO regmap quirks to set output direction first
>
> I thought you had determined it was possible to set output before
> direction?

Same thoughts when I saw an updated version of that patch. My
anticipation was to not see it at all.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 5/7] mfd: Add RTL8231 core device
  2021-05-24  7:50       ` Sander Vanheule
@ 2021-05-24  7:55         ` Andy Shevchenko
  2021-05-24  8:04           ` Sander Vanheule
  0 siblings, 1 reply; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-24  7:55 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List, kernel test robot

On Mon, May 24, 2021 at 10:50 AM Sander Vanheule <sander@svanheule.net> wrote:
> On Tue, 2021-05-18 at 00:18 +0300, Andy Shevchenko wrote:
> > On Mon, May 17, 2021 at 10:28 PM Sander Vanheule <sander@svanheule.net> wrote:
> > > +       err = regmap_read(map, RTL8231_REG_FUNC1, &v);
> >
> > > +       ready_code = FIELD_GET(RTL8231_FUNC1_READY_CODE_MASK, v);
> >
> > If we got an error why we need a read_core, what for?
>
> The chip has a static 5-bit field in register 0x01, called READY_CODE according
> to the datasheet. If a device is present, and a read from register 0x01
> succeeds, I still check that this field has the correct value. For the RTL8231,
> it should return 0x37. If this isn't the case, I assume this isn't an RTL8231,
> so the driver probe stops and returns an error value.

Right. And why do you get ready_code if you know that there is an error?

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 4/6] mfd: Add RTL8231 core device
  2021-05-23 22:34   ` [PATCH v3 4/6] mfd: Add RTL8231 core device Sander Vanheule
@ 2021-05-24  8:02     ` Andy Shevchenko
  2021-05-24  8:23       ` Sander Vanheule
  0 siblings, 1 reply; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-24  8:02 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List, kernel test robot

On Mon, May 24, 2021 at 1:34 AM Sander Vanheule <sander@svanheule.net> wrote:
>
> The RTL8231 is implemented as an MDIO device, and provides a regmap
> interface for register access by the core and child devices.
>
> The chip can also be a device on an SMI bus, an I2C-like bus by Realtek.
> Since kernel support for SMI is limited, and no real-world SMI
> implementations have been encountered for this device, this is currently
> unimplemented. The use of the regmap interface should make any future
> support relatively straightforward.
>
> After reset, all pins are muxed to GPIO inputs before the pin drivers
> are enabled. This is done to prevent accidental system resets, when a
> pin is connected to the parent SoC's reset line.

...

> [missing MDIO_BUS dependency, provided via REGMAP_MDIO]
> Reported-by: kernel test robot <lkp@intel.com>

What does this fix? Shouldn't it have a Fixes tag? (Yes, I know that
you answered in the other email, but here is a hint: before settling
these kinds of things do not send a new version. Instead of speeding
up the review you are closer to the chance to have this been not
applied for v5.14 at all)

...

> +       /* SOFT_RESET bit self-clears when done */
> +       regmap_update_bits(map, RTL8231_REG_PIN_HI_CFG,
> +               RTL8231_PIN_HI_CFG_SOFT_RESET, RTL8231_PIN_HI_CFG_SOFT_RESET);

> +       usleep_range(1000, 10000);

It's strange to see this big range of minimum and maximum sleep.
Usually the ratio should not be bigger than ~3-4 between the values.

...

> +       regmap_write(map, RTL8231_REG_PIN_MODE0, 0xffff);
> +       regmap_write(map, RTL8231_REG_GPIO_DIR0, 0xffff);
> +       regmap_write(map, RTL8231_REG_PIN_MODE1, 0xffff);
> +       regmap_write(map, RTL8231_REG_GPIO_DIR1, 0xffff);

GENMASK() ?
Actually it seems it deserves a special definition like

..._ALL_PIN_MASK  GENMASK(15, 0)

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v2 5/7] mfd: Add RTL8231 core device
  2021-05-24  7:55         ` Andy Shevchenko
@ 2021-05-24  8:04           ` Sander Vanheule
  0 siblings, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-24  8:04 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List, kernel test robot

On Mon, 2021-05-24 at 10:55 +0300, Andy Shevchenko wrote:
> On Mon, May 24, 2021 at 10:50 AM Sander Vanheule <sander@svanheule.net> wrote:
> > On Tue, 2021-05-18 at 00:18 +0300, Andy Shevchenko wrote:
> > > On Mon, May 17, 2021 at 10:28 PM Sander Vanheule <sander@svanheule.net>
> > > wrote:
> > > > +       err = regmap_read(map, RTL8231_REG_FUNC1, &v);
> > > 
> > > > +       ready_code = FIELD_GET(RTL8231_FUNC1_READY_CODE_MASK, v);
> > > 
> > > If we got an error why we need a read_core, what for?
> > 
> > The chip has a static 5-bit field in register 0x01, called READY_CODE
> > according
> > to the datasheet. If a device is present, and a read from register 0x01
> > succeeds, I still check that this field has the correct value. For the
> > RTL8231,
> > it should return 0x37. If this isn't the case, I assume this isn't an
> > RTL8231,
> > so the driver probe stops and returns an error value.
> 
> Right. And why do you get ready_code if you know that there is an error?

This has changed in v3. I now check if there was an error reading the register,
and return if there was. Only if there wasn't an error, the code continues to
extract and verify the READY_CODE value.

Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 4/6] mfd: Add RTL8231 core device
  2021-05-24  8:02     ` Andy Shevchenko
@ 2021-05-24  8:23       ` Sander Vanheule
  2021-05-24 10:18         ` Andy Shevchenko
  0 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-24  8:23 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List, kernel test robot

Hi Andy,

On Mon, 2021-05-24 at 11:02 +0300, Andy Shevchenko wrote:
> On Mon, May 24, 2021 at 1:34 AM Sander Vanheule <sander@svanheule.net> wrote:
> > 
> > The RTL8231 is implemented as an MDIO device, and provides a regmap
> > interface for register access by the core and child devices.
> > 
> > The chip can also be a device on an SMI bus, an I2C-like bus by Realtek.
> > Since kernel support for SMI is limited, and no real-world SMI
> > implementations have been encountered for this device, this is currently
> > unimplemented. The use of the regmap interface should make any future
> > support relatively straightforward.
> > 
> > After reset, all pins are muxed to GPIO inputs before the pin drivers
> > are enabled. This is done to prevent accidental system resets, when a
> > pin is connected to the parent SoC's reset line.
> 
> ...
> 
> > [missing MDIO_BUS dependency, provided via REGMAP_MDIO]
> > Reported-by: kernel test robot <lkp@intel.com>
> 
> What does this fix? Shouldn't it have a Fixes tag? (Yes, I know that
> you answered in the other email, but here is a hint: before settling
> these kinds of things do not send a new version. Instead of speeding
> up the review you are closer to the chance to have this been not
> applied for v5.14 at all)
> 

I'll drop this from the commit message, if this isn't appropriate without a
Fixes-tag (which I can't provide anyway).


> ...
> 
> > +       /* SOFT_RESET bit self-clears when done */
> > +       regmap_update_bits(map, RTL8231_REG_PIN_HI_CFG,
> > +               RTL8231_PIN_HI_CFG_SOFT_RESET,
> > RTL8231_PIN_HI_CFG_SOFT_RESET);
> 
> > +       usleep_range(1000, 10000);
> 
> It's strange to see this big range of minimum and maximum sleep.
> Usually the ratio should not be bigger than ~3-4 between the values.

I could also change this from a usleep to a polling loop that checks (with a
loop limit) if the reset bit has self-cleared already. 

The datasheet that I have doesn't mention how fast it should self-clear. So I
checked, and it appears to be done after one loop iteration already. So,
certainly faster than the current usleep.

Would a polling loop (with maybe like max. 10 iterations) be a good alternative
for you?

> 
> ...
> 
> > +       regmap_write(map, RTL8231_REG_PIN_MODE0, 0xffff);
> > +       regmap_write(map, RTL8231_REG_GPIO_DIR0, 0xffff);
> > +       regmap_write(map, RTL8231_REG_PIN_MODE1, 0xffff);
> > +       regmap_write(map, RTL8231_REG_GPIO_DIR1, 0xffff);
> 
> GENMASK() ?
> Actually it seems it deserves a special definition like
> 
> ..._ALL_PIN_MASK  GENMASK(15, 0)

Ok, I'll add the extra macro to clarify the intent of the values.


Best,
Sander



^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 4/6] mfd: Add RTL8231 core device
  2021-05-24  8:23       ` Sander Vanheule
@ 2021-05-24 10:18         ` Andy Shevchenko
  2021-05-24 11:41           ` Sander Vanheule
  0 siblings, 1 reply; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-24 10:18 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List, kernel test robot

On Mon, May 24, 2021 at 11:23 AM Sander Vanheule <sander@svanheule.net> wrote:
> On Mon, 2021-05-24 at 11:02 +0300, Andy Shevchenko wrote:
> > On Mon, May 24, 2021 at 1:34 AM Sander Vanheule <sander@svanheule.net> wrote:

...

> > > +       usleep_range(1000, 10000);
> >
> > It's strange to see this big range of minimum and maximum sleep.
> > Usually the ratio should not be bigger than ~3-4 between the values.
>
> I could also change this from a usleep to a polling loop that checks (with a
> loop limit) if the reset bit has self-cleared already.
>
> The datasheet that I have doesn't mention how fast it should self-clear. So I
> checked, and it appears to be done after one loop iteration already. So,
> certainly faster than the current usleep.
>
> Would a polling loop (with maybe like max. 10 iterations) be a good alternative
> for you?

I guess it's the right way to go. Just check the iopoll.h for helpers.
Also regmap has regmap_read_poll_timeout().

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 6/6] leds: Add support for RTL8231 LED scan matrix
  2021-05-23 22:34   ` [PATCH v3 6/6] leds: Add support for RTL8231 LED scan matrix Sander Vanheule
@ 2021-05-24 10:24     ` Andy Shevchenko
  2021-05-24 12:04       ` Sander Vanheule
  0 siblings, 1 reply; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-24 10:24 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List

On Mon, May 24, 2021 at 1:34 AM Sander Vanheule <sander@svanheule.net> wrote:
>
> Both single and bi-color scanning modes are supported. The driver will
> verify that the addresses are valid for the current mode, before
> registering the LEDs. LEDs can be turned on, off, or toggled at one of
> six predefined rates from 40ms to 1280ms.
>
> Implements a platform device for use as a child device with RTL8231 MFD,
> and uses the parent regmap to access the required registers.

...

> +         This options enables support for using the LED scanning matrix output

option

> +         of the RTL8231 GPIO and LED expander chip.
> +         When built as a module, this module will be named leds-rtl8231.

...

> +               interval_ms = 500;

Does this deserve a #define?

...

> +       ret = fwnode_property_count_u32(fwnode, "reg");
> +       if (ret < 0)
> +               return ret;
> +       if (ret != 2)
> +               return -ENODEV;

I would say -EINVAL, but -ENODEV is similarly okay.

...

> +       int err;

ret or err? Be consistent across a single driver.

...

> +       int err;

Ditto.


-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 4/6] mfd: Add RTL8231 core device
  2021-05-24 10:18         ` Andy Shevchenko
@ 2021-05-24 11:41           ` Sander Vanheule
  0 siblings, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-24 11:41 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List, kernel test robot

On Mon, 2021-05-24 at 13:18 +0300, Andy Shevchenko wrote:
> On Mon, May 24, 2021 at 11:23 AM Sander Vanheule <sander@svanheule.net> wrote:
> > On Mon, 2021-05-24 at 11:02 +0300, Andy Shevchenko wrote:
> > > On Mon, May 24, 2021 at 1:34 AM Sander Vanheule <sander@svanheule.net>
> > > wrote:
> 
> ...
> 
> > > > +       usleep_range(1000, 10000);
> > > 
> > > It's strange to see this big range of minimum and maximum sleep.
> > > Usually the ratio should not be bigger than ~3-4 between the values.
> > 
> > I could also change this from a usleep to a polling loop that checks (with a
> > loop limit) if the reset bit has self-cleared already.
> > 
> > The datasheet that I have doesn't mention how fast it should self-clear. So
> > I
> > checked, and it appears to be done after one loop iteration already. So,
> > certainly faster than the current usleep.
> > 
> > Would a polling loop (with maybe like max. 10 iterations) be a good
> > alternative
> > for you?
> 
> I guess it's the right way to go. Just check the iopoll.h for helpers.
> Also regmap has regmap_read_poll_timeout().

Thanks for the pointers. Replaced the usleep by regmap_read_poll_timeout.


Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-24  7:53     ` Andy Shevchenko
@ 2021-05-24 11:41       ` Sander Vanheule
  2021-05-24 12:54         ` Andy Shevchenko
  2021-05-28  6:37         ` Michael Walle
  0 siblings, 2 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-24 11:41 UTC (permalink / raw)
  To: Andy Shevchenko, Andrew Lunn
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Linux Kernel Mailing List

Hi Andy, Andrew,

On Mon, 2021-05-24 at 10:53 +0300, Andy Shevchenko wrote:
> On Mon, May 24, 2021 at 4:11 AM Andrew Lunn <andrew@lunn.ch> wrote:
> > 
> > > Changes since v2:
> > >   - MDIO regmap support was merged, so patch is dropped here
> > 
> > Do you have any idea how this will get merged. It sounds like one of
> > the Maintainers will need a stable branch of regmap.
> 
> This is not a problem if Mark provides an immutable branch to pull from.

Mark has a tag (regmap-mdio) for this patch:
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap.git/tag/?h=regmap-mdio

> 
> > >   - Introduce GPIO regmap quirks to set output direction first
> > 
> > I thought you had determined it was possible to set output before
> > direction?
> 
> Same thoughts when I saw an updated version of that patch. My
> anticipation was to not see it at all.

The two devices I've been trying to test the behaviour on are:
 * Netgear GS110TPP: has an RTL8231 with three LEDs, each driven via a pin
   configured as (active-low) GPIO. The LEDs are easy for a quick visual check.
 * Zyxel GS1900-8: RTL8231 used for the front panel button, and an active-low
   GPIO used to hard reset the main SoC (an RTL8380). I've modified this board
   to change some of the strapping pin values, but testing with the jumpers and
   pull-up/down resistors is a bit more tedious.

On the Netgear, I tested the following with and without the quirk:
   
   # Set as OUT-LOW twice, to avoid the quirk. Always turns the LED on
   gpioset 1 32=0; gpioset 1 32=0
   # Get value to change to input, turns the LED off (high impedance)
   # Will return 1 due to (weak) internal pull-up
   gpioget 1 32
   # Set as OUT-HIGH, should result in LED off
   # When the quirk is disabled, the LED turns on (i.e. old OUT-LOW value)
   # When the quirk is enabled, the LED remains off (i.e. correct OUT-HIGH value)
   gpioset 1 32=1

Now, what's confusing (to me) is that the inverse doesn't depend on the quirk:
   
   # Set as OUT-HIGH twice
   gpioset 1 32=1; gpioset 1 32=1
   # Change to high-Z
   gpioget 1 32
   # Set to OUT-LOW, always results in LED on, with or without quirk
   gpioset 1 32=0

Any idea why this would be (or appear) broken on the former case, but not on the
latter?

I was trying to reproduce this behaviour on the Zyxel, but using the strapping
pins that are also used to configure the device's address. So perhaps the pull-
ups/-downs were confusing the results. Using a separate pin on the Zyxel's
RTL8231, I've now been able to confirm the same behaviour as on the Netgear,
including capturing the resulting glitch (with my simple logic analyser) when
enabling the quirk in the first test case.

I hope this explains why I've still included the quirk in this revision. If not,
please let me know what isn't clear.


Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 6/6] leds: Add support for RTL8231 LED scan matrix
  2021-05-24 10:24     ` Andy Shevchenko
@ 2021-05-24 12:04       ` Sander Vanheule
  2021-05-24 12:47         ` Andy Shevchenko
  0 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-24 12:04 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List

On Mon, 2021-05-24 at 13:24 +0300, Andy Shevchenko wrote:
> On Mon, May 24, 2021 at 1:34 AM Sander Vanheule <sander@svanheule.net> wrote:
> > 
> > Both single and bi-color scanning modes are supported. The driver will
> > verify that the addresses are valid for the current mode, before
> > registering the LEDs. LEDs can be turned on, off, or toggled at one of
> > six predefined rates from 40ms to 1280ms.
> > 
> > Implements a platform device for use as a child device with RTL8231 MFD,
> > and uses the parent regmap to access the required registers.
> 
> ...
> 
> > +         This options enables support for using the LED scanning matrix
> > output
> 
> option

Fixed.

> 
> > +         of the RTL8231 GPIO and LED expander chip.
> > +         When built as a module, this module will be named leds-rtl8231.
> 
> ...
> 
> > +               interval_ms = 500;
> 
> Does this deserve a #define?

Fine by me. Doesn't make a difference for the binary anyway, but it helps
document the code a bit.

> ...
> 
> > +       ret = fwnode_property_count_u32(fwnode, "reg");
> > +       if (ret < 0)
> > +               return ret;
> > +       if (ret != 2)
> > +               return -ENODEV;
> 
> I would say -EINVAL, but -ENODEV is similarly okay.

Any specific reason you think EINVAL is more appropriate than ENODEV?


> ...
> 
> > +       int err;
> 
> ret or err? Be consistent across a single driver.

I had first used 'err' for both fwnode_property_count_u32() and
fwnode_property_read_u32_array(). The former returns "actual count or error
code", while the latter is only "error code". And I found it weird to read the
code as "does error code equal 2", if I used 'err' as variable name.

I've split this up:
 * addr_count for fwnode_property_count_u32's result
 * err for fwnode_property_read_u32_array's result

Since addr_count is only used before err is touched, I guess the compiler will
optimize this out anyway?


Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 6/6] leds: Add support for RTL8231 LED scan matrix
  2021-05-24 12:04       ` Sander Vanheule
@ 2021-05-24 12:47         ` Andy Shevchenko
  2021-05-24 15:30           ` Sander Vanheule
  0 siblings, 1 reply; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-24 12:47 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List

On Mon, May 24, 2021 at 3:04 PM Sander Vanheule <sander@svanheule.net> wrote:
> On Mon, 2021-05-24 at 13:24 +0300, Andy Shevchenko wrote:
> > On Mon, May 24, 2021 at 1:34 AM Sander Vanheule <sander@svanheule.net> wrote:

...

> > > +       if (ret != 2)
> > > +               return -ENODEV;
> >
> > I would say -EINVAL, but -ENODEV is similarly okay.
>
> Any specific reason you think EINVAL is more appropriate than ENODEV?

My logic is that the initial values (from resource provider) are incorrect.
But as I said, I'm fine with either.

...

> > > +       int err;
> >
> > ret or err? Be consistent across a single driver.
>
> I had first used 'err' for both fwnode_property_count_u32() and
> fwnode_property_read_u32_array(). The former returns "actual count or error
> code", while the latter is only "error code". And I found it weird to read the
> code as "does error code equal 2", if I used 'err' as variable name.
>
> I've split this up:
>  * addr_count for fwnode_property_count_u32's result
>  * err for fwnode_property_read_u32_array's result
>
> Since addr_count is only used before err is touched, I guess the compiler will
> optimize this out anyway?

Usually we do this pattern (and it seems you missed the point, name of
variable is ret in some functions and err in the rest):

err /* ret */ = foo();
if (err < 0)
  return err;
count = err;

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-24 11:41       ` Sander Vanheule
@ 2021-05-24 12:54         ` Andy Shevchenko
  2021-05-24 15:03           ` Sander Vanheule
  2021-05-24 15:20           ` Sander Vanheule
  2021-05-28  6:37         ` Michael Walle
  1 sibling, 2 replies; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-24 12:54 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Andrew Lunn, Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Linux Kernel Mailing List

On Mon, May 24, 2021 at 2:41 PM Sander Vanheule <sander@svanheule.net> wrote:
> On Mon, 2021-05-24 at 10:53 +0300, Andy Shevchenko wrote:
> > On Mon, May 24, 2021 at 4:11 AM Andrew Lunn <andrew@lunn.ch> wrote:

...

> > > > Changes since v2:
> > > >   - MDIO regmap support was merged, so patch is dropped here
> > >
> > > Do you have any idea how this will get merged. It sounds like one of
> > > the Maintainers will need a stable branch of regmap.
> >
> > This is not a problem if Mark provides an immutable branch to pull from.
>
> Mark has a tag (regmap-mdio) for this patch:
> https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap.git/tag/?h=regmap-mdio

Also works but you have to provide this information in the cover letter.

...

> > > >   - Introduce GPIO regmap quirks to set output direction first
> > >
> > > I thought you had determined it was possible to set output before
> > > direction?
> >
> > Same thoughts when I saw an updated version of that patch. My
> > anticipation was to not see it at all.
>
> The two devices I've been trying to test the behaviour on are:
>  * Netgear GS110TPP: has an RTL8231 with three LEDs, each driven via a pin
>    configured as (active-low) GPIO. The LEDs are easy for a quick visual check.
>  * Zyxel GS1900-8: RTL8231 used for the front panel button, and an active-low
>    GPIO used to hard reset the main SoC (an RTL8380). I've modified this board
>    to change some of the strapping pin values, but testing with the jumpers and
>    pull-up/down resistors is a bit more tedious.
>
> On the Netgear, I tested the following with and without the quirk:
>
>    # Set as OUT-LOW twice, to avoid the quirk. Always turns the LED on
>    gpioset 1 32=0; gpioset 1 32=0
>    # Get value to change to input, turns the LED off (high impedance)
>    # Will return 1 due to (weak) internal pull-up
>    gpioget 1 32
>    # Set as OUT-HIGH, should result in LED off
>    # When the quirk is disabled, the LED turns on (i.e. old OUT-LOW value)
>    # When the quirk is enabled, the LED remains off (i.e. correct OUT-HIGH value)
>    gpioset 1 32=1
>
> Now, what's confusing (to me) is that the inverse doesn't depend on the quirk:
>
>    # Set as OUT-HIGH twice
>    gpioset 1 32=1; gpioset 1 32=1
>    # Change to high-Z
>    gpioget 1 32
>    # Set to OUT-LOW, always results in LED on, with or without quirk
>    gpioset 1 32=0
>
> Any idea why this would be (or appear) broken on the former case, but not on the
> latter?

GPIO tools for the shell are context-less. Can you reproduce this with
the legacy sysfs interface?

> I was trying to reproduce this behaviour on the Zyxel, but using the strapping
> pins that are also used to configure the device's address. So perhaps the pull-
> ups/-downs were confusing the results. Using a separate pin on the Zyxel's
> RTL8231, I've now been able to confirm the same behaviour as on the Netgear,
> including capturing the resulting glitch (with my simple logic analyser) when
> enabling the quirk in the first test case.
>
> I hope this explains why I've still included the quirk in this revision. If not,
> please let me know what isn't clear.

Do you possess a schematic of either of the devices and a link to the
RTL datasheet (Btw, if it's publicly available, or you have a link
that will ask for necessary sign-in it would be nice to include the
link to it as a Datasheet: tag)?

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-24 12:54         ` Andy Shevchenko
@ 2021-05-24 15:03           ` Sander Vanheule
  2021-05-24 16:30             ` Andy Shevchenko
  2021-05-24 15:20           ` Sander Vanheule
  1 sibling, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-24 15:03 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Andrew Lunn, Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Linux Kernel Mailing List

On Mon, 2021-05-24 at 15:54 +0300, Andy Shevchenko wrote:
> On Mon, May 24, 2021 at 2:41 PM Sander Vanheule <sander@svanheule.net> wrote:
> > On Mon, 2021-05-24 at 10:53 +0300, Andy Shevchenko wrote:
> > > On Mon, May 24, 2021 at 4:11 AM Andrew Lunn <andrew@lunn.ch> wrote:
> 
> ...
> 
> > > > > Changes since v2:
> > > > >   - MDIO regmap support was merged, so patch is dropped here
> > > > 
> > > > Do you have any idea how this will get merged. It sounds like one of
> > > > the Maintainers will need a stable branch of regmap.
> > > 
> > > This is not a problem if Mark provides an immutable branch to pull from.
> > 
> > Mark has a tag (regmap-mdio) for this patch:
> > https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap.git/tag/?h=regmap-mdio
> 
> Also works but you have to provide this information in the cover letter.
> 

Ok, I will add the link to the cover letter for the next version. Does it need
to be in a Link-tag, or can just be a reference?


> ...
> 
> > > > >   - Introduce GPIO regmap quirks to set output direction first
> > > > 
> > > > I thought you had determined it was possible to set output before
> > > > direction?
> > > 
> > > Same thoughts when I saw an updated version of that patch. My
> > > anticipation was to not see it at all.
> > 
> > The two devices I've been trying to test the behaviour on are:
> >  * Netgear GS110TPP: has an RTL8231 with three LEDs, each driven via a pin
> >    configured as (active-low) GPIO. The LEDs are easy for a quick visual
> > check.
> >  * Zyxel GS1900-8: RTL8231 used for the front panel button, and an active-
> > low
> >    GPIO used to hard reset the main SoC (an RTL8380). I've modified this
> > board
> >    to change some of the strapping pin values, but testing with the jumpers
> > and
> >    pull-up/down resistors is a bit more tedious.
> > 
> > On the Netgear, I tested the following with and without the quirk:
> > 
> >    # Set as OUT-LOW twice, to avoid the quirk. Always turns the LED on
> >    gpioset 1 32=0; gpioset 1 32=0
> >    # Get value to change to input, turns the LED off (high impedance)
> >    # Will return 1 due to (weak) internal pull-up
> >    gpioget 1 32
> >    # Set as OUT-HIGH, should result in LED off
> >    # When the quirk is disabled, the LED turns on (i.e. old OUT-LOW value)
> >    # When the quirk is enabled, the LED remains off (i.e. correct OUT-HIGH
> > value)
> >    gpioset 1 32=1
> > 
> > Now, what's confusing (to me) is that the inverse doesn't depend on the
> > quirk:
> > 
> >    # Set as OUT-HIGH twice
> >    gpioset 1 32=1; gpioset 1 32=1
> >    # Change to high-Z
> >    gpioget 1 32
> >    # Set to OUT-LOW, always results in LED on, with or without quirk
> >    gpioset 1 32=0
> > 
> > Any idea why this would be (or appear) broken on the former case, but not on
> > the
> > latter?
> 
> GPIO tools for the shell are context-less. Can you reproduce this with
> the legacy sysfs interface?
> 
> > I was trying to reproduce this behaviour on the Zyxel, but using the
> > strapping
> > pins that are also used to configure the device's address. So perhaps the
> > pull-
> > ups/-downs were confusing the results. Using a separate pin on the Zyxel's
> > RTL8231, I've now been able to confirm the same behaviour as on the Netgear,
> > including capturing the resulting glitch (with my simple logic analyser)
> > when
> > enabling the quirk in the first test case.
> > 
> > I hope this explains why I've still included the quirk in this revision. If
> > not,
> > please let me know what isn't clear.
> 
> Do you possess a schematic of either of the devices and a link to the
> RTL datasheet (Btw, if it's publicly available, or you have a link
> that will ask for necessary sign-in it would be nice to include the
> link to it as a Datasheet: tag)?

Sadly, I don't. Most of the info we have comes from code archives of switch
vendors (Zyxel, Cisco etc). Boards need to be reverse engineered, and the few
leaked datasheets that can be found on the internet aren't exactly thick in
information.

The RTL8231 datasheet is actually quite useful, but makes no mention of the
output value isse. Since this isn't an official resource, I don't think it would
be appropriate to link it via a Datasheet: tag.
https://github.com/libc0607/Realtek_switch_hacking/blob/files/RTL8231_Datasheet_
1.2.pdf

Looking at the datasheet again, I came up with a... terrible hack to work around
the output value issue.

The chip also has GPIO_INVERT registers that I hadn't used until now, because
the logical inversion is handled in the kernel. However, these inversion
registers only apply to the output values. So, I could implement glitch-free
output behaviour in the following way:
 * After chip reset, and before enabling the output driver (MFD initialisation):
    - Mux all pins as GPIO
    - Change all pins to outputs, so the data registers (0x1c-0x1e) become writable
    - Write value 0 to all pins
    - Change all pins to GPI to change them into high-Z
 * In the pinctrl/gpio driver:
    - Use data registers as input-only
    - Use inversion register to determine output value (can be written any time)

The above gives glitch-free outputs, but the values that are read back (when
configured as output), come from the data registers. They should now be coming
from the inversion (reg_set_base) registers, but the code prefers to use the
data registers (reg_dat_base).


Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-24 12:54         ` Andy Shevchenko
  2021-05-24 15:03           ` Sander Vanheule
@ 2021-05-24 15:20           ` Sander Vanheule
  1 sibling, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-24 15:20 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Andrew Lunn, Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Linux Kernel Mailing List

Hi Andy,

Forgot to reply to the sysfs suggestion.

On Mon, 2021-05-24 at 15:54 +0300, Andy Shevchenko wrote:
> On Mon, May 24, 2021 at 2:41 PM Sander Vanheule <sander@svanheule.net> wrote:
> > On Mon, 2021-05-24 at 10:53 +0300, Andy Shevchenko wrote:
> > > On Mon, May 24, 2021 at 4:11 AM Andrew Lunn <andrew@lunn.ch> wrote:
> 
> > > > >   - Introduce GPIO regmap quirks to set output direction first
> > > > 
> > > > I thought you had determined it was possible to set output before
> > > > direction?
> > > 
> > > Same thoughts when I saw an updated version of that patch. My
> > > anticipation was to not see it at all.
> > 
> > The two devices I've been trying to test the behaviour on are:
> >  * Netgear GS110TPP: has an RTL8231 with three LEDs, each driven via a pin
> >    configured as (active-low) GPIO. The LEDs are easy for a quick visual
> > check.
> >  * Zyxel GS1900-8: RTL8231 used for the front panel button, and an active-
> > low
> >    GPIO used to hard reset the main SoC (an RTL8380). I've modified this
> > board
> >    to change some of the strapping pin values, but testing with the jumpers
> > and
> >    pull-up/down resistors is a bit more tedious.
> > 
> > On the Netgear, I tested the following with and without the quirk:
> > 
> >    # Set as OUT-LOW twice, to avoid the quirk. Always turns the LED on
> >    gpioset 1 32=0; gpioset 1 32=0
> >    # Get value to change to input, turns the LED off (high impedance)
> >    # Will return 1 due to (weak) internal pull-up
> >    gpioget 1 32
> >    # Set as OUT-HIGH, should result in LED off
> >    # When the quirk is disabled, the LED turns on (i.e. old OUT-LOW value)
> >    # When the quirk is enabled, the LED remains off (i.e. correct OUT-HIGH
> > value)
> >    gpioset 1 32=1
> > 
> > Now, what's confusing (to me) is that the inverse doesn't depend on the
> > quirk:
> > 
> >    # Set as OUT-HIGH twice
> >    gpioset 1 32=1; gpioset 1 32=1
> >    # Change to high-Z
> >    gpioget 1 32
> >    # Set to OUT-LOW, always results in LED on, with or without quirk
> >    gpioset 1 32=0
> > 
> > Any idea why this would be (or appear) broken on the former case, but not on
> > the
> > latter?
> 
> GPIO tools for the shell are context-less. Can you reproduce this with
> the legacy sysfs interface?

Using the sysfs interface produced the same behaviour for both test cases.

E.g. case 1:
   # Set to output low
   echo out > direction; echo 0 > value
   # Change to input (with weak pull-up)
   echo in > direction
   # Try to set to output high
   # Fails to go high if the pin value is set before the direction
   echo high > direction


Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 6/6] leds: Add support for RTL8231 LED scan matrix
  2021-05-24 12:47         ` Andy Shevchenko
@ 2021-05-24 15:30           ` Sander Vanheule
  0 siblings, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-24 15:30 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Andrew Lunn,
	Linux Kernel Mailing List

On Mon, 2021-05-24 at 15:47 +0300, Andy Shevchenko wrote:
> On Mon, May 24, 2021 at 3:04 PM Sander Vanheule <sander@svanheule.net> wrote:
> > On Mon, 2021-05-24 at 13:24 +0300, Andy Shevchenko wrote:
> > > On Mon, May 24, 2021 at 1:34 AM Sander Vanheule <sander@svanheule.net>
> > > wrote:
> 
> ...
> 
> > > > +       if (ret != 2)
> > > > +               return -ENODEV;
> > > 
> > > I would say -EINVAL, but -ENODEV is similarly okay.
> > 
> > Any specific reason you think EINVAL is more appropriate than ENODEV?
> 
> My logic is that the initial values (from resource provider) are incorrect.
> But as I said, I'm fine with either.

Ok, that makes sense. Actually, I'm already using "address invalid" in the error
messages when reading the address fails, so I'll change to EINVAL for
consistency.


> 
> > > > +       int err;
> > > 
> > > ret or err? Be consistent across a single driver.
> > 
> > I had first used 'err' for both fwnode_property_count_u32() and
> > fwnode_property_read_u32_array(). The former returns "actual count or error
> > code", while the latter is only "error code". And I found it weird to read
> > the
> > code as "does error code equal 2", if I used 'err' as variable name.
> > 
> > I've split this up:
> >  * addr_count for fwnode_property_count_u32's result
> >  * err for fwnode_property_read_u32_array's result
> > 
> > Since addr_count is only used before err is touched, I guess the compiler
> > will
> > optimize this out anyway?
> 
> Usually we do this pattern (and it seems you missed the point, name of
> variable is ret in some functions and err in the rest):
> 
> err /* ret */ = foo();
> if (err < 0)
>   return err;
> count = err;

I had only used 'ret' specifically in this one function, because I didn't like 
"if (err != 2)" (and I apparently decided that I disliked that more than the
inconsistency introduced by using 'ret'). I'll stick to calling the variable
'err', and change the clause to (err != ARRAY_SIZE(addr)) to make it more
obvious that 2 isn't just some random return value.


Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-24 15:03           ` Sander Vanheule
@ 2021-05-24 16:30             ` Andy Shevchenko
  2021-05-25 17:11               ` Andy Shevchenko
  0 siblings, 1 reply; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-24 16:30 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Andrew Lunn, Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Linux Kernel Mailing List

On Mon, May 24, 2021 at 6:03 PM Sander Vanheule <sander@svanheule.net> wrote:
> On Mon, 2021-05-24 at 15:54 +0300, Andy Shevchenko wrote:
> > On Mon, May 24, 2021 at 2:41 PM Sander Vanheule <sander@svanheule.net> wrote:
> > > On Mon, 2021-05-24 at 10:53 +0300, Andy Shevchenko wrote:
> > > > On Mon, May 24, 2021 at 4:11 AM Andrew Lunn <andrew@lunn.ch> wrote:

...

> Ok, I will add the link to the cover letter for the next version. Does it need
> to be in a Link-tag, or can just be a reference?

Some kind of reference, no need to have a special tag in the cover letter.


...

> > > > > >   - Introduce GPIO regmap quirks to set output direction first
> > > > >
> > > > > I thought you had determined it was possible to set output before
> > > > > direction?
> > > >
> > > > Same thoughts when I saw an updated version of that patch. My
> > > > anticipation was to not see it at all.
> > >
> > > The two devices I've been trying to test the behaviour on are:
> > >  * Netgear GS110TPP: has an RTL8231 with three LEDs, each driven via a pin
> > >    configured as (active-low) GPIO. The LEDs are easy for a quick visual
> > > check.
> > >  * Zyxel GS1900-8: RTL8231 used for the front panel button, and an active-
> > > low
> > >    GPIO used to hard reset the main SoC (an RTL8380). I've modified this
> > > board
> > >    to change some of the strapping pin values, but testing with the jumpers
> > > and
> > >    pull-up/down resistors is a bit more tedious.
> > >
> > > On the Netgear, I tested the following with and without the quirk:
> > >
> > >    # Set as OUT-LOW twice, to avoid the quirk. Always turns the LED on
> > >    gpioset 1 32=0; gpioset 1 32=0
> > >    # Get value to change to input, turns the LED off (high impedance)
> > >    # Will return 1 due to (weak) internal pull-up
> > >    gpioget 1 32
> > >    # Set as OUT-HIGH, should result in LED off
> > >    # When the quirk is disabled, the LED turns on (i.e. old OUT-LOW value)
> > >    # When the quirk is enabled, the LED remains off (i.e. correct OUT-HIGH
> > > value)
> > >    gpioset 1 32=1
> > >
> > > Now, what's confusing (to me) is that the inverse doesn't depend on the
> > > quirk:
> > >
> > >    # Set as OUT-HIGH twice
> > >    gpioset 1 32=1; gpioset 1 32=1
> > >    # Change to high-Z
> > >    gpioget 1 32
> > >    # Set to OUT-LOW, always results in LED on, with or without quirk
> > >    gpioset 1 32=0
> > >
> > > Any idea why this would be (or appear) broken on the former case, but not on
> > > the
> > > latter?
> >
> > GPIO tools for the shell are context-less. Can you reproduce this with
> > the legacy sysfs interface?
> >
> > > I was trying to reproduce this behaviour on the Zyxel, but using the
> > > strapping
> > > pins that are also used to configure the device's address. So perhaps the
> > > pull-
> > > ups/-downs were confusing the results. Using a separate pin on the Zyxel's
> > > RTL8231, I've now been able to confirm the same behaviour as on the Netgear,
> > > including capturing the resulting glitch (with my simple logic analyser)
> > > when
> > > enabling the quirk in the first test case.
> > >
> > > I hope this explains why I've still included the quirk in this revision. If
> > > not,
> > > please let me know what isn't clear.
> >
> > Do you possess a schematic of either of the devices and a link to the
> > RTL datasheet (Btw, if it's publicly available, or you have a link
> > that will ask for necessary sign-in it would be nice to include the
> > link to it as a Datasheet: tag)?
>
> Sadly, I don't. Most of the info we have comes from code archives of switch
> vendors (Zyxel, Cisco etc). Boards need to be reverse engineered, and the few
> leaked datasheets that can be found on the internet aren't exactly thick in
> information.
>
> The RTL8231 datasheet is actually quite useful, but makes no mention of the
> output value isse. Since this isn't an official resource, I don't think it would
> be appropriate to link it via a Datasheet: tag.
> https://github.com/libc0607/Realtek_switch_hacking/blob/files/RTL8231_Datasheet_
> 1.2.pdf
>
> Looking at the datasheet again, I came up with a... terrible hack to work around
> the output value issue.
>
> The chip also has GPIO_INVERT registers that I hadn't used until now, because
> the logical inversion is handled in the kernel. However, these inversion
> registers only apply to the output values. So, I could implement glitch-free
> output behaviour in the following way:
>  * After chip reset, and before enabling the output driver (MFD initialisation):
>     - Mux all pins as GPIO
>     - Change all pins to outputs,

No. no, no. This is much worse than the glitches. You never know what
the hardware is connected there and it's potential breakage (on hw
level) possible.

>  so the data registers (0x1c-0x1e) become writable
>     - Write value 0 to all pins
>     - Change all pins to GPI to change them into high-Z
>  * In the pinctrl/gpio driver:
>     - Use data registers as input-only
>     - Use inversion register to determine output value (can be written any time)
>
> The above gives glitch-free outputs, but the values that are read back (when
> configured as output), come from the data registers. They should now be coming
> from the inversion (reg_set_base) registers, but the code prefers to use the
> data registers (reg_dat_base).

Lemme read the datasheet and see if I find any clue for the hw behaviour.

--
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-24 16:30             ` Andy Shevchenko
@ 2021-05-25 17:11               ` Andy Shevchenko
  2021-05-25 18:00                 ` Sander Vanheule
  2021-05-26 21:02                 ` Sander Vanheule
  0 siblings, 2 replies; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-25 17:11 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Andrew Lunn, Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Linux Kernel Mailing List

On Mon, May 24, 2021 at 7:30 PM Andy Shevchenko
<andy.shevchenko@gmail.com> wrote:
> On Mon, May 24, 2021 at 6:03 PM Sander Vanheule <sander@svanheule.net> wrote:
> > On Mon, 2021-05-24 at 15:54 +0300, Andy Shevchenko wrote:

...

> > Sadly, I don't. Most of the info we have comes from code archives of switch
> > vendors (Zyxel, Cisco etc). Boards need to be reverse engineered, and the few
> > leaked datasheets that can be found on the internet aren't exactly thick in
> > information.
> >
> > The RTL8231 datasheet is actually quite useful, but makes no mention of the
> > output value isse. Since this isn't an official resource, I don't think it would
> > be appropriate to link it via a Datasheet: tag.
> > https://github.com/libc0607/Realtek_switch_hacking/blob/files/RTL8231_Datasheet_
> > 1.2.pdf
> >
> > Looking at the datasheet again, I came up with a... terrible hack to work around
> > the output value issue.
> >
> > The chip also has GPIO_INVERT registers that I hadn't used until now, because
> > the logical inversion is handled in the kernel. However, these inversion
> > registers only apply to the output values. So, I could implement glitch-free
> > output behaviour in the following way:
> >  * After chip reset, and before enabling the output driver (MFD initialisation):
> >     - Mux all pins as GPIO
> >     - Change all pins to outputs,
>
> No. no, no. This is much worse than the glitches. You never know what
> the hardware is connected there and it's potential breakage (on hw
> level) possible.
>
> >  so the data registers (0x1c-0x1e) become writable
> >     - Write value 0 to all pins
> >     - Change all pins to GPI to change them into high-Z
> >  * In the pinctrl/gpio driver:
> >     - Use data registers as input-only
> >     - Use inversion register to determine output value (can be written any time)
> >
> > The above gives glitch-free outputs, but the values that are read back (when
> > configured as output), come from the data registers. They should now be coming
> > from the inversion (reg_set_base) registers, but the code prefers to use the
> > data registers (reg_dat_base).
>
> Lemme read the datasheet and see if I find any clue for the hw behaviour.

Thank you for your patience!

Have you explored the possibility of using En_Sync_GPIO?

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-25 17:11               ` Andy Shevchenko
@ 2021-05-25 18:00                 ` Sander Vanheule
  2021-05-26 21:02                 ` Sander Vanheule
  1 sibling, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-05-25 18:00 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Andrew Lunn, Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Linux Kernel Mailing List

On Tue, 2021-05-25 at 20:11 +0300, Andy Shevchenko wrote:
> On Mon, May 24, 2021 at 7:30 PM Andy Shevchenko
> <andy.shevchenko@gmail.com> wrote:
> > On Mon, May 24, 2021 at 6:03 PM Sander Vanheule <sander@svanheule.net>
> > wrote:
> > > On Mon, 2021-05-24 at 15:54 +0300, Andy Shevchenko wrote:
> 
> ...
> 
> > > Sadly, I don't. Most of the info we have comes from code archives of
> > > switch
> > > vendors (Zyxel, Cisco etc). Boards need to be reverse engineered, and the
> > > few
> > > leaked datasheets that can be found on the internet aren't exactly thick
> > > in
> > > information.
> > > 
> > > The RTL8231 datasheet is actually quite useful, but makes no mention of
> > > the
> > > output value isse. Since this isn't an official resource, I don't think it
> > > would
> > > be appropriate to link it via a Datasheet: tag.
> > > https://github.com/libc0607/Realtek_switch_hacking/blob/files/RTL8231_Datasheet_
> > > 1.2.pdf
> > > 
> > > Looking at the datasheet again, I came up with a... terrible hack to work
> > > around
> > > the output value issue.
> > > 
> > > The chip also has GPIO_INVERT registers that I hadn't used until now,
> > > because
> > > the logical inversion is handled in the kernel. However, these inversion
> > > registers only apply to the output values. So, I could implement glitch-
> > > free
> > > output behaviour in the following way:
> > >  * After chip reset, and before enabling the output driver (MFD
> > > initialisation):
> > >     - Mux all pins as GPIO
> > >     - Change all pins to outputs,
> > 
> > No. no, no. This is much worse than the glitches. You never know what
> > the hardware is connected there and it's potential breakage (on hw
> > level) possible.
> > 
> > >  so the data registers (0x1c-0x1e) become writable
> > >     - Write value 0 to all pins
> > >     - Change all pins to GPI to change them into high-Z
> > >  * In the pinctrl/gpio driver:
> > >     - Use data registers as input-only
> > >     - Use inversion register to determine output value (can be written any
> > > time)
> > > 
> > > The above gives glitch-free outputs, but the values that are read back
> > > (when
> > > configured as output), come from the data registers. They should now be
> > > coming
> > > from the inversion (reg_set_base) registers, but the code prefers to use
> > > the
> > > data registers (reg_dat_base).
> > 
> > Lemme read the datasheet and see if I find any clue for the hw behaviour.
> 
> Thank you for your patience!
> 
> Have you explored the possibility of using En_Sync_GPIO?

I haven't (output latching doesn't really appear to be a thing in the gpio
framework?), but I did notice that the main SoC's RTL8231 integration uses it.

Let me play around with it to see if it also latches the pin direction, or if
that's always an immediate change.


Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-25 17:11               ` Andy Shevchenko
  2021-05-25 18:00                 ` Sander Vanheule
@ 2021-05-26 21:02                 ` Sander Vanheule
  2021-05-27 10:38                   ` Andy Shevchenko
  1 sibling, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-26 21:02 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Andrew Lunn, Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Linux Kernel Mailing List

On Tue, 2021-05-25 at 20:11 +0300, Andy Shevchenko wrote:
> On Mon, May 24, 2021 at 7:30 PM Andy Shevchenko
> <andy.shevchenko@gmail.com> wrote:
> > On Mon, May 24, 2021 at 6:03 PM Sander Vanheule <sander@svanheule.net>
> > wrote:
> > > On Mon, 2021-05-24 at 15:54 +0300, Andy Shevchenko wrote:
> 
> ...
> 
> > > Sadly, I don't. Most of the info we have comes from code archives of
> > > switch
> > > vendors (Zyxel, Cisco etc). Boards need to be reverse engineered, and the
> > > few
> > > leaked datasheets that can be found on the internet aren't exactly thick
> > > in
> > > information.
> > > 
> > > The RTL8231 datasheet is actually quite useful, but makes no mention of
> > > the
> > > output value isse. Since this isn't an official resource, I don't think it
> > > would
> > > be appropriate to link it via a Datasheet: tag.
> > > https://github.com/libc0607/Realtek_switch_hacking/blob/files/RTL8231_Datasheet_
> > > 1.2.pdf
> > > 
> > > Looking at the datasheet again, I came up with a... terrible hack to work
> > > around
> > > the output value issue.
> > > 
> > > The chip also has GPIO_INVERT registers that I hadn't used until now,
> > > because
> > > the logical inversion is handled in the kernel. However, these inversion
> > > registers only apply to the output values. So, I could implement glitch-
> > > free
> > > output behaviour in the following way:
> > >  * After chip reset, and before enabling the output driver (MFD
> > > initialisation):
> > >     - Mux all pins as GPIO
> > >     - Change all pins to outputs,
> > 
> > No. no, no. This is much worse than the glitches. You never know what
> > the hardware is connected there and it's potential breakage (on hw
> > level) possible.
> > 
> > >  so the data registers (0x1c-0x1e) become writable
> > >     - Write value 0 to all pins
> > >     - Change all pins to GPI to change them into high-Z
> > >  * In the pinctrl/gpio driver:
> > >     - Use data registers as input-only
> > >     - Use inversion register to determine output value (can be written any
> > > time)
> > > 
> > > The above gives glitch-free outputs, but the values that are read back
> > > (when
> > > configured as output), come from the data registers. They should now be
> > > coming
> > > from the inversion (reg_set_base) registers, but the code prefers to use
> > > the
> > > data registers (reg_dat_base).
> > 
> > Lemme read the datasheet and see if I find any clue for the hw behaviour.
> 
> Thank you for your patience!
> 
> Have you explored the possibility of using En_Sync_GPIO?

Got around to testing things.

If En_Sync_GPIO is enabled, it's still possible to change the pin direction
without also writing the Sync_GPIO bit. So even with the latching, glitches are
still produced.

As long as Sync_GPIO is not set to latch the new values, it also appears that
reads of the data registers result in the current output value, not the new one.

As a different test, I've added a pull-down, to make the input level low. Now I
see the opposite behaviour as before (with set-value-before-direction):
 * OUT-HIGH > IN (low) > OUT-LOW: results in a high level (i.e. old value)
 * OUT-HIGH > IN (low) > OUT-HIGH: results in a high level (new/old value)
 * OUT-LOW > IN (low) > OUT-HIGH: results in a high level (new value, or toggled
   old value?)
 * OUT-LOW > IN (low) > OUT-LOW: results in a low level (new/old value)

For reference, with a pull-up:
 * OUT-HIGH > IN (high) > OUT-HIGH: high result
 * OUT-HIGH > IN (high) > OUT-LOW: low result
 * OUT-LOW > IN (high) > OUT-HIGH: low result
 * OUT-LOW > IN (high) > OUT-LOW: low result

I've only tested this with the sysfs interface, so I don't know what the result
would be on multiple writes to the data register (during input, but probably not
very relevant). Nor have I tested direction changes if the input has changed
between two output values.

I may have some time tomorrow for more testing, but otherwise it'll have to wait
until the weekend. Any other ideas in the meantime?


Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-26 21:02                 ` Sander Vanheule
@ 2021-05-27 10:38                   ` Andy Shevchenko
  2021-05-27 10:41                     ` Hans de Goede
  0 siblings, 1 reply; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-27 10:38 UTC (permalink / raw)
  To: Sander Vanheule, Hans de Goede
  Cc: Andrew Lunn, Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Linux Kernel Mailing List

+Cc: Hans

Hans, sorry for disturbing you later too much. Here we have "nice"
hardware which can't be used in a glitch-free mode (somehow it reminds
me lynxpoint, baytrail, cherryview designs). If you have any ideas to
share (no need to dive deep or look at it if you have no time), you're
welcome.

On Thu, May 27, 2021 at 12:02 AM Sander Vanheule <sander@svanheule.net> wrote:
>
> On Tue, 2021-05-25 at 20:11 +0300, Andy Shevchenko wrote:
> > On Mon, May 24, 2021 at 7:30 PM Andy Shevchenko
> > <andy.shevchenko@gmail.com> wrote:
> > > On Mon, May 24, 2021 at 6:03 PM Sander Vanheule <sander@svanheule.net>
> > > wrote:
> > > > On Mon, 2021-05-24 at 15:54 +0300, Andy Shevchenko wrote:
> >
> > ...
> >
> > > > Sadly, I don't. Most of the info we have comes from code archives of
> > > > switch
> > > > vendors (Zyxel, Cisco etc). Boards need to be reverse engineered, and the
> > > > few
> > > > leaked datasheets that can be found on the internet aren't exactly thick
> > > > in
> > > > information.
> > > >
> > > > The RTL8231 datasheet is actually quite useful, but makes no mention of
> > > > the
> > > > output value isse. Since this isn't an official resource, I don't think it
> > > > would
> > > > be appropriate to link it via a Datasheet: tag.
> > > > https://github.com/libc0607/Realtek_switch_hacking/blob/files/RTL8231_Datasheet_
> > > > 1.2.pdf
> > > >
> > > > Looking at the datasheet again, I came up with a... terrible hack to work
> > > > around
> > > > the output value issue.
> > > >
> > > > The chip also has GPIO_INVERT registers that I hadn't used until now,
> > > > because
> > > > the logical inversion is handled in the kernel. However, these inversion
> > > > registers only apply to the output values. So, I could implement glitch-
> > > > free
> > > > output behaviour in the following way:
> > > >  * After chip reset, and before enabling the output driver (MFD
> > > > initialisation):
> > > >     - Mux all pins as GPIO
> > > >     - Change all pins to outputs,
> > >
> > > No. no, no. This is much worse than the glitches. You never know what
> > > the hardware is connected there and it's potential breakage (on hw
> > > level) possible.
> > >
> > > >  so the data registers (0x1c-0x1e) become writable
> > > >     - Write value 0 to all pins
> > > >     - Change all pins to GPI to change them into high-Z
> > > >  * In the pinctrl/gpio driver:
> > > >     - Use data registers as input-only
> > > >     - Use inversion register to determine output value (can be written any
> > > > time)
> > > >
> > > > The above gives glitch-free outputs, but the values that are read back
> > > > (when
> > > > configured as output), come from the data registers. They should now be
> > > > coming
> > > > from the inversion (reg_set_base) registers, but the code prefers to use
> > > > the
> > > > data registers (reg_dat_base).
> > >
> > > Lemme read the datasheet and see if I find any clue for the hw behaviour.
> >
> > Thank you for your patience!
> >
> > Have you explored the possibility of using En_Sync_GPIO?
>
> Got around to testing things.
>
> If En_Sync_GPIO is enabled, it's still possible to change the pin direction
> without also writing the Sync_GPIO bit. So even with the latching, glitches are
> still produced.
>
> As long as Sync_GPIO is not set to latch the new values, it also appears that
> reads of the data registers result in the current output value, not the new one.
>
> As a different test, I've added a pull-down, to make the input level low. Now I
> see the opposite behaviour as before (with set-value-before-direction):
>  * OUT-HIGH > IN (low) > OUT-LOW: results in a high level (i.e. old value)
>  * OUT-HIGH > IN (low) > OUT-HIGH: results in a high level (new/old value)
>  * OUT-LOW > IN (low) > OUT-HIGH: results in a high level (new value, or toggled
>    old value?)
>  * OUT-LOW > IN (low) > OUT-LOW: results in a low level (new/old value)
>
> For reference, with a pull-up:
>  * OUT-HIGH > IN (high) > OUT-HIGH: high result
>  * OUT-HIGH > IN (high) > OUT-LOW: low result
>  * OUT-LOW > IN (high) > OUT-HIGH: low result
>  * OUT-LOW > IN (high) > OUT-LOW: low result
>
> I've only tested this with the sysfs interface, so I don't know what the result
> would be on multiple writes to the data register (during input, but probably not
> very relevant). Nor have I tested direction changes if the input has changed
> between two output values.
>
> I may have some time tomorrow for more testing, but otherwise it'll have to wait
> until the weekend. Any other ideas in the meantime?

No ideas so far. In x86 we used to have something similar (baytrail,
cherryview, lynxpoint), but it's firmware assisted. I think that this
hardware (realtek) is supposed either
- to be firmware / bootloader assisted, so in a way that platform is
preconfigured when Linux starts and any GPIO request won't be harmful
as long as it doesn't change direction on the pins (which is usually
guaranteed by DT and corresponding drivers to do the correct things)
- be used for glitch-tolerant hardware (LEDs, for example, where
nobody usually will noticed 1ms blink)

That said, I have not been convinced we have to quirk gpio-regmap for
this one. Just describe the issues with hardware in the accompanying
documentation.

But if maintainers or somebody comes with a better / different
approach I am all ears.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-27 10:38                   ` Andy Shevchenko
@ 2021-05-27 10:41                     ` Hans de Goede
  0 siblings, 0 replies; 114+ messages in thread
From: Hans de Goede @ 2021-05-27 10:41 UTC (permalink / raw)
  To: Andy Shevchenko, Sander Vanheule
  Cc: Andrew Lunn, Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Linux Kernel Mailing List

Hi,

On 5/27/21 12:38 PM, Andy Shevchenko wrote:
> +Cc: Hans
> 
> Hans, sorry for disturbing you later too much. Here we have "nice"
> hardware which can't be used in a glitch-free mode (somehow it reminds
> me lynxpoint, baytrail, cherryview designs). If you have any ideas to
> share (no need to dive deep or look at it if you have no time), you're
> welcome.

I'm afraid I've no ideas how to solve this nicely. Documenting the
issue might be the best we can do.

Regards,

Hans



> 
> On Thu, May 27, 2021 at 12:02 AM Sander Vanheule <sander@svanheule.net> wrote:
>>
>> On Tue, 2021-05-25 at 20:11 +0300, Andy Shevchenko wrote:
>>> On Mon, May 24, 2021 at 7:30 PM Andy Shevchenko
>>> <andy.shevchenko@gmail.com> wrote:
>>>> On Mon, May 24, 2021 at 6:03 PM Sander Vanheule <sander@svanheule.net>
>>>> wrote:
>>>>> On Mon, 2021-05-24 at 15:54 +0300, Andy Shevchenko wrote:
>>>
>>> ...
>>>
>>>>> Sadly, I don't. Most of the info we have comes from code archives of
>>>>> switch
>>>>> vendors (Zyxel, Cisco etc). Boards need to be reverse engineered, and the
>>>>> few
>>>>> leaked datasheets that can be found on the internet aren't exactly thick
>>>>> in
>>>>> information.
>>>>>
>>>>> The RTL8231 datasheet is actually quite useful, but makes no mention of
>>>>> the
>>>>> output value isse. Since this isn't an official resource, I don't think it
>>>>> would
>>>>> be appropriate to link it via a Datasheet: tag.
>>>>> https://github.com/libc0607/Realtek_switch_hacking/blob/files/RTL8231_Datasheet_
>>>>> 1.2.pdf
>>>>>
>>>>> Looking at the datasheet again, I came up with a... terrible hack to work
>>>>> around
>>>>> the output value issue.
>>>>>
>>>>> The chip also has GPIO_INVERT registers that I hadn't used until now,
>>>>> because
>>>>> the logical inversion is handled in the kernel. However, these inversion
>>>>> registers only apply to the output values. So, I could implement glitch-
>>>>> free
>>>>> output behaviour in the following way:
>>>>>  * After chip reset, and before enabling the output driver (MFD
>>>>> initialisation):
>>>>>     - Mux all pins as GPIO
>>>>>     - Change all pins to outputs,
>>>>
>>>> No. no, no. This is much worse than the glitches. You never know what
>>>> the hardware is connected there and it's potential breakage (on hw
>>>> level) possible.
>>>>
>>>>>  so the data registers (0x1c-0x1e) become writable
>>>>>     - Write value 0 to all pins
>>>>>     - Change all pins to GPI to change them into high-Z
>>>>>  * In the pinctrl/gpio driver:
>>>>>     - Use data registers as input-only
>>>>>     - Use inversion register to determine output value (can be written any
>>>>> time)
>>>>>
>>>>> The above gives glitch-free outputs, but the values that are read back
>>>>> (when
>>>>> configured as output), come from the data registers. They should now be
>>>>> coming
>>>>> from the inversion (reg_set_base) registers, but the code prefers to use
>>>>> the
>>>>> data registers (reg_dat_base).
>>>>
>>>> Lemme read the datasheet and see if I find any clue for the hw behaviour.
>>>
>>> Thank you for your patience!
>>>
>>> Have you explored the possibility of using En_Sync_GPIO?
>>
>> Got around to testing things.
>>
>> If En_Sync_GPIO is enabled, it's still possible to change the pin direction
>> without also writing the Sync_GPIO bit. So even with the latching, glitches are
>> still produced.
>>
>> As long as Sync_GPIO is not set to latch the new values, it also appears that
>> reads of the data registers result in the current output value, not the new one.
>>
>> As a different test, I've added a pull-down, to make the input level low. Now I
>> see the opposite behaviour as before (with set-value-before-direction):
>>  * OUT-HIGH > IN (low) > OUT-LOW: results in a high level (i.e. old value)
>>  * OUT-HIGH > IN (low) > OUT-HIGH: results in a high level (new/old value)
>>  * OUT-LOW > IN (low) > OUT-HIGH: results in a high level (new value, or toggled
>>    old value?)
>>  * OUT-LOW > IN (low) > OUT-LOW: results in a low level (new/old value)
>>
>> For reference, with a pull-up:
>>  * OUT-HIGH > IN (high) > OUT-HIGH: high result
>>  * OUT-HIGH > IN (high) > OUT-LOW: low result
>>  * OUT-LOW > IN (high) > OUT-HIGH: low result
>>  * OUT-LOW > IN (high) > OUT-LOW: low result
>>
>> I've only tested this with the sysfs interface, so I don't know what the result
>> would be on multiple writes to the data register (during input, but probably not
>> very relevant). Nor have I tested direction changes if the input has changed
>> between two output values.
>>
>> I may have some time tomorrow for more testing, but otherwise it'll have to wait
>> until the weekend. Any other ideas in the meantime?
> 
> No ideas so far. In x86 we used to have something similar (baytrail,
> cherryview, lynxpoint), but it's firmware assisted. I think that this
> hardware (realtek) is supposed either
> - to be firmware / bootloader assisted, so in a way that platform is
> preconfigured when Linux starts and any GPIO request won't be harmful
> as long as it doesn't change direction on the pins (which is usually
> guaranteed by DT and corresponding drivers to do the correct things)
> - be used for glitch-tolerant hardware (LEDs, for example, where
> nobody usually will noticed 1ms blink)
> 
> That said, I have not been convinced we have to quirk gpio-regmap for
> this one. Just describe the issues with hardware in the accompanying
> documentation.
> 
> But if maintainers or somebody comes with a better / different
> approach I am all ears.
> 


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 3/6] dt-bindings: mfd: Binding for RTL8231
  2021-05-23 22:34   ` [PATCH v3 3/6] dt-bindings: mfd: Binding for RTL8231 Sander Vanheule
@ 2021-05-27 23:31     ` Linus Walleij
  2021-06-02 19:02     ` Rob Herring
  1 sibling, 0 replies; 114+ messages in thread
From: Linus Walleij @ 2021-05-27 23:31 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Bartosz Golaszewski, Linux LED Subsystem,
	open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
	open list:GPIO SUBSYSTEM, Andrew Lunn, Andy Shevchenko,
	linux-kernel

On Mon, May 24, 2021 at 12:34 AM Sander Vanheule <sander@svanheule.net> wrote:

> Add a binding description for the Realtek RTL8231, a GPIO and LED
> expander chip commonly used in ethernet switches based on a Realtek
> switch SoC. These chips can be addressed via an MDIO or SMI bus, or used
> as a plain 36-bit shift register.
>
> This binding only describes the feature set provided by the MDIO/SMI
> configuration, and covers the GPIO, PWM, and pin control properties. The
> LED properties are defined in a separate binding.
>
> Signed-off-by: Sander Vanheule <sander@svanheule.net>

This looks good to me from a GPIO and pin control PoV:
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 5/6] pinctrl: Add RTL8231 pin control and GPIO support
  2021-05-23 22:34   ` [PATCH v3 5/6] pinctrl: Add RTL8231 pin control and GPIO support Sander Vanheule
@ 2021-05-28  6:29     ` Michael Walle
  2021-05-28  6:42       ` Sander Vanheule
  0 siblings, 1 reply; 114+ messages in thread
From: Michael Walle @ 2021-05-28  6:29 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Linus Walleij,
	Bartosz Golaszewski, linux-leds, devicetree, linux-gpio,
	Andrew Lunn, Andy Shevchenko, linux-kernel

> +	gpio_cfg.reg_dat_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
> +	gpio_cfg.reg_set_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
> +	gpio_cfg.reg_dir_in_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DIR0);

Btw. you'd only need GPIO_REGMAP_ADDR(x) if x might be 0. Because you 
have
a constant != 0 there, you could save the GPIO_REGMAP_ADDR() call. You
could drop this if you like, but no need to respin the series for this.

-michael

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-24 11:41       ` Sander Vanheule
  2021-05-24 12:54         ` Andy Shevchenko
@ 2021-05-28  6:37         ` Michael Walle
  2021-05-30 16:19           ` Sander Vanheule
  1 sibling, 1 reply; 114+ messages in thread
From: Michael Walle @ 2021-05-28  6:37 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Andy Shevchenko, Andrew Lunn, Pavel Machek, Rob Herring,
	Lee Jones, Mark Brown, Greg Kroah-Hartman, Rafael J . Wysocki,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Linux Kernel Mailing List

Am 2021-05-24 13:41, schrieb Sander Vanheule:
> Hi Andy, Andrew,
> 
> On Mon, 2021-05-24 at 10:53 +0300, Andy Shevchenko wrote:
>> On Mon, May 24, 2021 at 4:11 AM Andrew Lunn <andrew@lunn.ch> wrote:
>> >
>> > > Changes since v2:
>> > >   - MDIO regmap support was merged, so patch is dropped here
>> >
>> > Do you have any idea how this will get merged. It sounds like one of
>> > the Maintainers will need a stable branch of regmap.
>> 
>> This is not a problem if Mark provides an immutable branch to pull 
>> from.
> 
> Mark has a tag (regmap-mdio) for this patch:
> https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap.git/tag/?h=regmap-mdio
> 
>> 
>> > >   - Introduce GPIO regmap quirks to set output direction first
>> >
>> > I thought you had determined it was possible to set output before
>> > direction?
>> 
>> Same thoughts when I saw an updated version of that patch. My
>> anticipation was to not see it at all.
> 
> The two devices I've been trying to test the behaviour on are:
>  * Netgear GS110TPP: has an RTL8231 with three LEDs, each driven via a 
> pin
>    configured as (active-low) GPIO. The LEDs are easy for a quick 
> visual check.
>  * Zyxel GS1900-8: RTL8231 used for the front panel button, and an 
> active-low
>    GPIO used to hard reset the main SoC (an RTL8380). I've modified 
> this board
>    to change some of the strapping pin values, but testing with the 
> jumpers and
>    pull-up/down resistors is a bit more tedious.
> 
> On the Netgear, I tested the following with and without the quirk:
> 
>    # Set as OUT-LOW twice, to avoid the quirk. Always turns the LED on
>    gpioset 1 32=0; gpioset 1 32=0
>    # Get value to change to input, turns the LED off (high impedance)
>    # Will return 1 due to (weak) internal pull-up
>    gpioget 1 32
>    # Set as OUT-HIGH, should result in LED off
>    # When the quirk is disabled, the LED turns on (i.e. old OUT-LOW 
> value)
>    # When the quirk is enabled, the LED remains off (i.e. correct
> OUT-HIGH value)
>    gpioset 1 32=1
> 
> Now, what's confusing (to me) is that the inverse doesn't depend on the 
> quirk:
> 
>    # Set as OUT-HIGH twice
>    gpioset 1 32=1; gpioset 1 32=1
>    # Change to high-Z
>    gpioget 1 32
>    # Set to OUT-LOW, always results in LED on, with or without quirk
>    gpioset 1 32=0
> 
> Any idea why this would be (or appear) broken on the former case, but 
> not on the
> latter?

Before reading this, I'd have guessed that they switch the internal 
register
depending on the GPIO direction; I mean there is only one register 
address
for both the input and the output register. Hm.

Did you try playing around with raw register accesses and see if the 
value
of the GPIO data register is changing when you switch GPIOs to 
input/output.

Eg. you could try https://github.com/kontron/miitool to access the 
registers
from userspace (your ethernet controller has to have support for the 
ioctl's
though, see commit a613bafec516 ("enetc: add ioctl() support for 
PHY-related
ops") for an example).

-michael

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 1/6] gpio: regmap: Add quirk for output data register
  2021-05-23 22:33   ` [PATCH v3 1/6] gpio: regmap: Add quirk for output data register Sander Vanheule
@ 2021-05-28  6:40     ` Michael Walle
  2021-06-03 10:03       ` Sander Vanheule
  2021-05-31  7:25     ` Bartosz Golaszewski
  1 sibling, 1 reply; 114+ messages in thread
From: Michael Walle @ 2021-05-28  6:40 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Linus Walleij,
	Bartosz Golaszewski, linux-leds, devicetree, linux-gpio,
	Andrew Lunn, Andy Shevchenko, linux-kernel

Am 2021-05-24 00:33, schrieb Sander Vanheule:
> GPIO chips may not support setting the output value when a pin is
> configured as an input, although the current implementation assumes 
> this
> is always possible.
> 
> Add support for setting pin direction before value. The order defaults
> to setting the value first, but this can be reversed by setting the
> GPIO_REGMAP_QUIRK_SET_DIRECTION_FIRST flag in regmap_config.quirks.

Nice! If this is really needed:

Reviewed-by: Michael Walle <michael@walle.cc>

-michael

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 5/6] pinctrl: Add RTL8231 pin control and GPIO support
  2021-05-28  6:29     ` Michael Walle
@ 2021-05-28  6:42       ` Sander Vanheule
  2021-05-28  6:43         ` Michael Walle
  0 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-28  6:42 UTC (permalink / raw)
  To: Michael Walle
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Linus Walleij,
	Bartosz Golaszewski, linux-leds, devicetree, linux-gpio,
	Andrew Lunn, Andy Shevchenko, linux-kernel

Hi Michael,

On Fri, 2021-05-28 at 08:29 +0200, Michael Walle wrote:
> > +       gpio_cfg.reg_dat_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
> > +       gpio_cfg.reg_set_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
> > +       gpio_cfg.reg_dir_in_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DIR0);
> 
> Btw. you'd only need GPIO_REGMAP_ADDR(x) if x might be 0. Because you 
> have
> a constant != 0 there, you could save the GPIO_REGMAP_ADDR() call. You
> could drop this if you like, but no need to respin the series for this.

I will need to respin this series anyway, so I can drop the GPIO_REGMAP_ADDR()
calls. I was aware they are no-ops in this case, as register address 0 is not
used for the GPIO functions, so mainly included them as a form of documentation.

Best,
Sander



^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 5/6] pinctrl: Add RTL8231 pin control and GPIO support
  2021-05-28  6:42       ` Sander Vanheule
@ 2021-05-28  6:43         ` Michael Walle
  0 siblings, 0 replies; 114+ messages in thread
From: Michael Walle @ 2021-05-28  6:43 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Linus Walleij,
	Bartosz Golaszewski, linux-leds, devicetree, linux-gpio,
	Andrew Lunn, Andy Shevchenko, linux-kernel

Am 2021-05-28 08:42, schrieb Sander Vanheule:
> On Fri, 2021-05-28 at 08:29 +0200, Michael Walle wrote:
>> > +       gpio_cfg.reg_dat_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
>> > +       gpio_cfg.reg_set_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA0);
>> > +       gpio_cfg.reg_dir_in_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DIR0);
>> 
>> Btw. you'd only need GPIO_REGMAP_ADDR(x) if x might be 0. Because you
>> have
>> a constant != 0 there, you could save the GPIO_REGMAP_ADDR() call. You
>> could drop this if you like, but no need to respin the series for 
>> this.
> 
> I will need to respin this series anyway, so I can drop the 
> GPIO_REGMAP_ADDR()
> calls. I was aware they are no-ops in this case, as register address 0 
> is not
> used for the GPIO functions, so mainly included them as a form of 
> documentation.

It's up to you if you like to change it or keep it.

-michael

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-28  6:37         ` Michael Walle
@ 2021-05-30 16:19           ` Sander Vanheule
  2021-05-30 16:51             ` Hans de Goede
  0 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-30 16:19 UTC (permalink / raw)
  To: Michael Walle, Andy Shevchenko
  Cc: Andrew Lunn, Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Linus Walleij,
	Bartosz Golaszewski, Linux LED Subsystem, devicetree,
	open list:GPIO SUBSYSTEM, Linux Kernel Mailing List,
	Hans de Goede

Hi Michael, Andy,

On Fri, 2021-05-28 at 08:37 +0200, Michael Walle wrote:
> Am 2021-05-24 13:41, schrieb Sander Vanheule:
> > Hi Andy, Andrew,
> > 
> > On Mon, 2021-05-24 at 10:53 +0300, Andy Shevchenko wrote:
> > > On Mon, May 24, 2021 at 4:11 AM Andrew Lunn <andrew@lunn.ch> wrote:
> > > > 
> > > > > Changes since v2:
> > > > >   - MDIO regmap support was merged, so patch is dropped here
> > > > 
> > > > Do you have any idea how this will get merged. It sounds like one of
> > > > the Maintainers will need a stable branch of regmap.
> > > 
> > > This is not a problem if Mark provides an immutable branch to pull 
> > > from.
> > 
> > Mark has a tag (regmap-mdio) for this patch:
> > https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap.git/tag/?h=regmap-mdio
> > 
> > > 
> > > > >   - Introduce GPIO regmap quirks to set output direction first
> > > > 
> > > > I thought you had determined it was possible to set output before
> > > > direction?
> > > 
> > > Same thoughts when I saw an updated version of that patch. My
> > > anticipation was to not see it at all.
> > 
> > The two devices I've been trying to test the behaviour on are:
> >  * Netgear GS110TPP: has an RTL8231 with three LEDs, each driven via a 
> > pin
> >    configured as (active-low) GPIO. The LEDs are easy for a quick 
> > visual check.
> >  * Zyxel GS1900-8: RTL8231 used for the front panel button, and an 
> > active-low
> >    GPIO used to hard reset the main SoC (an RTL8380). I've modified 
> > this board
> >    to change some of the strapping pin values, but testing with the 
> > jumpers and
> >    pull-up/down resistors is a bit more tedious.
> > 
> > On the Netgear, I tested the following with and without the quirk:
> > 
> >    # Set as OUT-LOW twice, to avoid the quirk. Always turns the LED on
> >    gpioset 1 32=0; gpioset 1 32=0
> >    # Get value to change to input, turns the LED off (high impedance)
> >    # Will return 1 due to (weak) internal pull-up
> >    gpioget 1 32
> >    # Set as OUT-HIGH, should result in LED off
> >    # When the quirk is disabled, the LED turns on (i.e. old OUT-LOW 
> > value)
> >    # When the quirk is enabled, the LED remains off (i.e. correct
> > OUT-HIGH value)
> >    gpioset 1 32=1
> > 
> > Now, what's confusing (to me) is that the inverse doesn't depend on the 
> > quirk:
> > 
> >    # Set as OUT-HIGH twice
> >    gpioset 1 32=1; gpioset 1 32=1
> >    # Change to high-Z
> >    gpioget 1 32
> >    # Set to OUT-LOW, always results in LED on, with or without quirk
> >    gpioset 1 32=0
> > 
> > Any idea why this would be (or appear) broken on the former case, but 
> > not on the
> > latter?
> 
> Before reading this, I'd have guessed that they switch the internal 
> register
> depending on the GPIO direction; I mean there is only one register 
> address
> for both the input and the output register. Hm.
> 
> Did you try playing around with raw register accesses and see if the 
> value
> of the GPIO data register is changing when you switch GPIOs to 
> input/output.
> 
> Eg. you could try https://github.com/kontron/miitool to access the 
> registers
> from userspace (your ethernet controller has to have support for the 
> ioctl's
> though, see commit a613bafec516 ("enetc: add ioctl() support for 
> PHY-related
> ops") for an example).

I think I found a solution!

As Michael suggested, I tried raw register reads and writes, to eliminate any
side effects of the intermediate code. I didn't use the ioctls (this isn't a
netdev), but I found regmap's debugfs write functionality, which allowed me to
do the same.

I was trying to reproduce the behaviour I reported earlier, but couldn't. The
output levels were always the intended ones. At some point I realised that the
regmap_update_bits function does a read-modify-write, which might shadow the
actual current output value.
For example:
 * Set output low: current out is low
 * Change to input with pull-up: current out is still low, but DATAx reads high
 * Set output high: RMW reads a high value (the input), so assumes a write is
   not necessary, leaving the old output value (low).

Currently, I see two options:
 * Use regmap_update_bits_base to avoid the lazy RMW behaviour
 * Add a cache for the output data values to the driver, and only use these
   values to write to the output registers. This would allow keeping lazy RMW
   behaviour, which may be a benefit on slow busses.

With either of these implemented, if I set the output value before the
direction, everything works! :-)

Would you like this to be added to regmap-gpio, or should I revert back to a
device-specific implementation?


Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-30 16:19           ` Sander Vanheule
@ 2021-05-30 16:51             ` Hans de Goede
  2021-05-30 18:16               ` Andy Shevchenko
  0 siblings, 1 reply; 114+ messages in thread
From: Hans de Goede @ 2021-05-30 16:51 UTC (permalink / raw)
  To: Sander Vanheule, Michael Walle, Andy Shevchenko
  Cc: Andrew Lunn, Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Linus Walleij,
	Bartosz Golaszewski, Linux LED Subsystem, devicetree,
	open list:GPIO SUBSYSTEM, Linux Kernel Mailing List

Hi,

On 5/30/21 6:19 PM, Sander Vanheule wrote:
> Hi Michael, Andy,
> 
> On Fri, 2021-05-28 at 08:37 +0200, Michael Walle wrote:
>> Am 2021-05-24 13:41, schrieb Sander Vanheule:
>>> Hi Andy, Andrew,
>>>
>>> On Mon, 2021-05-24 at 10:53 +0300, Andy Shevchenko wrote:
>>>> On Mon, May 24, 2021 at 4:11 AM Andrew Lunn <andrew@lunn.ch> wrote:
>>>>>
>>>>>> Changes since v2:
>>>>>>   - MDIO regmap support was merged, so patch is dropped here
>>>>>
>>>>> Do you have any idea how this will get merged. It sounds like one of
>>>>> the Maintainers will need a stable branch of regmap.
>>>>
>>>> This is not a problem if Mark provides an immutable branch to pull 
>>>> from.
>>>
>>> Mark has a tag (regmap-mdio) for this patch:
>>> https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap.git/tag/?h=regmap-mdio
>>>
>>>>
>>>>>>   - Introduce GPIO regmap quirks to set output direction first
>>>>>
>>>>> I thought you had determined it was possible to set output before
>>>>> direction?
>>>>
>>>> Same thoughts when I saw an updated version of that patch. My
>>>> anticipation was to not see it at all.
>>>
>>> The two devices I've been trying to test the behaviour on are:
>>>  * Netgear GS110TPP: has an RTL8231 with three LEDs, each driven via a 
>>> pin
>>>    configured as (active-low) GPIO. The LEDs are easy for a quick 
>>> visual check.
>>>  * Zyxel GS1900-8: RTL8231 used for the front panel button, and an 
>>> active-low
>>>    GPIO used to hard reset the main SoC (an RTL8380). I've modified 
>>> this board
>>>    to change some of the strapping pin values, but testing with the 
>>> jumpers and
>>>    pull-up/down resistors is a bit more tedious.
>>>
>>> On the Netgear, I tested the following with and without the quirk:
>>>
>>>    # Set as OUT-LOW twice, to avoid the quirk. Always turns the LED on
>>>    gpioset 1 32=0; gpioset 1 32=0
>>>    # Get value to change to input, turns the LED off (high impedance)
>>>    # Will return 1 due to (weak) internal pull-up
>>>    gpioget 1 32
>>>    # Set as OUT-HIGH, should result in LED off
>>>    # When the quirk is disabled, the LED turns on (i.e. old OUT-LOW 
>>> value)
>>>    # When the quirk is enabled, the LED remains off (i.e. correct
>>> OUT-HIGH value)
>>>    gpioset 1 32=1
>>>
>>> Now, what's confusing (to me) is that the inverse doesn't depend on the 
>>> quirk:
>>>
>>>    # Set as OUT-HIGH twice
>>>    gpioset 1 32=1; gpioset 1 32=1
>>>    # Change to high-Z
>>>    gpioget 1 32
>>>    # Set to OUT-LOW, always results in LED on, with or without quirk
>>>    gpioset 1 32=0
>>>
>>> Any idea why this would be (or appear) broken on the former case, but 
>>> not on the
>>> latter?
>>
>> Before reading this, I'd have guessed that they switch the internal 
>> register
>> depending on the GPIO direction; I mean there is only one register 
>> address
>> for both the input and the output register. Hm.
>>
>> Did you try playing around with raw register accesses and see if the 
>> value
>> of the GPIO data register is changing when you switch GPIOs to 
>> input/output.
>>
>> Eg. you could try https://github.com/kontron/miitool to access the 
>> registers
>> from userspace (your ethernet controller has to have support for the 
>> ioctl's
>> though, see commit a613bafec516 ("enetc: add ioctl() support for 
>> PHY-related
>> ops") for an example).
> 
> I think I found a solution!
> 
> As Michael suggested, I tried raw register reads and writes, to eliminate any
> side effects of the intermediate code. I didn't use the ioctls (this isn't a
> netdev), but I found regmap's debugfs write functionality, which allowed me to
> do the same.
> 
> I was trying to reproduce the behaviour I reported earlier, but couldn't. The
> output levels were always the intended ones. At some point I realised that the
> regmap_update_bits function does a read-modify-write, which might shadow the
> actual current output value.
> For example:
>  * Set output low: current out is low
>  * Change to input with pull-up: current out is still low, but DATAx reads high
>  * Set output high: RMW reads a high value (the input), so assumes a write is
>    not necessary, leaving the old output value (low).
> 
> Currently, I see two options:
>  * Use regmap_update_bits_base to avoid the lazy RMW behaviour
>  * Add a cache for the output data values to the driver, and only use these
>    values to write to the output registers. This would allow keeping lazy RMW
>    behaviour, which may be a benefit on slow busses.
> 
> With either of these implemented, if I set the output value before the
> direction, everything works! :-)
> 
> Would you like this to be added to regmap-gpio, or should I revert back to a
> device-specific implementation?

Regmap allows you to mark certain ranges as volatile, so that they will not
be cached, these GPIO registers containing the current pin value seems like
a good candidate for this. This is also necessary to make reading the GPIO
work without getting back a stale, cached value.

Regards,

Hans


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-30 16:51             ` Hans de Goede
@ 2021-05-30 18:16               ` Andy Shevchenko
  2021-05-30 21:22                 ` Michael Walle
  2021-06-01  9:59                 ` [PATCH v3 0/6] " Linus Walleij
  0 siblings, 2 replies; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-30 18:16 UTC (permalink / raw)
  To: Hans de Goede
  Cc: Sander Vanheule, Michael Walle, Andrew Lunn, Pavel Machek,
	Rob Herring, Lee Jones, Mark Brown, Greg Kroah-Hartman,
	Rafael J . Wysocki, Linus Walleij, Bartosz Golaszewski,
	Linux LED Subsystem, devicetree, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

On Sun, May 30, 2021 at 7:51 PM Hans de Goede <hdegoede@redhat.com> wrote:
> On 5/30/21 6:19 PM, Sander Vanheule wrote:
> > On Fri, 2021-05-28 at 08:37 +0200, Michael Walle wrote:

...

> > I think I found a solution!
> >
> > As Michael suggested, I tried raw register reads and writes, to eliminate any
> > side effects of the intermediate code. I didn't use the ioctls (this isn't a
> > netdev), but I found regmap's debugfs write functionality, which allowed me to
> > do the same.
> >
> > I was trying to reproduce the behaviour I reported earlier, but couldn't. The
> > output levels were always the intended ones. At some point I realised that the
> > regmap_update_bits function does a read-modify-write, which might shadow the
> > actual current output value.
> > For example:
> >  * Set output low: current out is low
> >  * Change to input with pull-up: current out is still low, but DATAx reads high
> >  * Set output high: RMW reads a high value (the input), so assumes a write is
> >    not necessary, leaving the old output value (low).
> >
> > Currently, I see two options:
> >  * Use regmap_update_bits_base to avoid the lazy RMW behaviour
> >  * Add a cache for the output data values to the driver, and only use these
> >    values to write to the output registers. This would allow keeping lazy RMW
> >    behaviour, which may be a benefit on slow busses.
> >
> > With either of these implemented, if I set the output value before the
> > direction, everything works! :-)
> >
> > Would you like this to be added to regmap-gpio, or should I revert back to a
> > device-specific implementation?
>
> Regmap allows you to mark certain ranges as volatile, so that they will not
> be cached, these GPIO registers containing the current pin value seems like
> a good candidate for this. This is also necessary to make reading the GPIO
> work without getting back a stale, cached value.

After all it seems a simple missed proper register configuration in
the driver for regmap.
Oh, as usual something easy-to-solve requires tons of time to find it. :-)

Sander, I think you may look at gpio-pca953x.c to understand how it
works (volatility of registers).

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-30 18:16               ` Andy Shevchenko
@ 2021-05-30 21:22                 ` Michael Walle
  2021-05-31  8:36                   ` Sander Vanheule
  2021-06-01  9:59                 ` [PATCH v3 0/6] " Linus Walleij
  1 sibling, 1 reply; 114+ messages in thread
From: Michael Walle @ 2021-05-30 21:22 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Hans de Goede, Sander Vanheule, Andrew Lunn, Pavel Machek,
	Rob Herring, Lee Jones, Mark Brown, Greg Kroah-Hartman,
	Rafael J . Wysocki, Linus Walleij, Bartosz Golaszewski,
	Linux LED Subsystem, devicetree, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

Am 2021-05-30 20:16, schrieb Andy Shevchenko:
> On Sun, May 30, 2021 at 7:51 PM Hans de Goede <hdegoede@redhat.com> 
> wrote:
>> On 5/30/21 6:19 PM, Sander Vanheule wrote:
>> > On Fri, 2021-05-28 at 08:37 +0200, Michael Walle wrote:
> 
> ...
> 
>> > I think I found a solution!

nice!

>> > As Michael suggested, I tried raw register reads and writes, to eliminate any
>> > side effects of the intermediate code. I didn't use the ioctls (this isn't a
>> > netdev), but I found regmap's debugfs write functionality, which allowed me to
>> > do the same.
>> >
>> > I was trying to reproduce the behaviour I reported earlier, but couldn't. The
>> > output levels were always the intended ones. At some point I realised that the
>> > regmap_update_bits function does a read-modify-write, which might shadow the
>> > actual current output value.
>> > For example:
>> >  * Set output low: current out is low
>> >  * Change to input with pull-up: current out is still low, but DATAx reads high
>> >  * Set output high: RMW reads a high value (the input), so assumes a write is
>> >    not necessary, leaving the old output value (low).
>> >
>> > Currently, I see two options:
>> >  * Use regmap_update_bits_base to avoid the lazy RMW behaviour
>> >  * Add a cache for the output data values to the driver, and only use these
>> >    values to write to the output registers. This would allow keeping lazy RMW
>> >    behaviour, which may be a benefit on slow busses.
>> >
>> > With either of these implemented, if I set the output value before the
>> > direction, everything works! :-)
>> >
>> > Would you like this to be added to regmap-gpio, or should I revert back to a
>> > device-specific implementation?
>> 
>> Regmap allows you to mark certain ranges as volatile, so that they 
>> will not
>> be cached, these GPIO registers containing the current pin value seems 
>> like
>> a good candidate for this. This is also necessary to make reading the 
>> GPIO
>> work without getting back a stale, cached value.
> 
> After all it seems a simple missed proper register configuration in
> the driver for regmap.
> Oh, as usual something easy-to-solve requires tons of time to find it. 
> :-)
> 
> Sander, I think you may look at gpio-pca953x.c to understand how it
> works (volatility of registers).

But as far as I see is the regmap instantiated without a cache?

-michael

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 1/6] gpio: regmap: Add quirk for output data register
  2021-05-23 22:33   ` [PATCH v3 1/6] gpio: regmap: Add quirk for output data register Sander Vanheule
  2021-05-28  6:40     ` Michael Walle
@ 2021-05-31  7:25     ` Bartosz Golaszewski
  1 sibling, 0 replies; 114+ messages in thread
From: Bartosz Golaszewski @ 2021-05-31  7:25 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Michael Walle,
	Linus Walleij, Linux LED Subsystem, linux-devicetree, linux-gpio,
	Andrew Lunn, Andy Shevchenko, LKML

On Mon, May 24, 2021 at 12:34 AM Sander Vanheule <sander@svanheule.net> wrote:
>
> GPIO chips may not support setting the output value when a pin is
> configured as an input, although the current implementation assumes this
> is always possible.
>
> Add support for setting pin direction before value. The order defaults
> to setting the value first, but this can be reversed by setting the
> GPIO_REGMAP_QUIRK_SET_DIRECTION_FIRST flag in regmap_config.quirks.
>
> Signed-off-by: Sander Vanheule <sander@svanheule.net>
> ---

Acked-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-30 21:22                 ` Michael Walle
@ 2021-05-31  8:36                   ` Sander Vanheule
  2021-05-31 10:02                     ` Michael Walle
  0 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-31  8:36 UTC (permalink / raw)
  To: Michael Walle, Andy Shevchenko
  Cc: Hans de Goede, Andrew Lunn, Pavel Machek, Rob Herring, Lee Jones,
	Mark Brown, Greg Kroah-Hartman, Rafael J . Wysocki,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Linux Kernel Mailing List

On Sun, 2021-05-30 at 23:22 +0200, Michael Walle wrote:
> Am 2021-05-30 20:16, schrieb Andy Shevchenko:
> > On Sun, May 30, 2021 at 7:51 PM Hans de Goede <hdegoede@redhat.com> 
> > wrote:
> > > On 5/30/21 6:19 PM, Sander Vanheule wrote:
> > > > As Michael suggested, I tried raw register reads and writes, to eliminate
> > > > any
> > > > side effects of the intermediate code. I didn't use the ioctls (this isn't
> > > > a
> > > > netdev), but I found regmap's debugfs write functionality, which allowed
> > > > me to
> > > > do the same.
> > > > 
> > > > I was trying to reproduce the behaviour I reported earlier, but couldn't.
> > > > The
> > > > output levels were always the intended ones. At some point I realised that
> > > > the
> > > > regmap_update_bits function does a read-modify-write, which might shadow
> > > > the
> > > > actual current output value.
> > > > For example:
> > > >  * Set output low: current out is low
> > > >  * Change to input with pull-up: current out is still low, but DATAx reads
> > > > high
> > > >  * Set output high: RMW reads a high value (the input), so assumes a write
> > > > is
> > > >    not necessary, leaving the old output value (low).
> > > > 
> > > > Currently, I see two options:
> > > >  * Use regmap_update_bits_base to avoid the lazy RMW behaviour
> > > >  * Add a cache for the output data values to the driver, and only use
> > > > these
> > > >    values to write to the output registers. This would allow keeping lazy
> > > > RMW
> > > >    behaviour, which may be a benefit on slow busses.
> > > > 
> > > > With either of these implemented, if I set the output value before the
> > > > direction, everything works! :-)
> > > > 
> > > > Would you like this to be added to regmap-gpio, or should I revert back to
> > > > a
> > > > device-specific implementation?
> > > 
> > > Regmap allows you to mark certain ranges as volatile, so that they 
> > > will not
> > > be cached, these GPIO registers containing the current pin value seems 
> > > like
> > > a good candidate for this. This is also necessary to make reading the 
> > > GPIO
> > > work without getting back a stale, cached value.
> > 
> > After all it seems a simple missed proper register configuration in
> > the driver for regmap.
> > Oh, as usual something easy-to-solve requires tons of time to find it. 
> > :-)
> > 
> > Sander, I think you may look at gpio-pca953x.c to understand how it
> > works (volatility of registers).
> 
> But as far as I see is the regmap instantiated without a cache?

That's correct, there currently is no cache, although I could add one.

The data register rather appears to be implemented as a read-only (pin inputs)
register and a write-only (pin outputs) register, aliased on the same register
address.

As I understand, marking the DATA registers as volatile wouldn't help. With a
cache this would force reads to not use the cache, which is indeed required for
the pin input values (DATA register reads). However, the output values (DATA
register writes) can in fact be cached.
Looking at _regmap_update_bits(), marking a register as volatile would only make
a difference if regmap.reg_update_bits is implemented. On an MDIO bus, this
would also be emulated with a lazy RMW (see mdiobus_modify()), which is why I
chose not to implement it for regmap-mdio.

So, I still think the issue lies with the lazy RMW behaviour. The patch below
would force a register update when reg_set_base (the data output register) and
reg_dat_base (the data input register) are identical. Otherwise the two
registers are assumed to have conventional RW behaviour. I'm just not entirely
sure gpio-regmap.c is the right place for this.

---8<---

diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c
index 95553734e169..c2fccd19548a 100644
--- a/drivers/gpio/gpio-regmap.c
+++ b/drivers/gpio/gpio-regmap.c
@@ -81,13 +81,16 @@ static void gpio_regmap_set(struct gpio_chip *chip, unsigned
int offset,
 {
        struct gpio_regmap *gpio = gpiochip_get_data(chip);
        unsigned int base = gpio_regmap_addr(gpio->reg_set_base);
+       bool force = gpio->reg_set_base == gpio->reg_dat_base;
        unsigned int reg, mask;
 
        gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
        if (val)
-               regmap_update_bits(gpio->regmap, reg, mask, mask);
+               regmap_update_bits_base(gpio->regmap, reg, mask, mask, NULL,
+                                       false, force);
        else
-               regmap_update_bits(gpio->regmap, reg, mask, 0);
+               regmap_update_bits_base(gpio->regmap, reg, mask, 0, NULL,
+                                       false, force);
 }
 
 static void gpio_regmap_set_with_clear(struct gpio_chip *chip,

--
Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-31  8:36                   ` Sander Vanheule
@ 2021-05-31 10:02                     ` Michael Walle
       [not found]                       ` <CAHp75VfOrUBRQH1vrXEwHN4ZPojQfQju-_wp_3djZeozEaatug@mail.gmail.com>
  0 siblings, 1 reply; 114+ messages in thread
From: Michael Walle @ 2021-05-31 10:02 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Andy Shevchenko, Hans de Goede, Andrew Lunn, Pavel Machek,
	Rob Herring, Lee Jones, Mark Brown, Greg Kroah-Hartman,
	Rafael J . Wysocki, Linus Walleij, Bartosz Golaszewski,
	Linux LED Subsystem, devicetree, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

Am 2021-05-31 10:36, schrieb Sander Vanheule:
> On Sun, 2021-05-30 at 23:22 +0200, Michael Walle wrote:
>> Am 2021-05-30 20:16, schrieb Andy Shevchenko:
>> > On Sun, May 30, 2021 at 7:51 PM Hans de Goede <hdegoede@redhat.com>
>> > wrote:
>> > > On 5/30/21 6:19 PM, Sander Vanheule wrote:
>> > > > As Michael suggested, I tried raw register reads and writes, to eliminate
>> > > > any
>> > > > side effects of the intermediate code. I didn't use the ioctls (this isn't
>> > > > a
>> > > > netdev), but I found regmap's debugfs write functionality, which allowed
>> > > > me to
>> > > > do the same.
>> > > >
>> > > > I was trying to reproduce the behaviour I reported earlier, but couldn't.
>> > > > The
>> > > > output levels were always the intended ones. At some point I realised that
>> > > > the
>> > > > regmap_update_bits function does a read-modify-write, which might shadow
>> > > > the
>> > > > actual current output value.
>> > > > For example:
>> > > >  * Set output low: current out is low
>> > > >  * Change to input with pull-up: current out is still low, but DATAx reads
>> > > > high
>> > > >  * Set output high: RMW reads a high value (the input), so assumes a write
>> > > > is
>> > > >    not necessary, leaving the old output value (low).
>> > > >
>> > > > Currently, I see two options:
>> > > >  * Use regmap_update_bits_base to avoid the lazy RMW behaviour
>> > > >  * Add a cache for the output data values to the driver, and only use
>> > > > these
>> > > >    values to write to the output registers. This would allow keeping lazy
>> > > > RMW
>> > > >    behaviour, which may be a benefit on slow busses.
>> > > >
>> > > > With either of these implemented, if I set the output value before the
>> > > > direction, everything works! :-)
>> > > >
>> > > > Would you like this to be added to regmap-gpio, or should I revert back to
>> > > > a
>> > > > device-specific implementation?
>> > >
>> > > Regmap allows you to mark certain ranges as volatile, so that they
>> > > will not
>> > > be cached, these GPIO registers containing the current pin value seems
>> > > like
>> > > a good candidate for this. This is also necessary to make reading the
>> > > GPIO
>> > > work without getting back a stale, cached value.
>> >
>> > After all it seems a simple missed proper register configuration in
>> > the driver for regmap.
>> > Oh, as usual something easy-to-solve requires tons of time to find it.
>> > :-)
>> >
>> > Sander, I think you may look at gpio-pca953x.c to understand how it
>> > works (volatility of registers).
>> 
>> But as far as I see is the regmap instantiated without a cache?
> 
> That's correct, there currently is no cache, although I could add one.
> 
> The data register rather appears to be implemented as a read-only (pin 
> inputs)
> register and a write-only (pin outputs) register, aliased on the same 
> register
> address.

Ahh so this makes more sense. If the data register is really write only
regardless of the direction mode, then RMW doesn't make any sense at 
all.
Please note, that even if regmap caches values, it might be marked as 
dirty
and it will re-read the values from hardware. So I don't know if that 
will
help you.

So a possible quirk could be
  GPIO_REGMAP_WRITE_ONLY_DATA_REG (or something like that)

I'm not sure if regmap can cache the value for us or if we have to do it
ourselves.

> As I understand, marking the DATA registers as volatile wouldn't help. 
> With a
> cache this would force reads to not use the cache, which is indeed 
> required for
> the pin input values (DATA register reads). However, the output values 
> (DATA
> register writes) can in fact be cached.
> Looking at _regmap_update_bits(), marking a register as volatile would 
> only make
> a difference if regmap.reg_update_bits is implemented. On an MDIO bus, 
> this
> would also be emulated with a lazy RMW (see mdiobus_modify()), which is 
> why I
> chose not to implement it for regmap-mdio.
> 
> So, I still think the issue lies with the lazy RMW behaviour. The patch 
> below
> would force a register update when reg_set_base (the data output 
> register) and
> reg_dat_base (the data input register) are identical. Otherwise the two
> registers are assumed to have conventional RW behaviour. I'm just not 
> entirely
> sure gpio-regmap.c is the right place for this.
> 
> ---8<---
> 
> diff --git a/drivers/gpio/gpio-regmap.c b/drivers/gpio/gpio-regmap.c
> index 95553734e169..c2fccd19548a 100644
> --- a/drivers/gpio/gpio-regmap.c
> +++ b/drivers/gpio/gpio-regmap.c
> @@ -81,13 +81,16 @@ static void gpio_regmap_set(struct gpio_chip *chip, 
> unsigned
> int offset,
>  {
>         struct gpio_regmap *gpio = gpiochip_get_data(chip);
>         unsigned int base = gpio_regmap_addr(gpio->reg_set_base);
> +       bool force = gpio->reg_set_base == gpio->reg_dat_base;

Ha I've thought of the same thing, but there might be hardware which
actually mux the data in and data out register. Thus I think we have
to distiguish between:

  (1) write only data registers
  (2) muxed data in/data out according to the direction

for (1) we'd have to cache the value (ourselves (?))
for (2) we'd only need to drop a cached value if we switch directions

>         unsigned int reg, mask;
> 
>         gpio->reg_mask_xlate(gpio, base, offset, &reg, &mask);
>         if (val)
> -               regmap_update_bits(gpio->regmap, reg, mask, mask);
> +               regmap_update_bits_base(gpio->regmap, reg, mask, mask, 
> NULL,
> +                                       false, force);

mh, I don't see how this will work with a write only register. I seems
that you might accidentially change the values of the other GPIOs in
this registers (that is depending on the input of them, because you
are still doing a RMW).

>         else
> -               regmap_update_bits(gpio->regmap, reg, mask, 0);
> +               regmap_update_bits_base(gpio->regmap, reg, mask, 0, 
> NULL,
> +                                       false, force);
>  }
> 
>  static void gpio_regmap_set_with_clear(struct gpio_chip *chip,

-michael

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 0/5] RTL8231 GPIO expander support
       [not found]                       ` <CAHp75VfOrUBRQH1vrXEwHN4ZPojQfQju-_wp_3djZeozEaatug@mail.gmail.com>
@ 2021-05-31 15:33                         ` Sander Vanheule
  2021-05-31 15:48                           ` Andy Shevchenko
  0 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-05-31 15:33 UTC (permalink / raw)
  To: Andy Shevchenko, Michael Walle
  Cc: Hans de Goede, Andrew Lunn, Pavel Machek, Rob Herring, Lee Jones,
	Mark Brown, Greg Kroah-Hartman, Rafael J . Wysocki,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Linux Kernel Mailing List

On Mon, 2021-05-31 at 14:16 +0300, Andy Shevchenko wrote:
> 
> 
> On Monday, May 31, 2021, Michael Walle <michael@walle.cc> wrote:
> > Am 2021-05-31 10:36, schrieb Sander Vanheule:

...

> > > The data register rather appears to be implemented as a read-only (pin
> > > inputs)
> > > register and a write-only (pin outputs) register, aliased on the same
> > > register
> > > address.
> > > 
> > 
> > Ahh so this makes more sense. If the data register is really write only
> > regardless of the direction mode, then RMW doesn't make any sense at all.
> > Please note, that even if regmap caches values, it might be marked as dirty
> > and it will re-read the values from hardware. So I don't know if that will
> > help you.
> > 
> > So a possible quirk could be
> >  GPIO_REGMAP_WRITE_ONLY_DATA_REG (or something like that)
> > 
> > 
> 
> Isn’t regmap property to do a such? I don’t think any quirks are needed since hw
> works as expected.

The HW works as expected, but it is regmap-gpio's assumption that values read
from reg_set_base reflect the current output value that fails.

I looked a bit more at the provided interface, to see if this can be done with
existing regmap functionality.

The data registers must not be marked volatile, to ensure cached reads. The pin
set function can wrap the RMW in regcache_cache_only + regcache_sync, but this
causes visible glitching on my device.

The pin input values can be read by wrapping the regmap_read in
regcache_cache_bypass guards.

If only the regmap's internal lock is used, the RMW cycle is no longer atomic.
Inside the cache_only guards you can't read the input data, and inside the
cache_bypass guards other register writes cannot be performed, or the cache may
get out of sync. regcache_sync_region could be used, but maybe we would then
miss other registers that were updated in the meantime.

Am I missing something here? It seems to me like the regmap interface can't
really accommodate what's required, unless maybe the rtl8231 regmap users
perform some manual locking. This all seems terribly complicated compared to
using an internal output-value cache inside regmap-gpio.


Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 0/5] RTL8231 GPIO expander support
  2021-05-31 15:33                         ` [PATCH 0/5] " Sander Vanheule
@ 2021-05-31 15:48                           ` Andy Shevchenko
  2021-06-01 11:49                             ` Michael Walle
  0 siblings, 1 reply; 114+ messages in thread
From: Andy Shevchenko @ 2021-05-31 15:48 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Michael Walle, Hans de Goede, Andrew Lunn, Pavel Machek,
	Rob Herring, Lee Jones, Mark Brown, Greg Kroah-Hartman,
	Rafael J . Wysocki, Linus Walleij, Bartosz Golaszewski,
	Linux LED Subsystem, devicetree, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

On Mon, May 31, 2021 at 6:33 PM Sander Vanheule <sander@svanheule.net> wrote:
> On Mon, 2021-05-31 at 14:16 +0300, Andy Shevchenko wrote:
> > On Monday, May 31, 2021, Michael Walle <michael@walle.cc> wrote:
> > > Am 2021-05-31 10:36, schrieb Sander Vanheule:

> Am I missing something here? It seems to me like the regmap interface can't
> really accommodate what's required, unless maybe the rtl8231 regmap users
> perform some manual locking. This all seems terribly complicated compared to
> using an internal output-value cache inside regmap-gpio.

Have you had a chance to look into the PCA953x driver?
Sounds to me that you are missing the APIs that regmap provides.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-05-30 18:16               ` Andy Shevchenko
  2021-05-30 21:22                 ` Michael Walle
@ 2021-06-01  9:59                 ` Linus Walleij
  2021-06-01 10:18                   ` Michael Walle
  1 sibling, 1 reply; 114+ messages in thread
From: Linus Walleij @ 2021-06-01  9:59 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Hans de Goede, Sander Vanheule, Michael Walle, Andrew Lunn,
	Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Bartosz Golaszewski,
	Linux LED Subsystem, devicetree, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

On Sun, May 30, 2021 at 8:16 PM Andy Shevchenko
<andy.shevchenko@gmail.com> wrote:
> On Sun, May 30, 2021 at 7:51 PM Hans de Goede <hdegoede@redhat.com> wrote:

> > Regmap allows you to mark certain ranges as volatile, so that they will not
> > be cached, these GPIO registers containing the current pin value seems like
> > a good candidate for this. This is also necessary to make reading the GPIO
> > work without getting back a stale, cached value.
>
> After all it seems a simple missed proper register configuration in
> the driver for regmap.
> Oh, as usual something easy-to-solve requires tons of time to find it. :-)

This is actually quite interesting.

In the discussion around adding Rust support for the Linux kernel
what I came to realize was that the memory safety that Rust adds is
similar in application and ambition to what e.g. regmap-mmio provides.

One aspect of writing kernel drivers in Rust is to always have
something like regmap between your code and the hardware to
strictly control the memory access pattern.

After all regmap is "memory safety implemented in C".

What we see in cases like this is that not only does that make
things more strict and controlled (after all we have regmap for
a reason) but also makes it possible to generate a whole new
set of bugs by doing an error in how you specify the memory
semantics. As all other paradigms, memory safety thinking
implies that never specify anything wrong.

Just regarding all registers/memory cells in a register page
as default volatile (which is what we do a lot of the time)
has its upsides: bugs like this doesn't happen.

(Just some sidetracking...)

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-06-01  9:59                 ` [PATCH v3 0/6] " Linus Walleij
@ 2021-06-01 10:18                   ` Michael Walle
  2021-06-01 10:51                     ` Linus Walleij
  0 siblings, 1 reply; 114+ messages in thread
From: Michael Walle @ 2021-06-01 10:18 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Andy Shevchenko, Hans de Goede, Sander Vanheule, Andrew Lunn,
	Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Bartosz Golaszewski,
	Linux LED Subsystem, devicetree, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

Am 2021-06-01 11:59, schrieb Linus Walleij:
> Just regarding all registers/memory cells in a register page
> as default volatile (which is what we do a lot of the time)
> has its upsides: bugs like this doesn't happen.

I don't think this is the bug here. If it is really a write-only 
register
the problem is the read in RMW. Because reading the register will return
the input value instead of the (previously written) output value.

-michael

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-06-01 10:18                   ` Michael Walle
@ 2021-06-01 10:51                     ` Linus Walleij
  2021-06-01 11:41                       ` Michael Walle
  0 siblings, 1 reply; 114+ messages in thread
From: Linus Walleij @ 2021-06-01 10:51 UTC (permalink / raw)
  To: Michael Walle
  Cc: Andy Shevchenko, Hans de Goede, Sander Vanheule, Andrew Lunn,
	Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Bartosz Golaszewski,
	Linux LED Subsystem, devicetree, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

On Tue, Jun 1, 2021 at 12:18 PM Michael Walle <michael@walle.cc> wrote:
> Am 2021-06-01 11:59, schrieb Linus Walleij:

> > Just regarding all registers/memory cells in a register page
> > as default volatile (which is what we do a lot of the time)
> > has its upsides: bugs like this doesn't happen.
>
> I don't think this is the bug here. If it is really a write-only
> register
> the problem is the read in RMW. Because reading the register will return
> the input value instead of the (previously written) output value.

True that. Write and read semantics differ on the register.

Volatile is used for this and some other things,
like for example interrupts being cleared when a register
is read so it is strictly read-once.

So the regmap config is really important to get right.

IIUC one of the ambitions around Rust is to encode this
in how memory is specified in the language. (I am still
thinking about whether that is really a good idea or not.)

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-06-01 10:51                     ` Linus Walleij
@ 2021-06-01 11:41                       ` Michael Walle
  2021-06-01 11:48                         ` Linus Walleij
  0 siblings, 1 reply; 114+ messages in thread
From: Michael Walle @ 2021-06-01 11:41 UTC (permalink / raw)
  To: Linus Walleij
  Cc: Andy Shevchenko, Hans de Goede, Sander Vanheule, Andrew Lunn,
	Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Bartosz Golaszewski,
	Linux LED Subsystem, devicetree, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

Am 2021-06-01 12:51, schrieb Linus Walleij:
> On Tue, Jun 1, 2021 at 12:18 PM Michael Walle <michael@walle.cc> wrote:
>> Am 2021-06-01 11:59, schrieb Linus Walleij:
> 
>> > Just regarding all registers/memory cells in a register page
>> > as default volatile (which is what we do a lot of the time)
>> > has its upsides: bugs like this doesn't happen.
>> 
>> I don't think this is the bug here. If it is really a write-only
>> register
>> the problem is the read in RMW. Because reading the register will 
>> return
>> the input value instead of the (previously written) output value.
> 
> True that. Write and read semantics differ on the register.
> 
> Volatile is used for this and some other things,
> like for example interrupts being cleared when a register
> is read so it is strictly read-once.

Isn't that what precious is for?

> So the regmap config is really important to get right.
> 
> IIUC one of the ambitions around Rust is to encode this
> in how memory is specified in the language. (I am still
> thinking about whether that is really a good idea or not.)

-- 
-michael

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 0/6] RTL8231 GPIO expander support
  2021-06-01 11:41                       ` Michael Walle
@ 2021-06-01 11:48                         ` Linus Walleij
  0 siblings, 0 replies; 114+ messages in thread
From: Linus Walleij @ 2021-06-01 11:48 UTC (permalink / raw)
  To: Michael Walle
  Cc: Andy Shevchenko, Hans de Goede, Sander Vanheule, Andrew Lunn,
	Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Bartosz Golaszewski,
	Linux LED Subsystem, devicetree, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

On Tue, Jun 1, 2021 at 1:41 PM Michael Walle <michael@walle.cc> wrote:
> Am 2021-06-01 12:51, schrieb Linus Walleij:
> > On Tue, Jun 1, 2021 at 12:18 PM Michael Walle <michael@walle.cc> wrote:
> >> Am 2021-06-01 11:59, schrieb Linus Walleij:
> >
> >> > Just regarding all registers/memory cells in a register page
> >> > as default volatile (which is what we do a lot of the time)
> >> > has its upsides: bugs like this doesn't happen.
> >>
> >> I don't think this is the bug here. If it is really a write-only
> >> register
> >> the problem is the read in RMW. Because reading the register will
> >> return
> >> the input value instead of the (previously written) output value.
> >
> > True that. Write and read semantics differ on the register.
> >
> > Volatile is used for this and some other things,
> > like for example interrupts being cleared when a register
> > is read so it is strictly read-once.
>
> Isn't that what precious is for?

I never figured that one out. But I assume you are right.

Proper regmap semantics documentation is forthcoming! ;)

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 0/5] RTL8231 GPIO expander support
  2021-05-31 15:48                           ` Andy Shevchenko
@ 2021-06-01 11:49                             ` Michael Walle
  2021-06-01 15:24                               ` Andy Shevchenko
  0 siblings, 1 reply; 114+ messages in thread
From: Michael Walle @ 2021-06-01 11:49 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Sander Vanheule, Hans de Goede, Andrew Lunn, Pavel Machek,
	Rob Herring, Lee Jones, Mark Brown, Greg Kroah-Hartman,
	Rafael J . Wysocki, Linus Walleij, Bartosz Golaszewski,
	Linux LED Subsystem, devicetree, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

Am 2021-05-31 17:48, schrieb Andy Shevchenko:
> On Mon, May 31, 2021 at 6:33 PM Sander Vanheule <sander@svanheule.net> 
> wrote:
>> On Mon, 2021-05-31 at 14:16 +0300, Andy Shevchenko wrote:
>> > On Monday, May 31, 2021, Michael Walle <michael@walle.cc> wrote:
>> > > Am 2021-05-31 10:36, schrieb Sander Vanheule:
> 
>> Am I missing something here? It seems to me like the regmap interface 
>> can't
>> really accommodate what's required, unless maybe the rtl8231 regmap 
>> users
>> perform some manual locking. This all seems terribly complicated 
>> compared to
>> using an internal output-value cache inside regmap-gpio.
> 
> Have you had a chance to look into the PCA953x driver?
> Sounds to me that you are missing the APIs that regmap provides.

What would that be? The register cache? We need to cache the
value somehow, because (still assuming it is write only) we cannot
read it back. Thus the read of the RMW, would need get the
value from the cache. Thus the user of gpio-regmap would need
to make sure, to (a) use a cache for the regmap supplied to
gpio-regmap and (b) populate its initial values correctly. Is
that what you are suggesting? And hopefully, no other user
of the regmap will call regcache_mark_dirty() or something
like that.

I had a quick look at the PCA953x driver but it all its
registers are readable according to the comment on the top
of the file.

-michael

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 0/5] RTL8231 GPIO expander support
  2021-06-01 11:49                             ` Michael Walle
@ 2021-06-01 15:24                               ` Andy Shevchenko
  2021-06-02 20:20                                 ` Sander Vanheule
  0 siblings, 1 reply; 114+ messages in thread
From: Andy Shevchenko @ 2021-06-01 15:24 UTC (permalink / raw)
  To: Michael Walle
  Cc: Sander Vanheule, Hans de Goede, Andrew Lunn, Pavel Machek,
	Rob Herring, Lee Jones, Mark Brown, Greg Kroah-Hartman,
	Rafael J . Wysocki, Linus Walleij, Bartosz Golaszewski,
	Linux LED Subsystem, devicetree, open list:GPIO SUBSYSTEM,
	Linux Kernel Mailing List

On Tue, Jun 1, 2021 at 2:49 PM Michael Walle <michael@walle.cc> wrote:
> Am 2021-05-31 17:48, schrieb Andy Shevchenko:
> > On Mon, May 31, 2021 at 6:33 PM Sander Vanheule <sander@svanheule.net>
> > wrote:
> >> On Mon, 2021-05-31 at 14:16 +0300, Andy Shevchenko wrote:
> >> > On Monday, May 31, 2021, Michael Walle <michael@walle.cc> wrote:
> >> > > Am 2021-05-31 10:36, schrieb Sander Vanheule:
> >
> >> Am I missing something here? It seems to me like the regmap interface
> >> can't
> >> really accommodate what's required, unless maybe the rtl8231 regmap
> >> users
> >> perform some manual locking. This all seems terribly complicated
> >> compared to
> >> using an internal output-value cache inside regmap-gpio.
> >
> > Have you had a chance to look into the PCA953x driver?
> > Sounds to me that you are missing the APIs that regmap provides.
>
> What would that be? The register cache? We need to cache the
> value somehow, because (still assuming it is write only) we cannot
> read it back. Thus the read of the RMW, would need get the
> value from the cache. Thus the user of gpio-regmap would need
> to make sure, to (a) use a cache for the regmap supplied to
> gpio-regmap and (b) populate its initial values correctly. Is
> that what you are suggesting? And hopefully, no other user
> of the regmap will call regcache_mark_dirty() or something
> like that.
>
> I had a quick look at the PCA953x driver but it all its
> registers are readable according to the comment on the top
> of the file.

Since regmap doesn't have a facility to support the registers _at the
same offset_ with different meaning (depending on data direction), the
only way to handle this (without redesign regmap internals) is to add
specific "pages" via additional bits in the address space. So, let's
say 0 = RW, 1 = RO, 2 = WO. In this case see the following offset
mapping of the hypothetical hardware registers:

REG1 (RW)   0x00 -> 0x000
REG2 (RW)   0x04 -> 0x004
REG3 (RO)   0x08 -> 0x108
REG3 (RW)   0x08 -> 0x208

Then these bits should be masked out. Something similar is done in the
PCA953x driver for extended addresses and autoincrement.

This is what I propose to look at as the starter.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 2/6] dt-bindings: leds: Binding for RTL8231 scan matrix
  2021-05-23 22:34   ` [PATCH v3 2/6] dt-bindings: leds: Binding for RTL8231 scan matrix Sander Vanheule
@ 2021-06-02 18:58     ` Rob Herring
  0 siblings, 0 replies; 114+ messages in thread
From: Rob Herring @ 2021-06-02 18:58 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Greg Kroah-Hartman, Lee Jones, Andrew Lunn, Rafael J . Wysocki,
	linux-gpio, devicetree, Andy Shevchenko, Rob Herring,
	linux-kernel, Linus Walleij, linux-leds, Bartosz Golaszewski,
	Mark Brown, Pavel Machek, Michael Walle

On Mon, 24 May 2021 00:34:00 +0200, Sander Vanheule wrote:
> Add a binding description for the Realtek RTL8231's LED support, which
> consists of up to 88 LEDs arranged in a number of scanning matrices.
> 
> Signed-off-by: Sander Vanheule <sander@svanheule.net>
> ---
>  .../bindings/leds/realtek,rtl8231-leds.yaml   | 166 ++++++++++++++++++
>  1 file changed, 166 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
> 

Reviewed-by: Rob Herring <robh@kernel.org>

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 3/6] dt-bindings: mfd: Binding for RTL8231
  2021-05-23 22:34   ` [PATCH v3 3/6] dt-bindings: mfd: Binding for RTL8231 Sander Vanheule
  2021-05-27 23:31     ` Linus Walleij
@ 2021-06-02 19:02     ` Rob Herring
  1 sibling, 0 replies; 114+ messages in thread
From: Rob Herring @ 2021-06-02 19:02 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Lee Jones, devicetree, linux-kernel, Greg Kroah-Hartman,
	Linus Walleij, Michael Walle, Rafael J . Wysocki, Rob Herring,
	linux-gpio, Andy Shevchenko, linux-leds, Mark Brown,
	Bartosz Golaszewski, Pavel Machek, Andrew Lunn

On Mon, 24 May 2021 00:34:01 +0200, Sander Vanheule wrote:
> Add a binding description for the Realtek RTL8231, a GPIO and LED
> expander chip commonly used in ethernet switches based on a Realtek
> switch SoC. These chips can be addressed via an MDIO or SMI bus, or used
> as a plain 36-bit shift register.
> 
> This binding only describes the feature set provided by the MDIO/SMI
> configuration, and covers the GPIO, PWM, and pin control properties. The
> LED properties are defined in a separate binding.
> 
> Signed-off-by: Sander Vanheule <sander@svanheule.net>
> ---
>  .../bindings/mfd/realtek,rtl8231.yaml         | 190 ++++++++++++++++++
>  1 file changed, 190 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
> 

Reviewed-by: Rob Herring <robh@kernel.org>

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH 0/5] RTL8231 GPIO expander support
  2021-06-01 15:24                               ` Andy Shevchenko
@ 2021-06-02 20:20                                 ` Sander Vanheule
  0 siblings, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-06-02 20:20 UTC (permalink / raw)
  To: Andy Shevchenko, Michael Walle
  Cc: Hans de Goede, Andrew Lunn, Pavel Machek, Rob Herring, Lee Jones,
	Mark Brown, Greg Kroah-Hartman, Rafael J . Wysocki,
	Linus Walleij, Bartosz Golaszewski, Linux LED Subsystem,
	devicetree, open list:GPIO SUBSYSTEM, Linux Kernel Mailing List

Hi Andy,

On Tue, 2021-06-01 at 18:24 +0300, Andy Shevchenko wrote:
> On Tue, Jun 1, 2021 at 2:49 PM Michael Walle <michael@walle.cc> wrote:
> > Am 2021-05-31 17:48, schrieb Andy Shevchenko:
> > > On Mon, May 31, 2021 at 6:33 PM Sander Vanheule <sander@svanheule.net>
> > > wrote:
> > > > On Mon, 2021-05-31 at 14:16 +0300, Andy Shevchenko wrote:
> > > > > On Monday, May 31, 2021, Michael Walle <michael@walle.cc> wrote:
> > > > > > Am 2021-05-31 10:36, schrieb Sander Vanheule:
> > > 
> > > > Am I missing something here? It seems to me like the regmap interface
> > > > can't
> > > > really accommodate what's required, unless maybe the rtl8231 regmap
> > > > users
> > > > perform some manual locking. This all seems terribly complicated
> > > > compared to
> > > > using an internal output-value cache inside regmap-gpio.
> > > 
> > > Have you had a chance to look into the PCA953x driver?
> > > Sounds to me that you are missing the APIs that regmap provides.
> > 
> > What would that be? The register cache? We need to cache the
> > value somehow, because (still assuming it is write only) we cannot
> > read it back. Thus the read of the RMW, would need get the
> > value from the cache. Thus the user of gpio-regmap would need
> > to make sure, to (a) use a cache for the regmap supplied to
> > gpio-regmap and (b) populate its initial values correctly. Is
> > that what you are suggesting? And hopefully, no other user
> > of the regmap will call regcache_mark_dirty() or something
> > like that.
> > 
> > I had a quick look at the PCA953x driver but it all its
> > registers are readable according to the comment on the top
> > of the file.
> 
> Since regmap doesn't have a facility to support the registers _at the
> same offset_ with different meaning (depending on data direction), the
> only way to handle this (without redesign regmap internals) is to add
> specific "pages" via additional bits in the address space. So, let's
> say 0 = RW, 1 = RO, 2 = WO. In this case see the following offset
> mapping of the hypothetical hardware registers:
> 
> REG1 (RW)   0x00 -> 0x000
> REG2 (RW)   0x04 -> 0x004
> REG3 (RO)   0x08 -> 0x108
> REG3 (RW)   0x08 -> 0x208
> 
> Then these bits should be masked out. Something similar is done in the
> PCA953x driver for extended addresses and autoincrement.
> 
> This is what I propose to look at as the starter.

Thank you for clarifying. Essentialy this is then the same solution as an extra
cache in gpio-regmap for the output values, except the cacheing is handled by
the regmap layer.

I think this was the last issue standing, so after I implement this, I'll spin a
v4.

Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v4 0/5] RTL8231 GPIO expander support
  2021-05-11 12:25 [PATCH 0/5] RTL8231 GPIO expander support Sander Vanheule
                   ` (7 preceding siblings ...)
  2021-05-23 22:33 ` [PATCH v3 0/6] " Sander Vanheule
@ 2021-06-03 10:00 ` Sander Vanheule
  2021-06-03 10:00   ` [PATCH v4 1/5] dt-bindings: leds: Binding for RTL8231 scan matrix Sander Vanheule
                     ` (4 more replies)
  8 siblings, 5 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-06-03 10:00 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij,
	Michael Walle, Andy Shevchenko, linux-leds, devicetree,
	linux-gpio
  Cc: Hans de Goede, Andrew Lunn, linux-kernel, Sander Vanheule

The RTL8231 GPIO and LED expander can be configured for use as an MDIO or SMI
bus device. Currently only the MDIO mode is supported, although SMI mode
support should be fairly straightforward, once an SMI bus driver is available.

Provided features by the RTL8231:
  - Up to 37 GPIOs
    - Configurable drive strength: 8mA or 4mA (currently unsupported)
    - Input debouncing on high GPIOs (currently unsupported)
  - Up to 88 LEDs in multiple scan matrix groups
    - On, off, or one of six toggling intervals
    - "single-color mode": 2×36 single color LEDs + 8 bi-color LEDs
    - "bi-color mode": (12 + 2×6) bi-color LEDs + 24 single color LEDs
  - Up to one PWM output (currently unsupported)
    - Fixed duty cycle, 8 selectable frequencies (1.2kHz - 4.8kHz)

The GPIO controller uses gpio-regmap. The assumed read-modify-write behaviour
of the data output is provided by using a cached virtual address range for the
output values.

Register access is provided through a new MDIO regmap provider. The required
MDIO regmap support was merged in Mark Brown's regmap repository, and can be
found under the regmap-mdio tag:
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap.git/tag/?h=regmap-mdio

Changes since v3:
  - Drop gpio-regmap direction-before-value quirk
  - Use secondary virtual register range to enable proper read-modify-write
    behaviour on GPIO output values
  - Add pin debounce support
  - Switch to generic pinmux functions

Changes since v2:
  - MDIO regmap support was merged, so patch is dropped here
  - Implement feedback for DT bindings
  - Use correct module names in Kconfigs
  - Fix k*alloc return value checks
  - Introduce GPIO regmap quirks to set output direction first
  - pinctrl: Use static pin descriptions for pin controller
  - pinctrl: Fix gpio consumer resource leak
  - mfd: Replace CONFIG_PM-ifdef'ery
  - leds: Rename interval to interval_ms

Changes since v1:
  - Reintroduce MDIO regmap, with fixed Kconfig dependencies
  - Add configurable dir/value order for gpio-regmap direction_out call
  - Drop allocations for regmap fields that are used only on init
  - Move some definitions to MFD header
  - Add PM ops to replace driver remove for MFD
  - Change pinctrl driver to (modified) gpio-regmap
  - Change leds driver to use fwnode

Changes since RFC:
  - Dropped MDIO regmap interface. I was unable to resolve the Kconfig
    dependency issue, so have reverted to using regmap_config.reg_read/write.
  - Added pinctrl support
  - Added LED support
  - Changed root device to MFD, with pinctrl and leds child devices. Root
    device is now an mdio_device driver.

Sander Vanheule (5):
  dt-bindings: leds: Binding for RTL8231 scan matrix
  dt-bindings: mfd: Binding for RTL8231
  mfd: Add RTL8231 core device
  pinctrl: Add RTL8231 pin control and GPIO support
  leds: Add support for RTL8231 LED scan matrix

 .../bindings/leds/realtek,rtl8231-leds.yaml   | 166 +++++++
 .../bindings/mfd/realtek,rtl8231.yaml         | 190 ++++++++
 drivers/leds/Kconfig                          |  10 +
 drivers/leds/Makefile                         |   1 +
 drivers/leds/leds-rtl8231.c                   | 291 ++++++++++++
 drivers/mfd/Kconfig                           |   9 +
 drivers/mfd/Makefile                          |   1 +
 drivers/mfd/rtl8231.c                         | 240 ++++++++++
 drivers/pinctrl/Kconfig                       |  11 +
 drivers/pinctrl/Makefile                      |   1 +
 drivers/pinctrl/pinctrl-rtl8231.c             | 438 ++++++++++++++++++
 include/linux/mfd/rtl8231.h                   |  78 ++++
 12 files changed, 1436 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
 create mode 100644 Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
 create mode 100644 drivers/leds/leds-rtl8231.c
 create mode 100644 drivers/mfd/rtl8231.c
 create mode 100644 drivers/pinctrl/pinctrl-rtl8231.c
 create mode 100644 include/linux/mfd/rtl8231.h

-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v4 1/5] dt-bindings: leds: Binding for RTL8231 scan matrix
  2021-06-03 10:00 ` [PATCH v4 0/5] " Sander Vanheule
@ 2021-06-03 10:00   ` Sander Vanheule
  2021-06-03 10:00   ` [PATCH v4 2/5] dt-bindings: mfd: Binding for RTL8231 Sander Vanheule
                     ` (3 subsequent siblings)
  4 siblings, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-06-03 10:00 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij,
	Michael Walle, Andy Shevchenko, linux-leds, devicetree,
	linux-gpio
  Cc: Hans de Goede, Andrew Lunn, linux-kernel, Sander Vanheule, Rob Herring

Add a binding description for the Realtek RTL8231's LED support, which
consists of up to 88 LEDs arranged in a number of scanning matrices.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
Reviewed-by: Rob Herring <robh@kernel.org>

---
v4:
- Add Rob's review tag
---
 .../bindings/leds/realtek,rtl8231-leds.yaml   | 166 ++++++++++++++++++
 1 file changed, 166 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml

diff --git a/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml b/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
new file mode 100644
index 000000000000..560249cd7e8c
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/realtek,rtl8231-leds.yaml
@@ -0,0 +1,166 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/realtek,rtl8231-leds.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Realtek RTL8231 LED scan matrix.
+
+maintainers:
+  - Sander Vanheule <sander@svanheule.net>
+
+description: |
+  The RTL8231 has support for driving a number of LED matrices, by scanning
+  over the LEDs pins, alternatingly lighting different columns and/or rows.
+
+  This functionality is available on an RTL8231, when it is configured for use
+  as an MDIO device, or SMI device.
+
+  In single color scan mode, 88 LEDs are supported. These are grouped into
+  three output matrices:
+    - Group A of 6×6 single color LEDs. Rows and columns are driven by GPIO
+      pins 0-11.
+               L0[n]    L1[n]    L2[n]    L0[n+6]  L1[n+6]  L2[n+6]
+                |        |        |        |        |        |
+       P0/P6  --<--------<--------<--------<--------<--------< (3)
+                |        |        |        |        |        |
+       P1/P7  --<--------<--------<--------<--------<--------< (4)
+                |        |        |        |        |        |
+       P2/P8  --<--------<--------<--------<--------<--------< (5)
+                |        |        |        |        |        |
+       P3/P9  --<--------<--------<--------<--------<--------< (6)
+                |        |        |        |        |        |
+       P4/P10 --<--------<--------<--------<--------<--------< (7)
+                |        |        |        |        |        |
+       P5/P11 --<--------<--------<--------<--------<--------< (8)
+               (0)      (1)      (2)      (9)     (10)     (11)
+    - Group B of 6×6 single color LEDs. Rows and columns are driven by GPIO
+      pins 12-23.
+               L0[n]    L1[n]    L2[n]    L0[n+6]  L1[n+6]  L2[n+6]
+                |        |        |        |        |        |
+      P12/P18 --<--------<--------<--------<--------<--------< (15)
+                |        |        |        |        |        |
+      P13/P19 --<--------<--------<--------<--------<--------< (16)
+                |        |        |        |        |        |
+      P14/P20 --<--------<--------<--------<--------<--------< (17)
+                |        |        |        |        |        |
+      P15/P21 --<--------<--------<--------<--------<--------< (18)
+                |        |        |        |        |        |
+      P16/P22 --<--------<--------<--------<--------<--------< (19)
+                |        |        |        |        |        |
+      P17/P23 --<--------<--------<--------<--------<--------< (20)
+              (12)     (13)     (14)    (21)      (22)     (23)
+    - Group C of 8 pairs of anti-parallel (or bi-color) LEDs. LED selection is
+      provided by GPIO pins 24-27 and 29-32, polarity selection by GPIO 28.
+               P24     P25  ...  P30     P31
+                |       |         |       |
+      LED POL --X-------X---/\/---X-------X (28)
+              (24)    (25)  ... (31)    (32)
+
+  In bi-color scan mode, 72 LEDs are supported. These are grouped into four
+  output matrices:
+    - Group A of 12 pairs of anti-parallel LEDs. LED selection is provided
+      by GPIO pins 0-11, polarity selection by GPIO 12.
+    - Group B of 6 pairs of anti-parallel LEDs. LED selection is provided
+      by GPIO pins 23-28, polarity selection by GPIO 21.
+    - Group C of 6 pairs of anti-parallel LEDs. LED selection is provided
+      by GPIO pins 29-34, polarity selection by GPIO 22.
+    - Group of 4×6 single color LEDs. Rows are driven by GPIO pins 15-20,
+      columns by GPIO pins 13-14 and 21-22 (shared with groups B and C).
+           L2[n]    L2[n+6]   L2[n+12]  L2[n+18]
+            |        |         |         |
+       +0 --<--------<---------<---------< (15)
+            |        |         |         |
+       +1 --<--------<---------<---------< (16)
+            |        |         |         |
+       +2 --<--------<---------<---------< (17)
+            |        |         |         |
+       +3 --<--------<---------<---------< (18)
+            |        |         |         |
+       +4 --<--------<---------<---------< (19)
+            |        |         |         |
+       +6 --<--------<---------<---------< (20)
+          (13)     (14)      (21)      (22)
+
+  This node must always be a child of a 'realtek,rtl8231' node.
+
+properties:
+  $nodename:
+    const: led-controller
+
+  compatible:
+    const: realtek,rtl8231-leds
+
+  "#address-cells":
+    const: 2
+
+  "#size-cells":
+    const: 0
+
+  realtek,led-scan-mode:
+    $ref: /schemas/types.yaml#/definitions/string
+    description: |
+      Specify the scanning mode the chip should run in. See general description
+      for how the scanning matrices are wired up.
+    enum: ["single-color", "bi-color"]
+
+patternProperties:
+  "^led@":
+    description: |
+      LEDs are addressed by their port index and led index. Ports 0-23 always
+      support three LEDs. Additionally, but only when used in single color scan
+      mode, ports 24-31 support two LEDs.
+    type: object
+
+    properties:
+      reg:
+        items:
+          - description: port index
+            maximum: 31
+          - description: led index
+            maximum: 2
+
+    allOf:
+      - $ref: ../leds/common.yaml#
+
+    required:
+      - reg
+
+required:
+  - compatible
+  - "#address-cells"
+  - "#size-cells"
+  - realtek,led-scan-mode
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/leds/common.h>
+    led-controller {
+        compatible = "realtek,rtl8231-leds";
+        #address-cells = <2>;
+        #size-cells = <0>;
+
+        realtek,led-scan-mode = "single-color";
+
+        led@0,0 {
+            reg = <0 0>;
+            color = <LED_COLOR_ID_GREEN>;
+            function = LED_FUNCTION_LAN;
+            function-enumerator = <0>;
+        };
+
+        led@0,1 {
+            reg = <0 1>;
+            color = <LED_COLOR_ID_AMBER>;
+            function = LED_FUNCTION_LAN;
+            function-enumerator = <0>;
+        };
+
+        led@0,2 {
+            reg = <0 2>;
+            color = <LED_COLOR_ID_GREEN>;
+            function = LED_FUNCTION_STATUS;
+        };
+    };
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v4 2/5] dt-bindings: mfd: Binding for RTL8231
  2021-06-03 10:00 ` [PATCH v4 0/5] " Sander Vanheule
  2021-06-03 10:00   ` [PATCH v4 1/5] dt-bindings: leds: Binding for RTL8231 scan matrix Sander Vanheule
@ 2021-06-03 10:00   ` Sander Vanheule
  2021-06-03 10:00   ` [PATCH v4 3/5] mfd: Add RTL8231 core device Sander Vanheule
                     ` (2 subsequent siblings)
  4 siblings, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-06-03 10:00 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij,
	Michael Walle, Andy Shevchenko, linux-leds, devicetree,
	linux-gpio
  Cc: Hans de Goede, Andrew Lunn, linux-kernel, Sander Vanheule, Rob Herring

Add a binding description for the Realtek RTL8231, a GPIO and LED
expander chip commonly used in ethernet switches based on a Realtek
switch SoC. These chips can be addressed via an MDIO or SMI bus, or used
as a plain 36-bit shift register.

This binding only describes the feature set provided by the MDIO/SMI
configuration, and covers the GPIO, PWM, and pin control properties. The
LED properties are defined in a separate binding.

Signed-off-by: Sander Vanheule <sander@svanheule.net>
Reviewed-by: Rob Herring <robh@kernel.org>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>

---
v4:
- Add Rob's and Linus' review tags
---
 .../bindings/mfd/realtek,rtl8231.yaml         | 190 ++++++++++++++++++
 1 file changed, 190 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml

diff --git a/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml b/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
new file mode 100644
index 000000000000..4e2326401560
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/realtek,rtl8231.yaml
@@ -0,0 +1,190 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/realtek,rtl8231.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Realtek RTL8231 GPIO and LED expander.
+
+maintainers:
+  - Sander Vanheule <sander@svanheule.net>
+
+description: |
+  The RTL8231 is a GPIO and LED expander chip, providing up to 37 GPIOs, up to
+  88 LEDs, and up to one PWM output. This device is frequently used alongside
+  Realtek switch SoCs, to provide additional I/O capabilities.
+
+  To manage the RTL8231's features, its strapping pins can be used to configure
+  it in one of three modes: shift register, MDIO device, or SMI device. The
+  shift register mode does not need special support. In MDIO or SMI mode, most
+  pins can be configured as a GPIO output, LED matrix scan line/column, or as a
+  PWM output.
+
+  The GPIO, PWM, and pin control are part of the main node. LED support is
+  configured as a sub-node.
+
+properties:
+  compatible:
+    const: realtek,rtl8231
+
+  reg:
+    description: MDIO or SMI device address.
+    maxItems: 1
+
+  # GPIO support
+  gpio-controller: true
+
+  "#gpio-cells":
+    const: 2
+    description: |
+      The first cell is the pin number and the second cell is used to specify
+      the GPIO active state.
+
+  gpio-ranges:
+    description: |
+      Must reference itself, and provide a zero-based mapping for 37 pins.
+    maxItems: 1
+
+  # Pin muxing and configuration
+  drive-strength:
+    description: |
+      Common drive strength used for all GPIO output pins, must be 4mA or 8mA.
+      On reset, this value will default to 8mA.
+    enum: [4, 8]
+
+  # LED scanning matrix
+  led-controller:
+    $ref: ../leds/realtek,rtl8231-leds.yaml#
+
+  # PWM output
+  "#pwm-cells":
+    description: |
+      Twos cells with PWM index (must be 0) and PWM frequency in Hz. To use
+      the PWM output, gpio35 must be muxed to its 'pwm' function. Valid
+      frequency values for consumers are 1200, 1600, 2000, 2400, 2800, 3200,
+      4000, and 4800.
+    const: 2
+
+patternProperties:
+  "-pins$":
+    type: object
+    $ref: ../pinctrl/pinmux-node.yaml#
+
+    properties:
+      pins:
+        items:
+          enum: ["gpio0", "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6",
+                 "gpio7", "gpio8", "gpio9", "gpio10", "gpio11", "gpio12", "gpio13",
+                 "gpio14", "gpio15", "gpio16", "gpio17", "gpio18", "gpio19", "gpio20",
+                 "gpio21", "gpio22", "gpio23", "gpio24", "gpio25", "gpio26", "gpio27",
+                 "gpio28", "gpio29", "gpio30", "gpio31", "gpio32", "gpio33", "gpio34",
+                 "gpio35", "gpio36"]
+        minItems: 1
+        maxItems: 37
+      function:
+        description: |
+          Select which function to use. "gpio" is supported for all pins, "led" is supported
+          for pins 0-34, "pwm" is supported for pin 35.
+        enum: ["gpio", "led", "pwm"]
+
+    required:
+      - pins
+      - function
+
+required:
+  - compatible
+  - reg
+  - gpio-controller
+  - "#gpio-cells"
+  - gpio-ranges
+
+additionalProperties: false
+
+examples:
+  - |
+    // Minimal example
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        expander0: expander@0 {
+            compatible = "realtek,rtl8231";
+            reg = <0>;
+
+            gpio-controller;
+            #gpio-cells = <2>;
+            gpio-ranges = <&expander0 0 0 37>;
+        };
+    };
+  - |
+    // All bells and whistles included
+    #include <dt-bindings/leds/common.h>
+    mdio {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        expander1: expander@1 {
+            compatible = "realtek,rtl8231";
+            reg = <1>;
+
+            gpio-controller;
+            #gpio-cells = <2>;
+            gpio-ranges = <&expander1 0 0 37>;
+
+            #pwm-cells = <2>;
+
+            drive-strength = <4>;
+
+            button-pins {
+                pins = "gpio36";
+                function = "gpio";
+                input-debounce = "100000";
+            };
+
+            pwm-pins {
+                pins = "gpio35";
+                function = "pwm";
+            };
+
+            led-pins {
+                pins = "gpio0", "gpio1", "gpio3", "gpio4";
+                function = "led";
+            };
+
+            led-controller {
+                compatible = "realtek,rtl8231-leds";
+                #address-cells = <2>;
+                #size-cells = <0>;
+
+                realtek,led-scan-mode = "single-color";
+
+                led@0,0 {
+                    reg = <0 0>;
+                    color = <LED_COLOR_ID_GREEN>;
+                    function = LED_FUNCTION_LAN;
+                    function-enumerator = <0>;
+                };
+
+                led@0,1 {
+                    reg = <0 1>;
+                    color = <LED_COLOR_ID_AMBER>;
+                    function = LED_FUNCTION_LAN;
+                    function-enumerator = <0>;
+                };
+
+                led@1,0 {
+                    reg = <1 0>;
+                    color = <LED_COLOR_ID_GREEN>;
+                    function = LED_FUNCTION_LAN;
+                    function-enumerator = <1>;
+                };
+
+                led@1,1 {
+                    reg = <1 1>;
+                    color = <LED_COLOR_ID_AMBER>;
+                    function = LED_FUNCTION_LAN;
+                    function-enumerator = <1>;
+                };
+            };
+        };
+    };
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v4 3/5] mfd: Add RTL8231 core device
  2021-06-03 10:00 ` [PATCH v4 0/5] " Sander Vanheule
  2021-06-03 10:00   ` [PATCH v4 1/5] dt-bindings: leds: Binding for RTL8231 scan matrix Sander Vanheule
  2021-06-03 10:00   ` [PATCH v4 2/5] dt-bindings: mfd: Binding for RTL8231 Sander Vanheule
@ 2021-06-03 10:00   ` Sander Vanheule
  2021-06-03 10:58     ` Andy Shevchenko
  2021-06-03 10:00   ` [PATCH v4 4/5] pinctrl: Add RTL8231 pin control and GPIO support Sander Vanheule
  2021-06-03 10:00   ` [PATCH v4 5/5] leds: Add support for RTL8231 LED scan matrix Sander Vanheule
  4 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-06-03 10:00 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij,
	Michael Walle, Andy Shevchenko, linux-leds, devicetree,
	linux-gpio
  Cc: Hans de Goede, Andrew Lunn, linux-kernel, Sander Vanheule

The RTL8231 is implemented as an MDIO device, and provides a regmap
interface for register access by the core and child devices.

The chip can also be a device on an SMI bus, an I2C-like bus by Realtek.
Since kernel support for SMI is limited, and no real-world SMI
implementations have been encountered for this device, this is currently
unimplemented. The use of the regmap interface should make any future
support relatively straightforward.

After reset, all pins are muxed to GPIO inputs before the pin drivers
are enabled. This is done to prevent accidental system resets, when a
pin is connected to the parent SoC's reset line.

To provide different read and write semantics for the GPIO data
registers, a secondary virtual register range is used to enable separate
cacheing properties of pin input and output values.

Signed-off-by: Sander Vanheule <sander@svanheule.net>

---
v4:
- Define more bit masks
- Force writes to critical bit fields (reset, output enable)
- Add virtual addresses and cacheing

v3:
- Replace CONFIG_PM-ifdef'ery

v2:
- A missing MDIO_BUS dependency, as was reported by kernel test robot
  <lkp@intel.com>, is provided via REGMAP_MDIO.
  Link: https://lore.kernel.org/lkml/202105122003.JzBO0lrM-lkp@intel.com/
  Link: https://lore.kernel.org/lkml/202105122140.ZFyj5hQy-lkp@intel.com/
---
 drivers/mfd/Kconfig         |   9 ++
 drivers/mfd/Makefile        |   1 +
 drivers/mfd/rtl8231.c       | 240 ++++++++++++++++++++++++++++++++++++
 include/linux/mfd/rtl8231.h |  78 ++++++++++++
 4 files changed, 328 insertions(+)
 create mode 100644 drivers/mfd/rtl8231.c
 create mode 100644 include/linux/mfd/rtl8231.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 5c7f2b100191..68f28a335b8c 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1076,6 +1076,15 @@ config MFD_RDC321X
 	  southbridge which provides access to GPIOs and Watchdog using the
 	  southbridge PCI device configuration space.
 
+config MFD_RTL8231
+	tristate "Realtek RTL8231 GPIO and LED expander"
+	select MFD_CORE
+	select REGMAP_MDIO
+	help
+	  Support for the Realtek RTL8231 GPIO and LED expander.
+	  Provides up to 37 GPIOs, 88 LEDs, and one PWM output.
+	  When built as a module, this module will be named rtl8231.
+
 config MFD_RT5033
 	tristate "Richtek RT5033 Power Management IC"
 	depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 4f6d2b8a5f76..4b27c2486ccc 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -234,6 +234,7 @@ obj-$(CONFIG_MFD_MENF21BMC)	+= menf21bmc.o
 obj-$(CONFIG_MFD_HI6421_PMIC)	+= hi6421-pmic-core.o
 obj-$(CONFIG_MFD_HI655X_PMIC)   += hi655x-pmic.o
 obj-$(CONFIG_MFD_DLN2)		+= dln2.o
+obj-$(CONFIG_MFD_RTL8231)	+= rtl8231.o
 obj-$(CONFIG_MFD_RT5033)	+= rt5033.o
 obj-$(CONFIG_MFD_SKY81452)	+= sky81452.o
 
diff --git a/drivers/mfd/rtl8231.c b/drivers/mfd/rtl8231.c
new file mode 100644
index 000000000000..85ecdab49c3c
--- /dev/null
+++ b/drivers/mfd/rtl8231.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mfd/core.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/rtl8231.h>
+
+#define RTL8231_ALL_PINS_MASK	GENMASK(RTL8231_BITS_VAL - 1, 0)
+#define RTL8231_REAL_REG(reg)	(reg & GENMASK(4, 0))
+
+/* Only specify non-volatile registers that non-zero or write-only */
+static const struct reg_default rtl8231_reg_defaults[] = {
+	{ .reg = RTL8231_REG_PIN_MODE1,       .def = 0xf840 },
+	{ .reg = RTL8231_VREG_GPIO_DATA_OUT0, .def = 0x0000 },
+	{ .reg = RTL8231_VREG_GPIO_DATA_OUT1, .def = 0x0000 },
+	{ .reg = RTL8231_VREG_GPIO_DATA_OUT2, .def = 0x0000 },
+};
+
+static const struct regmap_range rtl8231_readable_ranges[] = {
+	regmap_reg_range(RTL8231_REG_FUNC0, RTL8231_REG_LED_END),
+	regmap_reg_range(RTL8231_REG_GPIO_DATA_IN0, RTL8231_REG_GPIO_DATA_IN2),
+};
+
+static const struct regmap_range rtl8231_non_readable_ranges[] = {
+	regmap_reg_range(0x1f, 0x1f),
+	regmap_reg_range(RTL8231_VREG(RTL8231_REG_FUNC0), RTL8231_VREG(RTL8231_REG_LED_END)),
+	regmap_reg_range(RTL8231_VREG_GPIO_DATA_OUT0, RTL8231_VREG_GPIO_DATA_OUT2),
+};
+
+static const struct regmap_range rtl8231_writeable_ranges[] = {
+	regmap_reg_range(RTL8231_REG_FUNC0, RTL8231_REG_LED_END),
+	regmap_reg_range(RTL8231_VREG_GPIO_DATA_OUT0, RTL8231_VREG_GPIO_DATA_OUT2),
+};
+
+static const struct regmap_range rtl8231_non_writeable_ranges[] = {
+	regmap_reg_range(0x1f, 0x1f),
+	regmap_reg_range(RTL8231_VREG(RTL8231_REG_FUNC0), RTL8231_VREG(RTL8231_REG_LED_END)),
+	regmap_reg_range(RTL8231_REG_GPIO_DATA_IN0, RTL8231_REG_GPIO_DATA_IN2),
+};
+
+static const struct regmap_access_table rtl8231_readable_table = {
+	.yes_ranges = rtl8231_readable_ranges,
+	.n_yes_ranges = ARRAY_SIZE(rtl8231_readable_ranges),
+	.no_ranges = rtl8231_non_readable_ranges,
+	.n_no_ranges = ARRAY_SIZE(rtl8231_non_readable_ranges),
+};
+
+static const struct regmap_access_table rtl8231_writeable_table = {
+	.yes_ranges = rtl8231_writeable_ranges,
+	.n_yes_ranges = ARRAY_SIZE(rtl8231_writeable_ranges),
+	.no_ranges = rtl8231_non_writeable_ranges,
+	.n_no_ranges = ARRAY_SIZE(rtl8231_non_writeable_ranges),
+};
+
+static bool rtl8231_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	/* Registers with self-clearing bits, strapping pin values and inputs */
+	case RTL8231_REG_FUNC0:
+	case RTL8231_REG_FUNC1:
+	case RTL8231_REG_PIN_HI_CFG:
+	case RTL8231_REG_LED_END:
+	case RTL8231_REG_GPIO_DATA_IN0:
+	case RTL8231_REG_GPIO_DATA_IN1:
+	case RTL8231_REG_GPIO_DATA_IN2:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static int rtl8231_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+	struct mdio_device *mdio_dev = context;
+	int ret;
+
+	ret = mdiobus_read(mdio_dev->bus, mdio_dev->addr, RTL8231_REAL_REG(reg));
+
+	if (ret < 0)
+		return ret;
+
+	*val = ret & 0xffff;
+
+	return 0;
+}
+
+static int rtl8231_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+	struct mdio_device *mdio_dev = context;
+
+	return mdiobus_write(mdio_dev->bus, mdio_dev->addr, RTL8231_REAL_REG(reg), val);
+}
+
+static const struct reg_field RTL8231_FIELD_LED_START = REG_FIELD(RTL8231_REG_FUNC0, 1, 1);
+
+static const struct mfd_cell rtl8231_cells[] = {
+	{
+		.name = "rtl8231-pinctrl",
+	},
+	{
+		.name = "rtl8231-leds",
+		.of_compatible = "realtek,rtl8231-leds",
+	},
+};
+
+static int rtl8231_init(struct device *dev, struct regmap *map)
+{
+	unsigned int val;
+	int err;
+
+	err = regmap_read(map, RTL8231_REG_FUNC1, &val);
+	if (err) {
+		dev_err(dev, "failed to read READY_CODE\n");
+		return err;
+	}
+
+	val = FIELD_GET(RTL8231_FUNC1_READY_CODE_MASK, val);
+	if (val != RTL8231_FUNC1_READY_CODE_VALUE) {
+		dev_err(dev, "RTL8231 not present or ready 0x%x != 0x%x\n",
+			val, RTL8231_FUNC1_READY_CODE_VALUE);
+		return -ENODEV;
+	}
+
+	/* SOFT_RESET bit self-clears when done */
+	regmap_write_bits(map, RTL8231_REG_PIN_HI_CFG,
+		RTL8231_PIN_HI_CFG_SOFT_RESET, RTL8231_PIN_HI_CFG_SOFT_RESET);
+	err = regmap_read_poll_timeout(map, RTL8231_REG_PIN_HI_CFG, val,
+		!(val & RTL8231_PIN_HI_CFG_SOFT_RESET), 50, 1000);
+	if (err)
+		return err;
+
+	/*
+	 * Chip reset results in a pin configuration that is a mix of LED and GPIO outputs.
+	 * Select GPI functionality for all pins before enabling pin outputs.
+	 */
+	regmap_write(map, RTL8231_REG_PIN_MODE0, RTL8231_ALL_PINS_MASK);
+	regmap_write(map, RTL8231_REG_GPIO_DIR0, RTL8231_ALL_PINS_MASK);
+	regmap_write(map, RTL8231_REG_PIN_MODE1, RTL8231_ALL_PINS_MASK);
+	regmap_write(map, RTL8231_REG_GPIO_DIR1, RTL8231_ALL_PINS_MASK);
+	regmap_write(map, RTL8231_REG_PIN_HI_CFG,
+		RTL8231_PIN_HI_CFG_MODE_MASK | RTL8231_PIN_HI_CFG_DIR_MASK);
+
+	return 0;
+}
+
+static const struct regmap_config rtl8231_mdio_regmap_config = {
+	.val_bits = RTL8231_BITS_VAL,
+	.reg_bits = RTL8231_BITS_REG,
+	.volatile_reg = rtl8231_volatile_reg,
+	.rd_table = &rtl8231_readable_table,
+	.wr_table = &rtl8231_writeable_table,
+	.max_register = RTL8231_VREG(RTL8231_REG_COUNT - 1),
+	.use_single_read = true,
+	.use_single_write = true,
+	.reg_format_endian = REGMAP_ENDIAN_BIG,
+	.val_format_endian = REGMAP_ENDIAN_BIG,
+	.reg_read = rtl8231_reg_read,
+	.reg_write = rtl8231_reg_write,
+	.cache_type = REGCACHE_FLAT,
+	.reg_defaults = rtl8231_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(rtl8231_reg_defaults),
+};
+
+static int rtl8231_mdio_probe(struct mdio_device *mdiodev)
+{
+	struct device *dev = &mdiodev->dev;
+	struct regmap_field *led_start;
+	struct regmap *map;
+	int err;
+
+	map = devm_regmap_init_mdio(mdiodev, &rtl8231_mdio_regmap_config);
+	if (IS_ERR(map)) {
+		dev_err(dev, "failed to init regmap\n");
+		return PTR_ERR(map);
+	}
+
+	led_start = devm_regmap_field_alloc(dev, map, RTL8231_FIELD_LED_START);
+	if (IS_ERR(led_start))
+		return PTR_ERR(led_start);
+
+	dev_set_drvdata(dev, led_start);
+
+	mdiodev->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+	device_property_read_u32(dev, "reset-assert-delay", &mdiodev->reset_assert_delay);
+	device_property_read_u32(dev, "reset-deassert-delay", &mdiodev->reset_deassert_delay);
+
+	err = rtl8231_init(dev, map);
+	if (err)
+		return err;
+
+	/* LED_START enables power to output pins, and starts the LED engine */
+	regmap_field_force_write(led_start, 1);
+
+	return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, rtl8231_cells,
+		ARRAY_SIZE(rtl8231_cells), NULL, 0, NULL);
+}
+
+__maybe_unused static int rtl8231_suspend(struct device *dev)
+{
+	struct regmap_field *led_start = dev_get_drvdata(dev);
+
+	return regmap_field_force_write(led_start, 0);
+}
+
+__maybe_unused static int rtl8231_resume(struct device *dev)
+{
+	struct regmap_field *led_start = dev_get_drvdata(dev);
+
+	return regmap_field_force_write(led_start, 1);
+}
+
+static SIMPLE_DEV_PM_OPS(rtl8231_pm_ops, rtl8231_suspend, rtl8231_resume);
+
+static const struct of_device_id rtl8231_of_match[] = {
+	{ .compatible = "realtek,rtl8231" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, rtl8231_of_match);
+
+static struct mdio_driver rtl8231_mdio_driver = {
+	.mdiodrv.driver = {
+		.name = "rtl8231-expander",
+		.of_match_table	= rtl8231_of_match,
+		.pm = pm_ptr(&rtl8231_pm_ops),
+	},
+	.probe = rtl8231_mdio_probe,
+};
+mdio_module_driver(rtl8231_mdio_driver);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek RTL8231 GPIO and LED expander");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/rtl8231.h b/include/linux/mfd/rtl8231.h
new file mode 100644
index 000000000000..4ce0eab815f4
--- /dev/null
+++ b/include/linux/mfd/rtl8231.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Register definitions the RTL8231 GPIO and LED expander chip
+ */
+
+#ifndef __LINUX_MFD_RTL8231_H
+#define __LINUX_MFD_RTL8231_H
+
+#include <linux/bits.h>
+
+/*
+ * Registers addresses are 5 bit, values are 16 bit
+ * Also define a duplicated range of virtual addresses, to enable
+ * different read/write behaviour on the GPIO data registers
+ */
+#define RTL8231_BITS_VAL		16
+#define RTL8231_BITS_REG		5
+#define RTL8231_VREG(reg)		((reg) | BIT(RTL8231_BITS_REG))
+
+/* Chip control */
+#define RTL8231_REG_FUNC0		0x00
+#define RTL8231_FUNC0_SCAN_MODE		BIT(0)
+#define RTL8231_FUNC0_SCAN_SINGLE	0
+#define RTL8231_FUNC0_SCAN_BICOLOR	BIT(0)
+
+#define RTL8231_REG_FUNC1		0x01
+#define RTL8231_FUNC1_READY_CODE_VALUE	0x37
+#define RTL8231_FUNC1_READY_CODE_MASK	GENMASK(9, 4)
+#define RTL8231_FUNC1_DEBOUNCE_MASK	GENMASK(15, 10)
+
+/* Pin control */
+#define RTL8231_REG_PIN_MODE0		0x02
+#define RTL8231_REG_PIN_MODE1		0x03
+
+#define RTL8231_PIN_MODE_LED		0
+#define RTL8231_PIN_MODE_GPIO		1
+
+/* Pin high config: pin and GPIO control for pins 32-26 */
+#define RTL8231_REG_PIN_HI_CFG		0x04
+#define RTL8231_PIN_HI_CFG_MODE_MASK	GENMASK(4, 0)
+#define RTL8231_PIN_HI_CFG_DIR_MASK	GENMASK(9, 5)
+#define RTL8231_PIN_HI_CFG_INV_MASK	GENMASK(14, 10)
+#define RTL8231_PIN_HI_CFG_SOFT_RESET	BIT(15)
+
+/* GPIO control registers */
+#define RTL8231_REG_GPIO_DIR0		0x05
+#define RTL8231_REG_GPIO_DIR1		0x06
+#define RTL8231_REG_GPIO_INVERT0	0x07
+#define RTL8231_REG_GPIO_INVERT1	0x08
+
+#define RTL8231_GPIO_DIR_IN		1
+#define RTL8231_GPIO_DIR_OUT		0
+
+/*
+ * GPIO data registers
+ * Only the output data can be written to these registers, and only the input
+ * data can be read. Use a second, virtual register range to ensure proper
+ * read-modify-write behaviour of the output values.
+ */
+#define RTL8231_REG_GPIO_DATA_IN0	0x1c
+#define RTL8231_REG_GPIO_DATA_IN1	0x1d
+#define RTL8231_REG_GPIO_DATA_IN2	0x1e
+
+#define RTL8231_VREG_GPIO_DATA_OUT0	RTL8231_VREG(0x1c)
+#define RTL8231_VREG_GPIO_DATA_OUT1	RTL8231_VREG(0x1d)
+#define RTL8231_VREG_GPIO_DATA_OUT2	RTL8231_VREG(0x1e)
+
+#define RTL8231_PIN_HI_DATA_MASK	GENMASK(4, 0)
+
+/* LED control base registers */
+#define RTL8231_REG_LED0_BASE		0x09
+#define RTL8231_REG_LED1_BASE		0x10
+#define RTL8231_REG_LED2_BASE		0x17
+#define RTL8231_REG_LED_END		0x1b
+
+#define RTL8231_REG_COUNT		0x1f
+
+#endif /* __LINUX_MFD_RTL8231_H */
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v4 4/5] pinctrl: Add RTL8231 pin control and GPIO support
  2021-06-03 10:00 ` [PATCH v4 0/5] " Sander Vanheule
                     ` (2 preceding siblings ...)
  2021-06-03 10:00   ` [PATCH v4 3/5] mfd: Add RTL8231 core device Sander Vanheule
@ 2021-06-03 10:00   ` Sander Vanheule
  2021-06-03 10:18     ` Andy Shevchenko
  2021-06-04 22:10     ` Linus Walleij
  2021-06-03 10:00   ` [PATCH v4 5/5] leds: Add support for RTL8231 LED scan matrix Sander Vanheule
  4 siblings, 2 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-06-03 10:00 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij,
	Michael Walle, Andy Shevchenko, linux-leds, devicetree,
	linux-gpio
  Cc: Hans de Goede, Andrew Lunn, linux-kernel, Sander Vanheule

This driver implements the GPIO and pin muxing features provided by the
RTL8231. The device should be instantiated as an MFD child, where the
parent device has already configured the regmap used for register
access.

Debouncing is only available for the six highest GPIOs, and must be
emulated when other pins are used for (button) inputs. Although
described in the bindings, drive strength selection is currently not
implemented.

Signed-off-by: Sander Vanheule <sander@svanheule.net>

---
v4:
- Switch to pinmux_generic for pin functions
- Add pin debounce pinconf
- Virtual addresses and cacheing
- Use PRT_ERR_OR_ZERO in pinctrl/gpio probe
- Drop direction-first quirk for gpio-regmap

v3:
- Use static pin description for pin controller
- Fix gpio consumer resource leak

v2:
- Use gpio-regmap with direction-before-value output
---
 drivers/pinctrl/Kconfig           |  11 +
 drivers/pinctrl/Makefile          |   1 +
 drivers/pinctrl/pinctrl-rtl8231.c | 438 ++++++++++++++++++++++++++++++
 3 files changed, 450 insertions(+)
 create mode 100644 drivers/pinctrl/pinctrl-rtl8231.c

diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index c2c7e7963ed0..a02c1befbee4 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -221,6 +221,17 @@ config PINCTRL_ROCKCHIP
 	help
           This support pinctrl and gpio driver for Rockchip SoCs.
 
+config PINCTRL_RTL8231
+	tristate "Realtek RTL8231 GPIO expander's pin controller"
+	depends on MFD_RTL8231
+	default MFD_RTL8231
+	select GPIO_REGMAP
+	select GENERIC_PINCONF
+	select GENERIC_PINMUX_FUNCTIONS
+	help
+	  Support for RTL8231 expander's GPIOs and pin controller.
+	  When built as a module, the module will be called pinctrl-rtl8231.
+
 config PINCTRL_SINGLE
 	tristate "One-register-per-pin type device tree based pinctrl driver"
 	depends on OF
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 5ef5334a797f..239603efb317 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_PINCTRL_PALMAS)	+= pinctrl-palmas.o
 obj-$(CONFIG_PINCTRL_PIC32)	+= pinctrl-pic32.o
 obj-$(CONFIG_PINCTRL_PISTACHIO)	+= pinctrl-pistachio.o
 obj-$(CONFIG_PINCTRL_ROCKCHIP)	+= pinctrl-rockchip.o
+obj-$(CONFIG_PINCTRL_RTL8231)	+= pinctrl-rtl8231.o
 obj-$(CONFIG_PINCTRL_SINGLE)	+= pinctrl-single.o
 obj-$(CONFIG_PINCTRL_SX150X)	+= pinctrl-sx150x.o
 obj-$(CONFIG_ARCH_TEGRA)	+= tegra/
diff --git a/drivers/pinctrl/pinctrl-rtl8231.c b/drivers/pinctrl/pinctrl-rtl8231.c
new file mode 100644
index 000000000000..a0f37633b744
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-rtl8231.c
@@ -0,0 +1,438 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bitfield.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/regmap.h>
+#include <linux/module.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "core.h"
+#include "pinmux.h"
+#include <linux/mfd/rtl8231.h>
+
+#define RTL8231_NUM_GPIOS		37
+#define RTL8231_DEBOUNCE_USEC		100000
+#define RTL8231_DEBOUNCE_MIN_OFFSET	31
+
+struct rtl8231_pin_ctrl {
+	struct pinctrl_desc pctl_desc;
+	struct regmap *map;
+};
+
+/*
+ * Pin controller functionality
+ */
+static const char * const rtl8231_pin_function_names[] = {
+	"gpio",
+	"led",
+	"pwm",
+};
+
+enum rtl8231_pin_function {
+	RTL8231_PIN_FUNCTION_GPIO = BIT(0),
+	RTL8231_PIN_FUNCTION_LED = BIT(1),
+	RTL8231_PIN_FUNCTION_PWM = BIT(2),
+};
+
+struct rtl8231_pin_desc {
+	const enum rtl8231_pin_function functions;
+	const u8 reg;
+	const u8 offset;
+	const u8 gpio_function_value;
+};
+
+#define RTL8231_PIN_DESC(_num, _func, _reg, _fld, _val)		\
+	[_num] = {						\
+		.functions = RTL8231_PIN_FUNCTION_GPIO | _func,	\
+		.reg = _reg,					\
+		.offset = _fld,					\
+		.gpio_function_value = _val,			\
+	}
+#define RTL8231_GPIO_PIN_DESC(_num, _reg, _fld)			\
+	RTL8231_PIN_DESC(_num, 0, _reg, _fld, RTL8231_PIN_MODE_GPIO)
+#define RTL8231_LED_PIN_DESC(_num, _reg, _fld)			\
+	RTL8231_PIN_DESC(_num, RTL8231_PIN_FUNCTION_LED, _reg, _fld, RTL8231_PIN_MODE_GPIO)
+#define RTL8231_PWM_PIN_DESC(_num, _reg, _fld)			\
+	RTL8231_PIN_DESC(_num, RTL8231_PIN_FUNCTION_PWM, _reg, _fld, 0)
+
+/*
+ * All pins have a GPIO/LED mux bit, but the bits for pins 35/36 are read-only. Use this bit
+ * for the GPIO-only pin instead of a placeholder, so the rest of the logic can stay generic.
+ */
+static struct rtl8231_pin_desc rtl8231_pin_data[RTL8231_NUM_GPIOS] = {
+	RTL8231_LED_PIN_DESC(0, RTL8231_REG_PIN_MODE0, 0),
+	RTL8231_LED_PIN_DESC(1, RTL8231_REG_PIN_MODE0, 1),
+	RTL8231_LED_PIN_DESC(2, RTL8231_REG_PIN_MODE0, 2),
+	RTL8231_LED_PIN_DESC(3, RTL8231_REG_PIN_MODE0, 3),
+	RTL8231_LED_PIN_DESC(4, RTL8231_REG_PIN_MODE0, 4),
+	RTL8231_LED_PIN_DESC(5, RTL8231_REG_PIN_MODE0, 5),
+	RTL8231_LED_PIN_DESC(6, RTL8231_REG_PIN_MODE0, 6),
+	RTL8231_LED_PIN_DESC(7, RTL8231_REG_PIN_MODE0, 7),
+	RTL8231_LED_PIN_DESC(8, RTL8231_REG_PIN_MODE0, 8),
+	RTL8231_LED_PIN_DESC(9, RTL8231_REG_PIN_MODE0, 9),
+	RTL8231_LED_PIN_DESC(10, RTL8231_REG_PIN_MODE0, 10),
+	RTL8231_LED_PIN_DESC(11, RTL8231_REG_PIN_MODE0, 11),
+	RTL8231_LED_PIN_DESC(12, RTL8231_REG_PIN_MODE0, 12),
+	RTL8231_LED_PIN_DESC(13, RTL8231_REG_PIN_MODE0, 13),
+	RTL8231_LED_PIN_DESC(14, RTL8231_REG_PIN_MODE0, 14),
+	RTL8231_LED_PIN_DESC(15, RTL8231_REG_PIN_MODE0, 15),
+	RTL8231_LED_PIN_DESC(16, RTL8231_REG_PIN_MODE1, 0),
+	RTL8231_LED_PIN_DESC(17, RTL8231_REG_PIN_MODE1, 1),
+	RTL8231_LED_PIN_DESC(18, RTL8231_REG_PIN_MODE1, 2),
+	RTL8231_LED_PIN_DESC(19, RTL8231_REG_PIN_MODE1, 3),
+	RTL8231_LED_PIN_DESC(20, RTL8231_REG_PIN_MODE1, 4),
+	RTL8231_LED_PIN_DESC(21, RTL8231_REG_PIN_MODE1, 5),
+	RTL8231_LED_PIN_DESC(22, RTL8231_REG_PIN_MODE1, 6),
+	RTL8231_LED_PIN_DESC(23, RTL8231_REG_PIN_MODE1, 7),
+	RTL8231_LED_PIN_DESC(24, RTL8231_REG_PIN_MODE1, 8),
+	RTL8231_LED_PIN_DESC(25, RTL8231_REG_PIN_MODE1, 9),
+	RTL8231_LED_PIN_DESC(26, RTL8231_REG_PIN_MODE1, 10),
+	RTL8231_LED_PIN_DESC(27, RTL8231_REG_PIN_MODE1, 11),
+	RTL8231_LED_PIN_DESC(28, RTL8231_REG_PIN_MODE1, 12),
+	RTL8231_LED_PIN_DESC(29, RTL8231_REG_PIN_MODE1, 13),
+	RTL8231_LED_PIN_DESC(30, RTL8231_REG_PIN_MODE1, 14),
+	RTL8231_LED_PIN_DESC(31, RTL8231_REG_PIN_MODE1, 15),
+	RTL8231_LED_PIN_DESC(32, RTL8231_REG_PIN_HI_CFG, 0),
+	RTL8231_LED_PIN_DESC(33, RTL8231_REG_PIN_HI_CFG, 1),
+	RTL8231_LED_PIN_DESC(34, RTL8231_REG_PIN_HI_CFG, 2),
+	RTL8231_PWM_PIN_DESC(35, RTL8231_REG_FUNC1, 3),
+	RTL8231_GPIO_PIN_DESC(36, RTL8231_REG_PIN_HI_CFG, 4),
+};
+
+#define RTL8231_PIN(_num)				\
+	{						\
+		.number = _num,				\
+		.name = "gpio" #_num,			\
+		.drv_data = &rtl8231_pin_data[_num]	\
+	}
+
+static const struct pinctrl_pin_desc rtl8231_pins[RTL8231_NUM_GPIOS] = {
+	RTL8231_PIN(0),
+	RTL8231_PIN(1),
+	RTL8231_PIN(2),
+	RTL8231_PIN(3),
+	RTL8231_PIN(4),
+	RTL8231_PIN(5),
+	RTL8231_PIN(6),
+	RTL8231_PIN(7),
+	RTL8231_PIN(8),
+	RTL8231_PIN(9),
+	RTL8231_PIN(10),
+	RTL8231_PIN(11),
+	RTL8231_PIN(12),
+	RTL8231_PIN(13),
+	RTL8231_PIN(14),
+	RTL8231_PIN(15),
+	RTL8231_PIN(16),
+	RTL8231_PIN(17),
+	RTL8231_PIN(18),
+	RTL8231_PIN(19),
+	RTL8231_PIN(20),
+	RTL8231_PIN(21),
+	RTL8231_PIN(22),
+	RTL8231_PIN(23),
+	RTL8231_PIN(24),
+	RTL8231_PIN(25),
+	RTL8231_PIN(26),
+	RTL8231_PIN(27),
+	RTL8231_PIN(28),
+	RTL8231_PIN(29),
+	RTL8231_PIN(30),
+	RTL8231_PIN(31),
+	RTL8231_PIN(32),
+	RTL8231_PIN(33),
+	RTL8231_PIN(34),
+	RTL8231_PIN(35),
+	RTL8231_PIN(36),
+};
+
+static int rtl8231_get_groups_count(struct pinctrl_dev *pctldev)
+{
+	return ARRAY_SIZE(rtl8231_pins);
+}
+
+static const char *rtl8231_get_group_name(struct pinctrl_dev *pctldev, unsigned int selector)
+{
+	return rtl8231_pins[selector].name;
+}
+
+static int rtl8231_get_group_pins(struct pinctrl_dev *pctldev, unsigned int selector,
+	const unsigned int **pins, unsigned int *num_pins)
+{
+	if (selector >= ARRAY_SIZE(rtl8231_pins))
+		return -EINVAL;
+
+	*pins = &rtl8231_pins[selector].number;
+	*num_pins = 1;
+
+	return 0;
+}
+
+static const struct pinctrl_ops rtl8231_pinctrl_ops = {
+	.get_groups_count = rtl8231_get_groups_count,
+	.get_group_name = rtl8231_get_group_name,
+	.get_group_pins = rtl8231_get_group_pins,
+	.dt_node_to_map = pinconf_generic_dt_node_to_map_all,
+	.dt_free_map = pinconf_generic_dt_free_map,
+};
+
+static int rtl8231_set_mux(struct pinctrl_dev *pctldev, unsigned int func_selector,
+	unsigned int group_selector)
+{
+	const struct function_desc *func = pinmux_generic_get_function(pctldev, func_selector);
+	const struct rtl8231_pin_desc *desc = rtl8231_pins[group_selector].drv_data;
+	const struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+	unsigned int func_flag = (unsigned int) func->data;
+	unsigned int function_mask;
+	unsigned int gpio_function;
+
+	if (!(desc->functions & func_flag))
+		return -EINVAL;
+
+	function_mask = BIT(desc->offset);
+	gpio_function = desc->gpio_function_value << desc->offset;
+
+	if (func_flag == RTL8231_PIN_FUNCTION_GPIO)
+		return regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
+	else
+		return regmap_update_bits(ctrl->map, desc->reg, function_mask, ~gpio_function);
+}
+
+static int rtl8231_gpio_request_enable(struct pinctrl_dev *pctldev,
+	struct pinctrl_gpio_range *range, unsigned int offset)
+{
+	const struct rtl8231_pin_desc *desc = rtl8231_pins[offset].drv_data;
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+	unsigned int function_mask;
+	unsigned int gpio_function;
+
+	function_mask = BIT(desc->offset);
+	gpio_function = desc->gpio_function_value << desc->offset;
+
+	return regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
+}
+
+static const struct pinmux_ops rtl8231_pinmux_ops = {
+	.get_functions_count = pinmux_generic_get_function_count,
+	.get_function_name = pinmux_generic_get_function_name,
+	.get_function_groups = pinmux_generic_get_function_groups,
+	.set_mux = rtl8231_set_mux,
+	.gpio_request_enable = rtl8231_gpio_request_enable,
+	.strict = true,
+};
+
+static int rtl8231_pin_config_get(struct pinctrl_dev *pctldev, unsigned int offset,
+	unsigned long *config)
+{
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+	unsigned int param = pinconf_to_config_param(*config);
+	unsigned int arg;
+	int err;
+	int v;
+
+	switch (param) {
+	case PIN_CONFIG_INPUT_DEBOUNCE:
+		if (offset < RTL8231_DEBOUNCE_MIN_OFFSET)
+			return -EINVAL;
+
+		err = regmap_read(ctrl->map, RTL8231_REG_FUNC1, &v);
+		if (err)
+			return err;
+
+		v = FIELD_GET(RTL8231_FUNC1_DEBOUNCE_MASK, v);
+		if (v & BIT(offset - RTL8231_DEBOUNCE_MIN_OFFSET))
+			arg = RTL8231_DEBOUNCE_USEC;
+		else
+			arg = 0;
+		break;
+	default:
+		return -ENOTSUPP;
+	}
+
+	*config = pinconf_to_config_packed(param, arg);
+
+	return 0;
+}
+
+static int rtl8231_pin_config_set(struct pinctrl_dev *pctldev, unsigned int offset,
+	unsigned long *configs, unsigned int num_configs)
+{
+	struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
+	unsigned int param, arg;
+	unsigned int pin_mask;
+	int err;
+	int i;
+
+	for (i = 0; i < num_configs; i++) {
+		param = pinconf_to_config_param(configs[i]);
+		arg = pinconf_to_config_argument(configs[i]);
+
+		switch (param) {
+		case PIN_CONFIG_INPUT_DEBOUNCE:
+			if (offset < RTL8231_DEBOUNCE_MIN_OFFSET)
+				return -EINVAL;
+
+			pin_mask = FIELD_PREP(RTL8231_FUNC1_DEBOUNCE_MASK,
+				BIT(offset - RTL8231_DEBOUNCE_MIN_OFFSET));
+
+			switch (arg) {
+			case 0:
+				err = regmap_update_bits(ctrl->map, RTL8231_REG_FUNC1,
+					pin_mask, 0);
+				break;
+			case RTL8231_DEBOUNCE_USEC:
+				err = regmap_update_bits(ctrl->map, RTL8231_REG_FUNC1,
+					pin_mask, pin_mask);
+				break;
+			default:
+				return -EINVAL;
+			}
+
+			break;
+		default:
+			return -ENOTSUPP;
+		}
+	}
+
+	return err;
+}
+
+static const struct pinconf_ops rtl8231_pinconf_ops = {
+	.is_generic = true,
+	.pin_config_get = rtl8231_pin_config_get,
+	.pin_config_set = rtl8231_pin_config_set,
+};
+
+static int rtl8231_pinctrl_init_functions(struct pinctrl_dev *pctl, struct rtl8231_pin_ctrl *ctrl)
+{
+	const char *function_name;
+	const char **groups;
+	unsigned int f_idx;
+	unsigned int pin;
+	int num_groups;
+	int err;
+
+	for (f_idx = 0; f_idx < ARRAY_SIZE(rtl8231_pin_function_names); f_idx++) {
+		function_name = rtl8231_pin_function_names[f_idx];
+
+		for (pin = 0, num_groups = 0; pin < ctrl->pctl_desc.npins; pin++)
+			if (rtl8231_pin_data[pin].functions & BIT(f_idx))
+				num_groups++;
+
+		groups = devm_kcalloc(pctl->dev, num_groups, sizeof(*groups), GFP_KERNEL);
+		if (!groups)
+			return -ENOMEM;
+
+		for (pin = 0, num_groups = 0; pin < ctrl->pctl_desc.npins; pin++)
+			if (rtl8231_pin_data[pin].functions & BIT(f_idx))
+				groups[num_groups++] = rtl8231_pins[pin].name;
+
+		err = pinmux_generic_add_function(pctl, function_name, groups, num_groups,
+			(void *) BIT(f_idx));
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int rtl8231_pinctrl_init(struct device *dev, struct rtl8231_pin_ctrl *ctrl)
+{
+	struct pinctrl_dev *pctldev;
+	int err;
+
+	ctrl->pctl_desc.name = "rtl8231-pinctrl";
+	ctrl->pctl_desc.owner = THIS_MODULE;
+	ctrl->pctl_desc.confops = &rtl8231_pinconf_ops;
+	ctrl->pctl_desc.pctlops = &rtl8231_pinctrl_ops;
+	ctrl->pctl_desc.pmxops = &rtl8231_pinmux_ops;
+	ctrl->pctl_desc.npins = ARRAY_SIZE(rtl8231_pins);
+	ctrl->pctl_desc.pins = rtl8231_pins;
+
+	err = devm_pinctrl_register_and_init(dev->parent, &ctrl->pctl_desc, ctrl, &pctldev);
+	if (err) {
+		dev_err(dev, "failed to register pin controller\n");
+		return err;
+	}
+
+	err = rtl8231_pinctrl_init_functions(pctldev, ctrl);
+	if (err)
+		return err;
+
+	err = pinctrl_enable(pctldev);
+	if (err)
+		dev_err(dev, "failed to enable pin controller\n");
+
+	return err;
+}
+
+/*
+ * GPIO controller functionality
+ */
+static int rtl8231_gpio_reg_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
+	unsigned int offset, unsigned int *reg, unsigned int *mask)
+{
+	unsigned int pin_mask = BIT(offset % RTL8231_BITS_VAL);
+
+	if (base == RTL8231_REG_GPIO_DATA_IN0 || base == RTL8231_VREG_GPIO_DATA_OUT0
+		|| offset < 32) {
+		*reg = base + offset / RTL8231_BITS_VAL;
+		*mask = pin_mask;
+	} else if (base == RTL8231_REG_GPIO_DIR0) {
+		*reg = RTL8231_REG_PIN_HI_CFG;
+		*mask = FIELD_PREP(RTL8231_PIN_HI_CFG_DIR_MASK, pin_mask);
+	} else {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rtl8231_pinctrl_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rtl8231_pin_ctrl *ctrl;
+	struct gpio_regmap_config gpio_cfg = {};
+	int err;
+
+	ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+	if (!ctrl)
+		return -ENOMEM;
+
+	ctrl->map = dev_get_regmap(dev->parent, NULL);
+	if (!ctrl->map)
+		return -ENODEV;
+
+	err = rtl8231_pinctrl_init(dev, ctrl);
+	if (err)
+		return err;
+
+	gpio_cfg.regmap = ctrl->map;
+	gpio_cfg.parent = dev->parent;
+	gpio_cfg.ngpio = RTL8231_NUM_GPIOS;
+	gpio_cfg.ngpio_per_reg = RTL8231_BITS_VAL;
+
+	gpio_cfg.reg_dat_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA_IN0);
+	gpio_cfg.reg_set_base = GPIO_REGMAP_ADDR(RTL8231_VREG_GPIO_DATA_OUT0);
+	gpio_cfg.reg_dir_in_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DIR0);
+
+	gpio_cfg.reg_mask_xlate = rtl8231_gpio_reg_mask_xlate;
+
+	return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_cfg));
+}
+
+static struct platform_driver rtl8231_pinctrl_driver = {
+	.driver = {
+		.name = "rtl8231-pinctrl",
+	},
+	.probe = rtl8231_pinctrl_probe,
+};
+module_platform_driver(rtl8231_pinctrl_driver);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek RTL8231 pin control and GPIO support");
+MODULE_LICENSE("GPL v2");
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* [PATCH v4 5/5] leds: Add support for RTL8231 LED scan matrix
  2021-06-03 10:00 ` [PATCH v4 0/5] " Sander Vanheule
                     ` (3 preceding siblings ...)
  2021-06-03 10:00   ` [PATCH v4 4/5] pinctrl: Add RTL8231 pin control and GPIO support Sander Vanheule
@ 2021-06-03 10:00   ` Sander Vanheule
  2021-06-03 11:01     ` Andy Shevchenko
  4 siblings, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-06-03 10:00 UTC (permalink / raw)
  To: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij,
	Michael Walle, Andy Shevchenko, linux-leds, devicetree,
	linux-gpio
  Cc: Hans de Goede, Andrew Lunn, linux-kernel, Sander Vanheule

Both single and bi-color scanning modes are supported. The driver will
verify that the addresses are valid for the current mode, before
registering the LEDs. LEDs can be turned on, off, or toggled at one of
six predefined rates from 40ms to 1280ms.

Implements a platform device for use as a child device with RTL8231 MFD,
and uses the parent regmap to access the required registers.

Signed-off-by: Sander Vanheule <sander@svanheule.net>

---
v4:
- Rename variable addr_count -> err
- Use -EINVAL instead of -ENODEV

v3:
- Rename 'interval' to 'interval_ms'

v2:
- Use fwnode-calls instead of OF-calls
---
 drivers/leds/Kconfig        |  10 ++
 drivers/leds/Makefile       |   1 +
 drivers/leds/leds-rtl8231.c | 291 ++++++++++++++++++++++++++++++++++++
 3 files changed, 302 insertions(+)
 create mode 100644 drivers/leds/leds-rtl8231.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 49d99cb084db..8cb869e8cd09 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -593,6 +593,16 @@ config LEDS_REGULATOR
 	help
 	  This option enables support for regulator driven LEDs.
 
+config LEDS_RTL8231
+	tristate "RTL8231 LED matrix support"
+	depends on LEDS_CLASS
+	depends on MFD_RTL8231
+	default MFD_RTL8231
+	help
+	  This option enables support for using the LED scanning matrix output
+	  of the RTL8231 GPIO and LED expander chip.
+	  When built as a module, this module will be named leds-rtl8231.
+
 config LEDS_BD2802
 	tristate "LED driver for BD2802 RGB LED"
 	depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 7e604d3028c8..ce0f44a87dee 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -80,6 +80,7 @@ obj-$(CONFIG_LEDS_PM8058)		+= leds-pm8058.o
 obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
 obj-$(CONFIG_LEDS_PWM)			+= leds-pwm.o
 obj-$(CONFIG_LEDS_REGULATOR)		+= leds-regulator.o
+obj-$(CONFIG_LEDS_RTL8231)		+= leds-rtl8231.o
 obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o
 obj-$(CONFIG_LEDS_SC27XX_BLTC)		+= leds-sc27xx-bltc.o
 obj-$(CONFIG_LEDS_SGM3140)		+= leds-sgm3140.o
diff --git a/drivers/leds/leds-rtl8231.c b/drivers/leds/leds-rtl8231.c
new file mode 100644
index 000000000000..fb2b1ca419c9
--- /dev/null
+++ b/drivers/leds/leds-rtl8231.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/device.h>
+#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/rtl8231.h>
+
+/**
+ * struct led_toggle_rate - description of an LED blinking mode
+ * @interval_ms:	LED toggle rate in milliseconds
+ * @mode:		Register field value used to activate this mode
+ *
+ * For LED hardware accelerated blinking, with equal on and off delay.
+ * Both delays are given by @interval, so the interval at which the LED blinks
+ * (i.e. turn on and off once) is double this value.
+ */
+struct led_toggle_rate {
+	u16 interval_ms;
+	u8 mode;
+};
+
+/**
+ * struct led_modes - description of all LED modes
+ * @toggle_rates:	Array of led_toggle_rate values, sorted by ascending interval
+ * @num_toggle_rates:	Number of elements in @led_toggle_rate
+ * @off:		Register field value to turn LED off
+ * @on:			Register field value to turn LED on
+ */
+struct led_modes {
+	const struct led_toggle_rate *toggle_rates;
+	unsigned int num_toggle_rates;
+	u8 off;
+	u8 on;
+};
+
+struct rtl8231_led {
+	struct led_classdev led;
+	const struct led_modes *modes;
+	struct regmap_field *reg_field;
+};
+#define to_rtl8231_led(_cdev) container_of(_cdev, struct rtl8231_led, led)
+
+#define RTL8231_NUM_LEDS	3
+#define RTL8231_LED_PER_REG	5
+#define RTL8231_BITS_PER_LED	3
+
+static const unsigned int rtl8231_led_port_counts_single[RTL8231_NUM_LEDS] = {32, 32, 24};
+static const unsigned int rtl8231_led_port_counts_bicolor[RTL8231_NUM_LEDS] = {24, 24, 24};
+
+static const unsigned int rtl8231_led_base[RTL8231_NUM_LEDS] = {
+	RTL8231_REG_LED0_BASE,
+	RTL8231_REG_LED1_BASE,
+	RTL8231_REG_LED2_BASE,
+};
+
+#define RTL8231_DEFAULT_TOGGLE_INTERVAL_MS	500
+
+static const struct led_toggle_rate rtl8231_toggle_rates[] = {
+	{  40, 1},
+	{  80, 2},
+	{ 160, 3},
+	{ 320, 4},
+	{ 640, 5},
+	{1280, 6},
+};
+
+static const struct led_modes rtl8231_led_modes = {
+	.off = 0,
+	.on = 7,
+	.num_toggle_rates = ARRAY_SIZE(rtl8231_toggle_rates),
+	.toggle_rates = rtl8231_toggle_rates,
+};
+
+static void rtl8231_led_brightness_set(struct led_classdev *led_cdev,
+	enum led_brightness brightness)
+{
+	struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
+
+	if (brightness)
+		regmap_field_write(pled->reg_field, pled->modes->on);
+	else
+		regmap_field_write(pled->reg_field, pled->modes->off);
+}
+
+static enum led_brightness rtl8231_led_brightness_get(struct led_classdev *led_cdev)
+{
+	struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
+	u32 current_mode = pled->modes->off;
+
+	regmap_field_read(pled->reg_field, &current_mode);
+
+	if (current_mode == pled->modes->off)
+		return LED_OFF;
+	else
+		return LED_ON;
+}
+
+static unsigned int rtl8231_led_current_interval(struct rtl8231_led *pled)
+{
+	unsigned int mode;
+	unsigned int i;
+
+	if (regmap_field_read(pled->reg_field, &mode))
+		return 0;
+
+	for (i = 0; i < pled->modes->num_toggle_rates; i++)
+		if (mode == pled->modes->toggle_rates[i].mode)
+			return pled->modes->toggle_rates[i].interval_ms;
+
+	return 0;
+}
+
+static int rtl8231_led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
+	unsigned long *delay_off)
+{
+	struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
+	const struct led_toggle_rate *rates = pled->modes->toggle_rates;
+	unsigned int num_rates = pled->modes->num_toggle_rates;
+	unsigned int interval_ms;
+	unsigned int i;
+	int err;
+
+	if (*delay_on == 0 && *delay_off == 0) {
+		interval_ms = RTL8231_DEFAULT_TOGGLE_INTERVAL_MS;
+	} else {
+		/*
+		 * If the current mode is blinking, choose the delay that (likely) changed.
+		 * Otherwise, choose the interval that would have the same total delay.
+		 */
+		interval_ms = rtl8231_led_current_interval(pled);
+		if (interval_ms > 0 && interval_ms == *delay_off)
+			interval_ms = *delay_on;
+		else if (interval_ms > 0 && interval_ms == *delay_on)
+			interval_ms = *delay_off;
+		else
+			interval_ms = (*delay_on + *delay_off) / 2;
+	}
+
+	/* Find clamped toggle interval */
+	for (i = 0; i < (num_rates - 1); i++)
+		if (interval_ms > rates[i].interval_ms)
+			break;
+
+	interval_ms = rates[i].interval_ms;
+
+	err = regmap_field_write(pled->reg_field, rates[i].mode);
+	if (err)
+		return err;
+
+	*delay_on = interval_ms;
+	*delay_off = interval_ms;
+
+	return 0;
+}
+
+static int rtl8231_led_read_address(struct fwnode_handle *fwnode, unsigned int *addr_port,
+	unsigned int *addr_led)
+{
+	u32 addr[2];
+	int err;
+
+	err = fwnode_property_count_u32(fwnode, "reg");
+	if (err < 0)
+		return err;
+	if (err != ARRAY_SIZE(addr))
+		return -EINVAL;
+
+	err = fwnode_property_read_u32_array(fwnode, "reg", addr, ARRAY_SIZE(addr));
+	if (err)
+		return err;
+
+	*addr_port = addr[0];
+	*addr_led = addr[1];
+
+	return 0;
+}
+
+static struct reg_field rtl8231_led_get_field(unsigned int port_index, unsigned int led_index)
+{
+	unsigned int offset, shift;
+	struct reg_field field;
+
+	offset = port_index / RTL8231_LED_PER_REG;
+	shift = (port_index % RTL8231_LED_PER_REG) * RTL8231_BITS_PER_LED;
+
+	field.reg = rtl8231_led_base[led_index] + offset;
+	field.lsb = shift;
+	field.msb = shift + RTL8231_BITS_PER_LED - 1;
+
+	return field;
+}
+
+static int rtl8231_led_probe_single(struct device *dev, struct regmap *map,
+	const unsigned int *port_counts, struct fwnode_handle *fwnode)
+{
+	struct led_init_data init_data = {};
+	struct rtl8231_led *pled;
+	unsigned int port_index;
+	unsigned int led_index;
+	struct reg_field field;
+	int err;
+
+	pled = devm_kzalloc(dev, sizeof(*pled), GFP_KERNEL);
+	if (!pled)
+		return -ENOMEM;
+
+	err = rtl8231_led_read_address(fwnode, &port_index, &led_index);
+	if (err) {
+		dev_err(dev, "LED address invalid\n");
+		return err;
+	}
+
+	if (led_index >= RTL8231_NUM_LEDS || port_index >= port_counts[led_index]) {
+		dev_err(dev, "LED address (%d.%d) invalid\n", port_index, led_index);
+		return -EINVAL;
+	}
+
+	field = rtl8231_led_get_field(port_index, led_index);
+	pled->reg_field = devm_regmap_field_alloc(dev, map, field);
+	if (IS_ERR(pled->reg_field))
+		return PTR_ERR(pled->reg_field);
+
+	pled->modes = &rtl8231_led_modes;
+
+	pled->led.max_brightness = 1;
+	pled->led.brightness_get = rtl8231_led_brightness_get;
+	pled->led.brightness_set = rtl8231_led_brightness_set;
+	pled->led.blink_set = rtl8231_led_blink_set;
+
+	init_data.fwnode = fwnode;
+
+	return devm_led_classdev_register_ext(dev, &pled->led, &init_data);
+}
+
+static int rtl8231_led_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const unsigned int *port_counts;
+	struct fwnode_handle *child;
+	struct regmap *map;
+	int err;
+
+	map = dev_get_regmap(dev->parent, NULL);
+	if (!map)
+		return -ENODEV;
+
+	if (device_property_match_string(dev, "realtek,led-scan-mode", "single-color") >= 0) {
+		port_counts = rtl8231_led_port_counts_single;
+		regmap_update_bits(map, RTL8231_REG_FUNC0,
+			RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_SINGLE);
+	} else if (device_property_match_string(dev, "realtek,led-scan-mode", "bi-color") >= 0) {
+		port_counts = rtl8231_led_port_counts_bicolor;
+		regmap_update_bits(map, RTL8231_REG_FUNC0,
+			RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_BICOLOR);
+	} else {
+		dev_err(dev, "scan mode missing or invalid\n");
+		return -EINVAL;
+	}
+
+	fwnode_for_each_available_child_node(dev->fwnode, child) {
+		err = rtl8231_led_probe_single(dev, map, port_counts, child);
+		if (err)
+			dev_warn(dev, "failed to register LED %pfwP\n", child);
+	}
+
+	return 0;
+}
+
+static const struct of_device_id of_rtl8231_led_match[] = {
+	{ .compatible = "realtek,rtl8231-leds" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, of_rtl8231_led_match);
+
+static struct platform_driver rtl8231_led_driver = {
+	.driver = {
+		.name = "rtl8231-leds",
+		.of_match_table = of_rtl8231_led_match,
+	},
+	.probe = rtl8231_led_probe,
+};
+module_platform_driver(rtl8231_led_driver);
+
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek RTL8231 LED support");
+MODULE_LICENSE("GPL v2");
-- 
2.31.1


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v3 1/6] gpio: regmap: Add quirk for output data register
  2021-05-28  6:40     ` Michael Walle
@ 2021-06-03 10:03       ` Sander Vanheule
  0 siblings, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-06-03 10:03 UTC (permalink / raw)
  To: Michael Walle, Bartosz Golaszewski
  Cc: Pavel Machek, Rob Herring, Lee Jones, Mark Brown,
	Greg Kroah-Hartman, Rafael J . Wysocki, Linus Walleij,
	linux-leds, devicetree, linux-gpio, Andrew Lunn, Andy Shevchenko,
	linux-kernel

Hi Michael, Bartosz,

On Fri, 2021-05-28 at 08:40 +0200, Michael Walle wrote:
> Am 2021-05-24 00:33, schrieb Sander Vanheule:
> > GPIO chips may not support setting the output value when a pin is
> > configured as an input, although the current implementation assumes 
> > this
> > is always possible.
> > 
> > Add support for setting pin direction before value. The order defaults
> > to setting the value first, but this can be reversed by setting the
> > GPIO_REGMAP_QUIRK_SET_DIRECTION_FIRST flag in regmap_config.quirks.
> 
> Nice! If this is really needed:
> 
> Reviewed-by: Michael Walle <michael@walle.cc>

Looks like the quirk won't be needed for this series, but I can always resubmit
it separately if needed.


Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v4 4/5] pinctrl: Add RTL8231 pin control and GPIO support
  2021-06-03 10:00   ` [PATCH v4 4/5] pinctrl: Add RTL8231 pin control and GPIO support Sander Vanheule
@ 2021-06-03 10:18     ` Andy Shevchenko
  2021-06-04 22:10     ` Linus Walleij
  1 sibling, 0 replies; 114+ messages in thread
From: Andy Shevchenko @ 2021-06-03 10:18 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij,
	Michael Walle, Linux LED Subsystem, devicetree,
	open list:GPIO SUBSYSTEM, Hans de Goede, Andrew Lunn,
	Linux Kernel Mailing List

On Thu, Jun 3, 2021 at 1:01 PM Sander Vanheule <sander@svanheule.net> wrote:
>
> This driver implements the GPIO and pin muxing features provided by the
> RTL8231. The device should be instantiated as an MFD child, where the
> parent device has already configured the regmap used for register
> access.
>
> Debouncing is only available for the six highest GPIOs, and must be
> emulated when other pins are used for (button) inputs. Although
> described in the bindings, drive strength selection is currently not
> implemented.

Now it looks so nice that I have a temptation to give 2+ tags, but
let's do with one in accordance with process:
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>

Thanks for doing this! It's a good example of how we can do it in
other cases with regmap and this kind of hardware limitation / design.

> Signed-off-by: Sander Vanheule <sander@svanheule.net>
>
> ---
> v4:
> - Switch to pinmux_generic for pin functions
> - Add pin debounce pinconf
> - Virtual addresses and cacheing
> - Use PRT_ERR_OR_ZERO in pinctrl/gpio probe
> - Drop direction-first quirk for gpio-regmap
>
> v3:
> - Use static pin description for pin controller
> - Fix gpio consumer resource leak
>
> v2:
> - Use gpio-regmap with direction-before-value output
> ---
>  drivers/pinctrl/Kconfig           |  11 +
>  drivers/pinctrl/Makefile          |   1 +
>  drivers/pinctrl/pinctrl-rtl8231.c | 438 ++++++++++++++++++++++++++++++
>  3 files changed, 450 insertions(+)
>  create mode 100644 drivers/pinctrl/pinctrl-rtl8231.c
>
> diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
> index c2c7e7963ed0..a02c1befbee4 100644
> --- a/drivers/pinctrl/Kconfig
> +++ b/drivers/pinctrl/Kconfig
> @@ -221,6 +221,17 @@ config PINCTRL_ROCKCHIP
>         help
>            This support pinctrl and gpio driver for Rockchip SoCs.
>
> +config PINCTRL_RTL8231
> +       tristate "Realtek RTL8231 GPIO expander's pin controller"
> +       depends on MFD_RTL8231
> +       default MFD_RTL8231
> +       select GPIO_REGMAP
> +       select GENERIC_PINCONF
> +       select GENERIC_PINMUX_FUNCTIONS
> +       help
> +         Support for RTL8231 expander's GPIOs and pin controller.
> +         When built as a module, the module will be called pinctrl-rtl8231.
> +
>  config PINCTRL_SINGLE
>         tristate "One-register-per-pin type device tree based pinctrl driver"
>         depends on OF
> diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
> index 5ef5334a797f..239603efb317 100644
> --- a/drivers/pinctrl/Makefile
> +++ b/drivers/pinctrl/Makefile
> @@ -30,6 +30,7 @@ obj-$(CONFIG_PINCTRL_PALMAS)  += pinctrl-palmas.o
>  obj-$(CONFIG_PINCTRL_PIC32)    += pinctrl-pic32.o
>  obj-$(CONFIG_PINCTRL_PISTACHIO)        += pinctrl-pistachio.o
>  obj-$(CONFIG_PINCTRL_ROCKCHIP) += pinctrl-rockchip.o
> +obj-$(CONFIG_PINCTRL_RTL8231)  += pinctrl-rtl8231.o
>  obj-$(CONFIG_PINCTRL_SINGLE)   += pinctrl-single.o
>  obj-$(CONFIG_PINCTRL_SX150X)   += pinctrl-sx150x.o
>  obj-$(CONFIG_ARCH_TEGRA)       += tegra/
> diff --git a/drivers/pinctrl/pinctrl-rtl8231.c b/drivers/pinctrl/pinctrl-rtl8231.c
> new file mode 100644
> index 000000000000..a0f37633b744
> --- /dev/null
> +++ b/drivers/pinctrl/pinctrl-rtl8231.c
> @@ -0,0 +1,438 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/bitfield.h>
> +#include <linux/gpio/driver.h>
> +#include <linux/gpio/regmap.h>
> +#include <linux/module.h>
> +#include <linux/pinctrl/pinconf.h>
> +#include <linux/pinctrl/pinctrl.h>
> +#include <linux/pinctrl/pinmux.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +#include "core.h"
> +#include "pinmux.h"
> +#include <linux/mfd/rtl8231.h>
> +
> +#define RTL8231_NUM_GPIOS              37
> +#define RTL8231_DEBOUNCE_USEC          100000
> +#define RTL8231_DEBOUNCE_MIN_OFFSET    31
> +
> +struct rtl8231_pin_ctrl {
> +       struct pinctrl_desc pctl_desc;
> +       struct regmap *map;
> +};
> +
> +/*
> + * Pin controller functionality
> + */
> +static const char * const rtl8231_pin_function_names[] = {
> +       "gpio",
> +       "led",
> +       "pwm",
> +};
> +
> +enum rtl8231_pin_function {
> +       RTL8231_PIN_FUNCTION_GPIO = BIT(0),
> +       RTL8231_PIN_FUNCTION_LED = BIT(1),
> +       RTL8231_PIN_FUNCTION_PWM = BIT(2),
> +};
> +
> +struct rtl8231_pin_desc {
> +       const enum rtl8231_pin_function functions;
> +       const u8 reg;
> +       const u8 offset;
> +       const u8 gpio_function_value;
> +};
> +
> +#define RTL8231_PIN_DESC(_num, _func, _reg, _fld, _val)                \
> +       [_num] = {                                              \
> +               .functions = RTL8231_PIN_FUNCTION_GPIO | _func, \
> +               .reg = _reg,                                    \
> +               .offset = _fld,                                 \
> +               .gpio_function_value = _val,                    \
> +       }
> +#define RTL8231_GPIO_PIN_DESC(_num, _reg, _fld)                        \
> +       RTL8231_PIN_DESC(_num, 0, _reg, _fld, RTL8231_PIN_MODE_GPIO)
> +#define RTL8231_LED_PIN_DESC(_num, _reg, _fld)                 \
> +       RTL8231_PIN_DESC(_num, RTL8231_PIN_FUNCTION_LED, _reg, _fld, RTL8231_PIN_MODE_GPIO)
> +#define RTL8231_PWM_PIN_DESC(_num, _reg, _fld)                 \
> +       RTL8231_PIN_DESC(_num, RTL8231_PIN_FUNCTION_PWM, _reg, _fld, 0)
> +
> +/*
> + * All pins have a GPIO/LED mux bit, but the bits for pins 35/36 are read-only. Use this bit
> + * for the GPIO-only pin instead of a placeholder, so the rest of the logic can stay generic.
> + */
> +static struct rtl8231_pin_desc rtl8231_pin_data[RTL8231_NUM_GPIOS] = {
> +       RTL8231_LED_PIN_DESC(0, RTL8231_REG_PIN_MODE0, 0),
> +       RTL8231_LED_PIN_DESC(1, RTL8231_REG_PIN_MODE0, 1),
> +       RTL8231_LED_PIN_DESC(2, RTL8231_REG_PIN_MODE0, 2),
> +       RTL8231_LED_PIN_DESC(3, RTL8231_REG_PIN_MODE0, 3),
> +       RTL8231_LED_PIN_DESC(4, RTL8231_REG_PIN_MODE0, 4),
> +       RTL8231_LED_PIN_DESC(5, RTL8231_REG_PIN_MODE0, 5),
> +       RTL8231_LED_PIN_DESC(6, RTL8231_REG_PIN_MODE0, 6),
> +       RTL8231_LED_PIN_DESC(7, RTL8231_REG_PIN_MODE0, 7),
> +       RTL8231_LED_PIN_DESC(8, RTL8231_REG_PIN_MODE0, 8),
> +       RTL8231_LED_PIN_DESC(9, RTL8231_REG_PIN_MODE0, 9),
> +       RTL8231_LED_PIN_DESC(10, RTL8231_REG_PIN_MODE0, 10),
> +       RTL8231_LED_PIN_DESC(11, RTL8231_REG_PIN_MODE0, 11),
> +       RTL8231_LED_PIN_DESC(12, RTL8231_REG_PIN_MODE0, 12),
> +       RTL8231_LED_PIN_DESC(13, RTL8231_REG_PIN_MODE0, 13),
> +       RTL8231_LED_PIN_DESC(14, RTL8231_REG_PIN_MODE0, 14),
> +       RTL8231_LED_PIN_DESC(15, RTL8231_REG_PIN_MODE0, 15),
> +       RTL8231_LED_PIN_DESC(16, RTL8231_REG_PIN_MODE1, 0),
> +       RTL8231_LED_PIN_DESC(17, RTL8231_REG_PIN_MODE1, 1),
> +       RTL8231_LED_PIN_DESC(18, RTL8231_REG_PIN_MODE1, 2),
> +       RTL8231_LED_PIN_DESC(19, RTL8231_REG_PIN_MODE1, 3),
> +       RTL8231_LED_PIN_DESC(20, RTL8231_REG_PIN_MODE1, 4),
> +       RTL8231_LED_PIN_DESC(21, RTL8231_REG_PIN_MODE1, 5),
> +       RTL8231_LED_PIN_DESC(22, RTL8231_REG_PIN_MODE1, 6),
> +       RTL8231_LED_PIN_DESC(23, RTL8231_REG_PIN_MODE1, 7),
> +       RTL8231_LED_PIN_DESC(24, RTL8231_REG_PIN_MODE1, 8),
> +       RTL8231_LED_PIN_DESC(25, RTL8231_REG_PIN_MODE1, 9),
> +       RTL8231_LED_PIN_DESC(26, RTL8231_REG_PIN_MODE1, 10),
> +       RTL8231_LED_PIN_DESC(27, RTL8231_REG_PIN_MODE1, 11),
> +       RTL8231_LED_PIN_DESC(28, RTL8231_REG_PIN_MODE1, 12),
> +       RTL8231_LED_PIN_DESC(29, RTL8231_REG_PIN_MODE1, 13),
> +       RTL8231_LED_PIN_DESC(30, RTL8231_REG_PIN_MODE1, 14),
> +       RTL8231_LED_PIN_DESC(31, RTL8231_REG_PIN_MODE1, 15),
> +       RTL8231_LED_PIN_DESC(32, RTL8231_REG_PIN_HI_CFG, 0),
> +       RTL8231_LED_PIN_DESC(33, RTL8231_REG_PIN_HI_CFG, 1),
> +       RTL8231_LED_PIN_DESC(34, RTL8231_REG_PIN_HI_CFG, 2),
> +       RTL8231_PWM_PIN_DESC(35, RTL8231_REG_FUNC1, 3),
> +       RTL8231_GPIO_PIN_DESC(36, RTL8231_REG_PIN_HI_CFG, 4),
> +};
> +
> +#define RTL8231_PIN(_num)                              \
> +       {                                               \
> +               .number = _num,                         \
> +               .name = "gpio" #_num,                   \
> +               .drv_data = &rtl8231_pin_data[_num]     \
> +       }
> +
> +static const struct pinctrl_pin_desc rtl8231_pins[RTL8231_NUM_GPIOS] = {
> +       RTL8231_PIN(0),
> +       RTL8231_PIN(1),
> +       RTL8231_PIN(2),
> +       RTL8231_PIN(3),
> +       RTL8231_PIN(4),
> +       RTL8231_PIN(5),
> +       RTL8231_PIN(6),
> +       RTL8231_PIN(7),
> +       RTL8231_PIN(8),
> +       RTL8231_PIN(9),
> +       RTL8231_PIN(10),
> +       RTL8231_PIN(11),
> +       RTL8231_PIN(12),
> +       RTL8231_PIN(13),
> +       RTL8231_PIN(14),
> +       RTL8231_PIN(15),
> +       RTL8231_PIN(16),
> +       RTL8231_PIN(17),
> +       RTL8231_PIN(18),
> +       RTL8231_PIN(19),
> +       RTL8231_PIN(20),
> +       RTL8231_PIN(21),
> +       RTL8231_PIN(22),
> +       RTL8231_PIN(23),
> +       RTL8231_PIN(24),
> +       RTL8231_PIN(25),
> +       RTL8231_PIN(26),
> +       RTL8231_PIN(27),
> +       RTL8231_PIN(28),
> +       RTL8231_PIN(29),
> +       RTL8231_PIN(30),
> +       RTL8231_PIN(31),
> +       RTL8231_PIN(32),
> +       RTL8231_PIN(33),
> +       RTL8231_PIN(34),
> +       RTL8231_PIN(35),
> +       RTL8231_PIN(36),
> +};
> +
> +static int rtl8231_get_groups_count(struct pinctrl_dev *pctldev)
> +{
> +       return ARRAY_SIZE(rtl8231_pins);
> +}
> +
> +static const char *rtl8231_get_group_name(struct pinctrl_dev *pctldev, unsigned int selector)
> +{
> +       return rtl8231_pins[selector].name;
> +}
> +
> +static int rtl8231_get_group_pins(struct pinctrl_dev *pctldev, unsigned int selector,
> +       const unsigned int **pins, unsigned int *num_pins)
> +{
> +       if (selector >= ARRAY_SIZE(rtl8231_pins))
> +               return -EINVAL;
> +
> +       *pins = &rtl8231_pins[selector].number;
> +       *num_pins = 1;
> +
> +       return 0;
> +}
> +
> +static const struct pinctrl_ops rtl8231_pinctrl_ops = {
> +       .get_groups_count = rtl8231_get_groups_count,
> +       .get_group_name = rtl8231_get_group_name,
> +       .get_group_pins = rtl8231_get_group_pins,
> +       .dt_node_to_map = pinconf_generic_dt_node_to_map_all,
> +       .dt_free_map = pinconf_generic_dt_free_map,
> +};
> +
> +static int rtl8231_set_mux(struct pinctrl_dev *pctldev, unsigned int func_selector,
> +       unsigned int group_selector)
> +{
> +       const struct function_desc *func = pinmux_generic_get_function(pctldev, func_selector);
> +       const struct rtl8231_pin_desc *desc = rtl8231_pins[group_selector].drv_data;
> +       const struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
> +       unsigned int func_flag = (unsigned int) func->data;
> +       unsigned int function_mask;
> +       unsigned int gpio_function;
> +
> +       if (!(desc->functions & func_flag))
> +               return -EINVAL;
> +
> +       function_mask = BIT(desc->offset);
> +       gpio_function = desc->gpio_function_value << desc->offset;
> +
> +       if (func_flag == RTL8231_PIN_FUNCTION_GPIO)
> +               return regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
> +       else
> +               return regmap_update_bits(ctrl->map, desc->reg, function_mask, ~gpio_function);
> +}
> +
> +static int rtl8231_gpio_request_enable(struct pinctrl_dev *pctldev,
> +       struct pinctrl_gpio_range *range, unsigned int offset)
> +{
> +       const struct rtl8231_pin_desc *desc = rtl8231_pins[offset].drv_data;
> +       struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
> +       unsigned int function_mask;
> +       unsigned int gpio_function;
> +
> +       function_mask = BIT(desc->offset);
> +       gpio_function = desc->gpio_function_value << desc->offset;
> +
> +       return regmap_update_bits(ctrl->map, desc->reg, function_mask, gpio_function);
> +}
> +
> +static const struct pinmux_ops rtl8231_pinmux_ops = {
> +       .get_functions_count = pinmux_generic_get_function_count,
> +       .get_function_name = pinmux_generic_get_function_name,
> +       .get_function_groups = pinmux_generic_get_function_groups,
> +       .set_mux = rtl8231_set_mux,
> +       .gpio_request_enable = rtl8231_gpio_request_enable,
> +       .strict = true,
> +};
> +
> +static int rtl8231_pin_config_get(struct pinctrl_dev *pctldev, unsigned int offset,
> +       unsigned long *config)
> +{
> +       struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
> +       unsigned int param = pinconf_to_config_param(*config);
> +       unsigned int arg;
> +       int err;
> +       int v;
> +
> +       switch (param) {
> +       case PIN_CONFIG_INPUT_DEBOUNCE:
> +               if (offset < RTL8231_DEBOUNCE_MIN_OFFSET)
> +                       return -EINVAL;
> +
> +               err = regmap_read(ctrl->map, RTL8231_REG_FUNC1, &v);
> +               if (err)
> +                       return err;
> +
> +               v = FIELD_GET(RTL8231_FUNC1_DEBOUNCE_MASK, v);
> +               if (v & BIT(offset - RTL8231_DEBOUNCE_MIN_OFFSET))
> +                       arg = RTL8231_DEBOUNCE_USEC;
> +               else
> +                       arg = 0;
> +               break;
> +       default:
> +               return -ENOTSUPP;
> +       }
> +
> +       *config = pinconf_to_config_packed(param, arg);
> +
> +       return 0;
> +}
> +
> +static int rtl8231_pin_config_set(struct pinctrl_dev *pctldev, unsigned int offset,
> +       unsigned long *configs, unsigned int num_configs)
> +{
> +       struct rtl8231_pin_ctrl *ctrl = pinctrl_dev_get_drvdata(pctldev);
> +       unsigned int param, arg;
> +       unsigned int pin_mask;
> +       int err;
> +       int i;
> +
> +       for (i = 0; i < num_configs; i++) {
> +               param = pinconf_to_config_param(configs[i]);
> +               arg = pinconf_to_config_argument(configs[i]);
> +
> +               switch (param) {
> +               case PIN_CONFIG_INPUT_DEBOUNCE:
> +                       if (offset < RTL8231_DEBOUNCE_MIN_OFFSET)
> +                               return -EINVAL;
> +
> +                       pin_mask = FIELD_PREP(RTL8231_FUNC1_DEBOUNCE_MASK,
> +                               BIT(offset - RTL8231_DEBOUNCE_MIN_OFFSET));
> +
> +                       switch (arg) {
> +                       case 0:
> +                               err = regmap_update_bits(ctrl->map, RTL8231_REG_FUNC1,
> +                                       pin_mask, 0);
> +                               break;
> +                       case RTL8231_DEBOUNCE_USEC:
> +                               err = regmap_update_bits(ctrl->map, RTL8231_REG_FUNC1,
> +                                       pin_mask, pin_mask);
> +                               break;
> +                       default:
> +                               return -EINVAL;
> +                       }
> +
> +                       break;
> +               default:
> +                       return -ENOTSUPP;
> +               }
> +       }
> +
> +       return err;
> +}
> +
> +static const struct pinconf_ops rtl8231_pinconf_ops = {
> +       .is_generic = true,
> +       .pin_config_get = rtl8231_pin_config_get,
> +       .pin_config_set = rtl8231_pin_config_set,
> +};
> +
> +static int rtl8231_pinctrl_init_functions(struct pinctrl_dev *pctl, struct rtl8231_pin_ctrl *ctrl)
> +{
> +       const char *function_name;
> +       const char **groups;
> +       unsigned int f_idx;
> +       unsigned int pin;
> +       int num_groups;
> +       int err;
> +
> +       for (f_idx = 0; f_idx < ARRAY_SIZE(rtl8231_pin_function_names); f_idx++) {
> +               function_name = rtl8231_pin_function_names[f_idx];
> +
> +               for (pin = 0, num_groups = 0; pin < ctrl->pctl_desc.npins; pin++)
> +                       if (rtl8231_pin_data[pin].functions & BIT(f_idx))
> +                               num_groups++;
> +
> +               groups = devm_kcalloc(pctl->dev, num_groups, sizeof(*groups), GFP_KERNEL);
> +               if (!groups)
> +                       return -ENOMEM;
> +
> +               for (pin = 0, num_groups = 0; pin < ctrl->pctl_desc.npins; pin++)
> +                       if (rtl8231_pin_data[pin].functions & BIT(f_idx))
> +                               groups[num_groups++] = rtl8231_pins[pin].name;
> +
> +               err = pinmux_generic_add_function(pctl, function_name, groups, num_groups,
> +                       (void *) BIT(f_idx));
> +               if (err < 0)
> +                       return err;
> +       }
> +
> +       return 0;
> +}
> +
> +static int rtl8231_pinctrl_init(struct device *dev, struct rtl8231_pin_ctrl *ctrl)
> +{
> +       struct pinctrl_dev *pctldev;
> +       int err;
> +
> +       ctrl->pctl_desc.name = "rtl8231-pinctrl";
> +       ctrl->pctl_desc.owner = THIS_MODULE;
> +       ctrl->pctl_desc.confops = &rtl8231_pinconf_ops;
> +       ctrl->pctl_desc.pctlops = &rtl8231_pinctrl_ops;
> +       ctrl->pctl_desc.pmxops = &rtl8231_pinmux_ops;
> +       ctrl->pctl_desc.npins = ARRAY_SIZE(rtl8231_pins);
> +       ctrl->pctl_desc.pins = rtl8231_pins;
> +
> +       err = devm_pinctrl_register_and_init(dev->parent, &ctrl->pctl_desc, ctrl, &pctldev);
> +       if (err) {
> +               dev_err(dev, "failed to register pin controller\n");
> +               return err;
> +       }
> +
> +       err = rtl8231_pinctrl_init_functions(pctldev, ctrl);
> +       if (err)
> +               return err;
> +
> +       err = pinctrl_enable(pctldev);
> +       if (err)
> +               dev_err(dev, "failed to enable pin controller\n");
> +
> +       return err;
> +}
> +
> +/*
> + * GPIO controller functionality
> + */
> +static int rtl8231_gpio_reg_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
> +       unsigned int offset, unsigned int *reg, unsigned int *mask)
> +{
> +       unsigned int pin_mask = BIT(offset % RTL8231_BITS_VAL);
> +
> +       if (base == RTL8231_REG_GPIO_DATA_IN0 || base == RTL8231_VREG_GPIO_DATA_OUT0
> +               || offset < 32) {
> +               *reg = base + offset / RTL8231_BITS_VAL;
> +               *mask = pin_mask;
> +       } else if (base == RTL8231_REG_GPIO_DIR0) {
> +               *reg = RTL8231_REG_PIN_HI_CFG;
> +               *mask = FIELD_PREP(RTL8231_PIN_HI_CFG_DIR_MASK, pin_mask);
> +       } else {
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +
> +static int rtl8231_pinctrl_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct rtl8231_pin_ctrl *ctrl;
> +       struct gpio_regmap_config gpio_cfg = {};
> +       int err;
> +
> +       ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
> +       if (!ctrl)
> +               return -ENOMEM;
> +
> +       ctrl->map = dev_get_regmap(dev->parent, NULL);
> +       if (!ctrl->map)
> +               return -ENODEV;
> +
> +       err = rtl8231_pinctrl_init(dev, ctrl);
> +       if (err)
> +               return err;
> +
> +       gpio_cfg.regmap = ctrl->map;
> +       gpio_cfg.parent = dev->parent;
> +       gpio_cfg.ngpio = RTL8231_NUM_GPIOS;
> +       gpio_cfg.ngpio_per_reg = RTL8231_BITS_VAL;
> +
> +       gpio_cfg.reg_dat_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DATA_IN0);
> +       gpio_cfg.reg_set_base = GPIO_REGMAP_ADDR(RTL8231_VREG_GPIO_DATA_OUT0);
> +       gpio_cfg.reg_dir_in_base = GPIO_REGMAP_ADDR(RTL8231_REG_GPIO_DIR0);
> +
> +       gpio_cfg.reg_mask_xlate = rtl8231_gpio_reg_mask_xlate;
> +
> +       return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_cfg));
> +}
> +
> +static struct platform_driver rtl8231_pinctrl_driver = {
> +       .driver = {
> +               .name = "rtl8231-pinctrl",
> +       },
> +       .probe = rtl8231_pinctrl_probe,
> +};
> +module_platform_driver(rtl8231_pinctrl_driver);
> +
> +MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
> +MODULE_DESCRIPTION("Realtek RTL8231 pin control and GPIO support");
> +MODULE_LICENSE("GPL v2");
> --
> 2.31.1
>


-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v4 3/5] mfd: Add RTL8231 core device
  2021-06-03 10:00   ` [PATCH v4 3/5] mfd: Add RTL8231 core device Sander Vanheule
@ 2021-06-03 10:58     ` Andy Shevchenko
  2021-06-03 11:28       ` Sander Vanheule
  2021-06-05 11:07       ` Sander Vanheule
  0 siblings, 2 replies; 114+ messages in thread
From: Andy Shevchenko @ 2021-06-03 10:58 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij,
	Michael Walle, Linux LED Subsystem, devicetree,
	open list:GPIO SUBSYSTEM, Hans de Goede, Andrew Lunn,
	Linux Kernel Mailing List

On Thu, Jun 3, 2021 at 1:01 PM Sander Vanheule <sander@svanheule.net> wrote:
>
> The RTL8231 is implemented as an MDIO device, and provides a regmap
> interface for register access by the core and child devices.
>
> The chip can also be a device on an SMI bus, an I2C-like bus by Realtek.
> Since kernel support for SMI is limited, and no real-world SMI
> implementations have been encountered for this device, this is currently
> unimplemented. The use of the regmap interface should make any future
> support relatively straightforward.
>
> After reset, all pins are muxed to GPIO inputs before the pin drivers
> are enabled. This is done to prevent accidental system resets, when a
> pin is connected to the parent SoC's reset line.
>
> To provide different read and write semantics for the GPIO data
> registers, a secondary virtual register range is used to enable separate
> cacheing properties of pin input and output values.

caching

...


> +static int rtl8231_reg_read(void *context, unsigned int reg, unsigned int *val)
> +{
> +       struct mdio_device *mdio_dev = context;
> +       int ret;
> +
> +       ret = mdiobus_read(mdio_dev->bus, mdio_dev->addr, RTL8231_REAL_REG(reg));
> +
> +       if (ret < 0)
> +               return ret;
> +
> +       *val = ret & 0xffff;
> +
> +       return 0;
> +}
> +
> +static int rtl8231_reg_write(void *context, unsigned int reg, unsigned int val)
> +{
> +       struct mdio_device *mdio_dev = context;
> +
> +       return mdiobus_write(mdio_dev->bus, mdio_dev->addr, RTL8231_REAL_REG(reg), val);
> +}

Hmm... Maybe we can amend regmap-mdio to avoid duplication of the
above? Something like xlate in gpio-regmap or so?

...

> +       mdiodev->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);

Missed

  if (IS_ERR(mdiodev->reset_gpio))
    return PTR_ERR(mdiodev->reset_gpio);

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v4 5/5] leds: Add support for RTL8231 LED scan matrix
  2021-06-03 10:00   ` [PATCH v4 5/5] leds: Add support for RTL8231 LED scan matrix Sander Vanheule
@ 2021-06-03 11:01     ` Andy Shevchenko
  0 siblings, 0 replies; 114+ messages in thread
From: Andy Shevchenko @ 2021-06-03 11:01 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij,
	Michael Walle, Linux LED Subsystem, devicetree,
	open list:GPIO SUBSYSTEM, Hans de Goede, Andrew Lunn,
	Linux Kernel Mailing List

On Thu, Jun 3, 2021 at 1:01 PM Sander Vanheule <sander@svanheule.net> wrote:
>
> Both single and bi-color scanning modes are supported. The driver will
> verify that the addresses are valid for the current mode, before
> registering the LEDs. LEDs can be turned on, off, or toggled at one of
> six predefined rates from 40ms to 1280ms.
>
> Implements a platform device for use as a child device with RTL8231 MFD,
> and uses the parent regmap to access the required registers.

FWIW,
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>

> Signed-off-by: Sander Vanheule <sander@svanheule.net>
>
> ---
> v4:
> - Rename variable addr_count -> err
> - Use -EINVAL instead of -ENODEV
>
> v3:
> - Rename 'interval' to 'interval_ms'
>
> v2:
> - Use fwnode-calls instead of OF-calls
> ---
>  drivers/leds/Kconfig        |  10 ++
>  drivers/leds/Makefile       |   1 +
>  drivers/leds/leds-rtl8231.c | 291 ++++++++++++++++++++++++++++++++++++
>  3 files changed, 302 insertions(+)
>  create mode 100644 drivers/leds/leds-rtl8231.c
>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index 49d99cb084db..8cb869e8cd09 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -593,6 +593,16 @@ config LEDS_REGULATOR
>         help
>           This option enables support for regulator driven LEDs.
>
> +config LEDS_RTL8231
> +       tristate "RTL8231 LED matrix support"
> +       depends on LEDS_CLASS
> +       depends on MFD_RTL8231
> +       default MFD_RTL8231
> +       help
> +         This option enables support for using the LED scanning matrix output
> +         of the RTL8231 GPIO and LED expander chip.
> +         When built as a module, this module will be named leds-rtl8231.
> +
>  config LEDS_BD2802
>         tristate "LED driver for BD2802 RGB LED"
>         depends on LEDS_CLASS
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index 7e604d3028c8..ce0f44a87dee 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -80,6 +80,7 @@ obj-$(CONFIG_LEDS_PM8058)             += leds-pm8058.o
>  obj-$(CONFIG_LEDS_POWERNV)             += leds-powernv.o
>  obj-$(CONFIG_LEDS_PWM)                 += leds-pwm.o
>  obj-$(CONFIG_LEDS_REGULATOR)           += leds-regulator.o
> +obj-$(CONFIG_LEDS_RTL8231)             += leds-rtl8231.o
>  obj-$(CONFIG_LEDS_S3C24XX)             += leds-s3c24xx.o
>  obj-$(CONFIG_LEDS_SC27XX_BLTC)         += leds-sc27xx-bltc.o
>  obj-$(CONFIG_LEDS_SGM3140)             += leds-sgm3140.o
> diff --git a/drivers/leds/leds-rtl8231.c b/drivers/leds/leds-rtl8231.c
> new file mode 100644
> index 000000000000..fb2b1ca419c9
> --- /dev/null
> +++ b/drivers/leds/leds-rtl8231.c
> @@ -0,0 +1,291 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
> +#include <linux/device.h>
> +#include <linux/leds.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +
> +#include <linux/mfd/rtl8231.h>
> +
> +/**
> + * struct led_toggle_rate - description of an LED blinking mode
> + * @interval_ms:       LED toggle rate in milliseconds
> + * @mode:              Register field value used to activate this mode
> + *
> + * For LED hardware accelerated blinking, with equal on and off delay.
> + * Both delays are given by @interval, so the interval at which the LED blinks
> + * (i.e. turn on and off once) is double this value.
> + */
> +struct led_toggle_rate {
> +       u16 interval_ms;
> +       u8 mode;
> +};
> +
> +/**
> + * struct led_modes - description of all LED modes
> + * @toggle_rates:      Array of led_toggle_rate values, sorted by ascending interval
> + * @num_toggle_rates:  Number of elements in @led_toggle_rate
> + * @off:               Register field value to turn LED off
> + * @on:                        Register field value to turn LED on
> + */
> +struct led_modes {
> +       const struct led_toggle_rate *toggle_rates;
> +       unsigned int num_toggle_rates;
> +       u8 off;
> +       u8 on;
> +};
> +
> +struct rtl8231_led {
> +       struct led_classdev led;
> +       const struct led_modes *modes;
> +       struct regmap_field *reg_field;
> +};
> +#define to_rtl8231_led(_cdev) container_of(_cdev, struct rtl8231_led, led)
> +
> +#define RTL8231_NUM_LEDS       3
> +#define RTL8231_LED_PER_REG    5
> +#define RTL8231_BITS_PER_LED   3
> +
> +static const unsigned int rtl8231_led_port_counts_single[RTL8231_NUM_LEDS] = {32, 32, 24};
> +static const unsigned int rtl8231_led_port_counts_bicolor[RTL8231_NUM_LEDS] = {24, 24, 24};
> +
> +static const unsigned int rtl8231_led_base[RTL8231_NUM_LEDS] = {
> +       RTL8231_REG_LED0_BASE,
> +       RTL8231_REG_LED1_BASE,
> +       RTL8231_REG_LED2_BASE,
> +};
> +
> +#define RTL8231_DEFAULT_TOGGLE_INTERVAL_MS     500
> +
> +static const struct led_toggle_rate rtl8231_toggle_rates[] = {
> +       {  40, 1},
> +       {  80, 2},
> +       { 160, 3},
> +       { 320, 4},
> +       { 640, 5},
> +       {1280, 6},
> +};
> +
> +static const struct led_modes rtl8231_led_modes = {
> +       .off = 0,
> +       .on = 7,
> +       .num_toggle_rates = ARRAY_SIZE(rtl8231_toggle_rates),
> +       .toggle_rates = rtl8231_toggle_rates,
> +};
> +
> +static void rtl8231_led_brightness_set(struct led_classdev *led_cdev,
> +       enum led_brightness brightness)
> +{
> +       struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
> +
> +       if (brightness)
> +               regmap_field_write(pled->reg_field, pled->modes->on);
> +       else
> +               regmap_field_write(pled->reg_field, pled->modes->off);
> +}
> +
> +static enum led_brightness rtl8231_led_brightness_get(struct led_classdev *led_cdev)
> +{
> +       struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
> +       u32 current_mode = pled->modes->off;
> +
> +       regmap_field_read(pled->reg_field, &current_mode);
> +
> +       if (current_mode == pled->modes->off)
> +               return LED_OFF;
> +       else
> +               return LED_ON;
> +}
> +
> +static unsigned int rtl8231_led_current_interval(struct rtl8231_led *pled)
> +{
> +       unsigned int mode;
> +       unsigned int i;
> +
> +       if (regmap_field_read(pled->reg_field, &mode))
> +               return 0;
> +
> +       for (i = 0; i < pled->modes->num_toggle_rates; i++)
> +               if (mode == pled->modes->toggle_rates[i].mode)
> +                       return pled->modes->toggle_rates[i].interval_ms;
> +
> +       return 0;
> +}
> +
> +static int rtl8231_led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
> +       unsigned long *delay_off)
> +{
> +       struct rtl8231_led *pled = to_rtl8231_led(led_cdev);
> +       const struct led_toggle_rate *rates = pled->modes->toggle_rates;
> +       unsigned int num_rates = pled->modes->num_toggle_rates;
> +       unsigned int interval_ms;
> +       unsigned int i;
> +       int err;
> +
> +       if (*delay_on == 0 && *delay_off == 0) {
> +               interval_ms = RTL8231_DEFAULT_TOGGLE_INTERVAL_MS;
> +       } else {
> +               /*
> +                * If the current mode is blinking, choose the delay that (likely) changed.
> +                * Otherwise, choose the interval that would have the same total delay.
> +                */
> +               interval_ms = rtl8231_led_current_interval(pled);
> +               if (interval_ms > 0 && interval_ms == *delay_off)
> +                       interval_ms = *delay_on;
> +               else if (interval_ms > 0 && interval_ms == *delay_on)
> +                       interval_ms = *delay_off;
> +               else
> +                       interval_ms = (*delay_on + *delay_off) / 2;
> +       }
> +
> +       /* Find clamped toggle interval */
> +       for (i = 0; i < (num_rates - 1); i++)
> +               if (interval_ms > rates[i].interval_ms)
> +                       break;
> +
> +       interval_ms = rates[i].interval_ms;
> +
> +       err = regmap_field_write(pled->reg_field, rates[i].mode);
> +       if (err)
> +               return err;
> +
> +       *delay_on = interval_ms;
> +       *delay_off = interval_ms;
> +
> +       return 0;
> +}
> +
> +static int rtl8231_led_read_address(struct fwnode_handle *fwnode, unsigned int *addr_port,
> +       unsigned int *addr_led)
> +{
> +       u32 addr[2];
> +       int err;
> +
> +       err = fwnode_property_count_u32(fwnode, "reg");
> +       if (err < 0)
> +               return err;
> +       if (err != ARRAY_SIZE(addr))
> +               return -EINVAL;
> +
> +       err = fwnode_property_read_u32_array(fwnode, "reg", addr, ARRAY_SIZE(addr));
> +       if (err)
> +               return err;
> +
> +       *addr_port = addr[0];
> +       *addr_led = addr[1];
> +
> +       return 0;
> +}
> +
> +static struct reg_field rtl8231_led_get_field(unsigned int port_index, unsigned int led_index)
> +{
> +       unsigned int offset, shift;
> +       struct reg_field field;
> +
> +       offset = port_index / RTL8231_LED_PER_REG;
> +       shift = (port_index % RTL8231_LED_PER_REG) * RTL8231_BITS_PER_LED;
> +
> +       field.reg = rtl8231_led_base[led_index] + offset;
> +       field.lsb = shift;
> +       field.msb = shift + RTL8231_BITS_PER_LED - 1;
> +
> +       return field;
> +}
> +
> +static int rtl8231_led_probe_single(struct device *dev, struct regmap *map,
> +       const unsigned int *port_counts, struct fwnode_handle *fwnode)
> +{
> +       struct led_init_data init_data = {};
> +       struct rtl8231_led *pled;
> +       unsigned int port_index;
> +       unsigned int led_index;
> +       struct reg_field field;
> +       int err;
> +
> +       pled = devm_kzalloc(dev, sizeof(*pled), GFP_KERNEL);
> +       if (!pled)
> +               return -ENOMEM;
> +
> +       err = rtl8231_led_read_address(fwnode, &port_index, &led_index);
> +       if (err) {
> +               dev_err(dev, "LED address invalid\n");
> +               return err;
> +       }
> +
> +       if (led_index >= RTL8231_NUM_LEDS || port_index >= port_counts[led_index]) {
> +               dev_err(dev, "LED address (%d.%d) invalid\n", port_index, led_index);
> +               return -EINVAL;
> +       }
> +
> +       field = rtl8231_led_get_field(port_index, led_index);
> +       pled->reg_field = devm_regmap_field_alloc(dev, map, field);
> +       if (IS_ERR(pled->reg_field))
> +               return PTR_ERR(pled->reg_field);
> +
> +       pled->modes = &rtl8231_led_modes;
> +
> +       pled->led.max_brightness = 1;
> +       pled->led.brightness_get = rtl8231_led_brightness_get;
> +       pled->led.brightness_set = rtl8231_led_brightness_set;
> +       pled->led.blink_set = rtl8231_led_blink_set;
> +
> +       init_data.fwnode = fwnode;
> +
> +       return devm_led_classdev_register_ext(dev, &pled->led, &init_data);
> +}
> +
> +static int rtl8231_led_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       const unsigned int *port_counts;
> +       struct fwnode_handle *child;
> +       struct regmap *map;
> +       int err;
> +
> +       map = dev_get_regmap(dev->parent, NULL);
> +       if (!map)
> +               return -ENODEV;
> +
> +       if (device_property_match_string(dev, "realtek,led-scan-mode", "single-color") >= 0) {
> +               port_counts = rtl8231_led_port_counts_single;
> +               regmap_update_bits(map, RTL8231_REG_FUNC0,
> +                       RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_SINGLE);
> +       } else if (device_property_match_string(dev, "realtek,led-scan-mode", "bi-color") >= 0) {
> +               port_counts = rtl8231_led_port_counts_bicolor;
> +               regmap_update_bits(map, RTL8231_REG_FUNC0,
> +                       RTL8231_FUNC0_SCAN_MODE, RTL8231_FUNC0_SCAN_BICOLOR);
> +       } else {
> +               dev_err(dev, "scan mode missing or invalid\n");
> +               return -EINVAL;
> +       }
> +
> +       fwnode_for_each_available_child_node(dev->fwnode, child) {
> +               err = rtl8231_led_probe_single(dev, map, port_counts, child);
> +               if (err)
> +                       dev_warn(dev, "failed to register LED %pfwP\n", child);
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id of_rtl8231_led_match[] = {
> +       { .compatible = "realtek,rtl8231-leds" },
> +       {}
> +};
> +MODULE_DEVICE_TABLE(of, of_rtl8231_led_match);
> +
> +static struct platform_driver rtl8231_led_driver = {
> +       .driver = {
> +               .name = "rtl8231-leds",
> +               .of_match_table = of_rtl8231_led_match,
> +       },
> +       .probe = rtl8231_led_probe,
> +};
> +module_platform_driver(rtl8231_led_driver);
> +
> +MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
> +MODULE_DESCRIPTION("Realtek RTL8231 LED support");
> +MODULE_LICENSE("GPL v2");
> --
> 2.31.1
>


-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v4 3/5] mfd: Add RTL8231 core device
  2021-06-03 10:58     ` Andy Shevchenko
@ 2021-06-03 11:28       ` Sander Vanheule
  2021-06-03 14:03         ` Andrew Lunn
  2021-06-05 11:07       ` Sander Vanheule
  1 sibling, 1 reply; 114+ messages in thread
From: Sander Vanheule @ 2021-06-03 11:28 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij,
	Michael Walle, Linux LED Subsystem, devicetree,
	open list:GPIO SUBSYSTEM, Hans de Goede, Andrew Lunn,
	Linux Kernel Mailing List

On Thu, 2021-06-03 at 13:58 +0300, Andy Shevchenko wrote:
> On Thu, Jun 3, 2021 at 1:01 PM Sander Vanheule <sander@svanheule.net> wrote:
> > 
> > The RTL8231 is implemented as an MDIO device, and provides a regmap
> > interface for register access by the core and child devices.
> > 
> > The chip can also be a device on an SMI bus, an I2C-like bus by Realtek.
> > Since kernel support for SMI is limited, and no real-world SMI
> > implementations have been encountered for this device, this is currently
> > unimplemented. The use of the regmap interface should make any future
> > support relatively straightforward.
> > 
> > After reset, all pins are muxed to GPIO inputs before the pin drivers
> > are enabled. This is done to prevent accidental system resets, when a
> > pin is connected to the parent SoC's reset line.
> > 
> > To provide different read and write semantics for the GPIO data
> > registers, a secondary virtual register range is used to enable separate
> > cacheing properties of pin input and output values.
> 
> caching
> 
> ...
> 
> 
> > +static int rtl8231_reg_read(void *context, unsigned int reg, unsigned int
> > *val)
> > +{
> > +       struct mdio_device *mdio_dev = context;
> > +       int ret;
> > +
> > +       ret = mdiobus_read(mdio_dev->bus, mdio_dev->addr,
> > RTL8231_REAL_REG(reg));
> > +
> > +       if (ret < 0)
> > +               return ret;
> > +
> > +       *val = ret & 0xffff;
> > +
> > +       return 0;
> > +}
> > +
> > +static int rtl8231_reg_write(void *context, unsigned int reg, unsigned int
> > val)
> > +{
> > +       struct mdio_device *mdio_dev = context;
> > +
> > +       return mdiobus_write(mdio_dev->bus, mdio_dev->addr,
> > RTL8231_REAL_REG(reg), val);
> > +}
> 
> Hmm... Maybe we can amend regmap-mdio to avoid duplication of the
> above? Something like xlate in gpio-regmap or so?
> 

I wanted to make the masking explicit, but since regmap-mdio currently requires
a register address width of 5 bit, it could move there.

Actually, can we safely assume that any MDIO driver implementing clause-22
access (5-bit register address width) will just ignore higher bits? In that
case, I could just drop these functions and not even modify regmap-mdio. It
appears to work for bitbanged MDIO.


> > +       mdiodev->reset_gpio = devm_gpiod_get_optional(dev, "reset",
> > GPIOD_OUT_LOW);
> 
> Missed
> 
>   if (IS_ERR(mdiodev->reset_gpio))
>     return PTR_ERR(mdiodev->reset_gpio);
> 

Will fix.

Best,
Sander



^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v4 3/5] mfd: Add RTL8231 core device
  2021-06-03 11:28       ` Sander Vanheule
@ 2021-06-03 14:03         ` Andrew Lunn
  2021-06-03 15:20           ` Sander Vanheule
  0 siblings, 1 reply; 114+ messages in thread
From: Andrew Lunn @ 2021-06-03 14:03 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Andy Shevchenko, Pavel Machek, Rob Herring, Lee Jones,
	Linus Walleij, Michael Walle, Linux LED Subsystem, devicetree,
	open list:GPIO SUBSYSTEM, Hans de Goede,
	Linux Kernel Mailing List

> I wanted to make the masking explicit, but since regmap-mdio currently requires
> a register address width of 5 bit, it could move there.
> 
> Actually, can we safely assume that any MDIO driver implementing clause-22
> access (5-bit register address width) will just ignore higher bits? In that
> case, I could just drop these functions and not even modify regmap-mdio. It
> appears to work for bitbanged MDIO.

How are C45 addresses handled? The API to the MDIO bus driver uses a
register value which is 32 bits in width. Bit 30 indicates the address
is a C45 address, and then you have 21 bits of actual address.
regmap-mdio needs to be generic and support both C22 and C45.

   Andrew

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v4 3/5] mfd: Add RTL8231 core device
  2021-06-03 14:03         ` Andrew Lunn
@ 2021-06-03 15:20           ` Sander Vanheule
  0 siblings, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-06-03 15:20 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Andy Shevchenko, Pavel Machek, Rob Herring, Lee Jones,
	Linus Walleij, Michael Walle, Linux LED Subsystem, devicetree,
	open list:GPIO SUBSYSTEM, Hans de Goede,
	Linux Kernel Mailing List

On Thu, 2021-06-03 at 16:03 +0200, Andrew Lunn wrote:
> > I wanted to make the masking explicit, but since regmap-mdio currently
> > requires
> > a register address width of 5 bit, it could move there.
> > 
> > Actually, can we safely assume that any MDIO driver implementing clause-22
> > access (5-bit register address width) will just ignore higher bits? In that
> > case, I could just drop these functions and not even modify regmap-mdio. It
> > appears to work for bitbanged MDIO.
> 
> How are C45 addresses handled? The API to the MDIO bus driver uses a
> register value which is 32 bits in width. Bit 30 indicates the address
> is a C45 address, and then you have 21 bits of actual address.
> regmap-mdio needs to be generic and support both C22 and C45.

Currently regmap-mdio will only accept regmap_config structs where the register
width is 5 bit, but this is not enforced for the reg_read/reg_write functions
and the regnum is passed verbatim to mdiobus_read/mdiobus_write.

So, if I understand correctly:
 * for a regmap configured for C22 access, register addresses should be masked
   with 0x1f to create a C22 regnum
 * for a regmap configured for C45 access, register addresses should be masked
   and formatted with 
   (MII_ADDR_C45 | (mdiodev->addr << MII_DEVADDR_C45_SHIFT) | (reg_addr 0xffff))

I would think that a device that supports both C22 and C45 access, can then just
be set up to have two regmaps. If they can be considered to be independent
register spaces, one regmap for each access type would make sense to me.

I'll cook up a patch for this.

Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v4 4/5] pinctrl: Add RTL8231 pin control and GPIO support
  2021-06-03 10:00   ` [PATCH v4 4/5] pinctrl: Add RTL8231 pin control and GPIO support Sander Vanheule
  2021-06-03 10:18     ` Andy Shevchenko
@ 2021-06-04 22:10     ` Linus Walleij
  1 sibling, 0 replies; 114+ messages in thread
From: Linus Walleij @ 2021-06-04 22:10 UTC (permalink / raw)
  To: Sander Vanheule
  Cc: Pavel Machek, Rob Herring, Lee Jones, Michael Walle,
	Andy Shevchenko, Linux LED Subsystem,
	open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
	open list:GPIO SUBSYSTEM, Hans de Goede, Andrew Lunn,
	linux-kernel

On Thu, Jun 3, 2021 at 12:01 PM Sander Vanheule <sander@svanheule.net> wrote:

> This driver implements the GPIO and pin muxing features provided by the
> RTL8231. The device should be instantiated as an MFD child, where the
> parent device has already configured the regmap used for register
> access.
>
> Debouncing is only available for the six highest GPIOs, and must be
> emulated when other pins are used for (button) inputs. Although
> described in the bindings, drive strength selection is currently not
> implemented.
>
> Signed-off-by: Sander Vanheule <sander@svanheule.net>

Wow this looks really good. Thanks for going the extra mile
and fix this. Special thanks to Andy for the thorough review.
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>

Yours,
Linus Walleij

^ permalink raw reply	[flat|nested] 114+ messages in thread

* Re: [PATCH v4 3/5] mfd: Add RTL8231 core device
  2021-06-03 10:58     ` Andy Shevchenko
  2021-06-03 11:28       ` Sander Vanheule
@ 2021-06-05 11:07       ` Sander Vanheule
  1 sibling, 0 replies; 114+ messages in thread
From: Sander Vanheule @ 2021-06-05 11:07 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Pavel Machek, Rob Herring, Lee Jones, Linus Walleij,
	Michael Walle, Linux LED Subsystem, devicetree,
	open list:GPIO SUBSYSTEM, Hans de Goede, Andrew Lunn,
	Linux Kernel Mailing List, Mark Brown

On Thu, 2021-06-03 at 13:58 +0300, Andy Shevchenko wrote:
> On Thu, Jun 3, 2021 at 1:01 PM Sander Vanheule <sander@svanheule.net> wrote:
> > +static int rtl8231_reg_read(void *context, unsigned int reg, unsigned int
> > *val)
> > +{
> > +       struct mdio_device *mdio_dev = context;
> > +       int ret;
> > +
> > +       ret = mdiobus_read(mdio_dev->bus, mdio_dev->addr,
> > RTL8231_REAL_REG(reg));
> > +
> > +       if (ret < 0)
> > +               return ret;
> > +
> > +       *val = ret & 0xffff;
> > +
> > +       return 0;
> > +}
> > +
> > +static int rtl8231_reg_write(void *context, unsigned int reg, unsigned int
> > val)
> > +{
> > +       struct mdio_device *mdio_dev = context;
> > +
> > +       return mdiobus_write(mdio_dev->bus, mdio_dev->addr,
> > RTL8231_REAL_REG(reg), val);
> > +}
> 
> Hmm... Maybe we can amend regmap-mdio to avoid duplication of the
> above? Something like xlate in gpio-regmap or so?

(+Cc Mark for the regmap discussion)

It turns out that I can't use both devm_regmap_init_mdio and the overrides
regmap_config.reg_read/write. This appears to be in contrast to what the
documentation for these two overrides suggest. devm_regmap_init_mdio provides a
bus for the regmap, which causes the overrides to be ignored in regmap.c

Then I tried to use the paging support provided by regmap, by adding the
following config:
   
      static struct regmap_range_cfg rtl8231_reg_ranges[] = {
             {
                     /* Specify an unused register with an empty mask */
                     .selector_reg = 0x1f,
                     .selector_mask = 0x00,
                     .selector_shift = 0,
                     .range_min = 0x00,
                     .range_max = RTL8231_VREG(RTL8231_REG_COUNT - 1),
                     .window_start = 0x00,
                     .window_len = 0x20,
             },
      };


This also doesn't work, because the used _regmap_bus_reg_read/write don't
resolve register pages. The patch below fixes this, but maybe this missing
functionality is intentional, and I should actually implement
regmap_bus.read/write?

----8<----
diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c
index 0d185ec018a5..20b6a2e0d2e3 100644
--- a/drivers/base/regmap/regmap.c
+++ b/drivers/base/regmap/regmap.c
@@ -1881,6 +1881,15 @@ static int _regmap_bus_reg_write(void *context, unsigned
int reg,
                                 unsigned int val)
 {
        struct regmap *map = context;
+       struct regmap_range_node *range;
+       int ret;
+
+       range = _regmap_range_lookup(map, reg);
+       if (range) {
+               ret = _regmap_select_page(map, &reg, range, 1);
+               if (ret != 0)
+                       return ret;
+       }
 
        return map->bus->reg_write(map->bus_context, reg, val);
 }
@@ -2651,6 +2660,15 @@ static int _regmap_bus_reg_read(void *context, unsigned
int reg,
                                unsigned int *val)
 {
        struct regmap *map = context;
+       struct regmap_range_node *range;
+       int ret;
+
+       range = _regmap_range_lookup(map, reg);
+       if (range) {
+               ret = _regmap_select_page(map, &reg, range, 1);
+               if (ret != 0)
+                       return ret;
+       }
 
        return map->bus->reg_read(map->bus_context, reg, val);
 }

--
Best,
Sander


^ permalink raw reply	[flat|nested] 114+ messages in thread

end of thread, other threads:[~2021-06-05 11:07 UTC | newest]

Thread overview: 114+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-05-11 12:25 [PATCH 0/5] RTL8231 GPIO expander support Sander Vanheule
2021-05-11 12:25 ` [PATCH 1/5] dt-bindings: leds: Binding for RTL8231 scan matrix Sander Vanheule
2021-05-17 22:31   ` Rob Herring
2021-05-19 16:39     ` Sander Vanheule
2021-05-11 12:25 ` [PATCH 2/5] dt-bindings: mfd: Binding for RTL8231 Sander Vanheule
2021-05-17 22:38   ` Rob Herring
2021-05-19 16:53     ` Sander Vanheule
2021-05-11 12:25 ` [PATCH 3/5] mfd: Add RTL8231 core device Sander Vanheule
2021-05-12 12:29   ` kernel test robot
2021-05-12 13:13   ` kernel test robot
2021-05-19 14:58     ` Lee Jones
2021-05-19 15:11       ` Sander Vanheule
2021-05-11 12:25 ` [PATCH 4/5] pinctrl: Add RTL8231 pin control and GPIO support Sander Vanheule
2021-05-11 12:25 ` [PATCH 5/5] leds: Add support for RTL8231 LED scan matrix Sander Vanheule
     [not found] ` <CAHp75VffoKyyPJbdtKMLx575c9LT0S8+EHOk7Mw36j=aTL6Q4Q@mail.gmail.com>
2021-05-16 21:40   ` [PATCH 0/5] RTL8231 GPIO expander support Sander Vanheule
2021-05-17  8:13     ` Andy Shevchenko
2021-05-17  8:50       ` Sander Vanheule
2021-05-17 19:32     ` Sander Vanheule
2021-05-17 19:28 ` [PATCH v2 0/7] " Sander Vanheule
2021-05-17 19:28   ` [PATCH v2 1/7] regmap: Add MDIO bus support Sander Vanheule
2021-05-19 16:12     ` Mark Brown
2021-05-17 19:28   ` [PATCH v2 2/7] gpio: regmap: Add configurable dir/value order Sander Vanheule
2021-05-17 21:06     ` Andy Shevchenko
2021-05-18  1:40     ` Andrew Lunn
2021-05-18 11:39       ` Sander Vanheule
2021-05-23 22:21       ` Sander Vanheule
2021-05-18  8:39     ` Michael Walle
2021-05-18 10:39       ` Andy Shevchenko
2021-05-23 21:19       ` Sander Vanheule
2021-05-17 19:28   ` [PATCH v2 3/7] dt-bindings: leds: Binding for RTL8231 scan matrix Sander Vanheule
2021-05-17 19:28   ` [PATCH v2 4/7] dt-bindings: mfd: Binding for RTL8231 Sander Vanheule
2021-05-18 22:02     ` Linus Walleij
2021-05-17 19:28   ` [PATCH v2 5/7] mfd: Add RTL8231 core device Sander Vanheule
2021-05-17 21:18     ` Andy Shevchenko
2021-05-23 21:28       ` Sander Vanheule
2021-05-24  7:49         ` Andy Shevchenko
2021-05-24  7:50       ` Sander Vanheule
2021-05-24  7:55         ` Andy Shevchenko
2021-05-24  8:04           ` Sander Vanheule
2021-05-17 19:28   ` [PATCH v2 6/7] pinctrl: Add RTL8231 pin control and GPIO support Sander Vanheule
2021-05-17 21:42     ` Andy Shevchenko
2021-05-17 21:46       ` Andy Shevchenko
2021-05-23 21:42       ` Sander Vanheule
2021-05-17 19:28   ` [PATCH v2 7/7] leds: Add support for RTL8231 LED scan matrix Sander Vanheule
2021-05-17 22:00     ` Andy Shevchenko
2021-05-23 21:53       ` Sander Vanheule
2021-05-19 16:10   ` (subset) [PATCH v2 0/7] RTL8231 GPIO expander support Mark Brown
2021-05-23 22:33 ` [PATCH v3 0/6] " Sander Vanheule
2021-05-23 22:33   ` [PATCH v3 1/6] gpio: regmap: Add quirk for output data register Sander Vanheule
2021-05-28  6:40     ` Michael Walle
2021-06-03 10:03       ` Sander Vanheule
2021-05-31  7:25     ` Bartosz Golaszewski
2021-05-23 22:34   ` [PATCH v3 2/6] dt-bindings: leds: Binding for RTL8231 scan matrix Sander Vanheule
2021-06-02 18:58     ` Rob Herring
2021-05-23 22:34   ` [PATCH v3 3/6] dt-bindings: mfd: Binding for RTL8231 Sander Vanheule
2021-05-27 23:31     ` Linus Walleij
2021-06-02 19:02     ` Rob Herring
2021-05-23 22:34   ` [PATCH v3 4/6] mfd: Add RTL8231 core device Sander Vanheule
2021-05-24  8:02     ` Andy Shevchenko
2021-05-24  8:23       ` Sander Vanheule
2021-05-24 10:18         ` Andy Shevchenko
2021-05-24 11:41           ` Sander Vanheule
2021-05-23 22:34   ` [PATCH v3 5/6] pinctrl: Add RTL8231 pin control and GPIO support Sander Vanheule
2021-05-28  6:29     ` Michael Walle
2021-05-28  6:42       ` Sander Vanheule
2021-05-28  6:43         ` Michael Walle
2021-05-23 22:34   ` [PATCH v3 6/6] leds: Add support for RTL8231 LED scan matrix Sander Vanheule
2021-05-24 10:24     ` Andy Shevchenko
2021-05-24 12:04       ` Sander Vanheule
2021-05-24 12:47         ` Andy Shevchenko
2021-05-24 15:30           ` Sander Vanheule
2021-05-24  1:10   ` [PATCH v3 0/6] RTL8231 GPIO expander support Andrew Lunn
2021-05-24  7:53     ` Andy Shevchenko
2021-05-24 11:41       ` Sander Vanheule
2021-05-24 12:54         ` Andy Shevchenko
2021-05-24 15:03           ` Sander Vanheule
2021-05-24 16:30             ` Andy Shevchenko
2021-05-25 17:11               ` Andy Shevchenko
2021-05-25 18:00                 ` Sander Vanheule
2021-05-26 21:02                 ` Sander Vanheule
2021-05-27 10:38                   ` Andy Shevchenko
2021-05-27 10:41                     ` Hans de Goede
2021-05-24 15:20           ` Sander Vanheule
2021-05-28  6:37         ` Michael Walle
2021-05-30 16:19           ` Sander Vanheule
2021-05-30 16:51             ` Hans de Goede
2021-05-30 18:16               ` Andy Shevchenko
2021-05-30 21:22                 ` Michael Walle
2021-05-31  8:36                   ` Sander Vanheule
2021-05-31 10:02                     ` Michael Walle
     [not found]                       ` <CAHp75VfOrUBRQH1vrXEwHN4ZPojQfQju-_wp_3djZeozEaatug@mail.gmail.com>
2021-05-31 15:33                         ` [PATCH 0/5] " Sander Vanheule
2021-05-31 15:48                           ` Andy Shevchenko
2021-06-01 11:49                             ` Michael Walle
2021-06-01 15:24                               ` Andy Shevchenko
2021-06-02 20:20                                 ` Sander Vanheule
2021-06-01  9:59                 ` [PATCH v3 0/6] " Linus Walleij
2021-06-01 10:18                   ` Michael Walle
2021-06-01 10:51                     ` Linus Walleij
2021-06-01 11:41                       ` Michael Walle
2021-06-01 11:48                         ` Linus Walleij
2021-06-03 10:00 ` [PATCH v4 0/5] " Sander Vanheule
2021-06-03 10:00   ` [PATCH v4 1/5] dt-bindings: leds: Binding for RTL8231 scan matrix Sander Vanheule
2021-06-03 10:00   ` [PATCH v4 2/5] dt-bindings: mfd: Binding for RTL8231 Sander Vanheule
2021-06-03 10:00   ` [PATCH v4 3/5] mfd: Add RTL8231 core device Sander Vanheule
2021-06-03 10:58     ` Andy Shevchenko
2021-06-03 11:28       ` Sander Vanheule
2021-06-03 14:03         ` Andrew Lunn
2021-06-03 15:20           ` Sander Vanheule
2021-06-05 11:07       ` Sander Vanheule
2021-06-03 10:00   ` [PATCH v4 4/5] pinctrl: Add RTL8231 pin control and GPIO support Sander Vanheule
2021-06-03 10:18     ` Andy Shevchenko
2021-06-04 22:10     ` Linus Walleij
2021-06-03 10:00   ` [PATCH v4 5/5] leds: Add support for RTL8231 LED scan matrix Sander Vanheule
2021-06-03 11:01     ` Andy Shevchenko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).