From patchwork Tue May 11 12:25:19 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Sander Vanheule X-Patchwork-Id: 1477104 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=23.128.96.18; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; secure) header.d=svanheule.net header.i=@svanheule.net header.a=rsa-sha256 header.s=mail1707 header.b=Zf7Vr0U7; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4FfcZq14wrz9sWq for ; Tue, 11 May 2021 22:25:39 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231538AbhEKM0j (ORCPT ); Tue, 11 May 2021 08:26:39 -0400 Received: from polaris.svanheule.net ([84.16.241.116]:35904 "EHLO polaris.svanheule.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231424AbhEKM0i (ORCPT ); Tue, 11 May 2021 08:26:38 -0400 Received: from terra.local.svanheule.net (unknown [IPv6:2a02:a03f:eafb:ee01:a5bf:613f:4f5:f348]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) (Authenticated sender: sander@svanheule.net) by polaris.svanheule.net (Postfix) with ESMTPSA id A074E1FCC1F; Tue, 11 May 2021 14:25:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=svanheule.net; s=mail1707; t=1620735930; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=RRj/oagag/Ud36oeIh4KW8xT2ij8dPUR8hrHV5ciy/k=; b=Zf7Vr0U7KpD304y3aHhgeheQPVMAS0XPiJXrqnj3mQ4yRc5VwzHkVotducAr5dRucYH5nz sXudpB03MfifYdvwYCF591CemxB/VOUf9ENds3tE3aemSk9MK0iSjo30xNOuuv0N9PfmBl Stg4IEIxcB+PVVC00WOdwuavW58fKkcG7AeAU3TXUN9khVHm8f+jRcnxsViF3DkjY/4Qca +ZTqaLyjIeLUo0xWknMJRTRERV73sFVei7/if/VDu5n45W+ny3yexiMNPzhSYAdcv204pX WP5f+TD/xuIytxL1QuFQOPjueA4DdEGuOXZjgqCDo05WqtxEUnkYLAa5i22wqA== From: Sander Vanheule To: Pavel Machek , Rob Herring , Lee Jones , Linus Walleij , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org Cc: linux-kernel@vger.kernel.org, Sander Vanheule Subject: [PATCH 1/5] dt-bindings: leds: Binding for RTL8231 scan matrix Date: Tue, 11 May 2021 14:25:19 +0200 Message-Id: X-Mailer: git-send-email 2.31.1 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org 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 --- .../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 + +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 + leds { + compatible = "realtek,rtl8231-leds"; + #address-cells = <2>; + #size-cells = <0>; + + realtek,led-scan-mode = "single-color"; + + led@0,0 { + reg = <0 0>; + color = ; + function = LED_FUNCTION_LAN; + function-enumerator = <0>; + }; + + led@0,1 { + reg = <0 1>; + color = ; + function = LED_FUNCTION_LAN; + function-enumerator = <0>; + }; + + led@0,2 { + reg = <0 2>; + color = ; + function = LED_FUNCTION_STATUS; + }; + }; From patchwork Tue May 11 12:25:20 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sander Vanheule X-Patchwork-Id: 1477105 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=23.128.96.18; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; secure) header.d=svanheule.net header.i=@svanheule.net header.a=rsa-sha256 header.s=mail1707 header.b=LgkPSm1B; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4FfcZq5FjSz9t1Q for ; Tue, 11 May 2021 22:25:39 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231478AbhEKM0m (ORCPT ); Tue, 11 May 2021 08:26:42 -0400 Received: from polaris.svanheule.net ([84.16.241.116]:35920 "EHLO polaris.svanheule.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231429AbhEKM0i (ORCPT ); Tue, 11 May 2021 08:26:38 -0400 Received: from terra.local.svanheule.net (unknown [IPv6:2a02:a03f:eafb:ee01:a5bf:613f:4f5:f348]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) (Authenticated sender: sander@svanheule.net) by polaris.svanheule.net (Postfix) with ESMTPSA id 31EA71FCC20; Tue, 11 May 2021 14:25:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=svanheule.net; s=mail1707; t=1620735931; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Pt5VbHjv7TStXCyOId4oblbPWK+NoFcaQiOp8x5WPqo=; b=LgkPSm1BZM3fRNKHQUxbn2GffmERC4XYR3V+zLw9ZkfrbnnaGXfAy22kAdD7V13Z7Wm+m4 Wh/AUbcehTDyX5cRKRhkkKuBgSzC8+humvzL6los91XWrZouDxxnb/gtIDbRzpl6q1dNf1 9UQo2UYJZLjOEshIqFDfsIEjLoLLVQfpYcf3KqLMa3malwDmRIl3Zlh2CJZyy7IoiNKAfR qye7czyIges5qODmQNJZP84AMV2QLZWQ42yf7TmufnOXYt5y1ATpvk2CPb3b/LhBnWktSc uzlgi4YQ9zSbtJspgomIvu0cZc99saLV/hT9WZMgAyr7/ro2lEaQIVbFXVy/tw== From: Sander Vanheule To: Pavel Machek , Rob Herring , Lee Jones , Linus Walleij , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org Cc: linux-kernel@vger.kernel.org, Sander Vanheule Subject: [PATCH 2/5] dt-bindings: mfd: Binding for RTL8231 Date: Tue, 11 May 2021 14:25:20 +0200 Message-Id: <73e017d08117cee1290b9483c23f79f956f41a6d.1620735871.git.sander@svanheule.net> X-Mailer: git-send-email 2.31.1 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org 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 --- .../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 + +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 + 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 = ; + function = LED_FUNCTION_LAN; + function-enumerator = <0>; + }; + + led@0,1 { + reg = <0 1>; + color = ; + function = LED_FUNCTION_LAN; + function-enumerator = <0>; + }; + + led@1,0 { + reg = <1 0>; + color = ; + function = LED_FUNCTION_LAN; + function-enumerator = <1>; + }; + + led@1,1 { + reg = <1 1>; + color = ; + function = LED_FUNCTION_LAN; + function-enumerator = <1>; + }; + }; + }; + }; From patchwork Tue May 11 12:25:21 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sander Vanheule X-Patchwork-Id: 1477110 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=23.128.96.18; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; secure) header.d=svanheule.net header.i=@svanheule.net header.a=rsa-sha256 header.s=mail1707 header.b=lcZvceVV; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4FfcZx4K02z9t1Q for ; Tue, 11 May 2021 22:25:45 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231613AbhEKM0o (ORCPT ); Tue, 11 May 2021 08:26:44 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48722 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231590AbhEKM0k (ORCPT ); Tue, 11 May 2021 08:26:40 -0400 Received: from polaris.svanheule.net (polaris.svanheule.net [IPv6:2a00:c98:2060:a004:1::200]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 0B76AC061574 for ; Tue, 11 May 2021 05:25:34 -0700 (PDT) Received: from terra.local.svanheule.net (unknown [IPv6:2a02:a03f:eafb:ee01:a5bf:613f:4f5:f348]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) (Authenticated sender: sander@svanheule.net) by polaris.svanheule.net (Postfix) with ESMTPSA id A25231FCC21; Tue, 11 May 2021 14:25:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=svanheule.net; s=mail1707; t=1620735931; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=esV9Zuj4EBqnkgQlnJyNxklrX3o+5HTkfxJdnbsza1I=; b=lcZvceVVi4TwOBf6l2SFtK5wx/wI4Pe75+Y18Oe3z+s5qN0sSlqZdr2L5z9fDpiEFc2JTe 6Kn3wLYEk60E/CiM44HZ+7WNN3fpbgu0Sa1W6I49Pziw9HWMkL6SeaNkFYJaeBhgzGXkyE SmUlSfNxnw11ZQOVk7f2axXinZF/eH0/535Is24/Paw1oxBlPo1S8HWDAKm+FSdu9m/muF nrXLWV4YpXyAhIy0+QSOy6NGg5AUEoeuWFPn5uz7V7FlMvdS7zBNH/iCbQG7eqpETdrPfc iiN5iKI+Ub/kYtiwn/bc1ija8ValAWtelz8I/Xf0fJB3nA4orOV6HJXFlFu2Ng== From: Sander Vanheule To: Pavel Machek , Rob Herring , Lee Jones , Linus Walleij , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org Cc: linux-kernel@vger.kernel.org, Sander Vanheule Subject: [PATCH 3/5] mfd: Add RTL8231 core device Date: Tue, 11 May 2021 14:25:21 +0200 Message-Id: X-Mailer: git-send-email 2.31.1 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org 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 Reported-by: kernel test robot Reported-by: kernel test robot --- 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 +#include +#include +#include +#include +#include +#include +#include + +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 "); +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 */ From patchwork Tue May 11 12:25:22 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sander Vanheule X-Patchwork-Id: 1477109 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=23.128.96.18; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; secure) header.d=svanheule.net header.i=@svanheule.net header.a=rsa-sha256 header.s=mail1707 header.b=4uI3B8tx; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4FfcZv6WXzz9sWp for ; Tue, 11 May 2021 22:25:43 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231508AbhEKM0n (ORCPT ); Tue, 11 May 2021 08:26:43 -0400 Received: from polaris.svanheule.net ([84.16.241.116]:35966 "EHLO polaris.svanheule.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231537AbhEKM0k (ORCPT ); Tue, 11 May 2021 08:26:40 -0400 Received: from terra.local.svanheule.net (unknown [IPv6:2a02:a03f:eafb:ee01:a5bf:613f:4f5:f348]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) (Authenticated sender: sander@svanheule.net) by polaris.svanheule.net (Postfix) with ESMTPSA id 169171FCC22; Tue, 11 May 2021 14:25:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=svanheule.net; s=mail1707; t=1620735932; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=bj/t+Ko5preUh1rbtCP0o4WD9qj0OBzuV+P6bWVSDyc=; b=4uI3B8txOlTUJIiBPZpBYQUfAYHQ4dDVrhiOFASIZ8WzLFTNFbmurk6cLbv0nhTiIlMe3z FJ08w4C1sG785u46INPZG9Mjl3QK9dFQz+/JjN4Wt+FHBvSPWDWVSfbLVZKmYffqjYw16G VLShdHSVN2P5nlaD0k6+bppgs4w22pUX1Xf/bZb2ngI7nJ0oON6zsmVNJTiMCBks72EsHw 1vkhmQ3QXyigwKcr4RFyaEPnNb2psaX1h+yYWBVp0AaoGMsr4e02rdbq7fEDdoS1i1Gmxl N7ctQUFYiKPtMpB4V2uu07XNX9IR7BUxNuk86fMkG/WeOvLqw5ZfvWzJ5GOfow== From: Sander Vanheule To: Pavel Machek , Rob Herring , Lee Jones , Linus Walleij , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org Cc: linux-kernel@vger.kernel.org, Sander Vanheule Subject: [PATCH 4/5] pinctrl: Add RTL8231 pin control and GPIO support Date: Tue, 11 May 2021 14:25:22 +0200 Message-Id: X-Mailer: git-send-email 2.31.1 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org 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 --- 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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, ®_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 "); +MODULE_DESCRIPTION("Realtek RTL8231 pin control and GPIO support"); +MODULE_LICENSE("GPL v2"); From patchwork Tue May 11 12:25:23 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sander Vanheule X-Patchwork-Id: 1477112 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=23.128.96.18; helo=vger.kernel.org; envelope-from=linux-gpio-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; secure) header.d=svanheule.net header.i=@svanheule.net header.a=rsa-sha256 header.s=mail1707 header.b=kJ58sO8V; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4FfcZy4Z7dz9t2b for ; Tue, 11 May 2021 22:25:46 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231594AbhEKM0o (ORCPT ); Tue, 11 May 2021 08:26:44 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48726 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231598AbhEKM0m (ORCPT ); Tue, 11 May 2021 08:26:42 -0400 Received: from polaris.svanheule.net (polaris.svanheule.net [IPv6:2a00:c98:2060:a004:1::200]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 8C397C061760 for ; Tue, 11 May 2021 05:25:34 -0700 (PDT) Received: from terra.local.svanheule.net (unknown [IPv6:2a02:a03f:eafb:ee01:a5bf:613f:4f5:f348]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) (Authenticated sender: sander@svanheule.net) by polaris.svanheule.net (Postfix) with ESMTPSA id 762711FCC23; Tue, 11 May 2021 14:25:32 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=svanheule.net; s=mail1707; t=1620735932; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=VSN9ihQDBGgg3PG9sg9c+JPnx1WVQVvooeQ9hc8V7Ho=; b=kJ58sO8V3UfaZNhT8VDZlfNxPsrjVoJSruZU+sjv6VFan6yLfuuP7DXCt60vczohVrxqsY 65KrP58t2ND+I0QzvuwaycNwb0t9u/C7qyGrbSd41kiFjY6oy6NbtysDu1sCenJaSj5bai 1e24ACvsyavyrwXOzqrbwky47I60hgHU7+0VNeKqMGi3DlfE02cNu8JM1dLAboR3HMCklt POkwBJLh6T31/D0o7QE9qydZrUURcZ7qbSk9QWKKh7HhMOlvU3IFHQiL0us3ZAGp7UUYe+ PtjONxLsBd1q7kpcuzhjbdutlzfAZaa1SoE7o+HaULTgmWxfaixNv2evDIUYXg== From: Sander Vanheule To: Pavel Machek , Rob Herring , Lee Jones , Linus Walleij , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org Cc: linux-kernel@vger.kernel.org, Sander Vanheule Subject: [PATCH 5/5] leds: Add support for RTL8231 LED scan matrix Date: Tue, 11 May 2021 14:25:23 +0200 Message-Id: X-Mailer: git-send-email 2.31.1 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-gpio@vger.kernel.org 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 --- 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 +#include +#include +#include +#include +#include +#include +#include +#include + +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, ¤t_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 "); +MODULE_DESCRIPTION("Realtek RTL8231 LED support"); +MODULE_LICENSE("GPL v2");