From nobody Tue Sep 29 07:39:05 2026 Received: from mail-pf1-f174.google.com (mail-pf1-f174.google.com [209.85.210.174]) (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 54778313E34 for ; Tue, 11 Aug 2026 05:02:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786424571; cv=none; b=KM5f4ylHMxSRF8gUVKbCiHQRIPIFRuRPi2IK/zECxj58t8HfsR7XhhY5QqpV/iJngJkgoo9YPTeKoFhPT/PC4WgkdERsE8ASl6b8IEh3zCQYCLu6qAXax7XXVAqrtcuFeIwnVEB522oH/EpRC/5T9yvHW+coq7jZy15712XZXFo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786424571; c=relaxed/simple; bh=cgOyPqDoAVmQIdEVzceGAUOAHO3s4QFFGfpc4LKyOz0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=XWtbOjmwY+lRuiDqDQaU0V7w6aoSPP2vxtqdnDZPyvF+nYFBOTpXf3iTUtc1yGZcCTm3X/Ja9Y0JsSpsikfjeapYR+pusDKVUdAeozUrcU5RgZmW4swTzrbMVb2QyQQYgjt9PsYdIOors6byUgURU1Nz0zyGkXNRrnODsExYraE= 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=OTgg2tYq; arc=none smtp.client-ip=209.85.210.174 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="OTgg2tYq" Received: by mail-pf1-f174.google.com with SMTP id d2e1a72fcca58-84536ecfc5bso2684149b3a.2 for ; Mon, 10 Aug 2026 22:02:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786424568; x=1787029368; 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=vzcklR1752px28ASDR7vjX+YItV4G0zo+zCsAv5XG1k=; b=OTgg2tYqHiO6uyGwDU67EVKFnU3oUg5KopP5g95V15g0Zmn3Y5g6xOixOSy10eLM3K Uchso8Km3vMjJh9gkRzo5GZwmtfVOKOfdRU13LXn3nFgPD23yOGXW0qK0iywVxSmnBcx DT7FWE7Ujg1aJxUiccxkSRq0meCruB8nCvvWHZ9Gx0ID5dmn+9g3FC8e3bAxkbj+xB0S DgTIHvthW/NXwaRf3vcGUdbNNi9zez4PiTElTmVLVjgHQDe3x8hf1lgvnEnepfsTAeZ2 V4mOVBaCB962HI6wp3A0IKLzbCN/mucn4FcLCqmE8Pt4bTwI008F3Z+oQr7oJPvOKrZp Nqhw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786424568; x=1787029368; 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=vzcklR1752px28ASDR7vjX+YItV4G0zo+zCsAv5XG1k=; b=dZ58r67tvuM6IEByWkvKJoBshyzU5ip9x5jXd4/hXP6AsgmgZUN0q30FDlnAJCpNs6 GyiqhDMkISJOb0Vc/ZFhn7LeeejIZoqzM/K0Uq06YT+4LSkLbsJNwl56Rh6QIN4/EjxE Mz5xWklA2NQB5FgVuHGHfOAoIL7Z/jxnbJMGh9Mi7VCBt7WwsL6fDRb+r9MsWQhXV1Xu nADlB4QY3709yr7/kJE2DaSYopguDvoqxH+uQWkHxpwUCRyWiZr6nkMCisYzVLjn4tMW 6CC9XpdQpDKj1/M2rVa0H2MAKGZAx68djuzDe18BOz7HY1CygXWd3sze6gN6EzojeaI7 +k9A== X-Forwarded-Encrypted: i=1; AHgh+RrwssNDwdSfJH8knzd4f56Wprn1Dn7nc9T5oFR9AN8qSXxAVLj+XHYkzzmAVtUimHdrf8/q3VuJMVhVees=@vger.kernel.org X-Gm-Message-State: AOJu0YwcKIdAaLSBFu3F9HhDI2VAbux6bCxNrV6BUxUl9K0ZCFc+gKOd jSckmS5resYCAM4A5NylzH+TDTO56QerzhaSgzN+LGojAHw1uVinGe1i X-Gm-Gg: AR+sD13y6dKcRE+zOUNBPwvCNKwR3bWTW8ynpUZz+Fzok/k+DDHKbhogabrqNbR3vHL LF5w8SLUCvhUWNgveKbpZDWYtpMzuhGvbU9NVHDihv62xfcmzUpC6GKBUsiDHbrKIcsjlijoaJb Hb3YXv76LEXmvQc6dHrHEzXBwpmEVFRF61Ccb56NtPTL0f4wyCfnRI7m2u7tNVbyGztjiYydskl mw4FDuMZ/vThPUg2Z4ZcUQKRWhn10CXKU3ThrhdpZw8I/lTsLT5GXu+KnjMXX3dZG0yEjTlJNz/ B6J1hKacfc6Bl9tePMT1o2735nQg2GU/7EHBTisD3IyuRBAvYlynWbN9EyLQbyNIUripwlRQp5K lPL2TWwFKzG7bSDct0lPkniUOUYSl1ZHnsZ/HTi9JLuwYwyTIqp50D5AsKoMGMiPkWxyGY9hts9 bAvRGg3QHQjchKmOQ1Slz7nQGxhcC4qcgZnOBNQKU7iVx17jc0LuNmVTT4MJtHCCuqaZV/6+Iey Hh/kr1Rd3wialGC7LM3iVe0q2PJI/L00EdcF10Eqgbyn7/ryg== X-Received: by 2002:a05:6a21:7008:b0:398:7ed3:a001 with SMTP id adf61e73a8af0-3cc2b7a6ae5mr1303294637.2.1786424568459; Mon, 10 Aug 2026 22:02:48 -0700 (PDT) Received: from Mac.localdomain (198-27-228-154.fiber.dynamic.sonic.net. [198.27.228.154]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-31c619cf07dsm660421eec.4.2026.08.10.22.02.47 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Mon, 10 Aug 2026 22:02:47 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Nam Tran , Kees Cook , "Gustavo A. R. Silva" , Peter Rosin , Andi Shyti , Richard Cochran , Vadim Fedorenko , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hardening@vger.kernel.org Subject: [PATCH net-next v3 1/5] dt-bindings: leds: Add IS32FL3207 controller Date: Mon, 10 Aug 2026 22:02:40 -0700 Message-ID: <8088c3333f464545297a642816316a090dcd3099.1786423898.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" 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 Reviewed-by: Krzysztof Kozlowski --- .../bindings/leds/issi,is32fl3207.yaml | 251 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 257 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 000000000000..4ee9db964ecc --- /dev/null +++ b/Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml @@ -0,0 +1,251 @@ +# 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. PWM + brightness control uses the controller's default 8-bit, 62 kHz mode. + 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: | + Zero-based LED output number. Values 0 through 17 correspond to + OUT1 through OUT18. + + led-max-microamp: + minimum: 1 + maximum: 38250 + + color: + oneOf: + - minimum: 0 + maximum: 7 + - minimum: 10 + maximum: 14 + + max-brightness: + minimum: 1 + maximum: 255 + + default-state: + description: + The controller is reset during initialization, so an existing st= ate + cannot be preserved. + enum: ["on", "off"] + + default-brightness: + maximum: 255 + description: | + Brightness used when default-state is "on". This value must not + exceed max-brightness when max-brightness is specified. + + anyOf: + - required: + - function + - required: + - color + + required: + - reg + - led-max-microamp + + "^multi-led@[0-9a-f]$": + type: object + $ref: leds-class-multicolor.yaml# + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 15 + description: | + Zero-based lowest-numbered LED output used by the group. Values 0 + through 15 correspond to OUT1 through OUT16. A group based at + channel 15 (OUT16) may include channels 16 and 17 (OUT17 and OUT= 18). + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + max-brightness: + minimum: 1 + maximum: 255 + + default-state: + description: + The controller is reset during initialization, so an existing st= ate + cannot be preserved. + enum: ["on", "off"] + + default-brightness: + maximum: 255 + description: | + Brightness used when default-state is "on". This value must not + exceed max-brightness when max-brightness is specified. + + patternProperties: + "^led@[0-9a-f]+$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 17 + description: | + Zero-based LED output number. Values 0 through 17 correspond= to + OUT1 through OUT18. + + led-max-microamp: + minimum: 1 + maximum: 38250 + + color: + oneOf: + - minimum: 0 + maximum: 7 + - minimum: 10 + maximum: 14 + + max-brightness: false + + default-state: false + + default-brightness: false + + retain-state-shutdown: false + + required: + - reg + - color + - led-max-microamp + + anyOf: + - required: [led@0] + - required: [led@1] + - required: [led@2] + - required: [led@3] + - required: [led@4] + - required: [led@5] + - required: [led@6] + - required: [led@7] + - required: [led@8] + - required: [led@9] + - required: [led@a] + - required: [led@b] + - required: [led@c] + - required: [led@d] + - required: [led@e] + - required: [led@f] + + 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 a82b6ec9c567..85e19b1c46cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13822,6 +13822,12 @@ S: Maintained F: Documentation/filesystems/isofs.rst F: fs/isofs/ =20 +ISSI IS32FL3207 LED DRIVER +M: Ahmad Byagowi +L: linux-leds@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml + IT87 HARDWARE MONITORING DRIVER M: Jean Delvare L: linux-hwmon@vger.kernel.org --=20 2.50.1 (Apple Git-155) From nobody Tue Sep 29 07:39:05 2026 Received: from mail-pg1-f178.google.com (mail-pg1-f178.google.com [209.85.215.178]) (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 CBB3131B83B for ; Tue, 11 Aug 2026 05:02:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786424574; cv=none; b=QqP5/kiz5Qfk8/8POWm7JduwNpvcnm5TNSnMiH87qvMY3qYKVJwW/hpOpmAWgM2J8J7CVdY3XyI71bqZvsvvjCKy3ZjIKhuGH4/vZEMPfubrJ+3rOLQqMy+6R/7uhV9vDArkg+7O/zeNDE23aBfvPPiu4y+0RpQ+1+Zz0OJq4bg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786424574; c=relaxed/simple; bh=q7ufGzThYvQ+QPvF3VWLyr4ute/KEp4pqfm33v9nOyI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=WN6mD4TmShG/+AxHWIvg09BhxqpNIB1tP31R0uxju45YpD1y5HVc0rfQc88xN0Moq9qrRA/Xx9BdiTbXIfW+MNCP2YP7kgplQPlzrKw4ywdYtCNFOoeNWmpJGcnbh4Ldkr1P9BaJHzHaH95CcTN1+aa7QlokcokcCPB19j2tYUs= 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=moIMNzDK; arc=none smtp.client-ip=209.85.215.178 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="moIMNzDK" Received: by mail-pg1-f178.google.com with SMTP id 41be03b00d2f7-cbe3fed2f58so2087941a12.3 for ; Mon, 10 Aug 2026 22:02:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786424570; x=1787029370; 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=zgZlVi6KLblhgttMabvW+gQZSSCrezqH9MroCV+FRE4=; b=moIMNzDKx7XeULXFyEb7/62ECuBcFyKiresfn5pUWhU+kzbD18R5+2fLPQp7U1tu8d tXMnGq5WbqhPgyh7qHsFHgYmhxfpLdNLZIKp6TmYrSL18nSPcmcP9RO6BHPUWU0icZ6l 2fi8Q6yJxcM5aD9u5jZIy97DOuH61ZwFqLAMm5u82iIIdtvZyNEPg3LNqgk/n86Sdu79 mU1Cbtw+Al1gM7k4pFJtB7RQA2u59xj4dN96KgrASE2G/rmmessp895FKPpKcoT+Mf1J Ri6ByZyCvViER+kGJZ3zTE5FNDqS7ZbBEmebLT3K72BSZDx/M9pdCkXFvTwyoSgJLCPN JZBg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786424570; x=1787029370; 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=zgZlVi6KLblhgttMabvW+gQZSSCrezqH9MroCV+FRE4=; b=h61ZsKk2jx+As+viLzWwBqd65ahLgYoAb3GG6ormWAgrVL1AET1YPnah4j0srqBPqk Vw0F/TfeqCjbtBcTCE3IKDmlvc0/35EQm5gP3Do9HqC1+nFG0gEj9pfSEgt2bFmsN7rp ihImrvc0Bc/i6KM1rpc4mR9Mu0N1NJdsTmBYudM+fpQJmjQiR97m6gGCieIl7LF4rbYh kR4MJDA6gWniwW4WIWcMQUg0ifH3iz6xLY3T1ZyrIv0x7ooOF/agIHXzDT5MvsMo51wm Z/cCRvMVD06UTR6S/BEB5AIptW0ehbnkp8Unjq1vZYsEcp/4E+WT3C6fL6SRvzwSWd1X nvhA== X-Forwarded-Encrypted: i=1; AHgh+Rpp9vprX7WIt5rpuVt7A2eCzn6k+0/TkvjWgleXEun6YjfyWviVYYF3QA4MynpBLWagZ4byLr9eW+1H+Ro=@vger.kernel.org X-Gm-Message-State: AOJu0YxRoerMeaSO0s4lBHQrb8NeSXPKgJjShprPQj7pvQbXtG3w5yLx hiZdUrJpEf+bYjoy6vPvOV2InJh27rJNHOUjyb8K8aV3mNyixtNPkxlL X-Gm-Gg: AR+sD13T0Jh++YawhRPZeQMGjblQ2nHdzE0dWR6z6AskfCmPkyDUqLUk0QNhE7IXAxd 672wGCbeIZ4Jsw3LFfNT0eWQUUHxqlYH5WanPtrJHhyKcttEMRpCVohmxcqMA6ap9uMzl2yUiog 2c9jD/kW5dl2zLmM6hVW4/e6Vb9cS2JLoiUSD74uFYOoJOZ/oRWWdmGoQYYyqbUqIrmxf43I6Ao bf5+kQZm++jVYRy5F24xO4zuAoX/pBQxnrya5PUMRGGAlNfchih1VD0tZEp8Pu4Mn4JDSaQC8zI TpW4RriFIrLPuga282qn2WV4wsNr6jdRH+9LgN1uqZfw6YuFTtDDyiF5OU4mkxe7ZbXIhXAlP4l mPsKARRrpGPwm0XEc+a43BR7A5gjIO2jC4SOPl9tRAXlTr7JbMoBtC7IPBws4vgCoicTIP+cYD/ yMhnjJDqDte3MuJF+61deLGJ1G4qG4LUaMhMiqzNy4o5N9i0rs0+7VxV3+AdcXIRjv3EPia8HFW qfw4LU9kSvWqtKb4pIgjdN13LFUurz/D7abCUQAOGIiW/v4og== X-Received: by 2002:a05:6a20:4325:b0:3c4:46ca:3347 with SMTP id adf61e73a8af0-3cc2b77a753mr1092126637.2.1786424570055; Mon, 10 Aug 2026 22:02:50 -0700 (PDT) Received: from Mac.localdomain (198-27-228-154.fiber.dynamic.sonic.net. [198.27.228.154]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-31c619cf07dsm660421eec.4.2026.08.10.22.02.48 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Mon, 10 Aug 2026 22:02:49 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Nam Tran , Kees Cook , "Gustavo A. R. Silva" , Peter Rosin , Andi Shyti , Richard Cochran , Vadim Fedorenko , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hardening@vger.kernel.org Subject: [PATCH net-next v3 2/5] leds: is32fl3207: Add controller driver Date: Mon, 10 Aug 2026 22:02:41 -0700 Message-ID: <0d99d91ced249e4eae7d06fc132a96f544f46598.1786423898.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 an 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. Serialize scaling, PWM, and controller-wide update operations. Keep the controller shut down while clearing retained current settings, resetting the device, and registering every LED. Enable outputs only after all channel limits and initial brightness values are programmed. Handle an optional supply and enable GPIO, and honor retained shutdown state. Signed-off-by: Ahmad Byagowi --- MAINTAINERS | 1 + drivers/leds/rgb/Kconfig | 11 + drivers/leds/rgb/Makefile | 1 + drivers/leds/rgb/leds-is32fl3207.c | 687 +++++++++++++++++++++++++++++ 4 files changed, 700 insertions(+) create mode 100644 drivers/leds/rgb/leds-is32fl3207.c diff --git a/MAINTAINERS b/MAINTAINERS index 85e19b1c46cf..ce4132a754d7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13827,6 +13827,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 IT87 HARDWARE MONITORING DRIVER M: Jean Delvare diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig index 6e9ab5f60714..c896be1318dc 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 cc0f2df66286..228923e8bb11 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 000000000000..c4e4a114a16e --- /dev/null +++ b/drivers/leds/rgb/leds-is32fl3207.c @@ -0,0 +1,687 @@ +// 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; + struct led_classdev *led_cdev; + 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, + unsigned int *brightness) +{ + enum led_default_state default_state; + u32 value; + int ret; + + *max_brightness =3D IS32FL3207_MAX_BRIGHTNESS; + if (fwnode_property_present(fwnode, "max-brightness")) { + 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; + } + + value =3D *max_brightness; + if (fwnode_property_present(fwnode, "default-brightness")) { + ret =3D fwnode_property_read_u32(fwnode, "default-brightness", + &value); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read default brightness for %pfw\n", + fwnode); + if (value > *max_brightness) + return dev_err_probe(chip->dev, -EINVAL, + "invalid default brightness %u for %pfw\n", + value, fwnode); + } + + default_state =3D led_init_default_state_get(fwnode); + if (default_state =3D=3D LEDS_DEFSTATE_KEEP) + return dev_err_probe(chip->dev, -EINVAL, + "default state keep is not supported for %pfw\n", + fwnode); + + *brightness =3D default_state =3D=3D LEDS_DEFSTATE_ON ? value : LED_OFF; + 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); + + guard(mutex)(&chip->lock); + 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; + unsigned int brightness; + u32 color; + int ret; + + if (!fwnode_property_present(fwnode, "function") && + !fwnode_property_present(fwnode, "color")) + return dev_err_probe(chip->dev, -EINVAL, + "single LED %pfw requires function or color\n", + fwnode); + + ret =3D is32fl3207_parse_led_properties(chip, fwnode, + &max_brightness, &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->led_cdev =3D &led->cdev; + led->cdev.brightness =3D brightness; + led->cdev.max_brightness =3D max_brightness; + led->cdev.brightness_set_blocking =3D is32fl3207_brightness_set; + + ret =3D is32fl3207_brightness_set(&led->cdev, brightness); + if (ret) + return ret; + + 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 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, &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->led_cdev =3D &led->mcdev.led_cdev; + led->mcdev.num_colors =3D count; + led->mcdev.subled_info =3D subleds; + led->mcdev.led_cdev.brightness =3D brightness; + led->mcdev.led_cdev.max_brightness =3D max_brightness; + led->mcdev.led_cdev.brightness_set_blocking =3D + is32fl3207_mc_brightness_set; + + led_mc_calc_color_components(&led->mcdev, brightness); + + ret =3D is32fl3207_write_channels(chip, subleds, count); + if (ret) + return ret; + + return devm_led_classdev_multicolor_register_ext(chip->dev, &led->mcdev, + &init_data); +} + +static int is32fl3207_register_led(struct is32fl3207 *chip, + struct fwnode_handle *fwnode, + struct is32fl3207_led *led) +{ + unsigned int count =3D fwnode_get_child_node_count(fwnode); + bool has_color =3D fwnode_property_present(fwnode, "color"); + u32 color =3D LED_COLOR_ID_MAX; + int ret; + + if (has_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=3D LED_COLOR_ID_RGB || color =3D=3D LED_COLOR_ID_MULTI) { + if (!count) + return dev_err_probe(chip->dev, -EINVAL, + "multicolor LED %pfw has no components\n", + fwnode); + + return is32fl3207_register_multicolor(chip, fwnode, led); + } + + if (count) + return dev_err_probe(chip->dev, -EINVAL, + "single LED %pfw must not have components\n", + fwnode); + + return is32fl3207_register_single(chip, fwnode, led); +} + +static int is32fl3207_prepare_reset(struct is32fl3207 *chip) +{ + u8 scaling[IS32FL3207_NUM_CHANNELS] =3D { }; + int ret; + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, 0); + if (ret) + return ret; + + return regmap_bulk_write(chip->regmap, + IS32FL3207_REG_SCALING(0), scaling, + sizeof(scaling)); +} + +static int is32fl3207_hw_init(struct is32fl3207 *chip) +{ + u8 scaling[IS32FL3207_NUM_CHANNELS] =3D { }; + u8 pwm[2 * IS32FL3207_NUM_CHANNELS] =3D { }; + int disable_ret; + int ret; + + /* Reset requires normal operation (SSD =3D 1). */ + 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, 0); + if (ret) + return ret; + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_GLOBAL_CURRENT, + IS32FL3207_GLOBAL_CURRENT_MAX); + if (ret) + return ret; + + 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; + + /* PWM data can be latched only in normal operation. */ + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, + IS32FL3207_CONTROL_ENABLE); + if (ret) + return ret; + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_PWM_UPDATE, 0); + disable_ret =3D regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, 0); + + return ret ?: disable_ret; +} + +static int is32fl3207_enable(struct is32fl3207 *chip) +{ + int ret; + + guard(mutex)(&chip->lock); + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, + IS32FL3207_CONTROL_ENABLE); + if (ret) + return ret; + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_PWM_UPDATE, 0); + if (ret) + regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, 0); + + return ret; +} + +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 *ddata; + 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); + + ddata =3D devm_kzalloc(dev, struct_size(ddata, leds, count), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + ddata->dev =3D dev; + ddata->num_leds =3D count; + i2c_set_clientdata(client, ddata); + + 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); + + ddata->output_max_microamp =3D div_u64(IS32FL3207_CURRENT_NUMERATOR, + riset_ohms); + if (!ddata->output_max_microamp) + return dev_err_probe(dev, -EINVAL, + "RISET value %u is too large\n", + riset_ohms); + + ddata->enable_gpio =3D devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(ddata->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(ddata->enable_gpio), + "failed to get enable GPIO\n"); + + ddata->regmap =3D devm_regmap_init_i2c(client, + &is32fl3207_regmap_config); + if (IS_ERR(ddata->regmap)) + return dev_err_probe(dev, PTR_ERR(ddata->regmap), + "failed to allocate register map\n"); + + ret =3D devm_mutex_init(dev, &ddata->lock); + if (ret) + return ret; + + 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"); + + ret =3D devm_add_action_or_reset(dev, is32fl3207_disable, ddata); + if (ret) + return ret; + + /* Let VCC settle while SDB keeps the outputs disabled. */ + usleep_range(1000, 2000); + + ret =3D is32fl3207_prepare_reset(ddata); + if (ret) + return dev_err_probe(dev, ret, + "failed to prepare controller reset\n"); + + if (ddata->enable_gpio) + gpiod_set_value_cansleep(ddata->enable_gpio, 1); + + /* Allow the SDB release and I2C interface reset to settle. */ + usleep_range(1000, 2000); + + ret =3D is32fl3207_hw_init(ddata); + 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 &ddata->leds[i]; + + ret =3D is32fl3207_register_led(ddata, child, led); + if (ret) + return ret; + + i++; + } + + ret =3D is32fl3207_enable(ddata); + if (ret) + return dev_err_probe(dev, ret, "failed to enable controller\n"); + + return 0; +} + +static void is32fl3207_shutdown(struct i2c_client *client) +{ + struct is32fl3207 *chip =3D i2c_get_clientdata(client); + bool retain_state =3D false; + unsigned int i; + + for (i =3D 0; i < chip->num_leds; i++) + if (chip->leds[i].led_cdev->flags & LED_RETAIN_AT_SHUTDOWN) { + retain_state =3D true; + break; + } + + if (!retain_state) { + is32fl3207_disable(chip); + return; + } + + for (i =3D 0; i < chip->num_leds; i++) { + struct led_classdev *cdev =3D chip->leds[i].led_cdev; + + if (cdev->flags & LED_RETAIN_AT_SHUTDOWN) + continue; + if (cdev->flags & LED_MULTI_COLOR) + is32fl3207_mc_brightness_set(cdev, LED_OFF); + else + is32fl3207_brightness_set(cdev, LED_OFF); + } +} + +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 { + { .name =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 Tue Sep 29 07:39:05 2026 Received: from mail-pj1-f45.google.com (mail-pj1-f45.google.com [209.85.216.45]) (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 3FB0631AF07 for ; Tue, 11 Aug 2026 05:02:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786424574; cv=none; b=O0esLOPHLCPYBTIR3oytmo/9LCYg/DvAk25d4IPHP4A5NIKFZNcOedxuJVHF6XFYPW9xbEB5japgpt2SN4O8XQF1yfh9rYQm19PJqbdyhtnnR5/BnA2NeDhPBzJLuXXiC8imU8fcP0mVmn+O8YiJ4r92enKv5v8C0W/p8Re7S1E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786424574; c=relaxed/simple; bh=umZL5fn54uXO5itW8ulnK8HBtP6zF1pdNSWeGLlPGMw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=XczZXdqLEX6hDx59Zts8Pdzt2t6QzwDKQ37ii5GVRBECpbbc2l18R3F883ItJJCNh+Twbg13crJxIpm11HYzcJVWTxIAmB1+EdWxY342jXMEFo2BD7LFOkVlMIrUHkPaLpamzWshXC28PRnrW8LMQQHhpnU57Pqe9Ghp35XvvqU= 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=oGdUgFkQ; arc=none smtp.client-ip=209.85.216.45 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="oGdUgFkQ" Received: by mail-pj1-f45.google.com with SMTP id 98e67ed59e1d1-383cb94f742so2884530a91.3 for ; Mon, 10 Aug 2026 22:02:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786424572; x=1787029372; 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=Unu0bdi+4TO6yVD3obvp/HhqPNGfRa0ua+IlLQHYNqs=; b=oGdUgFkQAt2/jUvrXsDKMRk3l+XzskqVkBRDpeHUntjcfw4feSMKgsdpJvuhFUjMow aELEz5QedJlMe8VW1lIW0KGarfhsIFLByZBYQA0R4T0yjAniSbjlamQvyx6U/AZZp38w qtZCAi8x5DsT/rP0OPfWRyzCQ9Jr1FANeHbG7DBUmazzfWtqHz0MzFgPg1qBwj/smKPG NB2L2KGu1lUQ8GiQ4TADRZF03GOW6Qa4qswW3aytR97oycAK6V3jtGhTwOdce1bz6mQh Ecy7MdMGR20vuqHjXro3WEXRKqYtz0zP+KpgZpJ7QsAmURscD/3gs39aED+zFtt3w0gj cD0A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786424572; x=1787029372; 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=Unu0bdi+4TO6yVD3obvp/HhqPNGfRa0ua+IlLQHYNqs=; b=CqPZSqgw7wHhxUHmnSdwwUBT5H2ep6FgLzvsid9Zs2fId0sW5tvWz/lFd/KNtw1cuJ twgnc7Cz2KUHviz7w/ZL0kY3m86agPng/owbEk3U15fFha/ePwBt+br993RQEVOU4rQH RpdaeJul4dVTTYkfS4jFJcrAZZt0DRNhaKf41h+kUWpxZi5f+/uBfTEQcN5PE3rZdQJ9 sAcu8TQSq/NvF5jR8B9DVK+VT3JsH0yPylunpw8vhm7mqEIk6wjwl3WQ7wf6EaBUPIRM RcOEaC8eMt1ns5GbvWuk6pUfnWUG1G5E/XVAPPZeDy8fciuYOEyAXSahTEv7l98dpjMm YWRg== X-Forwarded-Encrypted: i=1; AHgh+RofdMeXaijrPSlzKRzJCx9Zk6DCSvIl4ao4No1fgDNgLswFeMqeJBM5QvfrX4De3tnxU4xKfKFJEMdwSo8=@vger.kernel.org X-Gm-Message-State: AOJu0YxKxhxLB4BRP9YhPb/IjA1d4x/+ZjgSxiMk4Akd+uY97MM+BpQt 0yKXvaQx2K3bJ2H9okiLYgtZ4CNpRiEXLcIEyhQ7OaFaWJOyVOTzril0 X-Gm-Gg: AR+sD10J+Qn9iR2VO/dKdj9kgR4VRM4sw3xheoqBjC7dJ157xL796zOQhuZfUerrowA lHo9K/gyRUNMG15Gk1K1qj//mJjBNaptaVP2dQQSkLkNVPeO6toIuE4ZnqCWTOexOyNC3Tf9MbZ +eQKP9IZhuCQmc4qFbNCatY3ftV8pPKVREmd4V+KryLOevZhzloG9w9rOYON5AhlIii2puDV15J zI0AC4leoPAuTvwG7k0YFcLz4k5MNDkq/J82PPIE2S7r5BxuSrxFTHvki8WA7LvzRrchFSSEhB7 /0oBVfbTXpUgiWWTRvTHgL/a/hquAhefvCRi2SvBgOhxJ3jdOe2ikHSEHbK2veo3ZnHNqACZL4A JBAmR8XaBNB075EpeeqyiH0u2K8D+/OXQjBdDkOrE4arwEUOmDWV5DFd0XXTeeFCK89GCavcaEA qxQ5tHEovivqyO9y7KOkDvl4kEKxCQ77Fq9Rw48C7ktnrRS3mzQtJQ9On9gjHkzjwJGOFrXDisV xn7Ok4cotrxrQgAQ68yp96xCD2cFFebQ3IWH8o/VGTiH8crXw== X-Received: by 2002:a17:90b:3b42:b0:37f:f8c5:93c2 with SMTP id 98e67ed59e1d1-392ec6e960emr616624a91.21.1786424571561; Mon, 10 Aug 2026 22:02:51 -0700 (PDT) Received: from Mac.localdomain (198-27-228-154.fiber.dynamic.sonic.net. [198.27.228.154]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-31c619cf07dsm660421eec.4.2026.08.10.22.02.50 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Mon, 10 Aug 2026 22:02:51 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Nam Tran , Kees Cook , "Gustavo A. R. Silva" , Peter Rosin , Andi Shyti , Richard Cochran , Vadim Fedorenko , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hardening@vger.kernel.org Subject: [PATCH net-next v3 3/5] i2c: mux: Propagate software nodes to channel adapters Date: Mon, 10 Aug 2026 22:02:42 -0700 Message-ID: <144d1e75321e084642f10086e554cccf51fa98ac.1786423898.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 | 69 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c index 681a201c239b..8b54bb13c613 100644 --- a/drivers/i2c/i2c-mux.c +++ b/drivers/i2c/i2c-mux.c @@ -20,11 +20,13 @@ */ =20 #include +#include #include #include #include #include #include +#include #include #include =20 @@ -264,6 +266,55 @@ 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; + + /* A software node supplementing ACPI is the secondary fwnode. */ + if (!is_software_node(dev_node)) { + if (IS_ERR_OR_NULL(dev_node)) + return NULL; + dev_node =3D dev_node->secondary; + } + 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) { @@ -324,8 +375,8 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, priv->adap.lock_ops =3D &i2c_parent_lock_ops; =20 /* - * Try to populate the mux adapter's of_node, expands to - * nothing if !CONFIG_OF. + * Associate the mux adapter with its DT or software-node channel. + * DT support expands to nothing if !CONFIG_OF. */ if (muxc->dev->of_node) { struct device_node *dev_node =3D muxc->dev->of_node; @@ -364,6 +415,18 @@ 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; + const struct software_node *swnode; + + fwnode =3D i2c_mux_get_channel_swnode(muxc, chan_id); + if (fwnode) { + swnode =3D to_software_node(fwnode); + ret =3D device_add_software_node(&priv->adap.dev, swnode); + fwnode_handle_put(fwnode); + if (ret) + goto err_free_priv; + } } =20 /* @@ -408,6 +471,7 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, return 0; =20 err_free_priv: + device_remove_software_node(&priv->adap.dev); kfree(priv); return ret; } @@ -429,6 +493,7 @@ void i2c_mux_del_adapters(struct i2c_mux_core *muxc) sysfs_remove_link(&muxc->dev->kobj, symlink_name); =20 sysfs_remove_link(&priv->adap.dev.kobj, "mux_device"); + device_remove_software_node(&priv->adap.dev); i2c_del_adapter(adap); of_node_put(np); kfree(priv); --=20 2.50.1 (Apple Git-155) From nobody Tue Sep 29 07:39:05 2026 Received: from mail-pl1-f176.google.com (mail-pl1-f176.google.com [209.85.214.176]) (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 909BA29A31C for ; Tue, 11 Aug 2026 05:02:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786424577; cv=none; b=lY+XdP63kX4al3IJG6lu8OJRBOofbpmyh9o54RUmT8I0NtnMwGhmygCBO7POPz35Fz8aKyHp10WAIel1StjfRZQ+8zbCwq8ZTq78Jow4UtoNmbMkbxi/1LYcLde/GqrpvO/hh67CbBqA1IlcdeHAK3YyPxU0+jrq8QMPbJrTupo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786424577; c=relaxed/simple; bh=qbT29I2CzZWeVZnhnEyPFxOtc1aecRPQo1/N7wBb2Hs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=shvNRrnn4bC4QKcBvFAewyXsJ8ZRWybJeCIW+KsatKwx0609Rzfqc1GkgGigskwaeeWJyhAVFexA5IZwN7ukykAdYVrth2o9xTvfr5JiWKJR5fRz+yh5iR4dgvmEeVxzQiS0NtUmjYGjeuSxsmAtTkMuhaXFpnePaYsVcFD7Dqs= 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=Ke+W3VeU; arc=none smtp.client-ip=209.85.214.176 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="Ke+W3VeU" Received: by mail-pl1-f176.google.com with SMTP id d9443c01a7336-2cea3004256so33237365ad.0 for ; Mon, 10 Aug 2026 22:02:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786424573; x=1787029373; 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=moijbzDzsmh5XF7idfs/D6eyKTrAZoDD9bA5sLCOiyM=; b=Ke+W3VeUDMnHFwQ0IjJMs8Pcm+N6ijyu6ebMPHQ3X7QD/b6bwvxN+Zv/gSKT5pwV0k /f5NL+gR36nIr7d/FR5bKaZhpom1V3k31Z5w0z0L4khWPHeyVr/4H3tRCLcrPzB+0wKu fC7jViwX1cUvTAt6t2jF+OQ+aNvhEgVhR+Ss7U88f5V+a259HuXy73aTeL7yt54pkzp6 ICcUseBIgenV3tT+YCloFUWoG2PxpNU+fYCS2aFRC5e6kbOBMDgqBlBI3O4FSh4o1AdE 332FCPvbQKAI5SvAFR6EYiMkvcva75czWj02MqE70Ic9mRsoGER3+aPVKfMb9SlV4q73 6Xew== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786424573; x=1787029373; 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=moijbzDzsmh5XF7idfs/D6eyKTrAZoDD9bA5sLCOiyM=; b=eGNY3Tg0sPRetBmnl1zZNsBV/YqRutxdgLpa3Bxv4sdYYJK+nBN4jSz8cMDmWXCa98 CUjWOlPYHEABijv3w/6NJ0y/YEOKaodw/8yoEDuxHkfpHFvNV2swX12HBkiUXwviYSN9 6MpLCyVTiOgcW7w3wXvYfmsgpnPRG7dWbf4fW5f/F7tkBJgAQR/cke8OaiJHP4GPuWHA y1Jc4PsYO8cZBsBK9+NLOpDCo6jyWf3nG5NT5lXa+i9+Xnt27BGd4RxUrqwrpPi2CT2U gVMd05M8Dfdsr8FujCMqY2bEmK11kFLJaFkmNjrR573CqAjRgXE7Id541ovk27E6sQ/6 613A== X-Forwarded-Encrypted: i=1; AHgh+Rp6UzowFwanHKAuYK9n+nnGtHAKBUx7cVQUlySvQ4aIRxHK9n0BCj26jGF0U8ryx8jfqM5sXhf6oWO9660=@vger.kernel.org X-Gm-Message-State: AOJu0Ywo0u2ChoTdkg4QxLU1d75Oh0dGCktKu8CAZDvwOw6nH33GHd9Z FEAlDRPs+8DCpQDpgseFfDQrQaSbK0RadFDYUM1i10Lk6krYrhcrMBPY X-Gm-Gg: AR+sD13DmpNFYcJgsVkKUVGB6fTPgF23eMKYIk08O2CqkNmBU1uWZBCBFuzmuVLx9A/ EbwB8U42OMhlIDWoqEZpMLPzZIPUF8ClzJkAS22iE2pmwME7yyb0M32j1YSudzCRv99LmzY5s4u pwUThqiz8Zih0xr3f3NSWTERC3fn11tMyYcAKSnIe8i4Eh+3xeyS/utPIaNzACum9jkWCWfdVky qn/E5fjFxL4p+x+6H4Jggrjtw1dtbtyebpn6q8tb1bzfo74gpmwwf+uQZnJFsnckvD20QePP4Zp 7nIbf6U/nLty0ruoc3fB1vlmicp9sQBy5mLVxT0RV0kx0+pD18pnI525cyCn9Sgajx4aaG5UNSS r5CLXlggSpBUOf5sf1uBClG4JHA74fwCWCsElPej+OksEQ+MEoPZJF6k9RdVYpSyxGyAbS+eH8U jt/yoyX2Q5bbLqNnBPhFcSi+J20TE8oq7U1l9TZk6V406Klxwm0tF3ojc1TN7y3wMT2oGl9zFwF sDW9jfbsOvulKI/S3vjBNeBTzxsNN+Z/YS+DIue3awBVn9eQA== X-Received: by 2002:a17:903:26cb:b0:2c9:e9db:8167 with SMTP id d9443c01a7336-2d3177fb273mr9852895ad.7.1786424573298; Mon, 10 Aug 2026 22:02:53 -0700 (PDT) Received: from Mac.localdomain (198-27-228-154.fiber.dynamic.sonic.net. [198.27.228.154]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-31c619cf07dsm660421eec.4.2026.08.10.22.02.51 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Mon, 10 Aug 2026 22:02:52 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Nam Tran , Kees Cook , "Gustavo A. R. Silva" , Peter Rosin , Andi Shyti , Richard Cochran , Vadim Fedorenko , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hardening@vger.kernel.org Subject: [PATCH net-next v3 4/5] ptp: ocp: Add R4006 I2C peripheral topology Date: Mon, 10 Aug 2026 22:02:43 -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" 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. Describe the per-card topology with software nodes and instantiate standard I2C clients after their adapters appear. Keep the mux channel, RISET value, and per-output current limit in the board profile, and validate every profile index before constructing nodes. Select the profile only on supported Time Card PCI devices whose fixed-width EEPROM ID contains printable text, valid zero or 0xff padding, and an R4006 prefix. Leave erased, malformed, and unknown IDs unconfigured without changing the EEPROM data. Leave the channel containing the BNO08x empty because no upstream driver exists. Serialize topology changes with a private mutex and stable device references. Retry transient setup failures, report exhaustion once, and continue low-rate recovery so late adapter or client availability can still complete setup. Signed-off-by: Ahmad Byagowi --- drivers/ptp/ptp_ocp.c | 810 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 764 insertions(+), 46 deletions(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 3d26ec1f7b9e..f2c2979daaf1 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,70 @@ struct ptp_ocp_serial_port { #define OCP_SIGNAL_NUM 4 #define OCP_FREQ_NUM 4 =20 +#define OCP_I2C_MUX_CHANNELS 4 +#define OCP_I2C_MAX_SENSOR_COUNT 5 +#define OCP_I2C_MAX_LED_COUNT 5 +#define OCP_I2C_MAX_LED_COMPONENT_COUNT (3 * OCP_I2C_MAX_LED_COUNT) +#define OCP_I2C_MUX_NAME_LEN 32 +#define OCP_I2C_MUX_COMPATIBLE "nxp,pca9546" +#define OCP_I2C_MUX_TYPE "pca9546" +#define OCP_I2C_MUX_ADDRESS 0x70 +#define OCP_I2C_RETRY_MAX 10 +#define OCP_I2C_RECOVERY_SECS 30 +#define OCP_I2C_MAX_NODE_COUNT (1 + OCP_I2C_MUX_CHANNELS + \ + OCP_I2C_MAX_SENSOR_COUNT + 1 + \ + OCP_I2C_MAX_LED_COUNT + \ + OCP_I2C_MAX_LED_COMPONENT_COUNT) + +struct ptp_ocp_i2c_device { + const char *node_name; + const char *compatible; + const char *type; + u8 channel; + u8 address; +}; + +struct ptp_ocp_led { + const char *node_name; + const char *function; + u8 function_enumerator; + bool has_function_enumerator; + u8 channel[3]; +}; + +struct ptp_ocp_i2c_profile { + const char *name; + const struct ptp_ocp_i2c_device *sensors; + unsigned int sensor_count; + const struct ptp_ocp_led *leds; + unsigned int led_count; + const char *led_node_name; + u8 led_mux_channel; + u8 led_address; + u32 led_riset_ohms; + u32 led_max_microamp; +}; + +struct ptp_ocp_i2c_topology { + char mux_name[OCP_I2C_MUX_NAME_LEN]; + struct software_node mux_node; + struct property_entry mux_props[6]; + struct software_node channel_nodes[OCP_I2C_MUX_CHANNELS]; + struct property_entry channel_props[OCP_I2C_MUX_CHANNELS][4]; + struct software_node sensor_nodes[OCP_I2C_MAX_SENSOR_COUNT]; + struct property_entry sensor_props[OCP_I2C_MAX_SENSOR_COUNT][3]; + struct software_node led_node; + struct property_entry led_props[6]; + struct software_node led_nodes[OCP_I2C_MAX_LED_COUNT]; + struct property_entry led_group_props[OCP_I2C_MAX_LED_COUNT][7]; + struct software_node component_nodes[OCP_I2C_MAX_LED_COMPONENT_COUNT]; + struct property_entry + component_props[OCP_I2C_MAX_LED_COMPONENT_COUNT][4]; + const struct software_node *node_group[OCP_I2C_MAX_NODE_COUNT + 1]; + const struct ptp_ocp_i2c_profile *profile; + bool registered; +}; + enum { PORT_GNSS, PORT_GNSS2, @@ -403,6 +471,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 +483,15 @@ struct ptp_ocp { const struct ocp_sma_op *sma_op; struct dpll_device *dpll; dpll_tracker tracker; + const struct ptp_ocp_i2c_profile *i2c_profile; + struct ptp_ocp_i2c_topology *i2c_topology; + struct mutex i2c_topology_lock; /* Serializes topology updates. */ + struct delayed_work i2c_work; + struct notifier_block i2c_notifier; + atomic_t i2c_retry_count; + bool i2c_root_present; + bool i2c_resources_ready; + bool i2c_notifier_registered; int signals_nr; int freq_in_nr; }; @@ -444,6 +522,8 @@ static int ptp_ocp_signal_from_perout(struct ptp_ocp *b= p, int gen, struct ptp_perout_request *req); static int ptp_ocp_signal_enable(void *priv, u32 req, bool enable); static int ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_= nr); +static int ptp_ocp_i2c_notifier_call(struct notifier_block *nb, + unsigned long action, void *data); =20 static int ptp_ocp_art_board_init(struct ptp_ocp *bp, struct ocp_resource = *r); =20 @@ -488,6 +568,68 @@ static struct ptp_ocp_eeprom_map art_eeprom_map[] =3D { { } }; =20 +/* Channel 3's BNO08x at 0x4a has no upstream Linux driver, so omit it. */ +static const struct ptp_ocp_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", NULL, "sht3x", 1, 0x44 }, + { "pressure@63", "invensense,icp10100", "icp10100", 2, 0x63 }, +}; + +static const struct ptp_ocp_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_sensors) <=3D + OCP_I2C_MAX_SENSOR_COUNT); +static_assert(ARRAY_SIZE(ptp_ocp_r4006_leds) <=3D OCP_I2C_MAX_LED_COUNT); + +static const struct ptp_ocp_i2c_profile ptp_ocp_r4006_profile =3D { + .name =3D "r4006", + .sensors =3D ptp_ocp_r4006_sensors, + .sensor_count =3D ARRAY_SIZE(ptp_ocp_r4006_sensors), + .leds =3D ptp_ocp_r4006_leds, + .led_count =3D ARRAY_SIZE(ptp_ocp_r4006_leds), + .led_node_name =3D "led-controller@34", + .led_mux_channel =3D 1, + .led_address =3D 0x34, + .led_riset_ohms =3D 4700, + .led_max_microamp =3D 8150, +}; + #define bp_assign_entry(bp, res, val) ({ \ uintptr_t addr =3D (uintptr_t)(bp) + (res)->bp_offset; \ *(typeof(val) *)addr =3D val; \ @@ -1969,19 +2111,29 @@ ptp_ocp_nvmem_device_put(struct nvmem_device **nvme= mp) *nvmemp =3D NULL; } =20 -static void +static bool +ptp_ocp_has_eeprom_data(struct ptp_ocp *bp) +{ + return smp_load_acquire(&bp->has_eeprom_data); +} + +static int ptp_ocp_read_eeprom(struct ptp_ocp *bp) { const struct ptp_ocp_eeprom_map *map; struct nvmem_device *nvmem; const void *tag; - int ret; - - if (!bp->i2c_ctrl) - return; + int ret =3D 0; =20 tag =3D NULL; nvmem =3D NULL; + mutex_lock(&bp->eeprom_lock); + if (ptp_ocp_has_eeprom_data(bp)) + goto out; + if (!bp->i2c_ctrl || !bp->eeprom_map) { + ret =3D -ENODEV; + goto out; + } =20 for (map =3D bp->eeprom_map; map->len; map++) { if (map->tag !=3D tag) { @@ -1997,21 +2149,536 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp) } ret =3D nvmem_device_read(nvmem, map->off, map->len, BP_MAP_ENTRY_ADDR(bp, map)); - if (ret !=3D map->len) + if (ret !=3D map->len) { + if (ret >=3D 0) + ret =3D -EIO; 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); + ret =3D 0; =20 out: ptp_ocp_nvmem_device_put(&nvmem); - return; + mutex_unlock(&bp->eeprom_lock); + return ret; =20 fail: - dev_err(&bp->pdev->dev, "could not read eeprom: %d\n", ret); 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 platform_device *i2c_ctrl) +{ + struct i2c_adapter *adapter; + struct device *dev; + + dev =3D device_find_child(&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 caller owns the reference returned by device_find_child(). */ + return adapter; +} + +static bool +ptp_ocp_i2c_supported(struct ptp_ocp *bp) +{ + /* PCI IDs identify FPGA images, not a unique PCB revision. */ + return (bp->pdev->vendor =3D=3D PCI_VENDOR_ID_META && + bp->pdev->device =3D=3D PCI_DEVICE_ID_META_TIMECARD) || + (bp->pdev->vendor =3D=3D PCI_VENDOR_ID_CELESTICA && + bp->pdev->device =3D=3D PCI_DEVICE_ID_CELESTICA_TIMECARD); +} + +static bool +ptp_ocp_board_id_valid(const u8 *board_id, size_t *text_len) +{ + unsigned int len; + + for (len =3D 0; len < OCP_BOARD_ID_LEN; len++) + if (board_id[len] < 0x20 || board_id[len] > 0x7e) + break; + + if (!len) + return false; + + *text_len =3D len; + for (; len < OCP_BOARD_ID_LEN; len++) + if (board_id[len] !=3D 0 && board_id[len] !=3D 0xff) + return false; + + return true; +} + +static const struct ptp_ocp_i2c_profile * +ptp_ocp_i2c_select_profile(struct ptp_ocp *bp) +{ + static const char r4006_id[] =3D "R4006"; + size_t board_id_len; + + if (!ptp_ocp_has_eeprom_data(bp)) + return NULL; + + if (!ptp_ocp_board_id_valid(bp->board_id, &board_id_len)) + return NULL; + + if (board_id_len >=3D sizeof(r4006_id) - 1 && + !memcmp(bp->board_id, r4006_id, sizeof(r4006_id) - 1)) + return &ptp_ocp_r4006_profile; + + return NULL; +} + +static int +ptp_ocp_i2c_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, + }; + const struct ptp_ocp_i2c_profile *profile =3D bp->i2c_profile; + struct device *dev =3D &bp->pdev->dev; + struct ptp_ocp_i2c_topology *topology; + unsigned int component =3D 0; + unsigned int node =3D 0; + unsigned int expected_nodes; + unsigned int i, j, prop; + int ret; + + if (!profile) + return -EINVAL; + if (bp->i2c_topology && bp->i2c_topology->registered) + return 0; + if (bp->i2c_topology) { + topology =3D bp->i2c_topology; + goto register_nodes; + } + if (profile->sensor_count > OCP_I2C_MAX_SENSOR_COUNT || + profile->led_count > OCP_I2C_MAX_LED_COUNT || + profile->led_mux_channel >=3D OCP_I2C_MUX_CHANNELS) + return -EINVAL; + + for (i =3D 0; i < profile->sensor_count; i++) + if (profile->sensors[i].channel >=3D OCP_I2C_MUX_CHANNELS) + return -EINVAL; + + for (i =3D 0; i < profile->led_count; i++) + for (j =3D 0; j < ARRAY_SIZE(profile->leds[i].channel); j++) + if (profile->leds[i].channel[j] >=3D + ARRAY_SIZE(output_names)) + return -EINVAL; + + topology =3D devm_kzalloc(dev, sizeof(*topology), GFP_KERNEL); + if (!topology) + return -ENOMEM; + + topology->profile =3D profile; + snprintf(topology->mux_name, sizeof(topology->mux_name), + "ocp%d-%s-mux", bp->id, profile->name); + topology->mux_node.name =3D topology->mux_name; + topology->mux_node.properties =3D topology->mux_props; + topology->mux_props[0] =3D + PROPERTY_ENTRY_STRING("compatible", OCP_I2C_MUX_COMPATIBLE); + topology->mux_props[1] =3D + PROPERTY_ENTRY_U32("reg", OCP_I2C_MUX_ADDRESS); + topology->mux_props[2] =3D + PROPERTY_ENTRY_BOOL("i2c-mux-idle-disconnect"); + topology->mux_props[3] =3D PROPERTY_ENTRY_U32("#address-cells", 1); + topology->mux_props[4] =3D PROPERTY_ENTRY_U32("#size-cells", 0); + topology->node_group[node++] =3D &topology->mux_node; + + for (i =3D 0; i < OCP_I2C_MUX_CHANNELS; i++) { + topology->channel_nodes[i].name =3D channel_names[i]; + topology->channel_nodes[i].parent =3D &topology->mux_node; + topology->channel_nodes[i].properties =3D + topology->channel_props[i]; + topology->channel_props[i][0] =3D PROPERTY_ENTRY_U32("reg", i); + topology->channel_props[i][1] =3D + PROPERTY_ENTRY_U32("#address-cells", 1); + topology->channel_props[i][2] =3D + PROPERTY_ENTRY_U32("#size-cells", 0); + topology->node_group[node++] =3D &topology->channel_nodes[i]; + } + + for (i =3D 0; i < profile->sensor_count; i++) { + const struct ptp_ocp_i2c_device *sensor; + + sensor =3D &profile->sensors[i]; + topology->sensor_nodes[i].name =3D sensor->node_name; + topology->sensor_nodes[i].parent =3D + &topology->channel_nodes[sensor->channel]; + topology->sensor_nodes[i].properties =3D + topology->sensor_props[i]; + prop =3D 0; + if (sensor->compatible) + topology->sensor_props[i][prop++] =3D + PROPERTY_ENTRY_STRING("compatible", + sensor->compatible); + topology->sensor_props[i][prop] =3D + PROPERTY_ENTRY_U32("reg", sensor->address); + topology->node_group[node++] =3D &topology->sensor_nodes[i]; + } + + topology->led_node.name =3D profile->led_node_name; + topology->led_node.parent =3D + &topology->channel_nodes[profile->led_mux_channel]; + topology->led_node.properties =3D topology->led_props; + topology->led_props[0] =3D + PROPERTY_ENTRY_STRING("compatible", "issi,is32fl3207"); + topology->led_props[1] =3D + PROPERTY_ENTRY_U32("reg", profile->led_address); + topology->led_props[2] =3D + PROPERTY_ENTRY_U32("issi,riset-ohms", + profile->led_riset_ohms); + topology->led_props[3] =3D PROPERTY_ENTRY_U32("#address-cells", 1); + topology->led_props[4] =3D PROPERTY_ENTRY_U32("#size-cells", 0); + topology->node_group[node++] =3D &topology->led_node; + + for (i =3D 0; i < profile->led_count; i++) { + const struct ptp_ocp_led *led =3D &profile->leds[i]; + u32 group_reg; + + group_reg =3D min3(led->channel[0], led->channel[1], + led->channel[2]); + topology->led_nodes[i].name =3D led->node_name; + topology->led_nodes[i].parent =3D &topology->led_node; + topology->led_nodes[i].properties =3D + topology->led_group_props[i]; + prop =3D 0; + topology->led_group_props[i][prop++] =3D + PROPERTY_ENTRY_U32("reg", group_reg); + topology->led_group_props[i][prop++] =3D + PROPERTY_ENTRY_U32("color", LED_COLOR_ID_RGB); + topology->led_group_props[i][prop++] =3D + PROPERTY_ENTRY_STRING("function", led->function); + if (led->has_function_enumerator) + topology->led_group_props[i][prop++] =3D + PROPERTY_ENTRY_U32("function-enumerator", + led->function_enumerator); + topology->led_group_props[i][prop++] =3D + PROPERTY_ENTRY_U32("#address-cells", 1); + topology->led_group_props[i][prop] =3D + PROPERTY_ENTRY_U32("#size-cells", 0); + topology->node_group[node++] =3D &topology->led_nodes[i]; + + for (j =3D 0; j < ARRAY_SIZE(led->channel); j++, component++) { + u8 channel =3D led->channel[j]; + + topology->component_nodes[component].name =3D + output_names[channel]; + topology->component_nodes[component].parent =3D + &topology->led_nodes[i]; + topology->component_nodes[component].properties =3D + topology->component_props[component]; + topology->component_props[component][0] =3D + PROPERTY_ENTRY_U32("reg", channel); + topology->component_props[component][1] =3D + PROPERTY_ENTRY_U32("color", colors[j]); + topology->component_props[component][2] =3D + PROPERTY_ENTRY_U32("led-max-microamp", + profile->led_max_microamp); + topology->node_group[node++] =3D + &topology->component_nodes[component]; + } + } + + expected_nodes =3D 1 + OCP_I2C_MUX_CHANNELS + + profile->sensor_count + 1 + profile->led_count + + 3 * profile->led_count; + if (WARN_ON(node !=3D expected_nodes)) { + devm_kfree(dev, topology); + return -EINVAL; + } + + bp->i2c_topology =3D topology; + +register_nodes: + ret =3D software_node_register_node_group(topology->node_group); + if (ret) + return ret; + + topology->registered =3D true; + return 0; +} + +static int +ptp_ocp_i2c_add_device(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 PTR_ERR(client); + + return 0; +} + +static int +ptp_ocp_i2c_populate_channel(struct ptp_ocp *bp, unsigned int channel) +{ + struct ptp_ocp_i2c_topology *topology =3D bp->i2c_topology; + const struct ptp_ocp_i2c_profile *profile =3D topology->profile; + const struct software_node *node; + struct fwnode_handle *fwnode; + struct i2c_adapter *adapter; + unsigned int i; + int err, ret =3D 0; + + fwnode =3D software_node_fwnode(&topology->channel_nodes[channel]); + adapter =3D i2c_get_adapter_by_fwnode(fwnode); + if (!adapter) + return -EAGAIN; + + for (i =3D 0; i < profile->sensor_count; i++) { + const struct ptp_ocp_i2c_device *sensor; + + sensor =3D &profile->sensors[i]; + if (sensor->channel !=3D channel) + continue; + + node =3D &topology->sensor_nodes[i]; + err =3D ptp_ocp_i2c_add_device(adapter, node, sensor->type, + sensor->address); + if (err && !ret) + ret =3D err; + } + + if (channel =3D=3D profile->led_mux_channel) { + err =3D ptp_ocp_i2c_add_device(adapter, &topology->led_node, + "is32fl3207", + profile->led_address); + if (err && !ret) + ret =3D err; + } + + i2c_put_adapter(adapter); + return ret; +} + +static void +ptp_ocp_i2c_remove_mux(struct ptp_ocp *bp) +{ + struct fwnode_handle *fwnode; + struct i2c_client *client; + + if (!bp->i2c_topology || !bp->i2c_topology->registered) + return; + + fwnode =3D software_node_fwnode(&bp->i2c_topology->mux_node); + client =3D i2c_find_device_by_fwnode(fwnode); + if (!client) + return; + + i2c_unregister_device(client); + put_device(&client->dev); +} + +static void +ptp_ocp_i2c_kick(struct ptp_ocp *bp) +{ + if (!ptp_ocp_i2c_supported(bp)) + return; + if (!READ_ONCE(bp->i2c_resources_ready)) + return; + if (!READ_ONCE(bp->i2c_root_present)) + return; + + mod_delayed_work(system_wq, &bp->i2c_work, 1); +} + +static void +ptp_ocp_i2c_retry(struct ptp_ocp *bp, int error) +{ + unsigned long delay =3D HZ; + int retries; + + if (!READ_ONCE(bp->i2c_resources_ready)) + return; + if (!READ_ONCE(bp->i2c_root_present)) + return; + + retries =3D atomic_inc_return(&bp->i2c_retry_count); + if (retries >=3D OCP_I2C_RETRY_MAX) { + if (retries =3D=3D OCP_I2C_RETRY_MAX) { + dev_err(&bp->pdev->dev, + "I2C topology failed after %d attempts: %pe; " + "retrying every %d seconds\n", + OCP_I2C_RETRY_MAX, ERR_PTR(error), + OCP_I2C_RECOVERY_SECS); + } else { + atomic_set(&bp->i2c_retry_count, OCP_I2C_RETRY_MAX); + dev_err_ratelimited(&bp->pdev->dev, + "I2C topology setup still failing: %pe\n", + ERR_PTR(error)); + } + delay =3D OCP_I2C_RECOVERY_SECS * HZ; + } + + /* Preserve a faster rerun queued by an I2C bus notification. */ + queue_delayed_work(system_wq, &bp->i2c_work, delay); +} + +static int +ptp_ocp_i2c_populate_topology(struct ptp_ocp *bp, + struct platform_device *i2c_ctrl) +{ + const struct software_node *node; + struct i2c_adapter *adapter; + unsigned int channel; + int err, ret =3D 0; + + if (!READ_ONCE(bp->i2c_root_present)) + return 0; + if (!ptp_ocp_i2c_supported(bp) || !bp->eeprom_map) + return 0; + + adapter =3D ptp_ocp_i2c_root_adapter(i2c_ctrl); + if (!adapter) + return -EAGAIN; + + if (!ptp_ocp_has_eeprom_data(bp)) { + ret =3D ptp_ocp_read_eeprom(bp); + if (ret) + goto out_put_adapter; + } + if (!ptp_ocp_has_eeprom_data(bp)) { + ret =3D -EAGAIN; + goto out_put_adapter; + } + if (!bp->i2c_profile) + bp->i2c_profile =3D ptp_ocp_i2c_select_profile(bp); + if (!bp->i2c_profile) + goto out_put_adapter; + + ret =3D ptp_ocp_i2c_init_nodes(bp); + if (ret) + goto out_put_adapter; + + node =3D &bp->i2c_topology->mux_node; + ret =3D ptp_ocp_i2c_add_device(adapter, node, OCP_I2C_MUX_TYPE, + OCP_I2C_MUX_ADDRESS); + if (ret) + goto out_put_adapter; + + for (channel =3D 0; channel < OCP_I2C_MUX_CHANNELS; channel++) { + err =3D ptp_ocp_i2c_populate_channel(bp, channel); + if (err && !ret) + ret =3D err; + } + +out_put_adapter: + i2c_put_adapter(adapter); + return ret; +} + +static void +ptp_ocp_i2c_work(struct work_struct *work) +{ + struct ptp_ocp *bp =3D container_of(work, struct ptp_ocp, i2c_work.work); + struct platform_device *i2c_ctrl; + struct device *i2c_ctrl_dev; + int retries, ret =3D 0; + + /* Pair with resource publication after registration. */ + if (!smp_load_acquire(&bp->i2c_resources_ready)) + return; + if (!ptp_ocp_i2c_supported(bp)) + return; + + mutex_lock(&bp->i2c_topology_lock); + if (!READ_ONCE(bp->i2c_resources_ready) || + !READ_ONCE(bp->i2c_root_present)) { + mutex_unlock(&bp->i2c_topology_lock); + return; + } + + i2c_ctrl =3D READ_ONCE(bp->i2c_ctrl); + if (!i2c_ctrl) { + ret =3D -EAGAIN; + goto out_unlock; + } + + i2c_ctrl_dev =3D get_device(&i2c_ctrl->dev); + ret =3D ptp_ocp_i2c_populate_topology(bp, i2c_ctrl); + put_device(i2c_ctrl_dev); + +out_unlock: + mutex_unlock(&bp->i2c_topology_lock); + + if (ret) { + ptp_ocp_i2c_retry(bp, ret); + return; + } + + retries =3D atomic_xchg(&bp->i2c_retry_count, 0); + if (retries >=3D OCP_I2C_RETRY_MAX) + dev_info(&bp->pdev->dev, "I2C topology setup recovered\n"); +} + +static void +ptp_ocp_i2c_unregister(struct ptp_ocp *bp) +{ + struct ptp_ocp_i2c_topology *topology; + + WRITE_ONCE(bp->i2c_resources_ready, false); + WRITE_ONCE(bp->i2c_root_present, false); + disable_delayed_work_sync(&bp->i2c_work); + mutex_lock(&bp->i2c_topology_lock); + topology =3D bp->i2c_topology; + if (!topology) { + bp->i2c_profile =3D NULL; + goto out; + } + + if (topology->registered) { + ptp_ocp_i2c_remove_mux(bp); + software_node_unregister_node_group(topology->node_group); + topology->registered =3D false; + } + + bp->i2c_topology =3D NULL; + bp->i2c_profile =3D NULL; + +out: + mutex_unlock(&bp->i2c_topology_lock); +} + static struct device * ptp_ocp_find_flash(struct ptp_ocp *bp) { @@ -2166,9 +2833,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 @@ -3758,7 +4425,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); @@ -4729,6 +5396,8 @@ 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); + mutex_init(&bp->i2c_topology_lock); =20 for (i =3D 0; i < __PORT_COUNT; i++) bp->port[i].line =3D -1; @@ -4867,6 +5536,11 @@ ptp_ocp_detach(struct ptp_ocp *bp) { int i; =20 + if (bp->i2c_notifier_registered) { + bus_unregister_notifier(&i2c_bus_type, &bp->i2c_notifier); + bp->i2c_notifier_registered =3D false; + } + ptp_ocp_i2c_unregister(bp); ptp_ocp_debugfs_remove_device(bp); ptp_ocp_detach_sysfs(bp); ptp_ocp_attr_group_del(bp); @@ -5081,6 +5755,8 @@ 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); + atomic_set(&bp->i2c_retry_count, 0); =20 /* compat mode. * Older FPGA firmware only returns 2 irq's. @@ -5095,9 +5771,21 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci= _device_id *id) bp->n_irqs =3D err; pci_set_master(pdev); =20 + bp->i2c_notifier.notifier_call =3D ptp_ocp_i2c_notifier_call; + err =3D bus_register_notifier(&i2c_bus_type, &bp->i2c_notifier); + if (err) { + dev_err(&pdev->dev, + "failed to register I2C notifier: %d\n", err); + goto out; + } + bp->i2c_notifier_registered =3D true; + err =3D ptp_ocp_register_resources(bp, id->driver_data); if (err) goto out; + /* Publish all board resources before I2C topology work can run. */ + smp_store_release(&bp->i2c_resources_ready, true); + ptp_ocp_i2c_kick(bp); =20 bp->ptp =3D ptp_clock_register(&bp->ptp_info, &pdev->dev); if (IS_ERR(bp->ptp)) { @@ -5191,46 +5879,84 @@ static struct pci_driver ptp_ocp_driver =3D { .shutdown =3D ptp_ocp_remove, }; =20 +static bool +ptp_ocp_i2c_is_child(struct ptp_ocp *bp, struct device *child) +{ + struct device *dev =3D child; + + while (dev) { + if (dev =3D=3D &bp->pdev->dev) + return true; + dev =3D dev->parent; + } + + return false; +} + +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 ptp_ocp *bp; - bool add; + struct i2c_adapter *adapter; + struct i2c_client *client; + struct device *child =3D data; + struct ptp_ocp *bp =3D container_of(nb, struct ptp_ocp, i2c_notifier); + + if (!ptp_ocp_i2c_is_child(bp, child)) + return 0; =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; + if (ptp_ocp_i2c_is_root(bp, adapter)) { + WRITE_ONCE(bp->i2c_root_present, true); + ptp_ocp_symlink(bp, child, "i2c"); + } + ptp_ocp_i2c_kick(bp); return 0; - } =20 - if (!i2c_verify_adapter(child)) + case BUS_NOTIFY_DEL_DEVICE: + adapter =3D i2c_verify_adapter(child); + if (!adapter) + return 0; + if (ptp_ocp_i2c_is_root(bp, adapter)) { + WRITE_ONCE(bp->i2c_root_present, false); + 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; + if (!ptp_ocp_i2c_is_root(bp, client->adapter)) + return 0; + ptp_ocp_i2c_kick(bp); + 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 -static struct notifier_block ptp_ocp_i2c_notifier =3D { - .notifier_call =3D ptp_ocp_i2c_notifier_call, -}; - static int __init ptp_ocp_init(void) { @@ -5244,21 +5970,14 @@ ptp_ocp_init(void) if (err) goto out; =20 - what =3D "i2c notifier"; - err =3D bus_register_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier); - if (err) - goto out_notifier; - what =3D "ptp_ocp driver"; err =3D pci_register_driver(&ptp_ocp_driver); if (err) - goto out_register; + goto out_class; =20 return 0; =20 -out_register: - bus_unregister_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier); -out_notifier: +out_class: class_unregister(&timecard_class); out: ptp_ocp_debugfs_fini(); @@ -5269,7 +5988,6 @@ ptp_ocp_init(void) static void __exit ptp_ocp_fini(void) { - bus_unregister_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier); pci_unregister_driver(&ptp_ocp_driver); class_unregister(&timecard_class); ptp_ocp_debugfs_fini(); --=20 2.50.1 (Apple Git-155) From nobody Tue Sep 29 07:39:05 2026 Received: from mail-pg1-f180.google.com (mail-pg1-f180.google.com [209.85.215.180]) (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 8B70031B80D for ; Tue, 11 Aug 2026 05:02:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786424584; cv=none; b=MrkWvdLdTy6AQF2HumZChYPMy8TgPWFadLkruk7pGf63y49bu3Erfj+PWQs1t1/sN029zOccv382GsJuyZfzUsEhtkZXENrFqpgTeehshCfdh2+8BL1k5xOxacYuP5MB5/Xnt+KQB/AOkKvQnNrsy2Nzcy/99sLG28p8KUseI5s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786424584; c=relaxed/simple; bh=GfzBwg4ZHphT0HU4JIpIzgvGvublxLx3VU0uduAtHN8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jumWILE8PfCS9JZ+0SdwrTeefYjlD7UvcUJio1uSRwy2irR2KTiCMAe7irO3MPPSUZp7z5w6Lw8rB+K/3P5267nuAKL9ybLU9SRfRBt+kr8Jq2q2rD6PQKyLmhrIRcwx73XGREyH/DSmufLrQlAYknEkKK+jBdd0SjXBSwZnjQU= 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=SINizHnq; arc=none smtp.client-ip=209.85.215.180 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="SINizHnq" Received: by mail-pg1-f180.google.com with SMTP id 41be03b00d2f7-ca88130e09aso1883872a12.3 for ; Mon, 10 Aug 2026 22:02:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786424575; x=1787029375; 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=/70XqHwKtpkb8Nem1mvPAp3age5VnZSIpQXUrCBp0Sw=; b=SINizHnqCGCjyEBUdA2HTT8VNbxB6jGkoPBjT1PkxzNvfoAEEWJ+EF440Pvjo9VdLC dOVcum7R+XjGAFnFDjGKkPSh7YMIhpclrH0J4YOZoqM5USz/Qr5M6M57AJZbThdo7LHN /IR0qIEUcuGSS9umTLdeex/Hhkj8Sx8yOjX295VvScNJxrF1KdRrWLLv6djCCFkM2psu m4o+XxgcKsusz5dZkl+JSqjikHyrEk/YdDmcKHpUNdGX2FbnoAHk86yPHtVBE+VC9YHT cmLW3kF02QFyaO2GqgCkdvJJkSB5sBan1LykDnxQr8sbKLgUgsGdeEc9NX5j7MX2OejW No3Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786424575; x=1787029375; 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=/70XqHwKtpkb8Nem1mvPAp3age5VnZSIpQXUrCBp0Sw=; b=coRMgSfViJlfPJIJg0jZZMSS5SOXe4QAnhf6oXB753iF0YFPCsivfy6X87qx++sLXY vD7ujs84JapxcpaKubYQoEWpm9T7TyFL6Iux8367ku0tbFdnp+A2Id8ECLm6bBhkpM05 9KN7eTAyXLL5zwGJKaIcbw9RYoGBO5Sb46KsU0/p9CNKM2cvbBsIPaqYq+aiHnR/Xvn0 Xl+V3nMQ7F0GOchH+SuV+n/PFnzDz7zmUNxvS6mKRx5DhpbffEwlhNeY75+XjpScrr9u B/LK7L1cERAkskF3Yh0wsXGNtZ4EuoMUHrfBI5izy47JHwoXtce4PQYnkCIFe5VL5vIO wCyw== X-Forwarded-Encrypted: i=1; AHgh+RrAxNN2uC6lluJ//ZUXEkQOhPupl/w49+Vohc7f0NAV4t9U4wtvLPNGiajS8wvNhUs872KCnru9tr98q0Y=@vger.kernel.org X-Gm-Message-State: AOJu0YzG02iVIhHtdrBT9AJ6Q7n/OrvXonIyUkC/oRhFMiIqp9psKSRl h/6Ryc2n/60Y+skiwVKBLRtalWXdAA2kXGbxEBsM6MAezQsRABQObt1B X-Gm-Gg: AR+sD10sD3lYrusaHuxvybIAlgkv00RYuwhieEO9MM5PgPwR3JCXIzcx8AutnbGVlHb wsNp0dz0l3rjDeBLbej3xKnQ9WZqAO6Y/kB9ENrLgt2irv/KlV9ytPiMlYbN/NTNopYY+ONclJQ Tjs1PnidmXCx41GdmIPc2lMzgViFRYD5jFeMyi0W7Br1AthGcSTwUTRJgXSC9R1pF6qQFIMhqou 5PKQLSl7vvNTAB7ghXoqhzS1c7Zuv/XoLGhhG94r7GEga0+rE9q89THcjuR7YNesNEhnzkid/i1 xO+sVMd+AK4hHDgTOGrQW+mKQIH24EYedWEG3AtghEGxpsOTrh5jqSukf8c+3UJu5wdyMaZEu7x KWznsJp4zkw1GPieaq0E7ZiOB7+o8nFWYlzpj24ecsG5PiP8p3hWfBU1ivvzPbFMpFC5R2I6val Gq7bAK+yCvelJyvWhWmC/4gPSaIJAdIhy7EzezmUq4+VlDo6LQLqLuQXoQoRcdDQS+3nKTrxqY8 K2ovoS5zkfOmxYdJelin10pKbEvaegs0BYZr4rne4OPoHACrvze0q8w8/n7 X-Received: by 2002:a05:6a20:2d13:b0:3c4:397a:69ba with SMTP id adf61e73a8af0-3cc2bac423emr1261864637.28.1786424574951; Mon, 10 Aug 2026 22:02:54 -0700 (PDT) Received: from Mac.localdomain (198-27-228-154.fiber.dynamic.sonic.net. [198.27.228.154]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-31c619cf07dsm660421eec.4.2026.08.10.22.02.53 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Mon, 10 Aug 2026 22:02:54 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Nam Tran , Kees Cook , "Gustavo A. R. Silva" , Peter Rosin , Andi Shyti , Richard Cochran , Vadim Fedorenko , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hardening@vger.kernel.org Subject: [PATCH net-next v3 5/5] ptp: ocp: Add Time Card V9 I2C peripheral topology Date: Mon, 10 Aug 2026 22:02:44 -0700 Message-ID: <1931d6019cdaa80c681c38b6ff4d0359ec42230d.1786423898.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" Time Card V9 places a BME280 at 0x76, a BNO055 at 0x29, and an IS32FL3207 at 0x37 on PCA9546 channel 1. Describe GNSS1 and GNSS2 as RGB LEDs on outputs 0 through 5 and SMA1 through SMA4 on outputs 6 through 17. The board uses a 4.7 kohm RISET resistor, sequential red, green, and blue output wiring, and an 8100 uA per-output limit. Select this profile only for the exact fixed-width EEPROM ID "TIMECARD-V9". Earlier revisions expose the same I2C devices with different LED routing, so probing cannot distinguish them safely. Leave erased, malformed, and unknown board IDs unconfigured. Signed-off-by: Ahmad Byagowi --- drivers/ptp/ptp_ocp.c | 73 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index f2c2979daaf1..a843198796ab 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -353,7 +353,7 @@ struct ptp_ocp_serial_port { =20 #define OCP_I2C_MUX_CHANNELS 4 #define OCP_I2C_MAX_SENSOR_COUNT 5 -#define OCP_I2C_MAX_LED_COUNT 5 +#define OCP_I2C_MAX_LED_COUNT 6 #define OCP_I2C_MAX_LED_COMPONENT_COUNT (3 * OCP_I2C_MAX_LED_COUNT) #define OCP_I2C_MUX_NAME_LEN 32 #define OCP_I2C_MUX_COMPATIBLE "nxp,pca9546" @@ -613,9 +613,61 @@ static const struct ptp_ocp_led ptp_ocp_r4006_leds[] = =3D { }, }; =20 +static const struct ptp_ocp_i2c_device ptp_ocp_v9_sensors[] =3D { + { "pressure@76", "bosch,bme280", "bme280", 1, 0x76 }, + { "imu@29", "bosch,bno055", "bno055", 1, 0x29 }, +}; + +static const struct ptp_ocp_led ptp_ocp_v9_leds[] =3D { + { + .node_name =3D "multi-led@0", + .function =3D LED_FUNCTION_STATUS, + .function_enumerator =3D 1, + .has_function_enumerator =3D true, + .channel =3D { 0, 1, 2 }, + }, + { + .node_name =3D "multi-led@3", + .function =3D LED_FUNCTION_STATUS, + .function_enumerator =3D 2, + .has_function_enumerator =3D true, + .channel =3D { 3, 4, 5 }, + }, + { + .node_name =3D "multi-led@6", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 1, + .has_function_enumerator =3D true, + .channel =3D { 6, 7, 8 }, + }, + { + .node_name =3D "multi-led@9", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 2, + .has_function_enumerator =3D true, + .channel =3D { 9, 10, 11 }, + }, + { + .node_name =3D "multi-led@c", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 3, + .has_function_enumerator =3D true, + .channel =3D { 12, 13, 14 }, + }, + { + .node_name =3D "multi-led@f", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 4, + .has_function_enumerator =3D true, + .channel =3D { 15, 16, 17 }, + }, +}; + static_assert(ARRAY_SIZE(ptp_ocp_r4006_sensors) <=3D OCP_I2C_MAX_SENSOR_COUNT); static_assert(ARRAY_SIZE(ptp_ocp_r4006_leds) <=3D OCP_I2C_MAX_LED_COUNT); +static_assert(ARRAY_SIZE(ptp_ocp_v9_sensors) <=3D OCP_I2C_MAX_SENSOR_COUNT= ); +static_assert(ARRAY_SIZE(ptp_ocp_v9_leds) <=3D OCP_I2C_MAX_LED_COUNT); =20 static const struct ptp_ocp_i2c_profile ptp_ocp_r4006_profile =3D { .name =3D "r4006", @@ -630,6 +682,19 @@ static const struct ptp_ocp_i2c_profile ptp_ocp_r4006_= profile =3D { .led_max_microamp =3D 8150, }; =20 +static const struct ptp_ocp_i2c_profile ptp_ocp_v9_profile =3D { + .name =3D "v9", + .sensors =3D ptp_ocp_v9_sensors, + .sensor_count =3D ARRAY_SIZE(ptp_ocp_v9_sensors), + .leds =3D ptp_ocp_v9_leds, + .led_count =3D ARRAY_SIZE(ptp_ocp_v9_leds), + .led_node_name =3D "led-controller@37", + .led_mux_channel =3D 1, + .led_address =3D 0x37, + .led_riset_ohms =3D 4700, + .led_max_microamp =3D 8100, +}; + #define bp_assign_entry(bp, res, val) ({ \ uintptr_t addr =3D (uintptr_t)(bp) + (res)->bp_offset; \ *(typeof(val) *)addr =3D val; \ @@ -2230,6 +2295,7 @@ static const struct ptp_ocp_i2c_profile * ptp_ocp_i2c_select_profile(struct ptp_ocp *bp) { static const char r4006_id[] =3D "R4006"; + static const char v9_id[] =3D "TIMECARD-V9"; size_t board_id_len; =20 if (!ptp_ocp_has_eeprom_data(bp)) @@ -2242,6 +2308,11 @@ ptp_ocp_i2c_select_profile(struct ptp_ocp *bp) !memcmp(bp->board_id, r4006_id, sizeof(r4006_id) - 1)) return &ptp_ocp_r4006_profile; =20 + /* Older revisions share V9's I2C devices but not its LED wiring. */ + if (board_id_len =3D=3D sizeof(v9_id) - 1 && + !memcmp(bp->board_id, v9_id, sizeof(v9_id) - 1)) + return &ptp_ocp_v9_profile; + return NULL; } =20 --=20 2.50.1 (Apple Git-155)