From nobody Sun Feb 8 14:41:53 2026 Received: from smtpout-02.galae.net (smtpout-02.galae.net [185.246.84.56]) (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 5A9CA35B126 for ; Tue, 27 Jan 2026 13:51:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.246.84.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769521882; cv=none; b=nWSno11qxumLG70pQ6GwsFUPM+ffnSZoZaD8+zPzW4VT69u15qncf5wrp/DzvFvSLS21K+EJQoe7xoq/ujLTYYx6je73sTWMtQJy+Otz7lAYFUIbhXE39Iy7AYtJrzDUxwvtxOXfcJ9YPXy2UzsjzhmJ0BxIv/+Zhf65/oRecks= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769521882; c=relaxed/simple; bh=EhDs+6PdLY09bwrEjy6oLc/QWMgQGLEsXtzy9NBOYYw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=GHr5GmioLxsNOyOIAk7Vyegfx2tZjw9JsAVlOeVoY1HbgnhwjR6KQUeg7FluOy/ix1lFZo8J2yYS9ZL3SFtujmbm9aJwhFdbg2tHufdwuNxA8Nb+OE0JuXz36Ux/1iW3l8sKK01o4B/ncdbP0Wy7Vgn9jjE+0jmn0E7Vas0+5Uk= 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=XYsF5QM6; arc=none smtp.client-ip=185.246.84.56 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="XYsF5QM6" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-02.galae.net (Postfix) with ESMTPS id D0C911A2A84; Tue, 27 Jan 2026 13:51:19 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id A846B606F5; Tue, 27 Jan 2026 13:51:19 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id E0F6F119A864D; Tue, 27 Jan 2026 14:51:17 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1769521878; h=from:subject:date:message-id:to:cc:mime-version:content-type: content-transfer-encoding:in-reply-to:references; bh=pg8UaP+E+u/jU7UCV1uDi0k3PQIk59zgUwfS5Ki6ZnQ=; b=XYsF5QM6ZKWrqrax79YmYPfrri+Uf4nc9qKAxUk8vkcaJafiqn3SLU3bsamgkmLVK9PmBO VQEJWyh/vRx5XUqnyDFmOQM1y3B6wQ5pPhFUgP9Ro4FZy/QNtlNVW6TfxB0o40jrotWEaA zEyAF0TTQul5t8puSSaUsOQ3n/5fUQhC5Y7/eGA3zf45N7V6PkVzd18KXnG+ukQRXgqhvu gn8UitoCyxeViG+zvP9SWCu5Qt39OpC3Uf6blfXqxaIs8W3B6awcrmRwbY227lH6DKE+fD SE+21tMELgg2dLuMPzBOqzoLymZ/zh8RYnTt6yiwjUfaDT4OOBgnsMKZrA3XtA== From: Romain Gantois Date: Tue, 27 Jan 2026 14:51:10 +0100 Subject: [PATCH 1/2] 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: <20260127-fpc202-leds-v1-1-ebd0cfb9f9a1@bootlin.com> References: <20260127-fpc202-leds-v1-0-ebd0cfb9f9a1@bootlin.com> In-Reply-To: <20260127-fpc202-leds-v1-0-ebd0cfb9f9a1@bootlin.com> To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Arnd Bergmann , Greg Kroah-Hartman Cc: 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. Describe these special-purpose GPIO lines. Signed-off-by: Romain Gantois Acked-by: Conor Dooley --- .../devicetree/bindings/misc/ti,fpc202.yaml | 22 ++++++++++++++++++= ++++ 1 file changed, 22 insertions(+) diff --git a/Documentation/devicetree/bindings/misc/ti,fpc202.yaml b/Docume= ntation/devicetree/bindings/misc/ti,fpc202.yaml index a8cb10f2d0df..32913966a22a 100644 --- a/Documentation/devicetree/bindings/misc/ti,fpc202.yaml +++ b/Documentation/devicetree/bindings/misc/ti,fpc202.yaml @@ -53,6 +53,23 @@ patternProperties: =20 unevaluatedProperties: false =20 + "^led@2[0-7]$": + $ref: /schemas/leds/common.yaml# + description: Output GPIO line with advanced LED features enabled. + + properties: + reg: + minimum: 20 + maximum: 27 + description: + GPIO line ID + + required: + - reg + - label + + unevaluatedProperties: false + required: - compatible - reg @@ -89,6 +106,11 @@ examples: #size-cells =3D <0>; reg =3D <1>; }; + + led@20 { + reg =3D <20>; + label =3D "phy0:green:indicator"; + }; }; }; ... --=20 2.52.0 From nobody Sun Feb 8 14:41:53 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 9308A35B628; Tue, 27 Jan 2026 13:51:22 +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=1769521884; cv=none; b=eaWr9cjaFBf8OY76ZUvQWP+hHjYtyAYKUD579MOGGYdDjoxevfb8yDbEPolmhqxbJffgOeQTTEgu+SDTb3yKECjmngGwrM3S5TUr6Eo5l4KYmk8JWdw9ak0qbEyuhsm+VOHdPZ5PwMRK/VfG4n637WsJdIq02Wr1PgbTdOmpkbA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769521884; c=relaxed/simple; bh=t44Cf5bqPL06luIo6kX/DYsw4PKoyWbwQf+ZReZKXLg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=YpbXLjkQaqMfYdoRFpYhHrxOUW9al+0KkCZvTRpAANEePZwykQImoPjL2IvNBLMCOKTdCKZN68gDhWxfI0O3CRdF38HYXnxM2gooZage1fBriPw5oeb+0I9K40e707xlXvvd4PEfWz2cteeJ9ik19wXGoTNYkrL5gmqM9mQGbGs= 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=XXPBzyZp; 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="XXPBzyZp" Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-03.galae.net (Postfix) with ESMTPS id 575C94E422D4; Tue, 27 Jan 2026 13:51:21 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id 2C4C7606F5; Tue, 27 Jan 2026 13:51:21 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id DDE65119A867D; Tue, 27 Jan 2026 14:51:19 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1769521880; h=from:subject:date:message-id:to:cc:mime-version:content-type: content-transfer-encoding:in-reply-to:references; bh=mvwERiecWcwSNUnmJKFUBJMp/aCcykYyE8o/z0taI2g=; b=XXPBzyZpgooa8++KvY/Hk21/yHvmLOSwTrfDY/XwyYhpbsRYEMnZVuUWJ8YRZcD6WgNLmS GdIkcGMkawDzQI0Qj6Y6tIBS5jNPJRWGPxnEq3AlPV3hIcbN9GHa5mACzWm0z4FSIO55w3 E2Z/XNKPwi4b4DtqsXGfbkf2Rp2pRbueuNg20xjB7UWmmszH7QV38SFlDL0tNyjZbXeS1x mQ6CbAOW3wFVrLlkrq2p0b5ulYhHc+n5cpdn6bokN1KmcF05p/Cv/lb1yHunX+q4eftwXY Nv/u92p54dODCZai86UmNO5AW09LEg604Kguovr1GsV4jAM4mKspQMchkPuZiQ== From: Romain Gantois Date: Tue, 27 Jan 2026 14:51:11 +0100 Subject: [PATCH 2/2] 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: <20260127-fpc202-leds-v1-2-ebd0cfb9f9a1@bootlin.com> References: <20260127-fpc202-leds-v1-0-ebd0cfb9f9a1@bootlin.com> In-Reply-To: <20260127-fpc202-leds-v1-0-ebd0cfb9f9a1@bootlin.com> To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Arnd Bergmann , Greg Kroah-Hartman Cc: 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 d7d41b054b98..18253d1caed6 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -118,6 +118,7 @@ config TI_FPC202 depends on I2C select GPIOLIB select I2C_ATR + select LEDS_CLASS help If you say yes here you get support for the Texas Instruments FPC202 Dual Port Controller. diff --git a/drivers/misc/ti_fpc202.c b/drivers/misc/ti_fpc202.c index 8eb2b5ac9850..c1bd50c19005 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 /* @@ -272,6 +389,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 { }; @@ -310,13 +604,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); @@ -354,6 +649,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) { @@ -366,11 +667,8 @@ static int fpc202_probe(struct i2c_client *client) goto unregister_chans; } =20 - if (port_id > 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 > FPC202_NUM_PORTS) + continue; =20 ret =3D fpc202_probe_port(priv, i2c_handle, port_id); if (ret) { @@ -385,11 +683,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; @@ -398,11 +703,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.52.0