From nobody Wed Apr 1 12:42:05 2026 Received: from smtpout-04.galae.net (smtpout-04.galae.net [185.171.202.116]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5BBE33ED12B for ; Tue, 31 Mar 2026 09:21:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.171.202.116 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774948870; cv=none; b=EDYR8ZrgwEV+J1rBmJCviB1igA8nTZGc2eLr4GKyiTA5WmUZQKNRmjQr9Qm1L7YKxcvdupIWb7DWBb05RHhVQWZ1xmxJu6Zcj0afEGXyy4KGR6aawl1+GJw90jR6vDrHj9RPKo+UULllVDoYsoxudYpNHSe9X/an9r4mBg+cJcc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774948870; c=relaxed/simple; bh=rzOohUkZTOoqYpibYNuaz3QGJj+GiDLIsIh9/HH27SA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=XNfHkiLlGw8i0b52FWZrLo23Z54vq0p6xgTE61G58K92ri+kN/Mk5Pf67wely+QGEKRnb1aYy4aK7PrmgA17jwxDqSv2xXvPlgXrXLBFrmgabcoUswI8WDQcNXR4ZiLTIgtVebmNhUNZscmKX3Y3h7+0aCLFvFQsLcdTuDy5/Co= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=o/unU2/Q; arc=none smtp.client-ip=185.171.202.116 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="o/unU2/Q" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-04.galae.net (Postfix) with ESMTPS id 26F40C59952; Tue, 31 Mar 2026 09:21:37 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id 9B6396029D; Tue, 31 Mar 2026 09:21:06 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id AA77D10451001; Tue, 31 Mar 2026 11:21:04 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1774948865; h=from:subject:date:message-id:to:cc:mime-version:content-type: content-transfer-encoding:in-reply-to:references; bh=2lnl520+yzWlpBNDXIzMGXX9Wqn+c56EDcuaCfLAuPs=; b=o/unU2/QVOjJ3r/KaK5sRbqa9E6vPx4Bc0gmO82bWUnvi6Ony3VNi2iHrUFz9SdXFpKNzn YPd4WXCeg3MXowy4c9jUtxYiNgFTjo00JWhVzQIAyEkjUXKDdGqNgaTxxL3wtmdt8+ewTr 4cvGYaOLJadpflRCCpW0o0t/tg8PnJQfrcpGaJ4LefaztV3LEs7GVeTAT+eu29bP8QKGpD bYpEaJrJZgqn5skVr7zthI9CWoEdLUcP5A2lhPJxJqocEYRdwPHy+A4QDxZLHOA2VraPc4 pHME79dPqoAS/DsfR1O2EJDJ9cWXEvdxxh70q4P09FFyhSSoc2/D6cdYiUhlQQ== From: Romain Gantois Date: Tue, 31 Mar 2026 11:20:56 +0200 Subject: [PATCH v3 1/3] misc: ti_fpc202: Depend on GPIOLIB instead of selecting it Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260331-fpc202-leds-v3-1-74b173537d42@bootlin.com> References: <20260331-fpc202-leds-v3-0-74b173537d42@bootlin.com> In-Reply-To: <20260331-fpc202-leds-v3-0-74b173537d42@bootlin.com> To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Arnd Bergmann , Greg Kroah-Hartman Cc: Felix Gu , Thomas Petazzoni , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, Romain Gantois X-Mailer: b4 0.14.3 X-Last-TLS-Session-Version: TLSv1.3 Selecting a foreign subsystem such as GPIOLIB may lead to dependency loops. Use a "depends on" instead. Signed-off-by: Romain Gantois --- drivers/misc/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 5cc79d1517af5..dcb36e39d7079 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -116,7 +116,7 @@ config RPMB config TI_FPC202 tristate "TI FPC202 Dual Port Controller" depends on I2C - select GPIOLIB + depends on GPIOLIB select I2C_ATR help If you say yes here you get support for the Texas Instruments FPC202 --=20 2.53.0 From nobody Wed Apr 1 12:42:05 2026 Received: from smtpout-03.galae.net (smtpout-03.galae.net [185.246.85.4]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3F4CB3EDAA7; Tue, 31 Mar 2026 09:21:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.246.85.4 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774948878; cv=none; b=IIKVjKSLn6np5nQ4R9P/LNBWm48dqwpr3gdKW2GAtA95d2B1ActKiz5hEsjxBw3ApZXkwz0m9wJ7EWI3mDfZSte4vIc6zAKez/QsF35Xj+e+ZO8awhQwQylIa1sfnuWPI2L7UFMrMr/FP5uy9g8SMxCRafke0FaoBFjCWw4mjKc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774948878; c=relaxed/simple; bh=GIK4OZilvzltbW6bnGRySBlxlnGZd82vBfqb5T5IGPU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=qU0oV1Ft555saTLwr61hSTc/dvJInzZGrNan/760vDV/uviSzbiMiAgO7IaSgUXPK42Z8t6J6FBWgkQpnCiJ8Bbi/8+i6dX/a1cDKzPjA31dcTsczN4Ov3SkuoHdlNUWIoU/07FyPr+7WYNzQIl4+Z9XeUJNkldqE74CMymecyk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=Y/VLmCjI; arc=none smtp.client-ip=185.246.85.4 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="Y/VLmCjI" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-03.galae.net (Postfix) with ESMTPS id 843CC4E42884; Tue, 31 Mar 2026 09:21:08 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id 533A26029D; Tue, 31 Mar 2026 09:21:08 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id 39669104511BF; Tue, 31 Mar 2026 11:21:06 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1774948867; h=from:subject:date:message-id:to:cc:mime-version:content-type: content-transfer-encoding:in-reply-to:references; bh=Z6eQULr6vi+XAO7HwlYxBjsXuvozmVrpGeGaYTRfmEk=; b=Y/VLmCjINRLUsnT/7cqcc44kmZZ9P4OkLSWbOqpcx4MKLAmfe9RKzMNVnhg3Q2Bah596bj vOkAlVX+OmuidvJkrPr7ezxO5CqL7FzuJNhG+FkZ5cx9pB9sdulJHMMLd+NCrvZXEXmaAC Xra3Y6scQPGZw5AiIKxf5tyJnTw9dMIhfxxRgKWfBt7S7coH6H2k58L0TiQ8zZHTNYwwUL j4CZdwexKC4k7iPVAoAnDQg/PGnZhA5ToOYfikpB5ktDjyLswv8Op3VFwZ5QMXBy2DjyKk hh+AEdvE6g4K6RQtw0KksGm39ZTKmYR6Wyw19MlowDG9GLaaLH2UhcVET+T8og== From: Romain Gantois Date: Tue, 31 Mar 2026 11:20:57 +0200 Subject: [PATCH v3 2/3] dt-bindings: misc: Describe FPC202 LED features Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260331-fpc202-leds-v3-2-74b173537d42@bootlin.com> References: <20260331-fpc202-leds-v3-0-74b173537d42@bootlin.com> In-Reply-To: <20260331-fpc202-leds-v3-0-74b173537d42@bootlin.com> To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Arnd Bergmann , Greg Kroah-Hartman Cc: Felix Gu , Thomas Petazzoni , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, Romain Gantois , Conor Dooley X-Mailer: b4 0.14.3 X-Last-TLS-Session-Version: TLSv1.3 The FPC202 dual port controller has 20 regular GPIO lines and 8 special GPIO lines with LED features. Each one of these "LED GPIOs" can output PWM and blink signals. Describe these special-purpose GPIO lines. Acked-by: Conor Dooley Signed-off-by: Romain Gantois --- .../devicetree/bindings/misc/ti,fpc202.yaml | 21 +++++++++++++++++= ++++ 1 file changed, 21 insertions(+) diff --git a/Documentation/devicetree/bindings/misc/ti,fpc202.yaml b/Docume= ntation/devicetree/bindings/misc/ti,fpc202.yaml index a8cb10f2d0df3..71c5859d2e135 100644 --- a/Documentation/devicetree/bindings/misc/ti,fpc202.yaml +++ b/Documentation/devicetree/bindings/misc/ti,fpc202.yaml @@ -53,6 +53,22 @@ patternProperties: =20 unevaluatedProperties: false =20 + "^led@1[4-b]$": + $ref: /schemas/leds/common.yaml# + description: Output GPIO line with advanced LED features enabled. + + properties: + reg: + minimum: 0x14 + maximum: 0x1b + description: + GPIO line ID + + required: + - reg + + unevaluatedProperties: false + required: - compatible - reg @@ -89,6 +105,11 @@ examples: #size-cells =3D <0>; reg =3D <1>; }; + + led@14 { + reg =3D <0x14>; + label =3D "phy0:green:indicator"; + }; }; }; ... --=20 2.53.0 From nobody Wed Apr 1 12:42:05 2026 Received: from smtpout-03.galae.net (smtpout-03.galae.net [185.246.85.4]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 391723ED12A for ; Tue, 31 Mar 2026 09:21:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.246.85.4 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774948880; cv=none; b=YJgpH17O8F8pY+sO8ChbyAv2GJHWMZa69gmVBhgz2/0z2bH+8WCrV6Xi0KyKb6D04pcShPMQoz16NvdKr0iBKjeafSNbTcF/2Ka8e+y5cVsbMlS3+E3eFfjAbOb1rnLqY/h8oVjLXTPst8KA3GUrd7LUuPNs/I/YmGXCdeaxCfk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774948880; c=relaxed/simple; bh=PrIEaAlWMqFxw5Jn7WHzl3O2r4YJ5hTpvd+Bgr97Aus=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=da8ZFkzParT6gwdG9qqeWHdYEmcbWZzg+yJsKQ6QQB5xjdKwNbZxj33OvNqqL0iNQL30Q9iKv5C6iZGoAZnOO6gIVS8qeUrlV2FS4x5xvMyqcZH8BgxO51/tIr4B1CdK8JB+gAFq6LOa2CdDjlxF62/8sMYgBJbb655Aehpqs74= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com; spf=pass smtp.mailfrom=bootlin.com; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b=MgyFIQxb; arc=none smtp.client-ip=185.246.85.4 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=bootlin.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=bootlin.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=bootlin.com header.i=@bootlin.com header.b="MgyFIQxb" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-03.galae.net (Postfix) with ESMTPS id DF7E94E42887; Tue, 31 Mar 2026 09:21:09 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id B5D566029D; Tue, 31 Mar 2026 09:21:09 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id EF6E5104507D9; Tue, 31 Mar 2026 11:21:07 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1774948869; h=from:subject:date:message-id:to:cc:mime-version:content-type: content-transfer-encoding:in-reply-to:references; bh=8/LVdrBvY/FY90R2CAyfW1c+RtotDC8zyPVeOMrbMIM=; b=MgyFIQxbx6H80nvzFf1a2EmVAWvayNhK/8yDpeVMlNrC6mFfeW1liLJ6Pi7va0pviidSp4 BsblAxyGeLs6/2bO/vNMdNvpl72EUfA5rchdxa+PQzO7u6oHOAleK8LO0o20lD7z3bKMra RQpm6CP5O4b/LBRkJEcNyYNFcNfB0nssQjq1hCt+WYqNxuGpQdlkE1AbecxEHXPAK5lOlo RNHhsAdM8rqX+LSH01AtnqKukZM4Omd1gxPQNYq4/UqTU2QcnTgI6x7b62s9FkHljDIhb0 MYEE81S21n606fclb6KpyI0YgCb/E+2691clYEgxmEADH2hUZeuDbRHU84c9nw== From: Romain Gantois Date: Tue, 31 Mar 2026 11:20:58 +0200 Subject: [PATCH v3 3/3] misc: ti_fpc202: Support special-purpose GPIO lines with LED features Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260331-fpc202-leds-v3-3-74b173537d42@bootlin.com> References: <20260331-fpc202-leds-v3-0-74b173537d42@bootlin.com> In-Reply-To: <20260331-fpc202-leds-v3-0-74b173537d42@bootlin.com> To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Arnd Bergmann , Greg Kroah-Hartman Cc: Felix Gu , Thomas Petazzoni , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, Romain Gantois X-Mailer: b4 0.14.3 X-Last-TLS-Session-Version: TLSv1.3 The FPC202 dual port controller has 20 regular GPIO lines and 8 special GPIO lines with LED features. Each one of these "LED GPIOs" can output PWM and blink signals. Add support for the eight special-purpose GPIO lines to the existing FPC202 driver's GPIO support. Add support for registering led-class devices on these GPIO lines. Signed-off-by: Romain Gantois --- drivers/misc/Kconfig | 1 + drivers/misc/ti_fpc202.c | 339 +++++++++++++++++++++++++++++++++++++++++++= ++-- 2 files changed, 327 insertions(+), 13 deletions(-) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index dcb36e39d7079..00683bf06258f 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -117,6 +117,7 @@ config TI_FPC202 tristate "TI FPC202 Dual Port Controller" depends on I2C depends on GPIOLIB + depends on LEDS_CLASS select I2C_ATR help If you say yes here you get support for the Texas Instruments FPC202 diff --git a/drivers/misc/ti_fpc202.c b/drivers/misc/ti_fpc202.c index 79a029d79f7a6..2aac83ec4a395 100644 --- a/drivers/misc/ti_fpc202.c +++ b/drivers/misc/ti_fpc202.c @@ -7,12 +7,17 @@ */ =20 #include +#include #include #include #include #include #include +#include +#include #include +#include +#include =20 #define FPC202_NUM_PORTS 2 #define FPC202_ALIASES_PER_PORT 2 @@ -34,18 +39,55 @@ * ... * 19: P1_S1_OUT_B * + * Ports with optional LED control: + * + * 20: P0_S0_OUT_C (P0_S0_LED1) + * ... + * 23: P1_S1_OUT_C (P1_S1_LED1) + * 24: P0_S0_OUT_D (P0_S0_LED2 + * ... + * 27: P1_S1_OUT_D (P1_S1_LED2) + * */ =20 -#define FPC202_GPIO_COUNT 20 +#define FPC202_GPIO_COUNT 28 #define FPC202_GPIO_P0_S0_IN_B 4 #define FPC202_GPIO_P0_S0_OUT_A 12 +#define FPC202_GPIO_P0_S0_OUT_C 20 +#define FPC202_GPIO_P0_S0_OUT_D 24 =20 #define FPC202_REG_IN_A_INT 0x6 #define FPC202_REG_IN_C_IN_B 0x7 #define FPC202_REG_OUT_A_OUT_B 0x8 +#define FPC202_REG_OUT_C_OUT_D 0x9 =20 #define FPC202_REG_OUT_A_OUT_B_VAL 0xa =20 +#define FPC202_LED_COUNT 8 + +/* There are four LED GPIO mode registers which manage two GPIOs each. */ +#define FPC202_REG_LED_MODE(offset) (0x1a + 0x20 * ((offset) % 4)) + +/* LED1 GPIOs (*_OUT_C) are configured in bits 1:0, LED2 GPIOs (*_OUT_D) i= n bits 3:2. */ +#define FPC202_LED_MODE_SHIFT(offset) ((offset) < FPC202_GPIO_P0_S0_OUT_D = ? 0 : 2) +#define FPC202_LED_MODE_MASK(offset) (GENMASK(1, 0) << FPC202_LED_MODE_SHI= FT(offset)) + +/* There is one PWM control register for each GPIO LED */ +#define FPC202_REG_LED_PWM(offset) \ + (((offset) < FPC202_GPIO_P0_S0_OUT_D ? 0x14 : 0x15) + 0x20 * ((offset) % = 4)) + +/* There are two blink delay registers (on/off time) for each GPIO LED */ +#define FPC202_REG_LED_BLINK_ON(offset) \ + (((offset) < FPC202_GPIO_P0_S0_OUT_D ? 0x16 : 0x18) + 0x20 * ((offset) % = 4)) +#define FPC202_REG_LED_BLINK_OFF(offset) (FPC202_REG_LED_BLINK_ON(offset) = + 1) + +/* The actual hardware precision is 2.5ms but since the LED API doesn't ha= ndle sub-millisecond + * timesteps this is rounded up to 5ms + */ +#define FPC202_LED_BLINK_PRECISION 5UL + +#define FPC202_LED_MAX_BRIGHTNESS 255 + #define FPC202_REG_MOD_DEV(port, dev) (0xb4 + ((port) * 4) + (dev)) #define FPC202_REG_AUX_DEV(port, dev) (0xb6 + ((port) * 4) + (dev)) =20 @@ -59,15 +101,34 @@ /* Even aliases are assigned to device 0 and odd aliases to device 1 */ #define fpc202_dev_num_from_alias(alias) ((alias) % 2) =20 +enum fpc202_led_mode { + FPC202_LED_MODE_OFF =3D 0, + FPC202_LED_MODE_ON =3D 1, + FPC202_LED_MODE_PWM =3D 2, + FPC202_LED_MODE_BLINK =3D 3, +}; + +struct fpc202_led { + int offset; + struct led_classdev led_cdev; + struct fpc202_priv *priv; + struct gpio_desc *gpio; + enum fpc202_led_mode mode; +}; + struct fpc202_priv { struct i2c_client *client; struct i2c_atr *atr; struct gpio_desc *en_gpio; struct gpio_chip gpio; + struct fpc202_led leds[FPC202_LED_COUNT]; =20 /* Lock REG_MOD/AUX_DEV and addr_caches during attach/detach */ struct mutex reg_dev_lock; =20 + /* Lock LED mode select register during accesses */ + struct mutex led_mode_lock; + /* Cached device addresses for both ports and their devices */ u8 addr_caches[2][2]; =20 @@ -97,6 +158,11 @@ static int fpc202_gpio_get_dir(int offset) return offset < FPC202_GPIO_P0_S0_OUT_A ? GPIO_LINE_DIRECTION_IN : GPIO_L= INE_DIRECTION_OUT; } =20 +static int fpc202_gpio_has_led_caps(int offset) +{ + return offset >=3D FPC202_GPIO_P0_S0_OUT_C; +} + static int fpc202_read(struct fpc202_priv *priv, u8 reg) { int val; @@ -118,6 +184,37 @@ static void fpc202_set_enable(struct fpc202_priv *priv= , int enable) gpiod_set_value(priv->en_gpio, enable); } =20 +static int fpc202_led_mode_write(struct fpc202_priv *priv, + int offset, + enum fpc202_led_mode mode) +{ + u8 val, reg =3D FPC202_REG_LED_MODE(offset); + int ret; + + guard(mutex)(&priv->led_mode_lock); + + ret =3D fpc202_read(priv, reg); + if (ret < 0) { + dev_err(&priv->client->dev, "failed to read LED mode %d! err %d\n", + offset, ret); + return ret; + } + + val =3D (u8)ret & ~FPC202_LED_MODE_MASK(offset); + val |=3D mode << FPC202_LED_MODE_SHIFT(offset); + + return fpc202_write(priv, reg, val); +} + +static int fpc202_led_mode_set(struct fpc202_led *led, enum fpc202_led_mod= e mode) +{ + struct fpc202_priv *priv =3D led->priv; + + led->mode =3D mode; + + return fpc202_led_mode_write(priv, led->offset, mode); +} + static int fpc202_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) { @@ -125,6 +222,16 @@ static int fpc202_gpio_set(struct gpio_chip *chip, uns= igned int offset, int ret; u8 val; =20 + if (fpc202_gpio_has_led_caps(offset)) { + ret =3D fpc202_led_mode_write(priv, offset, + value ? FPC202_LED_MODE_ON : FPC202_LED_MODE_OFF); + if (ret < 0) + dev_err(&priv->client->dev, "Failed to set GPIO %d LED mode! err %d\n", + offset, ret); + + return ret; + } + ret =3D fpc202_read(priv, FPC202_REG_OUT_A_OUT_B_VAL); if (ret < 0) { dev_err(&priv->client->dev, "Failed to set GPIO %d value! err %d\n", off= set, ret); @@ -153,9 +260,11 @@ static int fpc202_gpio_get(struct gpio_chip *chip, uns= igned int offset) } else if (offset < FPC202_GPIO_P0_S0_OUT_A) { reg =3D FPC202_REG_IN_C_IN_B; bit =3D BIT(offset - FPC202_GPIO_P0_S0_IN_B); - } else { + } else if (!fpc202_gpio_has_led_caps(offset)) { reg =3D FPC202_REG_OUT_A_OUT_B_VAL; bit =3D BIT(offset - FPC202_GPIO_P0_S0_OUT_A); + } else { + return -EOPNOTSUPP; } =20 ret =3D fpc202_read(priv, reg); @@ -177,21 +286,29 @@ static int fpc202_gpio_direction_output(struct gpio_c= hip *chip, unsigned int off int value) { struct fpc202_priv *priv =3D gpiochip_get_data(chip); + u8 reg, val, bit; int ret; - u8 val; =20 if (fpc202_gpio_get_dir(offset) =3D=3D GPIO_LINE_DIRECTION_IN) return -EINVAL; =20 fpc202_gpio_set(chip, offset, value); =20 - ret =3D fpc202_read(priv, FPC202_REG_OUT_A_OUT_B); + if (fpc202_gpio_has_led_caps(offset)) { + reg =3D FPC202_REG_OUT_C_OUT_D; + bit =3D BIT(offset - FPC202_GPIO_P0_S0_OUT_C); + } else { + reg =3D FPC202_REG_OUT_A_OUT_B; + bit =3D BIT(offset - FPC202_GPIO_P0_S0_OUT_A); + } + + ret =3D fpc202_read(priv, reg); if (ret < 0) return ret; =20 - val =3D (u8)ret | BIT(offset - FPC202_GPIO_P0_S0_OUT_A); + val =3D (u8)ret | bit; =20 - return fpc202_write(priv, FPC202_REG_OUT_A_OUT_B, val); + return fpc202_write(priv, reg, val); } =20 /* @@ -264,6 +381,183 @@ static const struct i2c_atr_ops fpc202_atr_ops =3D { .detach_addr =3D fpc202_detach_addr, }; =20 +static struct fpc202_led *fpc202_cdev_to_led(struct led_classdev *cdev) +{ + return container_of(cdev, struct fpc202_led, led_cdev); +} + +static struct fpc202_led *fpc202_led_get(struct fpc202_priv *priv, int off= set) +{ + return &priv->leds[offset - FPC202_GPIO_P0_S0_OUT_C]; +} + +static int fpc202_led_blink_set(struct led_classdev *cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct fpc202_led *led =3D fpc202_cdev_to_led(cdev); + struct fpc202_priv *priv =3D led->priv; + unsigned long val; + int ret; + + if (*delay_on =3D=3D 0 && *delay_off =3D=3D 0) { + *delay_on =3D 250; + *delay_off =3D 250; + } else { + if (*delay_on % FPC202_LED_BLINK_PRECISION) + *delay_on =3D roundup(*delay_on, FPC202_LED_BLINK_PRECISION); + + if (*delay_off % FPC202_LED_BLINK_PRECISION) + *delay_off =3D roundup(*delay_off, FPC202_LED_BLINK_PRECISION); + } + + /* Multiply the duration by two, since the actual precision is 2.5ms not = 5ms*/ + val =3D 2 * (*delay_on / FPC202_LED_BLINK_PRECISION); + if (val > 255) { + val =3D 255; + *delay_on =3D (val / 2) * FPC202_LED_BLINK_PRECISION; + } + + ret =3D fpc202_write(priv, FPC202_REG_LED_BLINK_ON(led->offset), val); + if (ret) { + dev_err(&priv->client->dev, + "Failed to set blink on duration for LED %d, err %d\n", + led->offset, ret); + return ret; + } + + val =3D 2 * (*delay_off / FPC202_LED_BLINK_PRECISION); + if (val > 255) { + val =3D 255; + *delay_off =3D (val / 2) * FPC202_LED_BLINK_PRECISION; + } + + ret =3D fpc202_write(priv, FPC202_REG_LED_BLINK_OFF(led->offset), val); + if (ret) { + dev_err(&priv->client->dev, + "Failed to set blink off duration for LED %d, err %d\n", + led->offset, ret); + return ret; + } + + return fpc202_led_mode_set(led, FPC202_LED_MODE_BLINK); +} + +static enum led_brightness fpc202_led_brightness_get(struct led_classdev *= cdev) +{ + struct fpc202_led *led =3D fpc202_cdev_to_led(cdev); + + if (led->mode =3D=3D FPC202_LED_MODE_OFF) + return LED_OFF; + + return LED_ON; +} + +static int fpc202_led_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct fpc202_led *led =3D fpc202_cdev_to_led(cdev); + struct fpc202_priv *priv =3D led->priv; + int ret; + + if (!brightness) + return fpc202_led_mode_set(led, FPC202_LED_MODE_OFF); + + if (led->mode !=3D FPC202_LED_MODE_BLINK) { + if (brightness =3D=3D FPC202_LED_MAX_BRIGHTNESS) + return fpc202_led_mode_set(led, FPC202_LED_MODE_ON); + + ret =3D fpc202_led_mode_set(led, FPC202_LED_MODE_PWM); + if (ret) { + dev_err(&priv->client->dev, "Failed to set LED %d mode, err %d\n", + led->offset, ret); + return ret; + } + } + + return fpc202_write(priv, FPC202_REG_LED_PWM(led->offset), brightness); +} + +static int fpc202_register_led(struct fpc202_priv *priv, int offset, + struct device_node *led_handle) +{ + struct fpc202_led *led =3D fpc202_led_get(priv, offset); + struct device *dev =3D &priv->client->dev; + struct led_init_data init_data =3D { }; + int ret =3D 0; + + led->priv =3D priv; + led->offset =3D offset; + led->led_cdev.max_brightness =3D FPC202_LED_MAX_BRIGHTNESS; + led->led_cdev.brightness_set_blocking =3D fpc202_led_brightness_set; + led->led_cdev.brightness_get =3D fpc202_led_brightness_get; + led->led_cdev.blink_set =3D fpc202_led_blink_set; + + init_data.fwnode =3D of_fwnode_handle(led_handle); + init_data.default_label =3D NULL; + init_data.devicename =3D NULL; + init_data.devname_mandatory =3D false; + + ret =3D fpc202_led_mode_set(led, FPC202_LED_MODE_OFF); + if (ret) { + dev_err(dev, "Failed to set LED %d mode, err %d\n", offset, ret); + return ret; + } + + ret =3D devm_led_classdev_register_ext(dev, &led->led_cdev, &init_data); + if (ret) { + dev_err(dev, "Failed to register LED %d cdev, err %d\n", offset, ret); + return ret; + } + + /* Claim corresponding GPIO line so that it cannot be interfered with */ + led->gpio =3D gpiochip_request_own_desc(&priv->gpio, offset, led->led_cde= v.name, + GPIO_ACTIVE_HIGH, GPIOD_ASIS); + if (IS_ERR(led->gpio)) { + ret =3D PTR_ERR(led->gpio); + dev_err(dev, "Failed to register LED %d cdev, err %d\n", offset, ret); + } + + return ret; +} + +static int fpc202_register_leds(struct fpc202_priv *priv) +{ + struct device *dev =3D &priv->client->dev; + int offset, ret =3D 0; + + if (!devres_open_group(dev, fpc202_register_leds, GFP_KERNEL)) + return -ENOMEM; + + for_each_child_of_node_scoped(dev->of_node, led_handle) { + ret =3D of_property_read_u32(led_handle, "reg", &offset); + if (ret) { + dev_err(dev, "Failed to read 'reg' property of child node, err %d\n", r= et); + return ret; + } + + if (offset < FPC202_GPIO_P0_S0_OUT_C || offset > FPC202_GPIO_COUNT) + continue; + + ret =3D fpc202_register_led(priv, offset, led_handle); + if (ret) { + dev_err(dev, "Failed to register LED %d, err %d\n", offset, + ret); + goto free_own_gpios; + } + } + + devres_close_group(dev, fpc202_register_leds); + + return 0; + +free_own_gpios: + for (offset =3D 0; offset < FPC202_LED_COUNT; offset++) + if (priv->leds[offset].gpio) + gpiochip_free_own_desc(priv->leds[offset].gpio); + return ret; +} + static int fpc202_probe_port(struct fpc202_priv *priv, struct device_node = *i2c_handle, int port_id) { u16 aliases[FPC202_ALIASES_PER_PORT] =3D { }; @@ -302,13 +596,14 @@ static int fpc202_probe(struct i2c_client *client) { struct device *dev =3D &client->dev; struct fpc202_priv *priv; - int ret, port_id; + int ret, port_id, led_id; =20 priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; =20 mutex_init(&priv->reg_dev_lock); + mutex_init(&priv->led_mode_lock); =20 priv->client =3D client; i2c_set_clientdata(client, priv); @@ -346,6 +641,12 @@ static int fpc202_probe(struct i2c_client *client) =20 i2c_atr_set_driver_data(priv->atr, priv); =20 + ret =3D fpc202_register_leds(priv); + if (ret) { + dev_err(dev, "Failed to register LEDs, err %d\n", ret); + goto delete_atr; + } + bitmap_zero(priv->probed_ports, FPC202_NUM_PORTS); =20 for_each_child_of_node_scoped(dev->of_node, i2c_handle) { @@ -358,11 +659,8 @@ static int fpc202_probe(struct i2c_client *client) goto unregister_chans; } =20 - if (port_id >=3D FPC202_NUM_PORTS) { - dev_err(dev, "port ID %d is out of range!\n", port_id); - ret =3D -EINVAL; - goto unregister_chans; - } + if (port_id >=3D FPC202_NUM_PORTS) + continue; =20 ret =3D fpc202_probe_port(priv, i2c_handle, port_id); if (ret) { @@ -377,11 +675,18 @@ static int fpc202_probe(struct i2c_client *client) for_each_set_bit(port_id, priv->probed_ports, FPC202_NUM_PORTS) fpc202_remove_port(priv, port_id); =20 + for (led_id =3D 0; led_id < FPC202_LED_COUNT; led_id++) + if (priv->leds[led_id].gpio) + gpiochip_free_own_desc(priv->leds[led_id].gpio); + + devres_release_group(&client->dev, fpc202_register_leds); +delete_atr: i2c_atr_delete(priv->atr); disable_gpio: fpc202_set_enable(priv, 0); gpiochip_remove(&priv->gpio); destroy_mutex: + mutex_destroy(&priv->led_mode_lock); mutex_destroy(&priv->reg_dev_lock); out: return ret; @@ -390,11 +695,19 @@ static int fpc202_probe(struct i2c_client *client) static void fpc202_remove(struct i2c_client *client) { struct fpc202_priv *priv =3D i2c_get_clientdata(client); - int port_id; + int port_id, led_id; =20 for_each_set_bit(port_id, priv->probed_ports, FPC202_NUM_PORTS) fpc202_remove_port(priv, port_id); =20 + for (led_id =3D 0; led_id < FPC202_LED_COUNT; led_id++) + if (priv->leds[led_id].gpio) + gpiochip_free_own_desc(priv->leds[led_id].gpio); + + /* Release led devices early so that blink handlers don't trigger. */ + devres_release_group(&client->dev, fpc202_register_leds); + + mutex_destroy(&priv->led_mode_lock); mutex_destroy(&priv->reg_dev_lock); =20 i2c_atr_delete(priv->atr); --=20 2.53.0