From nobody Fri Oct 2 10:07:02 2026 Received: from mail-pg1-f171.google.com (mail-pg1-f171.google.com [209.85.215.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0C3BC3BB9FE for ; Sun, 2 Aug 2026 15:46:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785685591; cv=none; b=sJIAusTfUPbUFehiP8qk6OU5NS3JJHYXICi0qOsYEKC2hDpVmp0q/7AxSHGHsZYpIPd8+i4KXe2u0mWeTPjszTNLqwOxhXrhvADzRyGZW/WcwVAqwrYWoJapva2fLULRbViKoJvvD829w/0bfCOJCq0SoQ4XIpxG4MP4em+qfag= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785685591; c=relaxed/simple; bh=cLrS7qXp57cQ0CJ93Ue4JPXTnXTOGbW99vQlMqWVd/Y=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lqND8Xd3uwSXjhGgYOvvqxAhSjvpISzYqeuErBKjtEEDz0hOYx9t16tF0kcB6XZ/Ynr14sZ6t/C63ZBfI46esQuv3YWhjCAIvDjHoETqqxeIbrhifpXoMVOErhkAfbT7n7oi7oX6dzhW8K56mxsvFVSO9dXvcDgb3gvqtt/LQCI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=F1fj5dKF; arc=none smtp.client-ip=209.85.215.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="F1fj5dKF" Received: by mail-pg1-f171.google.com with SMTP id 41be03b00d2f7-cbe431467e6so707158a12.1 for ; Sun, 02 Aug 2026 08:46:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785685589; x=1786290389; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=+VgJgsAsYz6ZQBu8o6pNRg8dz6ngWsg0lMioHUZEeJs=; b=F1fj5dKF9rGHy+T2vqrkIdEjgW3Fv6h3GgYVFy3ikHKrEASciEZMSsw2sOYW2KyiQy POETdTKSgxfywMF6Z4/iHs9VUVoLDng7MBOCI6YJsPbt1DaM7kWl+/ux0TqFJLNMEeYp LWfNei2B+TXcv5U//WhlXh5Q7mYjZhkDurpv5GSCLnv4aWaupdFpG6mx92CgRyIJVrA6 W7JumN0UzmPcKjBstPdnMyLyjI4OITirdMyhNy+oM3NnnnNzeoBkiN4dr3B9z/Kh2x2D 0/girRARsqUxgEZ6E/8JsCwqJgKXyNKbw+lZ6MuIXLNOmambq7LZftvyELAhNiKtKhhI Oe3A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785685589; x=1786290389; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=+VgJgsAsYz6ZQBu8o6pNRg8dz6ngWsg0lMioHUZEeJs=; b=KWDAT8CFkBW4lH0X1fsfX3G6s6Fr1e6iAYSMCBBo6FLdzQRnZPYNW2ZoIrlZ6+SND/ 98/OCUoJUlK5yy4I67k6jDMkjiIR1A3EdNgHmPzWCLRIs6yuuz537VWhZqCNNl0Y46xI rmQzG2HADJWMIc2H2pL/hGkouHCF6QdCyuxxmjbP5ln9M4sHmzxzCvSjK6hf9r7T0gUX goW7qLxWaHKGd1PyLncsIHnrGlDMWxCIiN1jRq50/smdLCzUngiN9Cqy7U5fUXqgnV6T l2+9aIcLkKeoIMLfttA7ZdGT4HahL4b3sc2EzxHmWjEZX9eVDaGG6HKrsFmB0PhntvwQ Sc0Q== X-Forwarded-Encrypted: i=1; AHgh+RpieSyg1ZiOS41pg/JteUZ8CS5Hx4+b1UfZPOx4Bd+5Izq/X+Sf4Gth9Wv0QfR+wWIAdOH7orT1zyT81DQ=@vger.kernel.org X-Gm-Message-State: AOJu0Yx21D6PTxxx8hnlZPcTEEGPLwv85v26KZ9UsDC/1bZ9ZE7l1XgT a426ftUAtMJDY4ciNbf1jzhRLU12oci0ZEQUOdraDjPtFYASDS0N9rAn X-Gm-Gg: AR+sD11h3npzf8i/cq00yTPEtjUnanajhYpUNVCEADbn4SK+B4IQUjHJOmCjNUVmSRs naG9OXS2Wq0CtiyW861aRlMp/vWOlSLVvy9Y1vVWSc+GQzukgZEVmDCWQ0DXHcxc7qMz2APbpFh PKNONSW/4fafmFglcTRaSj+69GXeZrCm3V6CJZTN43oSryMuO2REyDlmeLt0wdDUh73DwclUCh+ PjGqiJEBizc84OlUxG/SDXygdloaF7u09nL7iFSb3D7JtJqSMWMYh5ERVn4SFJCbJzciOfqoiRP hEtQNhdp58Yy29i6Ndvd/q8E79+UMxMdByQ44hSp73SnKqrcDYAYQ5karVXniF6SYdVwAkWqRMy TN3p259d7yx8BTeclhLXMhOrrmTMCd5mJTiKyGUg565/u3Z3YJsdC8CuxHYpqesUpn5/6udVhve MsqSs+tUn48rwiC2ddiVvs8Cv99DrTaTyM2Of5SQJl7Tm96iwHAUx7hIq2RpuPewVZlpnCAmnMM JoXgP+0eNLDTFGKAA9DSBGRQI9t1Ar+awGCaK8= X-Received: by 2002:a05:6300:6186:b0:3bf:b7e8:68ab with SMTP id adf61e73a8af0-3c91a7131eemr9627291637.2.1785685589201; Sun, 02 Aug 2026 08:46:29 -0700 (PDT) Received: from Mac.net (198-27-228-154.fiber.dynamic.sonic.net. [198.27.228.154]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-3153e29a126sm30602192eec.31.2026.08.02.08.46.27 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Sun, 02 Aug 2026 08:46:28 -0700 (PDT) From: Ahmad Byagowi To: Lee Jones , Pavel Machek , Richard Cochran , Vadim Fedorenko , Andi Shyti , Peter Rosin Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Andrew Lunn , davem@davemloft.net, Eric Dumazet , Jakub Kicinski , Paolo Abeni , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Ahmad Byagowi Subject: [RFC net-next 1/4] dt-bindings: leds: Add IS32FL3207 controller Date: Sun, 2 Aug 2026 08:46:17 -0700 Message-ID: X-Mailer: git-send-email 2.50.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The IS32FL3207 is an 18-channel constant-current LED controller with per-output PWM and current scaling. Describe I2C addressing, power and shutdown controls, RISET, and individual or multicolor output groupings. Require board current limits for each output. Signed-off-by: Ahmad Byagowi --- .../bindings/leds/issi,is32fl3207.yaml | 204 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 210 insertions(+) create mode 100644 Documentation/devicetree/bindings/leds/issi,is32fl3207.= yaml diff --git a/Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml b/= Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml new file mode 100644 index 000000000..06a12cfb1 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml @@ -0,0 +1,204 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/issi,is32fl3207.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Lumissil IS32FL3207 18-channel LED controller + +maintainers: + - Ahmad Byagowi + +description: | + The IS32FL3207 is an I2C LED controller with 18 individually controlled + constant-current outputs. Each output has 8-bit current scaling and up to + 16-bit PWM control. Outputs may be exposed as individual LEDs or grouped= as + multicolor LEDs. + + The data sheet is available at: + https://www.lumissil.com/assets/pdf/core/IS32FL3207_DS.pdf + +properties: + compatible: + const: issi,is32fl3207 + + reg: + enum: [0x34, 0x35, 0x36, 0x37] + + vcc-supply: + description: Regulator providing power to the VCC pin. + + enable-gpios: + maxItems: 1 + description: GPIO connected to SDB, asserted to enable the controller. + + issi,riset-ohms: + minimum: 2000 + maximum: 76500000 + description: | + Resistance in ohms of the external RISET resistor. The resistor sets= the + maximum output current according to IOUT(MAX) =3D 76.5 / RISET amper= es. + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + "^led@[0-9a-f]+$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 17 + description: LED output number. + + led-max-microamp: + minimum: 1 + maximum: 38250 + + color: + not: + enum: [8, 9] + + max-brightness: + minimum: 1 + maximum: 255 + + default-state: + const: "off" + + default-brightness: false + + retain-state-shutdown: false + + required: + - reg + - led-max-microamp + + "^multi-led@[0-9a-f]+$": + type: object + $ref: leds-class-multicolor.yaml# + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 17 + description: Lowest-numbered LED output used by the group. + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + max-brightness: + minimum: 1 + maximum: 255 + + default-state: + const: "off" + + default-brightness: false + + retain-state-shutdown: false + + patternProperties: + "^led@[0-9a-f]+$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 17 + description: LED output number. + + led-max-microamp: + minimum: 1 + maximum: 38250 + + color: + not: + enum: [8, 9] + + max-brightness: false + + default-state: false + + default-brightness: false + + retain-state-shutdown: false + + required: + - reg + - color + - led-max-microamp + + required: + - reg + - "#address-cells" + - "#size-cells" + +required: + - compatible + - reg + - issi,riset-ohms + - "#address-cells" + - "#size-cells" + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + led-controller@34 { + compatible =3D "issi,is32fl3207"; + reg =3D <0x34>; + #address-cells =3D <1>; + #size-cells =3D <0>; + + vcc-supply =3D <&led_3v3>; + enable-gpios =3D <&gpio0 4 GPIO_ACTIVE_HIGH>; + issi,riset-ohms =3D <4700>; + + multi-led@0 { + reg =3D <0>; + color =3D ; + function =3D LED_FUNCTION_STATUS; + #address-cells =3D <1>; + #size-cells =3D <0>; + + led@0 { + reg =3D <0>; + color =3D ; + led-max-microamp =3D <8100>; + }; + + led@1 { + reg =3D <1>; + color =3D ; + led-max-microamp =3D <8100>; + }; + + led@2 { + reg =3D <2>; + color =3D ; + led-max-microamp =3D <8100>; + }; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 932ea1db0..ff3a93474 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13821,6 +13821,12 @@ F: Documentation/devicetree/bindings/hwmon/renesas= ,isl28022.yaml F: Documentation/hwmon/isl28022.rst F: drivers/hwmon/isl28022.c =20 +ISSI IS32FL3207 LED DRIVER +M: Ahmad Byagowi +L: linux-leds@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml + ISOFS FILESYSTEM M: Jan Kara L: linux-fsdevel@vger.kernel.org --=20 2.50.1 (Apple Git-155) From nobody Fri Oct 2 10:07:02 2026 Received: from mail-pj1-f52.google.com (mail-pj1-f52.google.com [209.85.216.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9AAA03B774D for ; Sun, 2 Aug 2026 15:46:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785685600; cv=none; b=Q0yAQ9i1OntwBbsW9UXtMyOHJGen1FxiC+aWw7FWgvHgnKayoj2YwocrsHIOEAvJowoIBI8edk0SL8uRUVhIeA498aH2HQLMqyh+vXtQ8oO8fVdj/xP1tuYptQVovEF2JUHNKrhcMXuqC5dweSpXEfgAutuaZyry30l+SNBp/hk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785685600; c=relaxed/simple; bh=BfCtXNAs0rpVND8FtcehhULXV4e9FuL7GJC8+qet+34=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=M/y9CF5fhtaZg3u2rhUhTiREHIaDgr5MN0EsHcWcCY4NCFfJ/25YbyTXXWBS6RsdL4Dz0KZhzQ0ZaMW+caN0wZ5sr0mUul97+JEKmnTjk/5E4YFqfz//A8mQauOimXaUnLOt6XJYoeMEqsSMxurL55SPhuF36hfwdHJrFI9WAh8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=o/HF2eg9; arc=none smtp.client-ip=209.85.216.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="o/HF2eg9" Received: by mail-pj1-f52.google.com with SMTP id 98e67ed59e1d1-38e347638adso3038187a91.0 for ; Sun, 02 Aug 2026 08:46:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785685591; x=1786290391; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=vjKpl9kHUXDFB/t6rSVBQpzg6ZbRpgTFizpZNSt1ub8=; b=o/HF2eg9v2f39B9KLwJNbfVBR75v/bkUd1BvYmHscqtfnLAYLHyZb3JS/AsMUjHped 2Ow7ifE5r3WYUUMn+6gHaNiKe8n3GDluNR+0K8qZ3WzeGltc8CpWuFJEW/uBznWeO40j ykzIisrZepghCWXXuVCZZinu8sEDf2ILbg2/tYxXXaU+JdJH+7rgXZAcx4s7h+g9JG3H kNZwXJNyBi5xaRu5/zFbxywUt4WzJrSykbzicHrE2uQ56oY/J+yThN8fHx/QcshpqkkY HgRmkKrj1Z3ZcWw0rpD5Y7bZnt7iz0QrkHHBmrNjhatL6n8qMVedkPA/ns1RusGTQbUW Cftg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785685591; x=1786290391; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=vjKpl9kHUXDFB/t6rSVBQpzg6ZbRpgTFizpZNSt1ub8=; b=HFyDZzLE+nlG4acCKJzaAbW+xsVOlRJWnPHpIYdGGquAcY0KcP0mQLqJe0phqlxA4e 9bgKNuUKOkueECU8VeuD8iCSlpkimvB2q+fDOBbvDleylM3oPvqWhL952T12MPG5IqXe yxDQE+Kw3yGwJl2YA3P4zY8xVXrrHD3xgwd5fLn+ympTQeOUXELwTkoQ/+sHlh1zOE/E Dc0skZNvYBO4RE7K/KgZOybH8OFigmCiogg9ToL0jhDy9PHd+KcI/VoHwAq3QK9uKP2f /43QH0A7jUs9NF114WkFEYAAjzZgYgQXSm4or82pWbfMfLDEb+WLGvcgRUxGuGu0r5Mk C7lw== X-Forwarded-Encrypted: i=1; AHgh+RrjgDXx3xmVBQqlutgB6dH84k2LiVj4eWh5ITR4hwTzcM6VTVk8/ekI4KGU7ll8apmCenOqFmiGWsn9AL4=@vger.kernel.org X-Gm-Message-State: AOJu0YxNODHNWWIeC5xZuuhEXP+MCCoyKxLeLLcY8rwd7q7MgffvSx/P 6anOdjkfR5MBtKeMmorVIEon7LaQiL8PkXUHGo9dmuZaaTYuxtG9xTH2 X-Gm-Gg: AR+sD139R+8F/KQpVW+K+31rc8WyiQsIihDIa8QowgK9CG3v04EcjzaSkgtDD9omOjT k02a5yNdlu2SNIc/grPi+L2DCFcfgPCNDCsi5wSyWKGWnwL8TveHqpOxb+PvVfW5UvS3KDSrE+P OtKIIdTgSvuc7OFS6MGFlIox8gaEWBaMq4967fZW+fMFPhHswG0L6jB7p/FUly9Lk09/PMROZnk j5xGsQrAhdlPizczi5gDtTEAvBnA8pDbBLJh/evN8DgTw6yk/VdtPaReAHfKkrS/plaOojXRKfM IWM690CwWYpsWOkaiz7nPIEBC/nVMtl5uTKAIzWxaLnMMFoINN9gZ7fi4G+yGi3qI7jm+4j5qUw z98tZNxWf7YVCHKGgOckwdaKD/1bSJPfsnxERf1tvAZP8NqjLIuCTK8XkKdEBHEfHRK0nlUi0eA b1ne4wozS6IB7xiPsLpv5UzE7uEEEdg3YlB9vOfDjFEAiosgqXyEuhrATjxpy15k9b6UD0crFYv iffPCk8DY495LNCsO+7rGCz09Am0yrwYK1bzlI= X-Received: by 2002:a05:6a21:4596:b0:3bf:c49d:9183 with SMTP id adf61e73a8af0-3c92a8f0f68mr5702009637.50.1785685590821; Sun, 02 Aug 2026 08:46:30 -0700 (PDT) Received: from Mac.net (198-27-228-154.fiber.dynamic.sonic.net. [198.27.228.154]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-3153e29a126sm30602192eec.31.2026.08.02.08.46.29 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Sun, 02 Aug 2026 08:46:30 -0700 (PDT) From: Ahmad Byagowi To: Lee Jones , Pavel Machek , Richard Cochran , Vadim Fedorenko , Andi Shyti , Peter Rosin Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Andrew Lunn , davem@davemloft.net, Eric Dumazet , Jakub Kicinski , Paolo Abeni , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Ahmad Byagowi Subject: [RFC net-next 2/4] leds: rgb: Add IS32FL3207 controller driver Date: Sun, 2 Aug 2026 08:46:18 -0700 Message-ID: <7daca0186b6906af1d57314eebcecffc4cb68063.1785684064.git.ahmadexp@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add a generic I2C driver for the Lumissil IS32FL3207 18-channel LED controller. Expose individual and multicolor LEDs through the LED class, use the default 8-bit 62 kHz PWM mode, derive output current from RISET, and enforce each output current limit with the scaling registers. Handle an optional supply and enable GPIO and serialize controller-wide update latches. Signed-off-by: Ahmad Byagowi --- MAINTAINERS | 1 + drivers/leds/rgb/Kconfig | 11 + drivers/leds/rgb/Makefile | 1 + drivers/leds/rgb/leds-is32fl3207.c | 541 +++++++++++++++++++++++++++++ 4 files changed, 554 insertions(+) create mode 100644 drivers/leds/rgb/leds-is32fl3207.c diff --git a/MAINTAINERS b/MAINTAINERS index ff3a93474..459dd5147 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13826,6 +13826,7 @@ M: Ahmad Byagowi L: linux-leds@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml +F: drivers/leds/rgb/leds-is32fl3207.c =20 ISOFS FILESYSTEM M: Jan Kara diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig index 6e9ab5f60..c896be131 100644 --- a/drivers/leds/rgb/Kconfig +++ b/drivers/leds/rgb/Kconfig @@ -14,6 +14,17 @@ config LEDS_GROUP_MULTICOLOR To compile this driver as a module, choose M here: the module will be called leds-group-multicolor. =20 +config LEDS_IS32FL3207 + tristate "LED support for ISSI IS32FL3207" + depends on I2C + select REGMAP_I2C + help + Say Y here to include support for the Lumissil IS32FL3207 + 18-channel I2C LED controller. + + To compile this driver as a module, choose M here: the module will + be called leds-is32fl3207. + config LEDS_KTD202X tristate "LED support for KTD202x Chips" depends on I2C diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile index cc0f2df66..228923e8b 100644 --- a/drivers/leds/rgb/Makefile +++ b/drivers/leds/rgb/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 =20 obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) +=3D leds-group-multicolor.o +obj-$(CONFIG_LEDS_IS32FL3207) +=3D leds-is32fl3207.o obj-$(CONFIG_LEDS_KTD202X) +=3D leds-ktd202x.o obj-$(CONFIG_LEDS_LP5812) +=3D leds-lp5812.o obj-$(CONFIG_LEDS_LP5860_CORE) +=3D leds-lp5860-core.o diff --git a/drivers/leds/rgb/leds-is32fl3207.c b/drivers/leds/rgb/leds-is3= 2fl3207.c new file mode 100644 index 000000000..8f0453464 --- /dev/null +++ b/drivers/leds/rgb/leds-is32fl3207.c @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ISSI IS32FL3207 LED controller driver + * + * Copyright 2026 Ahmad Byagowi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define IS32FL3207_NUM_CHANNELS 18 +#define IS32FL3207_MAX_BRIGHTNESS 255 + +#define IS32FL3207_REG_CONTROL 0x00 +#define IS32FL3207_REG_PWM_LOW(channel) (0x01 + 2 * (channel)) +#define IS32FL3207_REG_PWM_UPDATE 0x49 +#define IS32FL3207_REG_SCALING(channel) (0x4a + (channel)) +#define IS32FL3207_REG_GLOBAL_CURRENT 0x6e +#define IS32FL3207_REG_RESET 0x7f + +#define IS32FL3207_CONTROL_ENABLE BIT(0) +#define IS32FL3207_GLOBAL_CURRENT_MAX 0xff + +/* IOUT(MAX) in microamperes =3D 76,500,000 / RISET in ohms. */ +#define IS32FL3207_CURRENT_NUMERATOR 76500000ULL + +struct is32fl3207; + +struct is32fl3207_led { + struct is32fl3207 *chip; + union { + struct led_classdev cdev; + struct led_classdev_mc mcdev; + }; + unsigned int channel; +}; + +struct is32fl3207 { + struct device *dev; + struct regmap *regmap; + struct gpio_desc *enable_gpio; + struct mutex lock; /* Serializes controller register updates. */ + unsigned long channels[BITS_TO_LONGS(IS32FL3207_NUM_CHANNELS)]; + u32 output_max_microamp; + unsigned int num_leds; + struct is32fl3207_led leds[] __counted_by(num_leds); +}; + +static int is32fl3207_parse_led_properties(struct is32fl3207 *chip, + struct fwnode_handle *fwnode, + unsigned int *max_brightness) +{ + const char *default_state; + u32 value; + int ret; + + if (fwnode_property_present(fwnode, "retain-state-shutdown")) + return dev_err_probe(chip->dev, -EINVAL, + "retain-state-shutdown is not supported for %pfw\n", + fwnode); + + if (fwnode_property_present(fwnode, "default-brightness")) + return dev_err_probe(chip->dev, -EINVAL, + "default-brightness is not supported for %pfw\n", + fwnode); + + if (fwnode_property_present(fwnode, "default-state")) { + ret =3D fwnode_property_read_string(fwnode, "default-state", + &default_state); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read default state for %pfw\n", + fwnode); + if (strcmp(default_state, "off")) + return dev_err_probe(chip->dev, -EINVAL, + "default state %s is not supported for %pfw\n", + default_state, fwnode); + } + + *max_brightness =3D IS32FL3207_MAX_BRIGHTNESS; + if (!fwnode_property_present(fwnode, "max-brightness")) + return 0; + + ret =3D fwnode_property_read_u32(fwnode, "max-brightness", &value); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read maximum brightness for %pfw\n", + fwnode); + if (!value || value > IS32FL3207_MAX_BRIGHTNESS) + return dev_err_probe(chip->dev, -EINVAL, + "invalid maximum brightness %u for %pfw\n", + value, fwnode); + + *max_brightness =3D value; + return 0; +} + +static int is32fl3207_validate_component(struct is32fl3207 *chip, + struct fwnode_handle *fwnode) +{ + static const char * const unsupported[] =3D { + "default-brightness", + "default-state", + "max-brightness", + "retain-state-shutdown", + }; + unsigned int i; + + for (i =3D 0; i < ARRAY_SIZE(unsupported); i++) + if (fwnode_property_present(fwnode, unsupported[i])) + return dev_err_probe(chip->dev, -EINVAL, + "%s is not supported for component %pfw\n", + unsupported[i], fwnode); + + return 0; +} + +static int is32fl3207_write_channels(struct is32fl3207 *chip, + const struct mc_subled *subleds, + unsigned int num_channels) +{ + unsigned int i; + int ret; + + guard(mutex)(&chip->lock); + + for (i =3D 0; i < num_channels; i++) { + ret =3D regmap_write(chip->regmap, + IS32FL3207_REG_PWM_LOW(subleds[i].channel), + subleds[i].brightness); + if (ret) + return ret; + } + + return regmap_write(chip->regmap, IS32FL3207_REG_PWM_UPDATE, 0); +} + +static int is32fl3207_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct is32fl3207_led *led =3D container_of(cdev, struct is32fl3207_led, + cdev); + struct mc_subled subled =3D { + .brightness =3D brightness, + .channel =3D led->channel, + }; + + return is32fl3207_write_channels(led->chip, &subled, 1); +} + +static int is32fl3207_mc_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mcdev =3D lcdev_to_mccdev(cdev); + struct is32fl3207_led *led =3D container_of(mcdev, struct is32fl3207_led, + mcdev); + + led_mc_calc_color_components(mcdev, brightness); + + return is32fl3207_write_channels(led->chip, mcdev->subled_info, + mcdev->num_colors); +} + +static int is32fl3207_configure_channel(struct is32fl3207 *chip, + struct fwnode_handle *fwnode, + unsigned int *channel) +{ + u64 scaling; + u32 max_microamp; + u32 reg; + int ret; + + ret =3D fwnode_property_read_u32(fwnode, "reg", ®); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read channel for %pfw\n", fwnode); + + if (reg >=3D IS32FL3207_NUM_CHANNELS) + return dev_err_probe(chip->dev, -EINVAL, + "channel %u is out of range\n", reg); + + if (test_bit(reg, chip->channels)) + return dev_err_probe(chip->dev, -EINVAL, + "channel %u is used more than once\n", reg); + + ret =3D fwnode_property_read_u32(fwnode, "led-max-microamp", + &max_microamp); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read current limit for channel %u\n", + reg); + + if (!max_microamp || max_microamp > chip->output_max_microamp) + return dev_err_probe(chip->dev, -EINVAL, + "invalid current limit %u uA for channel %u\n", + max_microamp, reg); + + /* GCC is fixed at 0xff, so use each output's scaling register. */ + scaling =3D div_u64((u64)max_microamp * 256 * 256, + (u64)chip->output_max_microamp * + IS32FL3207_GLOBAL_CURRENT_MAX); + if (!scaling) + return dev_err_probe(chip->dev, -EINVAL, + "current limit %u uA is below channel %u resolution\n", + max_microamp, reg); + + scaling =3D min_t(u64, scaling, 0xff); + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_SCALING(reg), + (unsigned int)scaling); + if (ret) + return ret; + + set_bit(reg, chip->channels); + *channel =3D reg; + + return 0; +} + +static int is32fl3207_register_single(struct is32fl3207 *chip, + struct fwnode_handle *fwnode, + struct is32fl3207_led *led) +{ + struct led_init_data init_data =3D { + .devicename =3D dev_name(chip->dev), + .devname_mandatory =3D true, + .fwnode =3D fwnode, + }; + unsigned int max_brightness; + u32 color; + int ret; + + ret =3D is32fl3207_parse_led_properties(chip, fwnode, + &max_brightness); + if (ret) + return ret; + + if (fwnode_property_present(fwnode, "color")) { + ret =3D fwnode_property_read_u32(fwnode, "color", &color); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read color for %pfw\n", + fwnode); + if (color >=3D LED_COLOR_ID_MAX || color =3D=3D LED_COLOR_ID_MULTI || + color =3D=3D LED_COLOR_ID_RGB) + return dev_err_probe(chip->dev, -EINVAL, + "invalid single LED color %u\n", color); + } + + ret =3D is32fl3207_configure_channel(chip, fwnode, &led->channel); + if (ret) + return ret; + + led->chip =3D chip; + led->cdev.max_brightness =3D max_brightness; + led->cdev.brightness_set_blocking =3D is32fl3207_brightness_set; + + return devm_led_classdev_register_ext(chip->dev, &led->cdev, &init_data); +} + +static int is32fl3207_register_multicolor(struct is32fl3207 *chip, + struct fwnode_handle *fwnode, + struct is32fl3207_led *led) +{ + struct led_init_data init_data =3D { + .devicename =3D dev_name(chip->dev), + .devname_mandatory =3D true, + .fwnode =3D fwnode, + }; + struct mc_subled *subleds; + DECLARE_BITMAP(color_map, LED_COLOR_ID_MAX); + unsigned int max_brightness; + unsigned int count; + unsigned int i =3D 0; + u32 group_color; + u32 group_reg; + unsigned int first_channel =3D IS32FL3207_NUM_CHANNELS; + int ret; + + ret =3D is32fl3207_parse_led_properties(chip, fwnode, + &max_brightness); + if (ret) + return ret; + + ret =3D fwnode_property_read_u32(fwnode, "color", &group_color); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read color for %pfw\n", fwnode); + + if (group_color !=3D LED_COLOR_ID_RGB && + group_color !=3D LED_COLOR_ID_MULTI) + return dev_err_probe(chip->dev, -EINVAL, + "invalid multicolor LED color %u\n", group_color); + + ret =3D fwnode_property_read_u32(fwnode, "reg", &group_reg); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read group index for %pfw\n", fwnode); + + count =3D fwnode_get_child_node_count(fwnode); + if (!count || count > LED_COLOR_ID_MAX) + return dev_err_probe(chip->dev, -EINVAL, + "invalid component count %u for %pfw\n", + count, fwnode); + + subleds =3D devm_kcalloc(chip->dev, count, sizeof(*subleds), GFP_KERNEL); + if (!subleds) + return -ENOMEM; + bitmap_zero(color_map, LED_COLOR_ID_MAX); + + fwnode_for_each_child_node_scoped(fwnode, child) { + u32 color; + + ret =3D is32fl3207_validate_component(chip, child); + if (ret) + return ret; + + ret =3D fwnode_property_read_u32(child, "color", &color); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read color for %pfw\n", + child); + + if (color >=3D LED_COLOR_ID_MAX || color =3D=3D LED_COLOR_ID_MULTI || + color =3D=3D LED_COLOR_ID_RGB) + return dev_err_probe(chip->dev, -EINVAL, + "invalid component color %u\n", color); + if (test_and_set_bit(color, color_map)) + return dev_err_probe(chip->dev, -EINVAL, + "component color %u is used more than once\n", + color); + + ret =3D is32fl3207_configure_channel(chip, child, + &subleds[i].channel); + if (ret) + return ret; + + subleds[i].color_index =3D color; + subleds[i].intensity =3D max_brightness; + subleds[i].max_intensity =3D 0; + first_channel =3D min(first_channel, subleds[i].channel); + i++; + } + + if (group_reg !=3D first_channel) + return dev_err_probe(chip->dev, -EINVAL, + "group index %u does not match first channel %u\n", + group_reg, first_channel); + + led->chip =3D chip; + led->mcdev.num_colors =3D count; + led->mcdev.subled_info =3D subleds; + led->mcdev.led_cdev.max_brightness =3D max_brightness; + led->mcdev.led_cdev.brightness_set_blocking =3D + is32fl3207_mc_brightness_set; + + return devm_led_classdev_multicolor_register_ext(chip->dev, &led->mcdev, + &init_data); +} + +static int is32fl3207_hw_init(struct is32fl3207 *chip) +{ + u8 scaling[IS32FL3207_NUM_CHANNELS]; + u8 pwm[2 * IS32FL3207_NUM_CHANNELS] =3D { }; + int ret; + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, + IS32FL3207_CONTROL_ENABLE); + if (ret) + return ret; + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_RESET, 0); + if (ret) + return ret; + usleep_range(200, 300); + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, + IS32FL3207_CONTROL_ENABLE); + if (ret) + return ret; + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_GLOBAL_CURRENT, + IS32FL3207_GLOBAL_CURRENT_MAX); + if (ret) + return ret; + + memset(scaling, 0xff, sizeof(scaling)); + ret =3D regmap_bulk_write(chip->regmap, IS32FL3207_REG_SCALING(0), + scaling, sizeof(scaling)); + if (ret) + return ret; + + ret =3D regmap_bulk_write(chip->regmap, IS32FL3207_REG_PWM_LOW(0), pwm, + sizeof(pwm)); + if (ret) + return ret; + + return regmap_write(chip->regmap, IS32FL3207_REG_PWM_UPDATE, 0); +} + +static void is32fl3207_disable(void *data) +{ + struct is32fl3207 *chip =3D data; + + guard(mutex)(&chip->lock); + regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, 0); + if (chip->enable_gpio) + gpiod_set_value_cansleep(chip->enable_gpio, 0); +} + +static const struct regmap_config is32fl3207_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 8, + .max_register =3D IS32FL3207_REG_RESET, +}; + +static int is32fl3207_probe(struct i2c_client *client) +{ + struct device *dev =3D &client->dev; + struct is32fl3207 *chip; + unsigned int count; + unsigned int i =3D 0; + u32 riset_ohms; + int ret; + + count =3D device_get_child_node_count(dev); + if (!count || count > IS32FL3207_NUM_CHANNELS) + return dev_err_probe(dev, -EINVAL, + "invalid LED count %u\n", count); + + chip =3D devm_kzalloc(dev, struct_size(chip, leds, count), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev =3D dev; + chip->num_leds =3D count; + i2c_set_clientdata(client, chip); + + ret =3D device_property_read_u32(dev, "issi,riset-ohms", &riset_ohms); + if (ret) + return dev_err_probe(dev, ret, "failed to read RISET value\n"); + + if (riset_ohms < 2000) + return dev_err_probe(dev, -EINVAL, + "RISET value %u is below 2000 ohms\n", + riset_ohms); + + chip->output_max_microamp =3D div_u64(IS32FL3207_CURRENT_NUMERATOR, + riset_ohms); + if (!chip->output_max_microamp) + return dev_err_probe(dev, -EINVAL, + "RISET value %u is too large\n", riset_ohms); + + ret =3D devm_regulator_get_enable_optional(dev, "vcc"); + if (ret && ret !=3D -ENODEV) + return dev_err_probe(dev, ret, "failed to enable VCC regulator\n"); + + chip->enable_gpio =3D devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_HIGH); + if (IS_ERR(chip->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(chip->enable_gpio), + "failed to get enable GPIO\n"); + + if (chip->enable_gpio) + usleep_range(1000, 2000); + + chip->regmap =3D devm_regmap_init_i2c(client, &is32fl3207_regmap_config); + if (IS_ERR(chip->regmap)) + return dev_err_probe(dev, PTR_ERR(chip->regmap), + "failed to allocate register map\n"); + + ret =3D devm_mutex_init(dev, &chip->lock); + if (ret) + return ret; + + ret =3D devm_add_action_or_reset(dev, is32fl3207_disable, chip); + if (ret) + return ret; + + ret =3D is32fl3207_hw_init(chip); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize controller\n"); + + device_for_each_child_node_scoped(dev, child) { + struct is32fl3207_led *led =3D &chip->leds[i]; + + if (fwnode_get_child_node_count(child)) + ret =3D is32fl3207_register_multicolor(chip, child, led); + else + ret =3D is32fl3207_register_single(chip, child, led); + if (ret) + return ret; + + i++; + } + + return 0; +} + +static void is32fl3207_shutdown(struct i2c_client *client) +{ + is32fl3207_disable(i2c_get_clientdata(client)); +} + +static const struct of_device_id is32fl3207_of_match[] =3D { + { .compatible =3D "issi,is32fl3207" }, + { } +}; +MODULE_DEVICE_TABLE(of, is32fl3207_of_match); + +static const struct i2c_device_id is32fl3207_id[] =3D { + { "is32fl3207" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, is32fl3207_id); + +static struct i2c_driver is32fl3207_driver =3D { + .driver =3D { + .name =3D "is32fl3207", + .of_match_table =3D is32fl3207_of_match, + }, + .probe =3D is32fl3207_probe, + .shutdown =3D is32fl3207_shutdown, + .id_table =3D is32fl3207_id, +}; +module_i2c_driver(is32fl3207_driver); + +MODULE_AUTHOR("Ahmad Byagowi "); +MODULE_DESCRIPTION("Lumissil IS32FL3207 LED controller driver"); +MODULE_LICENSE("GPL"); --=20 2.50.1 (Apple Git-155) From nobody Fri Oct 2 10:07:02 2026 Received: from mail-pg1-f172.google.com (mail-pg1-f172.google.com [209.85.215.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 28BD83C0A02 for ; Sun, 2 Aug 2026 15:46:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785685597; cv=none; b=jpCXtHasF2jcmP28kRiwd+1u3VbnAU7Iav3cdymU6WY9bTleVGcl0QYG7NZyBt/JuQ5+42cIrNtn7hvPWJhR7xpNuAsLQp7qMRTSNJv6Em4vOx+qJuSiNA3CWuq6xNN5loqEbbuOoIfBlHeUqQlFXC/SUxbCD/qVZtX3M+z4xRk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785685597; c=relaxed/simple; bh=Mv2fB5QTMgzygfKi4oX9eQwPWNNkjLy2vCUnRHb4K0E=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=LaFg5QQ2OT16BsYsDADwtfDCe0VGU+MzcrUWdBfsuMjX+w0XCjyrOJ+aO9YFx5xsasr8yso4mmEsg/o4gTzkGo1E37qaWENeBOsDQB/x258RwFjw0UEXIM+XbOrfMgrj6INp9q0MLr0U+3tiCLBBgbs9jrLnwyEISChsSMF7Rf4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=qMFtgvkJ; arc=none smtp.client-ip=209.85.215.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="qMFtgvkJ" Received: by mail-pg1-f172.google.com with SMTP id 41be03b00d2f7-ca7c1176317so2273769a12.1 for ; Sun, 02 Aug 2026 08:46:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785685592; x=1786290392; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=jXjf3n6y2o2sUp9ziyNWD3SU5x1Q1/QeHjG5pG2QUDY=; b=qMFtgvkJilu1Jb47IbDRWK0rgi8TfMB5dhX6YvSrkBWuPak0x9hqM1OXUoYHH4UsXq PWsKri8MRpuqN82Zifecsb4yB5xkRHZvS4dQLxWA9or7mtRec92uPOYU2IiAcf+K4lUR y7Cq8/W8dlzViTzq2o5GmjYvkg8GGbpF51J46oVLpfIOOgG1zZIQc9AhYs5lqzbIhZcd OIrEXtp4MMnfbl7fONSFA04QQ4epgrwWzD1FKLAtYkpCp6+zB6Tu8kLVQpJ8ESlBX5r1 5T++mjmoN5NNJn6dPlyhAoEaHoHN1Wv/g+3vzJiI+Pn/vRlWDbRQMRtYJHlRCezLX3qk QwIA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785685592; x=1786290392; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=jXjf3n6y2o2sUp9ziyNWD3SU5x1Q1/QeHjG5pG2QUDY=; b=nrP9S/rkPDcjkHsptt1/pnqiaJXJk/SZ6Gl0FNFQvgpAOTiUHSM0YwRT9EuUCP8v2M Xd6z0al2CF50iCQGdbfFlG4X8LrGaVPeTG6y2NmEl5qA5L+EA+t6/ZPdZffgUGPqrHc6 ewjsB1+MCEIjpCttzxfUSb7q/c71P8QEvIOcoOi86iS6imJvhF5L13C4UI39R6YUeKvj sAONnERXsOP2NqobGMBSaj3SgJN3g7+r1CoIAnuPwKKWb4O3GItKifn+CVkQtQxvX3NW 6NFV4ZzsNJJ9eJxNVe1cS3LoR0P6Nfth5+pCVhBN3Jng8JxMcip6EfQuTAyK3XcwI7gt WhcQ== X-Forwarded-Encrypted: i=1; AHgh+Rp2+qgPVKaMnNsofzo9cr9xXZ096e5bJwcXh/Z+V57D0QYtLi+ip2szEzXogFdyu4plKbnpVDEU0hXYGjM=@vger.kernel.org X-Gm-Message-State: AOJu0Yw09c+sjpNsAmDrlInLtc3Z9Hni77YU6WGxXQWEu+duk/9i7Uz1 fyagfWAiXfMcfT3IO3xL6w4fysQBO0YyUl5Xk09vof+3S6lnv6tEZ6f1 X-Gm-Gg: AR+sD10FUiw2rYFcrEIM4sY9oyb+or1sPhCuQ9KIOjWtsWSaIp6U2tvvijtsnm3m4dW 63bz8qTUvu1vLSroBh/sHR92qh7KA/NpgHOGONxea1p/Nx65U2xWoKvuXi6/qqcKFIS2mmIZS2j +nT8AQFIn6ll68oEKecGiz/qJV9X8EYpVCk0J3kePZG0m/bD3D0ossemDUKavnnkXuDWuN5Ew2W ftANPQyQKYwPUX+LX97Bs4BKnpacvG7mv/PnrXPT18n/qTGDzqVjUQFmLkndDmudcHEI6IP6Kgx LYYw4Va97k4vYMThYI3AkHmg1DeHZucvqtX/TjYDT8FTfo9gexHQlZpyA3Syt9JOXaGzUNZnGrg YyJAG0ZlGXPvly8T0ibn+SZnB3gsGClVt2/PT9Gx+Ba2uZ1//QA0w6/kmOuKseAyaO3/In6rkM5 K1x5MBx0a7QQuUKdK+lcolVrAn/t6sgSn/gO1qvAIhuzc/55K2TOkGvG5C2ReD1/PJhWtV+r2EN 0TtWivQEWqEQ71X7xePUM0usUARMcxUOdFdm/I= X-Received: by 2002:a05:6a21:7a95:b0:3c1:85d:fa2f with SMTP id adf61e73a8af0-3c92a89c7bbmr7671642637.36.1785685592428; Sun, 02 Aug 2026 08:46:32 -0700 (PDT) Received: from Mac.net (198-27-228-154.fiber.dynamic.sonic.net. [198.27.228.154]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-3153e29a126sm30602192eec.31.2026.08.02.08.46.30 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Sun, 02 Aug 2026 08:46:31 -0700 (PDT) From: Ahmad Byagowi To: Lee Jones , Pavel Machek , Richard Cochran , Vadim Fedorenko , Andi Shyti , Peter Rosin Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Andrew Lunn , davem@davemloft.net, Eric Dumazet , Jakub Kicinski , Paolo Abeni , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Ahmad Byagowi Subject: [RFC net-next 3/4] i2c: mux: Propagate software nodes to channel adapters Date: Sun, 2 Aug 2026 08:46:19 -0700 Message-ID: <3e76f1dc25b104a2c8558142c12e23b297f9c58c.1785684064.git.ahmadexp@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Device Tree channel nodes are associated with the adapters created by i2c-mux, but equivalent software-node descriptions are not. Find the software-node child whose reg value matches the channel and assign it to the new adapter. Keep a reference for the adapter lifetime so downstream clients can find the channel by firmware node. Signed-off-by: Ahmad Byagowi --- drivers/i2c/i2c-mux.c | 55 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c index 681a201c2..1e587ea4e 100644 --- a/drivers/i2c/i2c-mux.c +++ b/drivers/i2c/i2c-mux.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include =20 @@ -264,6 +265,49 @@ static const struct i2c_lock_operations i2c_parent_loc= k_ops =3D { .unlock_bus =3D i2c_parent_unlock_bus, }; =20 +static struct fwnode_handle * +i2c_mux_get_channel_swnode(struct i2c_mux_core *muxc, u32 chan_id) +{ + struct fwnode_handle *dev_node =3D dev_fwnode(muxc->dev); + struct fwnode_handle *mux_node, *child =3D NULL; + u32 reg; + + if (!is_software_node(dev_node)) + return NULL; + + if (muxc->arbitrator) + mux_node =3D fwnode_get_named_child_node(dev_node, "i2c-arb"); + else if (muxc->gate) + mux_node =3D fwnode_get_named_child_node(dev_node, "i2c-gate"); + else + mux_node =3D fwnode_get_named_child_node(dev_node, "i2c-mux"); + + if (mux_node) { + /* A "reg" property indicates an old-style firmware entry. */ + if (!fwnode_property_read_u32(mux_node, "reg", ®)) { + fwnode_handle_put(mux_node); + mux_node =3D NULL; + } + } + + if (!mux_node) + mux_node =3D fwnode_handle_get(dev_node); + else if (muxc->arbitrator || muxc->gate) + child =3D fwnode_handle_get(mux_node); + + if (!child) { + fwnode_for_each_child_node(mux_node, child) { + if (fwnode_property_read_u32(child, "reg", ®)) + continue; + if (chan_id =3D=3D reg) + break; + } + } + + fwnode_handle_put(mux_node); + return child; +} + int i2c_mux_add_adapter(struct i2c_mux_core *muxc, u32 force_nr, u32 chan_id) { @@ -364,6 +408,12 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, =20 priv->adap.dev.of_node =3D child; of_node_put(mux_node); + } else { + struct fwnode_handle *fwnode; + + fwnode =3D i2c_mux_get_channel_swnode(muxc, chan_id); + if (fwnode) + device_set_node(&priv->adap.dev, fwnode); } =20 /* @@ -408,6 +458,8 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, return 0; =20 err_free_priv: + if (is_software_node(dev_fwnode(&priv->adap.dev))) + fwnode_handle_put(dev_fwnode(&priv->adap.dev)); kfree(priv); return ret; } @@ -420,6 +472,7 @@ void i2c_mux_del_adapters(struct i2c_mux_core *muxc) while (muxc->num_adapters) { struct i2c_adapter *adap =3D muxc->adapter[--muxc->num_adapters]; struct i2c_mux_priv *priv =3D adap->algo_data; + struct fwnode_handle *fwnode =3D dev_fwnode(&adap->dev); struct device_node *np =3D adap->dev.of_node; =20 muxc->adapter[muxc->num_adapters] =3D NULL; @@ -430,6 +483,8 @@ void i2c_mux_del_adapters(struct i2c_mux_core *muxc) =20 sysfs_remove_link(&priv->adap.dev.kobj, "mux_device"); i2c_del_adapter(adap); + if (is_software_node(fwnode)) + fwnode_handle_put(fwnode); of_node_put(np); kfree(priv); } --=20 2.50.1 (Apple Git-155) From nobody Fri Oct 2 10:07:02 2026 Received: from mail-pl1-f182.google.com (mail-pl1-f182.google.com [209.85.214.182]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0B1EC3C3456 for ; Sun, 2 Aug 2026 15:46:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785685603; cv=none; b=X3Taqy1hPMTvxnV0qlZdkK+gh2IBZDd1pzguhJAyp1wwNGx2o7w52/dmp8+2qshD6XlavjVFj6SZztwx7UyhsK7A1rQjISb+MzwTn4iow5v8ombQwS7IFwWuzH2Z/S6YCmGzOIUQZrm41ae/KIRr2I1aX1JRP/9OEp6Gl8ukPzo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785685603; c=relaxed/simple; bh=OR37Iv+hKosJmgVoNOTvEX7Csyg/AuprGKf/TLFamgE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fn7HNrL9iPmYTiTJSHlWclAYJ08IDIHvrntDmWx0sPhJP3SnR9h1crxWPhZzta/x1PH3V5ecny0wqiIc13JbxB7Nfdi2U+PxhpxTaCoH7bPf4Hfm5jgEF8PiuXwaDKKiCzjDU6PxMILcL7N4f9gn0qf1dcSWBz7e12DyQh/8aO0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=nNtKBz/8; arc=none smtp.client-ip=209.85.214.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="nNtKBz/8" Received: by mail-pl1-f182.google.com with SMTP id d9443c01a7336-2cca0c5799eso20732405ad.0 for ; Sun, 02 Aug 2026 08:46:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785685594; x=1786290394; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=SiJGL86YIgemNjCRQw+qQrbA3LuaeRoAxQWIwG8BtCE=; b=nNtKBz/8a6nyhKJA60VccdCySaQEk1emGKlDGqCf7ds7GutkvIgEVj0fAb7kWyy+SA hH9d5t4ogOqvmjfIcTlQMim9qjIBzYeE0b+cb9EsfcJ8K1qB5nJeDnC5g6VO9m98yC2b q1MB577oDC7TLmv+7J2j7symxdAvY/mE62XZlfNozTIBhAfNsPAMTHRY0vSWdzLXtirp bS3aIrIW/503tk+Xtcik7wBTv63CsK/FoaiEv5g5s4mUrXZ0W57HLIp6pyidnz2RYvFz vkViCprTmKulbtydxmcYBaDooRdxwLFcM5AfBGlUXhbMMF2lLNLjm0RD8hOR4p7m0Vst +QsQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785685594; x=1786290394; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=SiJGL86YIgemNjCRQw+qQrbA3LuaeRoAxQWIwG8BtCE=; b=fxO50bVYGmNe7H5jQ5DglA756sZgI3okvP04WhvXQh/MFRiQSPvb58psmuFkB54AGe wKm1BRXbQXnranJl+qsOBh58y3ispYrGCVINQUIChjeUR1+QxKlwOFUVjh9LEGZHgXPF zDfKcF/ccwcEOugpynI3EW3Ze5TInzC3I3uOxJtJugK2gC4HZYI5brWKPckkkgde/6pj eV1++xBBJZ2JS/Bv4cRw0QcEqrXR0uIvlkdJ0BY/J6IxjBDLqae43aMjT3aD+X4yXPPO IZxxQyss/oNI7+jIsx1RdQNRkf2n0JLJQyWoGEaD8gXHfD65uB3OpqsWJDHhsrXTKMg2 MCpA== X-Forwarded-Encrypted: i=1; AHgh+RowWSrCv4Y/VR3AgFnPFCy3BJqa4ratOfAPLMuToxOnOC7UU3Lf3jIhdes0tqU4Cedc2oc+herCboFnpNk=@vger.kernel.org X-Gm-Message-State: AOJu0Yw/oDnUwRvwXFgLoJUsgOzrQjJ8E68sXVU/FFFg6Xahccngijlg ayII2tU5JsX/F+qgKcT4HEY8ZxCw3ipPrPR66vsyGaw3J5zIHI1fRvPr X-Gm-Gg: AR+sD109hKyG5VJKScWdWaqChniJAyog3+hTxMOfWDPKZeQHNFWaEaHSrkVGWhleruH j9SxD9BL///yp2Kg3SHOIzf0bH0z5X5yBVL8DQK+Oq7177/gVV1XaCoJBTmX5TaYFvtt69KU/br 9ndHfPh5kFJup/Dbr0PbTgWB7LQMSJvXPG+4BK+Mu7xCzIoGZmRy31E1fWgNCO6LJfq1gwshZLO R6L9/NFxkwJMFAbOaaLPcaslsgFwspnOuSdt+1yXt7lS9rWWfI8rkjpwi+K3kLUEDSvEnhWr3iP So2N/7FN6EugAXb638wAMq3wJOmfmuP7wpOY9bsJr5fQOWmcWSXc7bUy85s3/6v7jb16SlBDvI/ UwQpvJnSZF9C4EyRf4AFTRTxZFpjVDz+8jLWfoECwUmKJg57B8PMX4tzWljZr5f/GaWuCBU98HS cSOtSoWHjKeC+t9p8adQyYq3agTIKpKplTEqM8X5TAdTFGb78fvfJPlB3Af5Ei+qHF2fhMMju0r VYRIWqeD5b1nJ3UX4p7FGIlVLgJNJ5Aee3inWE= X-Received: by 2002:a17:903:1b43:b0:2ce:d957:59c4 with SMTP id d9443c01a7336-2d052188a6bmr70827165ad.6.1785685594212; Sun, 02 Aug 2026 08:46:34 -0700 (PDT) Received: from Mac.net (198-27-228-154.fiber.dynamic.sonic.net. [198.27.228.154]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-3153e29a126sm30602192eec.31.2026.08.02.08.46.32 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Sun, 02 Aug 2026 08:46:33 -0700 (PDT) From: Ahmad Byagowi To: Lee Jones , Pavel Machek , Richard Cochran , Vadim Fedorenko , Andi Shyti , Peter Rosin Cc: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Andrew Lunn , davem@davemloft.net, Eric Dumazet , Jakub Kicinski , Paolo Abeni , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Ahmad Byagowi Subject: [RFC net-next 4/4] ptp: ocp: Add R4006 I2C peripheral topology Date: Sun, 2 Aug 2026 08:46:20 -0700 Message-ID: <25f255ac12a006059e0a442a5d750c31e9da1ae2.1785684064.git.ahmadexp@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" R4006 cards place a PCA9546 mux behind the FPGA I2C controller. The mux exposes three LM75B temperature sensors, an SHT3x humidity sensor, an ICP10100 pressure sensor, and an IS32FL3207 controller for the GNSS and SMA LEDs. Detect the board through the existing EEPROM nvmem path and describe its per-card topology with software nodes. Instantiate the standard I2C clients after their adapters appear, allowing the component drivers to bind normally. Leave the channel containing the BNO08x empty because there is no upstream driver for that device. Serialize EEPROM discovery, retry topology population from I2C bus notifications, and atomically disable the delayed work during teardown. Limit the existing i2c sysfs link to the root adapter. Signed-off-by: Ahmad Byagowi --- drivers/ptp/ptp_ocp.c | 485 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 459 insertions(+), 26 deletions(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 35e911f1a..b362a7c4d 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -21,10 +21,14 @@ #include #include #include +#include #include +#include #include #include =20 +#include + #define PCI_DEVICE_ID_META_TIMECARD 0x0400 =20 #define PCI_VENDOR_ID_CELESTICA 0x18d4 @@ -347,6 +351,32 @@ struct ptp_ocp_serial_port { #define OCP_SIGNAL_NUM 4 #define OCP_FREQ_NUM 4 =20 +#define OCP_R4006_MUX_CHANNELS 4 +#define OCP_R4006_SENSOR_COUNT 5 +#define OCP_R4006_LED_COUNT 5 +#define OCP_R4006_LED_COMPONENT_COUNT (3 * OCP_R4006_LED_COUNT) +#define OCP_R4006_NODE_COUNT (1 + OCP_R4006_MUX_CHANNELS + \ + OCP_R4006_SENSOR_COUNT + 1 + \ + OCP_R4006_LED_COUNT + \ + OCP_R4006_LED_COMPONENT_COUNT) + +struct ptp_ocp_r4006 { + struct software_node mux_node; + struct property_entry mux_props[6]; + struct software_node channel_nodes[OCP_R4006_MUX_CHANNELS]; + struct property_entry channel_props[OCP_R4006_MUX_CHANNELS][4]; + struct software_node sensor_nodes[OCP_R4006_SENSOR_COUNT]; + struct property_entry sensor_props[OCP_R4006_SENSOR_COUNT][3]; + struct software_node led_node; + struct property_entry led_props[6]; + struct software_node led_nodes[OCP_R4006_LED_COUNT]; + struct property_entry led_group_props[OCP_R4006_LED_COUNT][7]; + struct software_node component_nodes[OCP_R4006_LED_COMPONENT_COUNT]; + struct property_entry component_props[OCP_R4006_LED_COMPONENT_COUNT][4]; + const struct software_node *node_group[OCP_R4006_NODE_COUNT + 1]; + bool registered; +}; + enum { PORT_GNSS, PORT_GNSS2, @@ -403,6 +433,7 @@ struct ptp_ocp { u16 fw_version; u8 board_id[OCP_BOARD_ID_LEN]; u8 serial[OCP_SERIAL_LEN]; + struct mutex eeprom_lock; /* Serializes EEPROM discovery. */ bool has_eeprom_data; u32 pps_req_map; int flash_start; @@ -414,6 +445,8 @@ struct ptp_ocp { const struct ocp_sma_op *sma_op; struct dpll_device *dpll; dpll_tracker tracker; + struct ptp_ocp_r4006 *r4006; + struct delayed_work i2c_work; int signals_nr; int freq_in_nr; }; @@ -488,6 +521,71 @@ static struct ptp_ocp_eeprom_map art_eeprom_map[] =3D { { } }; =20 +struct ptp_ocp_r4006_i2c_device { + const char *node_name; + const char *compatible; + const char *type; + u8 channel; + u8 address; +}; + +/* Channel 3's BNO08x at 0x4a has no upstream Linux driver, so omit it. */ +static const struct ptp_ocp_r4006_i2c_device ptp_ocp_r4006_sensors[] =3D { + { "temperature@48", "national,lm75b", "lm75b", 0, 0x48 }, + { "temperature@49", "national,lm75b", "lm75b", 0, 0x49 }, + { "temperature@4a", "national,lm75b", "lm75b", 0, 0x4a }, + { "humidity@44", "sensirion,sht3x", "sht3x", 1, 0x44 }, + { "pressure@63", "invensense,icp10100", "icp10100", 2, 0x63 }, +}; + +static_assert(ARRAY_SIZE(ptp_ocp_r4006_sensors) =3D=3D OCP_R4006_SENSOR_CO= UNT); + +struct ptp_ocp_r4006_led { + const char *node_name; + const char *function; + u8 function_enumerator; + bool has_function_enumerator; + u8 channel[3]; +}; + +static const struct ptp_ocp_r4006_led ptp_ocp_r4006_leds[] =3D { + { + .node_name =3D "multi-led@c", + .function =3D LED_FUNCTION_STATUS, + .channel =3D { 13, 12, 14 }, + }, + { + .node_name =3D "multi-led@6", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 1, + .has_function_enumerator =3D true, + .channel =3D { 7, 6, 8 }, + }, + { + .node_name =3D "multi-led@9", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 2, + .has_function_enumerator =3D true, + .channel =3D { 10, 9, 11 }, + }, + { + .node_name =3D "multi-led@0", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 3, + .has_function_enumerator =3D true, + .channel =3D { 1, 0, 2 }, + }, + { + .node_name =3D "multi-led@3", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 4, + .has_function_enumerator =3D true, + .channel =3D { 4, 3, 5 }, + }, +}; + +static_assert(ARRAY_SIZE(ptp_ocp_r4006_leds) =3D=3D OCP_R4006_LED_COUNT); + #define bp_assign_entry(bp, res, val) ({ \ uintptr_t addr =3D (uintptr_t)(bp) + (res)->bp_offset; \ *(typeof(val) *)addr =3D val; \ @@ -1969,6 +2067,12 @@ ptp_ocp_nvmem_device_put(struct nvmem_device **nvmem= p) *nvmemp =3D NULL; } =20 +static bool +ptp_ocp_has_eeprom_data(struct ptp_ocp *bp) +{ + return smp_load_acquire(&bp->has_eeprom_data); +} + static void ptp_ocp_read_eeprom(struct ptp_ocp *bp) { @@ -1977,11 +2081,11 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp) const void *tag; int ret; =20 - if (!bp->i2c_ctrl) - return; - tag =3D NULL; nvmem =3D NULL; + mutex_lock(&bp->eeprom_lock); + if (!bp->i2c_ctrl || ptp_ocp_has_eeprom_data(bp)) + goto out; =20 for (map =3D bp->eeprom_map; map->len; map++) { if (map->tag !=3D tag) { @@ -2001,10 +2105,12 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp) goto fail; } =20 - bp->has_eeprom_data =3D true; + /* Publish the EEPROM fields before readers observe valid data. */ + smp_store_release(&bp->has_eeprom_data, true); =20 out: ptp_ocp_nvmem_device_put(&nvmem); + mutex_unlock(&bp->eeprom_lock); return; =20 fail: @@ -2012,6 +2118,291 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp) goto out; } =20 +static int +ptp_ocp_i2c_adapter_match(struct device *dev, const void *data) +{ + return !!i2c_verify_adapter(dev); +} + +static struct i2c_adapter * +ptp_ocp_i2c_root_adapter(struct ptp_ocp *bp) +{ + struct i2c_adapter *adapter; + struct device *dev; + + if (!bp->i2c_ctrl) + return NULL; + + dev =3D device_find_child(&bp->i2c_ctrl->dev, NULL, + ptp_ocp_i2c_adapter_match); + if (!dev) + return NULL; + + adapter =3D i2c_verify_adapter(dev); + if (!adapter || !try_module_get(adapter->owner)) { + put_device(dev); + return NULL; + } + + /* The device reference from device_find_child() is owned by the caller. = */ + return adapter; +} + +static int +ptp_ocp_r4006_init_nodes(struct ptp_ocp *bp) +{ + static const char * const channel_names[] =3D { + "i2c@0", "i2c@1", "i2c@2", "i2c@3", + }; + static const char * const output_names[] =3D { + "led@0", "led@1", "led@2", "led@3", "led@4", "led@5", + "led@6", "led@7", "led@8", "led@9", "led@a", "led@b", + "led@c", "led@d", "led@e", "led@f", "led@10", "led@11", + }; + static const u32 colors[] =3D { + LED_COLOR_ID_RED, LED_COLOR_ID_GREEN, LED_COLOR_ID_BLUE, + }; + struct device *dev =3D &bp->pdev->dev; + struct ptp_ocp_r4006 *r4006; + unsigned int component =3D 0; + unsigned int node =3D 0; + unsigned int i, j, prop; + int ret; + + if (bp->r4006) + return 0; + + r4006 =3D devm_kzalloc(dev, sizeof(*r4006), GFP_KERNEL); + if (!r4006) + return -ENOMEM; + + r4006->mux_node.name =3D + devm_kasprintf(dev, GFP_KERNEL, "ocp%d-r4006-mux", bp->id); + if (!r4006->mux_node.name) + return -ENOMEM; + r4006->mux_node.properties =3D r4006->mux_props; + r4006->mux_props[0] =3D + PROPERTY_ENTRY_STRING("compatible", "nxp,pca9546"); + r4006->mux_props[1] =3D PROPERTY_ENTRY_U32("reg", 0x70); + r4006->mux_props[2] =3D PROPERTY_ENTRY_BOOL("i2c-mux-idle-disconnect"); + r4006->mux_props[3] =3D PROPERTY_ENTRY_U32("#address-cells", 1); + r4006->mux_props[4] =3D PROPERTY_ENTRY_U32("#size-cells", 0); + r4006->node_group[node++] =3D &r4006->mux_node; + + for (i =3D 0; i < OCP_R4006_MUX_CHANNELS; i++) { + r4006->channel_nodes[i].name =3D channel_names[i]; + r4006->channel_nodes[i].parent =3D &r4006->mux_node; + r4006->channel_nodes[i].properties =3D r4006->channel_props[i]; + r4006->channel_props[i][0] =3D PROPERTY_ENTRY_U32("reg", i); + r4006->channel_props[i][1] =3D PROPERTY_ENTRY_U32("#address-cells", 1); + r4006->channel_props[i][2] =3D PROPERTY_ENTRY_U32("#size-cells", 0); + r4006->node_group[node++] =3D &r4006->channel_nodes[i]; + } + + for (i =3D 0; i < ARRAY_SIZE(ptp_ocp_r4006_sensors); i++) { + const struct ptp_ocp_r4006_i2c_device *sensor; + + sensor =3D &ptp_ocp_r4006_sensors[i]; + r4006->sensor_nodes[i].name =3D sensor->node_name; + r4006->sensor_nodes[i].parent =3D + &r4006->channel_nodes[sensor->channel]; + r4006->sensor_nodes[i].properties =3D r4006->sensor_props[i]; + r4006->sensor_props[i][0] =3D + PROPERTY_ENTRY_STRING("compatible", sensor->compatible); + r4006->sensor_props[i][1] =3D + PROPERTY_ENTRY_U32("reg", sensor->address); + r4006->node_group[node++] =3D &r4006->sensor_nodes[i]; + } + + r4006->led_node.name =3D "led-controller@34"; + r4006->led_node.parent =3D &r4006->channel_nodes[1]; + r4006->led_node.properties =3D r4006->led_props; + r4006->led_props[0] =3D + PROPERTY_ENTRY_STRING("compatible", "issi,is32fl3207"); + r4006->led_props[1] =3D PROPERTY_ENTRY_U32("reg", 0x34); + r4006->led_props[2] =3D PROPERTY_ENTRY_U32("issi,riset-ohms", 4700); + r4006->led_props[3] =3D PROPERTY_ENTRY_U32("#address-cells", 1); + r4006->led_props[4] =3D PROPERTY_ENTRY_U32("#size-cells", 0); + r4006->node_group[node++] =3D &r4006->led_node; + + for (i =3D 0; i < ARRAY_SIZE(ptp_ocp_r4006_leds); i++) { + const struct ptp_ocp_r4006_led *led =3D &ptp_ocp_r4006_leds[i]; + u32 group_reg; + + group_reg =3D min3(led->channel[0], led->channel[1], + led->channel[2]); + r4006->led_nodes[i].name =3D led->node_name; + r4006->led_nodes[i].parent =3D &r4006->led_node; + r4006->led_nodes[i].properties =3D r4006->led_group_props[i]; + prop =3D 0; + r4006->led_group_props[i][prop++] =3D + PROPERTY_ENTRY_U32("reg", group_reg); + r4006->led_group_props[i][prop++] =3D + PROPERTY_ENTRY_U32("color", LED_COLOR_ID_RGB); + r4006->led_group_props[i][prop++] =3D + PROPERTY_ENTRY_STRING("function", led->function); + if (led->has_function_enumerator) + r4006->led_group_props[i][prop++] =3D + PROPERTY_ENTRY_U32("function-enumerator", + led->function_enumerator); + r4006->led_group_props[i][prop++] =3D + PROPERTY_ENTRY_U32("#address-cells", 1); + r4006->led_group_props[i][prop] =3D + PROPERTY_ENTRY_U32("#size-cells", 0); + r4006->node_group[node++] =3D &r4006->led_nodes[i]; + + for (j =3D 0; j < ARRAY_SIZE(led->channel); j++, component++) { + u8 channel =3D led->channel[j]; + + r4006->component_nodes[component].name =3D + output_names[channel]; + r4006->component_nodes[component].parent =3D + &r4006->led_nodes[i]; + r4006->component_nodes[component].properties =3D + r4006->component_props[component]; + r4006->component_props[component][0] =3D + PROPERTY_ENTRY_U32("reg", channel); + r4006->component_props[component][1] =3D + PROPERTY_ENTRY_U32("color", colors[j]); + r4006->component_props[component][2] =3D + PROPERTY_ENTRY_U32("led-max-microamp", 8150); + r4006->node_group[node++] =3D + &r4006->component_nodes[component]; + } + } + + if (WARN_ON(node !=3D OCP_R4006_NODE_COUNT)) + return -EINVAL; + + ret =3D software_node_register_node_group(r4006->node_group); + if (ret) + return dev_err_probe(dev, ret, + "failed to register R4006 firmware nodes\n"); + + r4006->registered =3D true; + bp->r4006 =3D r4006; + return 0; +} + +static int +ptp_ocp_r4006_add_device(struct ptp_ocp *bp, struct i2c_adapter *adapter, + const struct software_node *node, const char *type, + u16 address) +{ + struct fwnode_handle *fwnode =3D software_node_fwnode(node); + struct i2c_board_info info =3D { }; + struct i2c_client *client; + + client =3D i2c_find_device_by_fwnode(fwnode); + if (client) { + put_device(&client->dev); + return 0; + } + + strscpy(info.type, type, sizeof(info.type)); + info.addr =3D address; + info.fwnode =3D fwnode; + client =3D i2c_new_client_device(adapter, &info); + if (IS_ERR(client)) + return dev_err_probe(&bp->pdev->dev, PTR_ERR(client), + "failed to add %s at %d-00%02x\n", + type, i2c_adapter_id(adapter), address); + + return 0; +} + +static void +ptp_ocp_r4006_populate_channel(struct ptp_ocp *bp, unsigned int channel) +{ + struct ptp_ocp_r4006 *r4006 =3D bp->r4006; + const struct software_node *node; + struct fwnode_handle *fwnode; + struct i2c_adapter *adapter; + unsigned int i; + + fwnode =3D software_node_fwnode(&r4006->channel_nodes[channel]); + adapter =3D i2c_get_adapter_by_fwnode(fwnode); + if (!adapter) + return; + + for (i =3D 0; i < ARRAY_SIZE(ptp_ocp_r4006_sensors); i++) { + const struct ptp_ocp_r4006_i2c_device *sensor; + + sensor =3D &ptp_ocp_r4006_sensors[i]; + if (sensor->channel !=3D channel) + continue; + + node =3D &r4006->sensor_nodes[i]; + ptp_ocp_r4006_add_device(bp, adapter, node, sensor->type, + sensor->address); + } + + if (channel =3D=3D 1) + ptp_ocp_r4006_add_device(bp, adapter, &r4006->led_node, + "is32fl3207", 0x34); + + i2c_put_adapter(adapter); +} + +static void +ptp_ocp_i2c_work(struct work_struct *work) +{ + struct ptp_ocp *bp =3D container_of(work, struct ptp_ocp, i2c_work.work); + const struct software_node *node; + struct i2c_adapter *adapter; + unsigned int channel; + int ret; + + if (!bp->eeprom_map) + return; + + adapter =3D ptp_ocp_i2c_root_adapter(bp); + if (!adapter) + return; + + if (!ptp_ocp_has_eeprom_data(bp)) + ptp_ocp_read_eeprom(bp); + if (!ptp_ocp_has_eeprom_data(bp) || memcmp(bp->board_id, "R4006", 5)) + goto out_put_adapter; + + ret =3D ptp_ocp_r4006_init_nodes(bp); + if (ret) + goto out_put_adapter; + + node =3D &bp->r4006->mux_node; + ret =3D ptp_ocp_r4006_add_device(bp, adapter, node, "pca9546", 0x70); + if (ret) + goto out_put_adapter; + + for (channel =3D 0; channel < OCP_R4006_MUX_CHANNELS; channel++) + ptp_ocp_r4006_populate_channel(bp, channel); + +out_put_adapter: + i2c_put_adapter(adapter); +} + +static void +ptp_ocp_r4006_unregister(struct ptp_ocp *bp) +{ + struct fwnode_handle *fwnode; + struct i2c_client *client; + + disable_delayed_work_sync(&bp->i2c_work); + if (!bp->r4006) + return; + + fwnode =3D software_node_fwnode(&bp->r4006->mux_node); + client =3D i2c_find_device_by_fwnode(fwnode); + if (client) { + i2c_unregister_device(client); + put_device(&client->dev); + } + + if (bp->r4006->registered) + software_node_unregister_node_group(bp->r4006->node_group); + bp->r4006 =3D NULL; +} + static struct device * ptp_ocp_find_flash(struct ptp_ocp *bp) { @@ -2166,9 +2557,9 @@ ptp_ocp_devlink_info_get(struct devlink *devlink, str= uct devlink_info_req *req, if (err) return err; =20 - if (!bp->has_eeprom_data) { + if (!ptp_ocp_has_eeprom_data(bp)) { ptp_ocp_read_eeprom(bp); - if (!bp->has_eeprom_data) + if (!ptp_ocp_has_eeprom_data(bp)) return 0; } =20 @@ -3756,7 +4147,7 @@ serialnum_show(struct device *dev, struct device_attr= ibute *attr, char *buf) { struct ptp_ocp *bp =3D dev_get_drvdata(dev); =20 - if (!bp->has_eeprom_data) + if (!ptp_ocp_has_eeprom_data(bp)) ptp_ocp_read_eeprom(bp); =20 return sysfs_emit(buf, "%pM\n", bp->serial); @@ -4727,6 +5118,7 @@ ptp_ocp_device_init(struct ptp_ocp *bp, struct pci_de= v *pdev) =20 bp->ptp_info =3D ptp_ocp_clock_info; spin_lock_init(&bp->lock); + mutex_init(&bp->eeprom_lock); =20 for (i =3D 0; i < __PORT_COUNT; i++) bp->port[i].line =3D -1; @@ -4865,6 +5257,7 @@ ptp_ocp_detach(struct ptp_ocp *bp) { int i; =20 + ptp_ocp_r4006_unregister(bp); ptp_ocp_debugfs_remove_device(bp); ptp_ocp_detach_sysfs(bp); ptp_ocp_attr_group_del(bp); @@ -5079,6 +5472,7 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_= device_id *id) goto out_disable; =20 INIT_DELAYED_WORK(&bp->sync_work, ptp_ocp_sync_work); + INIT_DELAYED_WORK(&bp->i2c_work, ptp_ocp_i2c_work); =20 /* compat mode. * Older FPGA firmware only returns 2 irq's. @@ -5096,6 +5490,7 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_= device_id *id) err =3D ptp_ocp_register_resources(bp, id->driver_data); if (err) goto out; + mod_delayed_work(system_wq, &bp->i2c_work, 1); =20 bp->ptp =3D ptp_clock_register(&bp->ptp_info, &pdev->dev); if (IS_ERR(bp->ptp)) { @@ -5189,38 +5584,76 @@ static struct pci_driver ptp_ocp_driver =3D { .shutdown =3D ptp_ocp_remove, }; =20 +static struct ptp_ocp * +ptp_ocp_i2c_parent(struct device *child) +{ + struct device *dev =3D child; + + while ((dev =3D dev->parent)) + if (dev->driver && !strcmp(dev->driver->name, KBUILD_MODNAME)) + return dev_get_drvdata(dev); + + return NULL; +} + +static bool +ptp_ocp_i2c_is_root(struct ptp_ocp *bp, struct i2c_adapter *adapter) +{ + struct device *parent =3D adapter->dev.parent; + + if (!parent) + return false; + if (bp->i2c_ctrl && parent =3D=3D &bp->i2c_ctrl->dev) + return true; + + /* The adapter can notify before ptp_ocp_register_i2c() stores it. */ + return parent->parent =3D=3D &bp->pdev->dev; +} + static int ptp_ocp_i2c_notifier_call(struct notifier_block *nb, unsigned long action, void *data) { - struct device *dev, *child =3D data; + struct i2c_adapter *adapter; + struct i2c_client *client; + struct device *child =3D data; struct ptp_ocp *bp; - bool add; =20 switch (action) { case BUS_NOTIFY_ADD_DEVICE: - case BUS_NOTIFY_DEL_DEVICE: - add =3D action =3D=3D BUS_NOTIFY_ADD_DEVICE; - break; - default: + adapter =3D i2c_verify_adapter(child); + if (!adapter) + return 0; + bp =3D ptp_ocp_i2c_parent(child); + if (!bp) + return 0; + if (ptp_ocp_i2c_is_root(bp, adapter)) + ptp_ocp_symlink(bp, child, "i2c"); + mod_delayed_work(system_wq, &bp->i2c_work, 1); return 0; - } =20 - if (!i2c_verify_adapter(child)) + case BUS_NOTIFY_DEL_DEVICE: + adapter =3D i2c_verify_adapter(child); + if (!adapter) + return 0; + bp =3D ptp_ocp_i2c_parent(child); + if (bp && ptp_ocp_i2c_is_root(bp, adapter)) + sysfs_remove_link(&bp->dev.kobj, "i2c"); return 0; =20 - dev =3D child; - while ((dev =3D dev->parent)) - if (dev->driver && !strcmp(dev->driver->name, KBUILD_MODNAME)) - goto found; - return 0; + case BUS_NOTIFY_BOUND_DRIVER: + client =3D i2c_verify_client(child); + if (!client || client->addr !=3D 0x50) + return 0; + bp =3D ptp_ocp_i2c_parent(child); + if (!bp || !ptp_ocp_i2c_is_root(bp, client->adapter)) + return 0; + mod_delayed_work(system_wq, &bp->i2c_work, 1); + return 0; =20 -found: - bp =3D dev_get_drvdata(dev); - if (add) - ptp_ocp_symlink(bp, child, "i2c"); - else - sysfs_remove_link(&bp->dev.kobj, "i2c"); + default: + return 0; + } =20 return 0; } --=20 2.50.1 (Apple Git-155)