From nobody Mon Sep 28 23:50:55 2026 Received: from mail-pl1-f170.google.com (mail-pl1-f170.google.com [209.85.214.170]) (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 C0A5F3E2AD7 for ; Fri, 14 Aug 2026 23:11:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786749071; cv=none; b=ent0Y5QGjlyEmXYsadoOPQsitWWBhIlrC6QPd7t7gVnXmUQXQHCCrIndkOIqEfTvmMKEB8Lxw0fZ3uIbwwHqw5crAL8RfRfJL3nxWnAxtoDXxjW5oxWw10EySezYw/uahXnufrNW+0D9dVUhxi36J3zBqYwVTT2UshjAYuNC2k0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786749071; c=relaxed/simple; bh=44ug/C+wAEBpOTrhMYNtqAXfr0HoJRYwJfcphkPhMgQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=TlJGr8kBLa4mBdV1Myw9QTbjbSEP/X+kXOQEIWv2zIIrt9BKpBgvfM7PrY87G0niKRYoR7NsyM40R8nO8jedP9zsHcl83Ho+gue2Sv26ycshU3SP5iVXa51chaCyMLxKSN2VHsmgmboxCUJDMVYiZXZ4f+NowqrBDWi/eZ1H/GA= 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=OtdBbP5v; arc=none smtp.client-ip=209.85.214.170 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="OtdBbP5v" Received: by mail-pl1-f170.google.com with SMTP id d9443c01a7336-2d032846c95so23213535ad.1 for ; Fri, 14 Aug 2026 16:11:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786749067; x=1787353867; 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=ouiR3ivZXFGLAlzqaEsoWP9s70nb+07A9Idd51+C0NY=; b=OtdBbP5viyaFBCpnilni49s3Aj0WXt7Tp+O6YA/Ikvy4gu75n4gYFy4bhvtCZDoKcK n9CdNMfmOFulC9OGH+/1KboBbpcnMVMeQZwlrvenu03irjqLTLsNYJRicwpAzG44I9s+ kCN1SCpGwZa7BGmOE/R5bSEpXZl3bkmjJiAph/KqPldCeoLvXNStgbP0VBq5u4gqraCx 6itlDlOUEbZba/Ii7tMh8rojtVo4ozvhLxCpq3cwS18+VqM0AV9ToLhjoXLS9E08uA8s 8mjbL19t33u3dEbM7XKi6v3nzRm3+6YMZI0QlfXxY0LKt/m0k4999pSXvpiU1gyebjui zLrA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786749067; x=1787353867; 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=ouiR3ivZXFGLAlzqaEsoWP9s70nb+07A9Idd51+C0NY=; b=JJfnFAeK1gjXyKJoqkrHkhNYfRyMcDXOHsXWZ15/heGG28jDHAW9ql2VXGEXV4Bpo1 ihQUWTmcZARwKrM5bvvX2QIIUXysuMHW5eBe8mQj95K+Hj1cdO3MCE4HPeQ/Pip1o/Q+ BUwJFvYbciey7vVUSUjbgDys221dTDGrIczGOrLSmHlBy1cjmYZSsibU4lW722rehWYw 4PB6X1MCyBFisCVMBMsNuFo9QgXLdEectACp3YlqWKF5QFIEqgNyKx3SjiCxa3GAmtsc RMukII5123QcCMJWsj+K93IARDu9grroi0qDJblws0PTjmONI8Ach9pJQLZkW9wj5dHH fAWg== X-Forwarded-Encrypted: i=1; AHgh+RpCi+i7jQNBy+HvVAleW6eEHMNG3pOTRwi075YyeWQyPorkCTi9lZpC1B2vy3kyhY5AY4ak93VsV25FO84=@vger.kernel.org X-Gm-Message-State: AOJu0YwDL3kkJcFZvLRZ/uUdI8oUoVtld1Y04x73vW/41Sv1aarVBo6x GqY+jBjxXXNIxDiTFqErvjgMsyUYhbjjvjHSm/yCMyd4bT0F9CBjeEph X-Gm-Gg: AR+sD135nCrMSctKH3iU1dWNpLOWH3P6hYA6Z0mow0K/kRLCSznRvE5cGQAePNdM9Bm 8ORH9WIysfjfUngTRA2BxYcdOLV+5fL4N50nBrOU80tNi/XeXudvmcOL+OYhrIrEsSMfFFTGCap kw7oXHx9Y6hIUUDu5NINadyr7WULFhawx4ET7MNpdLhza4XWhm1a+chspjrbQifFRRm7gjNJS5L 6mVGaN+fRh0TavQbBpgBc2EfQBZvUA9K4mX8WQfCfiQ9TzmO5475a3PDuYM8JvJKaPGM/W25SQQ qQa7Br6nJuS8EoYclXm5vDh0uLqR6J2BPh5nkK1Kj5d2OM0s2/sni7xmEjnl5lfZZ7R6QO1KzWM iW23j2XKQs4NfqL/ki+1cvbTRMxY71xi7PMXowiSrw+i5LpS6fnY3oDcOT9ddISD/3Cz7ffeFa9 oqKRoJ7wGCmpFJgF0nMGZXU0loiCrR46Ke44QQ9PsteJS8J6Ryl/RG0d2S0oLBGWDtKJIZCwsAn jNaEaocbQNQb4fJGw== X-Received: by 2002:a05:6372:a743:b0:3cc:8f53:26c0 with SMTP id adf61e73a8af0-3cc8f532b1bmr417457637.17.1786749067016; Fri, 14 Aug 2026 16:11:07 -0700 (PDT) Received: from Mac.localdomain ([12.162.234.74]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-320d61e1568sm8563607eec.11.2026.08.14.16.11.03 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Fri, 14 Aug 2026 16:11:06 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Andi Shyti , Peter Rosin , Nam Tran , Vadim Fedorenko , Richard Cochran , Andrew Lunn , "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Kees Cook , "Gustavo A . R . Silva" , 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 v5 1/7] dt-bindings: leds: Add IS32FL3207 controller Date: Fri, 14 Aug 2026 16:10:49 -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 and document their relationship to the RISET-derived full scale. Allow dash-suffixed multicolor group names when a group begins at channel 16 or 17, which cannot be represented by the common schema unit-address form. Signed-off-by: Ahmad Byagowi Reviewed-by because v5 materially changed the binding: it adds the --- .../bindings/leds/issi,is32fl3207.yaml | 281 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 287 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..2e53aa1e202d --- /dev/null +++ b/Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml @@ -0,0 +1,281 @@ +# 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 + full-scale output current according to IOUT(MAX) =3D 76.5 / RISET am= peres. + Each LED's led-max-microamp value selects a board-safe peak current = no + greater than this full scale, using the per-output scaling register. + + "#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 + description: | + Board-safe peak current for this output. The value must not exce= ed + the full-scale current derived from issi,riset-ohms. + + color: + oneOf: + - minimum: 0 + maximum: 7 + - minimum: 10 + maximum: 14 + + max-brightness: + minimum: 1 + maximum: 255 + + default-state: + description: + The driver performs a software reset and clears the PWM state du= ring + initialization, so an existing state 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]|-[0-9]+)?$": + type: object + $ref: leds-class-multicolor.yaml# + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 15 + description: | + Optional zero-based lowest-numbered output used by a group named + multi-led@N. Values 0 through 15 correspond to OUT1 through OUT1= 6. + For a multi-led-N group without reg, the driver derives the lowe= st + output from its component nodes. This form permits groups beginn= ing + at channels 16 or 17 while following the common multicolor node-= name + schema. + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + max-brightness: + minimum: 1 + maximum: 255 + + default-state: + description: + The driver performs a software reset and clears the PWM state du= ring + initialization, so an existing state 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 + description: | + Board-safe peak current for this output. The value must not + exceed the full-scale current derived from issi,riset-ohms. + + 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: [led@10] + - required: [led@11] + + required: + - "#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>; + enable-gpios =3D <&gpio0 4 GPIO_ACTIVE_HIGH>; + #size-cells =3D <0>; + vcc-supply =3D <&led_3v3>; + issi,riset-ohms =3D <4700>; + + multi-led@0 { + reg =3D <0>; + #address-cells =3D <1>; + color =3D ; + function =3D LED_FUNCTION_STATUS; + #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>; + }; + }; + + multi-led-1 { + #address-cells =3D <1>; + color =3D ; + function =3D LED_FUNCTION_INDICATOR; + #size-cells =3D <0>; + + led@10 { + reg =3D <16>; + color =3D ; + led-max-microamp =3D <8100>; + }; + + led@11 { + reg =3D <17>; + color =3D ; + led-max-microamp =3D <8100>; + }; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 991460050da7..448e7b7be8df 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13827,6 +13827,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 Mon Sep 28 23:50:55 2026 Received: from mail-pf1-f173.google.com (mail-pf1-f173.google.com [209.85.210.173]) (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 2B5E33CC9F6 for ; Fri, 14 Aug 2026 23:11:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.173 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786749075; cv=none; b=hcaP5zdFOYvGKRO6754FR60g/a178nIgvqAaLSedMaplZPGXq0or3cjX7mavbXHQpHVc41WFVWyuHHU8ftDx05/qMIfunOaXMxs3SJhrlHoasPyZhDtpjfh4HpIjU8tVOObc662Z2jeJIGw6HKNnQJC1eobGiObpnjKBBsDQ630= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786749075; c=relaxed/simple; bh=yGy1Yh/NZzloouf3D9OwqAHLenu2a8wvCFWmQFmOEWQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=kkUiaIoivjt+aoyblM/fqNzxh+dpEE8oD0epRwCOfSLpg2sCZaGVHVdY4Z3rY0bFiwSpT/NXQt8O/epUhmNkEkDDIBWisBx1PRh77FjKQB6hvpiTYvgOsL/2IajcBXNQuL2ZBt5yWlHGGH6vpRjtM9f9WFDdZg+aPv5S7qN+P8g= 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=o7wjfDKW; arc=none smtp.client-ip=209.85.210.173 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="o7wjfDKW" Received: by mail-pf1-f173.google.com with SMTP id d2e1a72fcca58-84e507b079dso993140b3a.0 for ; Fri, 14 Aug 2026 16:11:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786749071; x=1787353871; 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=8K54vjc5Ca57j/7S2eaUMSZWs9fNAeOAqhLSnXjalJg=; b=o7wjfDKWirZS3d8bW1ALwpwuazqUE+KKaxJXzwKVSt3lEKqIpXq3++YIjdtabLX1gL YvVZHaQBrA/U8aUsJ443DGuNNvY0uIrwJPUmdVeyOpa9O+JR9FlLJUu83pbP3BscxiZR EA9SOd6XZ/exMFX8NN2cZJ+KyVz9e9qkPvocQ0fMZ0NqHXuLnXa3MJDD5EqorST9lQYs 1JAmSDl71sNb0oyrqutkSdl6mFMbMdHIsKtCpKvIWIalWKn8UqqahBcFOS2AkLuSXNPw Wdwcyz/q1n3g8Xz+mjjh1nr2UoRisjiirDoxEd5nRALxsluays4pJe8XxLqCEf0JaygM KYUQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786749071; x=1787353871; 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=8K54vjc5Ca57j/7S2eaUMSZWs9fNAeOAqhLSnXjalJg=; b=AZ5rIxVxgKekpHVWoE+VASUKT2J7gx2qyDVrgWlQZhzITmCt91cQjx4CMfpLwzcvP9 Td/0GBr/49Tvm6arMRLZviBtHZq1IeP75plH+J5yNyWyr1wg6A6YkO8u1XxK3vSbAkO2 BD6nQZGMPhUvYE5GQepvpFFg4+n/Ydv2ARjj/QjHrx9iM7X/ktudIKTXe4+uBDyyuq/I daqQvvAPPiXw8li2w8J4TL8dAiYWTizrl/AFeMBXywvACPEAyrjo3XAOCBsT1SclRBc4 zED0+CCV5SyQ69bFgsL3OIQQKDv4kpdTz2BLNHBz9uKnxhu0qGwlANB0x7b/33GBtVGM J6LQ== X-Forwarded-Encrypted: i=1; AHgh+RqikiVYZ8Bfsaz4DOj6MOZ8n4fIYSyWu8rBtWmbHfZa+k8NvuBopQRM+2Rqj+IQKHMxNxQVmMBOvi1Oh7s=@vger.kernel.org X-Gm-Message-State: AOJu0Yx+fKgYBy1be+EkIeteJ42pFu/6b+3xgMC/y5A/l4G32RmD23Mx 7VQBalWEOA66XrqiIeXNqmu4/lU/Ai9Npuv/bV3Z8KD9IxSL59RbHpbO X-Gm-Gg: AR+sD12vPWzB/al2Tt0TKJwkuL0dTgBk0z/R9xXt/2cFSJkAegJjKKGflkzH3KA0GqE 4+b/snXPTZUPsEEll+RRKzIxoXcWFbXlVCzmMFOgL21foTPNYlEZpvxYLO+bsbiUYKc4pcRcZ6B 1srmyPMUkVl9ck97pyplA3wyDqoLtyaSlhDfoJJzXDQO1u9CKuEGlkfIRi35PFgANjo1j2nEI0F +7HPuzuCiMGOhOkVP3Hzl1+K2CHrRA1o0gdQU1ySFBPYWdQYsm4KxnWIGYN9cM3XAT6XFDiPUq5 l67RqcEvyH6UxdUwB0WN2EAkuKUjeBKVXBmBzRjWFic3lXEDQ/yVSSDRSGD2MsInGRMV37jUNOx LtgmsHihgaROXQie8aBKD+sk26+m0OwhWia6hwRhvf6z0vTJ0qbCpS0d0on9fWr+AWRV3/jfJsx /M0xDxAXBX8sWCp+K9dPAy5otYj9PN9Oxgf/Sh79vvDoHxHksK8sZq65A+apV1lUg0dkyRQqUdn pOeog== X-Received: by 2002:a05:6a20:9143:b0:3c3:76a8:c0f with SMTP id adf61e73a8af0-3cc7196e809mr9113639637.4.1786749071235; Fri, 14 Aug 2026 16:11:11 -0700 (PDT) Received: from Mac.localdomain ([12.162.234.74]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-320d61e1568sm8563607eec.11.2026.08.14.16.11.07 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Fri, 14 Aug 2026 16:11:10 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Andi Shyti , Peter Rosin , Nam Tran , Vadim Fedorenko , Richard Cochran , Andrew Lunn , "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Kees Cook , "Gustavo A . R . Silva" , 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 v5 2/7] leds: is32fl3207: Add controller driver Date: Fri, 14 Aug 2026 16:10:50 -0700 Message-ID: <200b35cd5847c318f4330dd162147b648c402601.1786747084.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 multicolor calculation, scaling, PWM, and controller-wide update operations. Clear retained scaling while SDB holds the outputs disabled. Release SDB and briefly enable normal operation to issue the required software reset, then keep the controller in software shutdown while registering every LED. Enable outputs only after all limits and initial brightness values are programmed. Handle an optional supply and enable GPIO. Serialize shutdown against pending brightness updates 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 | 736 +++++++++++++++++++++++++++++ 4 files changed, 749 insertions(+) create mode 100644 drivers/leds/rgb/leds-is32fl3207.c diff --git a/MAINTAINERS b/MAINTAINERS index 448e7b7be8df..c6bd00f80cea 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13832,6 +13832,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..6a46f97ba50a --- /dev/null +++ b/drivers/leds/rgb/leds-is32fl3207.c @@ -0,0 +1,736 @@ +// 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; + bool shutting_down; + 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_locked(struct is32fl3207 *chip, + const struct mc_subled *subleds, + unsigned int num_channels) +{ + unsigned int i; + int ret; + + 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, + }; + + guard(mutex)(&led->chip->lock); + if (led->chip->shutting_down) + return 0; + + return is32fl3207_write_channels_locked(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); + + guard(mutex)(&led->chip->lock); + if (led->chip->shutting_down) + return 0; + + led_mc_calc_color_components(mcdev, brightness); + + return is32fl3207_write_channels_locked(led->chip, mcdev->subled_info, + mcdev->num_colors); +} + +static int is32fl3207_turn_off_locked(struct is32fl3207 *chip, + struct is32fl3207_led *led) +{ + struct led_classdev *cdev =3D led->led_cdev; + struct mc_subled subled =3D { + .brightness =3D LED_OFF, + .channel =3D led->channel, + }; + + if (cdev->flags & LED_MULTI_COLOR) { + struct led_classdev_mc *mcdev =3D lcdev_to_mccdev(cdev); + + led_mc_calc_color_components(mcdev, LED_OFF); + return is32fl3207_write_channels_locked(chip, + mcdev->subled_info, + mcdev->num_colors); + } + + return is32fl3207_write_channels_locked(chip, &subled, 1); +} + +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; + bool has_group_reg; + 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); + + has_group_reg =3D fwnode_property_present(fwnode, "reg"); + if (has_group_reg) { + 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 (has_group_reg && 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; + + ret =3D is32fl3207_mc_brightness_set(&led->mcdev.led_cdev, + brightness); + 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_clear_retained_scaling(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; + + /* Software 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_locked(struct is32fl3207 *chip) +{ + regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, 0); + if (chip->enable_gpio) + gpiod_set_value_cansleep(chip->enable_gpio, 0); +} + +static void is32fl3207_disable(void *data) +{ + struct is32fl3207 *chip =3D data; + + guard(mutex)(&chip->lock); + chip->shutting_down =3D true; + is32fl3207_disable_locked(chip); +} + +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); + + /* + * Registers remain accessible with SDB low. Clear retained scaling + * before releasing hardware shutdown. + */ + ret =3D is32fl3207_clear_retained_scaling(ddata); + if (ret) + return dev_err_probe(dev, ret, + "failed to clear retained current scaling\n"); + + if (ddata->enable_gpio) + gpiod_set_value_cansleep(ddata->enable_gpio, 1); + + /* The SDB rising edge resets the I2C interface; allow it 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; + int ret =3D 0; + + guard(mutex)(&chip->lock); + chip->shutting_down =3D true; + + 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_locked(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; + ret =3D is32fl3207_turn_off_locked(chip, &chip->leds[i]); + + if (ret) { + dev_warn(chip->dev, + "failed to turn off LEDs during shutdown: %d\n", + ret); + break; + } + } +} + +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 Mon Sep 28 23:50:55 2026 Received: from mail-pf1-f181.google.com (mail-pf1-f181.google.com [209.85.210.181]) (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 F30493E51D2 for ; Fri, 14 Aug 2026 23:11:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786749080; cv=none; b=Iez7e5+KvTqn5RkmUKKeRIVMVZB5DdT7KFILuCk2mOV4L/V/gP/OJVCNKLVKNquYNWoAXCqqa6EkTbID3OANX/J7iKJ8Sp1jYAQmd4ZFzbmR2u2m2PtXFcSR3Ps5ZXGi3f56xNll2gRVq7X6OovdGIFMmeI6bnV4BYjO/M+Rj0U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786749080; c=relaxed/simple; bh=RYpZR0sDvcmY2jyiYZI8hciof+9imEqq4HibNvC273c=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=aBkkB7LfGeCl+5Mypj+erijynaGU4YivTGsVItL/3TT4YS00WtmTAsv8coQEbef7SrzkdwydTu7uyx9q706zjCt0y22HmcSMGwD4rhfEycnbcpE7OjEMWj/1CXaHZnITfS9yIXeJkaNbCj2g5A3UAZrWiWfdAIVGwTjEdaHJk+w= 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=EA1hwfEz; arc=none smtp.client-ip=209.85.210.181 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="EA1hwfEz" Received: by mail-pf1-f181.google.com with SMTP id d2e1a72fcca58-8487b7b3fc8so1603906b3a.3 for ; Fri, 14 Aug 2026 16:11:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786749076; x=1787353876; 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=Haj+ZUCxdgSDciu2KvIwXZABMW8X15ZHr1Rjfu8Dbzk=; b=EA1hwfEzqbHH7ARy3QZRd8LirsHpv83R27QGDLyUQftT2OXZhTJ7n+h4219/9H8fFM rk7g0QWkVXYOCd8+qXvCEM/S6tP/HaFuDfmY/LyP8SXWP7MyqauscNFmrASpJhakBgNG QLbZyE+dnoi9Fxm1cqcFRSem2HoVLMA+eb60tC9WxpzzyqaMOgY4IVF26FOnzsnQX8vC 9muXib160oNFt32vb+v1UXQhIW3xJWIZrX0Mn7m0kOmHm2xJH+RuMp6iNH50phv010WP UPWlfRi59lT0l3KBM6heRV6fnVpd9QAfvVuGCBXXlyocS6kv8Sth8M7HFmylWilJUTfb 6xMA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786749076; x=1787353876; 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=Haj+ZUCxdgSDciu2KvIwXZABMW8X15ZHr1Rjfu8Dbzk=; b=Y5JWJa0rLu5x1DEP8WF/ueHhVO2exxhMIqfsW2vge1LPZ4t1WLU2+Zhk5jxe44LYG6 rRRTIcEVq7JWUxUNXBUMdXtEvigxO8wptcD1DSywwQK9pfeSDVuZGiQ5Sz5EGXHCsYhE 8sUbjrhlTcVlLciyFXp62GuRH9diLPJnxgZ13yxNhXy4tZRCXlj0u6rnb8la4nAmF6KV oTL3rxpyqkcZgoLo80qi9dtRecme7bhZ0A2ID51GhO4xXezQzZLwXoKSLAwpeJo/cf82 sbcsP67bEmt8ka8/auwgjaxdPBE7G9McCZo7vAb7FH/tDmXpssHqq6JDBTBCWA0NumMd 1hng== X-Forwarded-Encrypted: i=1; AHgh+Roz5KPZMeKdT8IPxQLUZiNYl79wUbgMp9S2OUMEh2kNjmTo1t5FXhnzfaVepeZbO6Jd3iiTEzCNDfhMUJc=@vger.kernel.org X-Gm-Message-State: AOJu0YwZdu3+QSq+BUb32m0MrWyts/S8jre+HjfGy9Atnr/6Fuf6RrM1 ZYEZ3X7jIMIUBlwseyRUNiuMkgJd6ueWYqRlIIuJhKGYi1LhhG6Hzqvf X-Gm-Gg: AR+sD13+YtS4jRM1MwZZe7NRHq7eaRkXcgqcJRLxFRMjZ2734T5apqaX6kuJcu/Wddn UcudXcXu8f+npACliIeoBBdq8BsLd9ma1GpG+D/YxQjUeBrIet3VdBtQiHwyNdEQEWuFfr/XJAI s/dyZdt4sUhrb+x7qgn9eyOOf7ccZo/ckuoH0uyaXsFt2oJrCMicjk2//jhzyoLMiV7KW49TW+K 8iWaTNJJ/tpY42fAshSdugcAOC/Qy4OzqlUxOoL6+W/TXAYijglK6DXpH3UuMxwFYN4BM2FWh+f 9OOkQ3kBd97/QHsBg16iPFnZaNywBWsmChLYosiaOJcpzZsklOB76RYk8O+pmqM+mxvR9qdzqsc YiLUx2I3VC3iYR/DBt81q8jIng5ZAvSNdan7wiHNb+cVk54dNpH9lKBFkubxelRx3sLvEkNJ4bz npoe3OS2KDsGnQI3dI3bpEVPeMWzYh8kO824UbeW2WzBuWTijeAGHQUgYTgi7hWqP+00v/D6cTp vf8LI0SAo8= X-Received: by 2002:a05:6300:2208:b0:3c3:ab31:3d2c with SMTP id adf61e73a8af0-3cc71e684aamr10443008637.35.1786749075489; Fri, 14 Aug 2026 16:11:15 -0700 (PDT) Received: from Mac.localdomain ([12.162.234.74]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-320d61e1568sm8563607eec.11.2026.08.14.16.11.11 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Fri, 14 Aug 2026 16:11:14 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Andi Shyti , Peter Rosin , Nam Tran , Vadim Fedorenko , Richard Cochran , Andrew Lunn , "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Kees Cook , "Gustavo A . R . Silva" , 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 v5 3/7] i2c: mux: Propagate software nodes to channel adapters Date: Fri, 14 Aug 2026 16:10:51 -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" 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 attach it to the new adapter. Preserve an ACPI primary node when present and extend I2C firmware-node adapter lookup to match the attached secondary node. Track attachment ownership so failure cleanup cannot detach a pre-existing node. Keep the owned node attached through child-client removal, then release its reference after adapter deletion. Signed-off-by: Ahmad Byagowi --- drivers/i2c/i2c-core-base.c | 24 +++++++++--- drivers/i2c/i2c-mux.c | 77 ++++++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 8 deletions(-) diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index 3ec04787a737..f39575ee0244 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -1876,12 +1876,22 @@ int devm_i2c_add_adapter(struct device *dev, struct= i2c_adapter *adapter) } EXPORT_SYMBOL_GPL(devm_i2c_add_adapter); =20 -static int i2c_dev_or_parent_fwnode_match(struct device *dev, const void *= data) +static bool i2c_device_match_fwnode(struct device *dev, const void *data) { + struct fwnode_handle *fwnode =3D dev_fwnode(dev); + if (device_match_fwnode(dev, data)) + return true; + + return !IS_ERR_OR_NULL(fwnode) && fwnode->secondary =3D=3D data; +} + +static int i2c_dev_or_parent_fwnode_match(struct device *dev, const void *= data) +{ + if (i2c_device_match_fwnode(dev, data)) return 1; =20 - if (dev->parent && device_match_fwnode(dev->parent, data)) + if (dev->parent && i2c_device_match_fwnode(dev->parent, data)) return 1; =20 return 0; @@ -1891,8 +1901,9 @@ static int i2c_dev_or_parent_fwnode_match(struct devi= ce *dev, const void *data) * i2c_find_adapter_by_fwnode() - find an i2c_adapter for the fwnode * @fwnode: &struct fwnode_handle corresponding to the &struct i2c_adapter * - * Look up and return the &struct i2c_adapter corresponding to the @fwnode. - * If no adapter can be found, or @fwnode is NULL, this returns NULL. + * Look up and return the &struct i2c_adapter corresponding to the @fwnode, + * including a secondary firmware node. If no adapter can be found, or + * @fwnode is NULL, this returns NULL. * * The user must call put_device(&adapter->dev) once done with the i2c ada= pter. */ @@ -1922,8 +1933,9 @@ EXPORT_SYMBOL(i2c_find_adapter_by_fwnode); * @fwnode: &struct fwnode_handle corresponding to the &struct i2c_adapter * * Look up and return the &struct i2c_adapter corresponding to the @fwnode, - * and increment the adapter module's use count. If no adapter can be foun= d, - * or @fwnode is NULL, this returns NULL. + * including a secondary firmware node, and increment the adapter module's + * use count. If no adapter can be found, or @fwnode is NULL, this returns + * NULL. * * The user must call i2c_put_adapter(adapter) once done with the i2c adap= ter. * Note that this is different from i2c_find_adapter_by_node(). diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c index 681a201c239b..3bca123907b3 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 @@ -33,6 +35,7 @@ struct i2c_mux_priv { struct i2c_adapter adap; struct i2c_algorithm algo; struct i2c_mux_core *muxc; + struct fwnode_handle *swnode; u32 chan_id; }; =20 @@ -264,10 +267,60 @@ static const struct i2c_lock_operations i2c_parent_lo= ck_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) { struct i2c_adapter *parent =3D muxc->parent; + struct fwnode_handle *channel_node =3D NULL; struct i2c_mux_priv *priv; char symlink_name[20]; int ret; @@ -324,8 +377,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 +417,8 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, =20 priv->adap.dev.of_node =3D child; of_node_put(mux_node); + } else { + channel_node =3D i2c_mux_get_channel_swnode(muxc, chan_id); } =20 /* @@ -374,6 +429,16 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, ACPI_COMPANION(muxc->dev), chan_id); =20 + if (channel_node) { + ret =3D device_add_software_node(&priv->adap.dev, + to_software_node(channel_node)); + if (!ret) + priv->swnode =3D channel_node; + fwnode_handle_put(channel_node); + if (ret) + goto err_free_priv; + } + if (force_nr) { priv->adap.nr =3D force_nr; ret =3D i2c_add_numbered_adapter(&priv->adap); @@ -408,6 +473,8 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, return 0; =20 err_free_priv: + if (priv->swnode) + device_remove_software_node(&priv->adap.dev); kfree(priv); return ret; } @@ -429,7 +496,13 @@ 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"); + /* + * Keep the software node through child removal. The adapter + * device is cleared on deletion, so release the software-node + * attachment reference via the saved handle afterwards. + */ i2c_del_adapter(adap); + fwnode_handle_put(priv->swnode); of_node_put(np); kfree(priv); } --=20 2.50.1 (Apple Git-155) From nobody Mon Sep 28 23:50:55 2026 Received: from mail-pg1-f175.google.com (mail-pg1-f175.google.com [209.85.215.175]) (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 E5F123E9F9E for ; Fri, 14 Aug 2026 23:11:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786749083; cv=none; b=UPotBJxIGM2oTPhZwAqCB3scdRRx/WgJN8u3WJln2BmSB/p4u3TADQrz6vgnRJvvk5VlgFNKx7CcUJK2eFJUNf8KOb1AoX+oHjHcoEGk9+0SQzNeqK8OOxqw2Zj4pAdf6VxbGqlOQpfPrvrnJWNNAX2v1StUXW0z91rOr+VM5II= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786749083; c=relaxed/simple; bh=yk1xUmsjBUNkDpcYm6K2hoMvGWCQ2/ftqNWggubTRGg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=qAQxvW11/8tHBzl48BuJY18my6EZwDs8linWRbTAH2zVaWekw8lTMV6dCiCinNQFX6q2gT2nSAAI9utPiaLr39m9eJWlFffgqtfJehlkilrxyFPzwgg4huunk3Zm6JYIaf5U5QtJg8E/cLi46+/aZ+7AScKBcKmkDxzNiENXzAk= 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=NkBy96o8; arc=none smtp.client-ip=209.85.215.175 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="NkBy96o8" Received: by mail-pg1-f175.google.com with SMTP id 41be03b00d2f7-c9b373d5af0so1167180a12.2 for ; Fri, 14 Aug 2026 16:11:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786749080; x=1787353880; 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=7Q+ZRt0xfZjY8IftriXm6xUrwkasW7ROT4+0l7wFhn0=; b=NkBy96o8VmdM3F9nk/zpawjrWn+mlauI8ifjYDDN8cG2+QVmw/ZxCekMoJS0lOLxMw Ns3qcLph7ReMurKibZQPEOZN+KGtpWyG9+NLrByIpY1+A2Q5zHcdEHEa8unM9mVftZ2f 9Bb1L7fMqFtFR635wtcIX+0tWPnTPwrZJYnNAl6Iy7VxBQg//kDFO2BnQh+nvx0yCN+b N6xb/tjzkYgkaHhVoE3OgpAfTuHDaQc0xFv4eNqZBCpyw3FJxb2uzFnDEzzzG/EzFpBA 4k0z98priJp/j6POZlEN5AwxfchrQgQ2Rqr1MxNyzSNti+f1w7MmzjcLeQxidlgarIb1 +oQQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786749080; x=1787353880; 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=7Q+ZRt0xfZjY8IftriXm6xUrwkasW7ROT4+0l7wFhn0=; b=Oc9TxgQP1408V9o1/HIMHGqtIdiBrE3F4ICJKSos4bzdlk+DLCNkeVBdW+LO+FCUL1 0YvxLEXM5ueMSNCRd8U/7FTTZPmbTRMxBDJPZM+nIcYpdXWqN4wRLtoDcRFm6JLjz14/ BuLNyOcKvOlbsLo35Bwn+e82vn/AeWOsrPDd8W4EzwCfDoibh15OnajZpiTUbI2lygne 5h+tG86UES2g+WxML2lWuqtSqk3OQTiUJJcjQ5iPuGcpOLkd1fSGjuA7gowBu0tR2Gfw MNmj5/VMwC4B0OZV5CHmPZ+MwK6zLkvv0lJjauSkTvInSiVhz4IDU/JSdGETniwPoBzZ RHxQ== X-Forwarded-Encrypted: i=1; AHgh+Rqlkh+fyPky4M8qmwwWEXbz6dRLqsOdBSEO14kZRpRvRPYALmwNbStRQyKx/4uIcKIwFo8t5Jv9fqsbC0U=@vger.kernel.org X-Gm-Message-State: AOJu0Ywo2RhWYs0wwQeCTltqxekwMDfGkt8wKOaP2myPr+fuJdHwk9bM 5qPWnAkWhDofKV3f7yLOKOf2G3A3Q2usDpCNJRPo0xbnDZHQZ1VrveU1 X-Gm-Gg: AR+sD12+b5UoqtQqBgKq7rIAEZZ3gqn9V6rEwYH1QtSaeJIVMAy5PJKVn/gbbE5Dxue TRDSzCJDlZT1n+MGV19P9Uv9QuIXFVH3ulDK8PJvPv3Fl775OySeokuKBXuKkR4UIl+Qee1Te7i p8N64eIIZpofIiWW/LaOOq03w6ufD5F3NLerLLViBI6UDmJwKIf16owMRX9EPszb80ek804mxLf yBNYn7I0iEut76ZtEwqDDzAEhYh87RapFfLFZN2VEAMysby4E6dCNmV4zF2OI4GCojE+PWfSR98 I5Mc/ubOyLVCKClzTq4qbhPdTn8MLO99zQHKGD4GFCktbtBWMtE6Cr5E6ckFA9ffyeI/Cswpo7+ Nv93rmHCM+rBySjpDWivHFmTaxnhPYE4kXmCBGmQ52DQlHCE+hDdwaEUlPLBHiROW0UCqrzfWA1 uwj9tUqaM9y1zNAw5n5rXGQQL1TGuAKm1qBeMAWUMqxVYDFw32cZMMw1oXVfUTgJVPVBW8dg4/S ek5D5taGGU= X-Received: by 2002:a05:6a20:12c8:b0:3c3:7fa2:9618 with SMTP id adf61e73a8af0-3cc71eb2586mr9668252637.37.1786749080088; Fri, 14 Aug 2026 16:11:20 -0700 (PDT) Received: from Mac.localdomain ([12.162.234.74]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-320d61e1568sm8563607eec.11.2026.08.14.16.11.15 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Fri, 14 Aug 2026 16:11:19 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Andi Shyti , Peter Rosin , Nam Tran , Vadim Fedorenko , Richard Cochran , Andrew Lunn , "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Kees Cook , "Gustavo A . R . Silva" , 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 v5 4/7] ptp: ocp: Track EEPROM fields independently Date: Fri, 14 Aug 2026 16:10:52 -0700 Message-ID: <3186cc1f1c51b548439fda558e9e4052a481b787.1786747084.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 board ID and serial number can reside in separate EEPROM devices. Requiring both reads to succeed before publishing either field makes a missing serial EEPROM block board profile selection and hides an otherwise valid board ID. Serialize EEPROM discovery, read each field into temporary storage, and publish its readiness only after a successful read. Let devlink report whichever fields are available and make the serial-number sysfs attribute propagate read failures. Signed-off-by: Ahmad Byagowi --- drivers/ptp/ptp_ocp.c | 129 ++++++++++++++++++++++++++++++------------ 1 file changed, 93 insertions(+), 36 deletions(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 3d26ec1f7b9e..31a4c9dfe391 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -343,6 +344,10 @@ struct ptp_ocp_serial_port { =20 #define OCP_BOARD_ID_LEN 13 #define OCP_SERIAL_LEN 6 +#define OCP_EEPROM_BOARD_ID BIT(0) +#define OCP_EEPROM_SERIAL BIT(1) +#define OCP_EEPROM_ALL (OCP_EEPROM_BOARD_ID | \ + OCP_EEPROM_SERIAL) #define OCP_SMA_NUM 4 #define OCP_SIGNAL_NUM 4 #define OCP_FREQ_NUM 4 @@ -403,7 +408,10 @@ struct ptp_ocp { u16 fw_version; u8 board_id[OCP_BOARD_ID_LEN]; u8 serial[OCP_SERIAL_LEN]; - bool has_eeprom_data; + struct mutex eeprom_lock; /* Serializes EEPROM discovery. */ + bool has_board_id; + bool has_serial; + bool eeprom_error_reported; u32 pps_req_map; int flash_start; u32 utc_tai_offset; @@ -1969,21 +1977,50 @@ ptp_ocp_nvmem_device_put(struct nvmem_device **nvme= mp) *nvmemp =3D NULL; } =20 -static void -ptp_ocp_read_eeprom(struct ptp_ocp *bp) +static int +ptp_ocp_read_eeprom(struct ptp_ocp *bp, unsigned int fields) { const struct ptp_ocp_eeprom_map *map; struct nvmem_device *nvmem; + u8 data[OCP_BOARD_ID_LEN]; + const char *field_name =3D "data"; const void *tag; - int ret; - - if (!bp->i2c_ctrl) - return; + unsigned int field; + int ret =3D 0; =20 tag =3D NULL; nvmem =3D NULL; + mutex_lock(&bp->eeprom_lock); + fields &=3D OCP_EEPROM_ALL; + if (bp->has_board_id) + fields &=3D ~OCP_EEPROM_BOARD_ID; + if (bp->has_serial) + fields &=3D ~OCP_EEPROM_SERIAL; + if (!fields) + 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->bp_offset =3D=3D offsetof(struct ptp_ocp, board_id)) { + field =3D OCP_EEPROM_BOARD_ID; + field_name =3D "board ID"; + if (bp->has_board_id) + continue; + } else if (map->bp_offset =3D=3D offsetof(struct ptp_ocp, serial)) { + field =3D OCP_EEPROM_SERIAL; + field_name =3D "serial number"; + if (bp->has_serial) + continue; + } else { + continue; + } + + if (!(fields & field)) + continue; + if (map->tag !=3D tag) { tag =3D map->tag; ptp_ocp_nvmem_device_put(&nvmem); @@ -1992,24 +2029,34 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp) nvmem =3D ptp_ocp_nvmem_device_get(bp, tag); if (IS_ERR(nvmem)) { ret =3D PTR_ERR(nvmem); - goto fail; + goto out; } } - ret =3D nvmem_device_read(nvmem, map->off, map->len, - BP_MAP_ENTRY_ADDR(bp, map)); - if (ret !=3D map->len) - goto fail; + ret =3D nvmem_device_read(nvmem, map->off, map->len, data); + if (ret < 0) + goto out; + + memcpy(BP_MAP_ENTRY_ADDR(bp, map), data, map->len); + if (field =3D=3D OCP_EEPROM_BOARD_ID) { + /* Publish the field before marking it ready. */ + smp_store_release(&bp->has_board_id, true); + } else { + /* Publish the field before marking it ready. */ + smp_store_release(&bp->has_serial, true); + } } =20 - bp->has_eeprom_data =3D true; + ret =3D 0; =20 out: + if (ret && ret !=3D -EPROBE_DEFER && !bp->eeprom_error_reported) { + dev_err(&bp->pdev->dev, "failed to read EEPROM %s: %pe\n", + field_name, ERR_PTR(ret)); + bp->eeprom_error_reported =3D true; + } ptp_ocp_nvmem_device_put(&nvmem); - return; - -fail: - dev_err(&bp->pdev->dev, "could not read eeprom: %d\n", ret); - goto out; + mutex_unlock(&bp->eeprom_lock); + return ret; } =20 static struct device * @@ -2156,6 +2203,7 @@ ptp_ocp_devlink_info_get(struct devlink *devlink, str= uct devlink_info_req *req, struct netlink_ext_ack *extack) { struct ptp_ocp *bp =3D devlink_priv(devlink); + const char *board_id_key =3D DEVLINK_INFO_VERSION_GENERIC_BOARD_ID; const char *fw_image; char buf[32]; int err; @@ -2166,24 +2214,24 @@ ptp_ocp_devlink_info_get(struct devlink *devlink, s= truct devlink_info_req *req, if (err) return err; =20 - if (!bp->has_eeprom_data) { - ptp_ocp_read_eeprom(bp); - if (!bp->has_eeprom_data) - return 0; - } + ptp_ocp_read_eeprom(bp, OCP_EEPROM_ALL); =20 - sprintf(buf, "%pM", bp->serial); - err =3D devlink_info_serial_number_put(req, buf); - if (err) - return err; + /* Pairs with field publication in ptp_ocp_read_eeprom(). */ + if (smp_load_acquire(&bp->has_serial)) { + sprintf(buf, "%pM", bp->serial); + err =3D devlink_info_serial_number_put(req, buf); + if (err) + return err; + } =20 - snprintf(buf, sizeof(buf), "%.*s", OCP_BOARD_ID_LEN, - (const char *)bp->board_id); - err =3D devlink_info_version_fixed_put(req, - DEVLINK_INFO_VERSION_GENERIC_BOARD_ID, - buf); - if (err) - return err; + /* Pairs with field publication in ptp_ocp_read_eeprom(). */ + if (smp_load_acquire(&bp->has_board_id)) { + snprintf(buf, sizeof(buf), "%.*s", OCP_BOARD_ID_LEN, + (const char *)bp->board_id); + err =3D devlink_info_version_fixed_put(req, board_id_key, buf); + if (err) + return err; + } =20 return 0; } @@ -3757,9 +3805,17 @@ static ssize_t serialnum_show(struct device *dev, struct device_attribute *attr, char *bu= f) { struct ptp_ocp *bp =3D dev_get_drvdata(dev); + int ret; =20 - if (!bp->has_eeprom_data) - ptp_ocp_read_eeprom(bp); + /* Pairs with field publication in ptp_ocp_read_eeprom(). */ + if (!smp_load_acquire(&bp->has_serial)) { + ret =3D ptp_ocp_read_eeprom(bp, OCP_EEPROM_SERIAL); + if (ret) + return ret; + /* Pairs with field publication in ptp_ocp_read_eeprom(). */ + if (!smp_load_acquire(&bp->has_serial)) + return -ENODATA; + } =20 return sysfs_emit(buf, "%pM\n", bp->serial); } @@ -4729,6 +4785,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; --=20 2.50.1 (Apple Git-155) From nobody Mon Sep 28 23:50:55 2026 Received: from mail-pl1-f181.google.com (mail-pl1-f181.google.com [209.85.214.181]) (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 5E66E3E51D2 for ; Fri, 14 Aug 2026 23:11:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786749090; cv=none; b=eOavCilQl9njZkpKt2Cwp1gmJDASVyQZrqtsZEF2elOWnBRW5r06vveB2yqDmJy5d8pSQRaS5y8S55GYYugiPxb7EBc9zoVVXqgCuuCRRDXEWoUTNwbNs2rWjOAJRSWr146GDn48kVmd9RUReg6nEH0hDTTnWf4PNcotudHjMqY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786749090; c=relaxed/simple; bh=U5tu6TCgfJTzwMdBVa0p0omZBfcc7LqNpP9Vb5gPnKc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=aNkwa+3Qn1WowCgn9A78d9nnlHhJJcXptry/730liFuNHHLRcUlN+papfBS8HU1McvZueKUjftEK5ivULkjG1NnnMewEkLxxQUk6Jc8N9bOKpNNuCyoXkcubKsbY7yLcKGHnetqboMWWly8apdGNCg1BNDpqxGMtIkRMDZH8XTo= 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=LVvT0UUi; arc=none smtp.client-ip=209.85.214.181 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="LVvT0UUi" Received: by mail-pl1-f181.google.com with SMTP id d9443c01a7336-2cc891373e0so22200465ad.2 for ; Fri, 14 Aug 2026 16:11:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786749085; x=1787353885; 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=2mzeBMG1OiYWjiftyE04kMKTcsbLkUEV53Apw3PiquE=; b=LVvT0UUioRLORgX3r/4EseFW3NONbSsDf0eJI7AvkG6Dol66NONUW2g7tK1LcPySjR aclA6XoU5nSLs5FZm+Ybj/5SCu7sMv5nWySP/kUy6jTpqdMZU0WYm6hdlnzKS/xdfWpW Mr0YTDUAxxy2thYiXyeQoH8HhnEpWT8XdhAvrIo42UlwQ9NNFcTPrXHnHoC/7sLIWjdd f8ekElpXI2FamiBr8BJqVzELm3v6a2yZQ1VDeRatiRkknTAGDP7hwFfpFau4w8Pb5A6S 51ZUUrfqFZUswFrG2FEl58GUQbH90iz8/hQBT/sReiISudxPUSjfh7IKJVmzakDZtNpI FPFg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786749085; x=1787353885; 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=2mzeBMG1OiYWjiftyE04kMKTcsbLkUEV53Apw3PiquE=; b=k8eSRyCpzZiBHs971PYgRyUQ/9euQLbY0KWt4qd6s/shibvciQm3MuGt8RZX7nxWKI yfh6cPKRAuO9KZ7dUe24SPs49KYEkN6WDJ/mv41PV+WnKs9JIKD/P+9NBpm5IfQ0Lv2M +rh4ko7grj6g+Y/Hn80y4UHYtv7H0myqFFiI4/qeeOOUBJqKphOfVo51jjyN2j4KM8fA fnP+x6kZ9rKS09FLKMQwx5fRTaK+rqZo51hXNnBoO2JKtmuZ4GRpD0PU4C/uyGVe/dme TpsQqLSGND28fmaxFf4lNHGmxSIJ+vKre6xE3Y//cO2x2ZyRFTyilWxnZtS4XqEOW91Q Ndng== X-Forwarded-Encrypted: i=1; AHgh+RrQwNButiOyCVdAlNvfEU8uDrfSq6HqZ94/xV5zS89JxtD43s/G7oYeTTT8SW9RyNWK2LuMNcXh75HP0B8=@vger.kernel.org X-Gm-Message-State: AOJu0YwxHgt8NKfiKMkfol/Ybo1ezdw0wEWbasqIMX8CLje7TCdcBWkC HybCs3SIdRqqxU+yb6p2m5f1Fj/9Z8UKCW6XX6Jz3aUSg5MR4Lj7/UjN X-Gm-Gg: AR+sD11ad97iWEQDMTrZ/zBSvc1K0pKcyuAXGXjYhd8vMMaziA65f7+3UbQRkukEu6w jjxPr9tLWFtExsVSivx+Uz9yDiQi2fb88oWbzDYKTKs4XMVg5BhMWt8YMR0cizsVdpgR5D6Dwfn IORbYe1bIzTHvv3pE80/Kuzgn5vZhprhZG+njYYJ9hd13PqeTnAl7J6gyKymA8LB8JSxcuRtBKM a7f1b2gtwy37kxHY5zQuFst/IImLXChO+BuoPst6wGNG77Q5Z5c9WCWAoJkm+/aHIlIimsdraO6 p4foufaSxojjBJ6K37cxhIAdGiVugWx5wYKdTnok8wVePcg4GPt2D730cTI8is9fb1EU2NnQmUe DYlCLK4N/tem+IpENJutYElxKPBVdblxO5g1QslSwIpm5T7E1SJ1ltLEdVi4OZwvRiqotf3TnH9 TPRxhQQkpKITm+iKG1+KigXCHEoBrCePzYrVJURA6U++IX35WWrGJfhPlYNpYlMnceOwjJn/8Tt F42yQ== X-Received: by 2002:a05:6a21:339a:b0:3c3:93ce:663 with SMTP id adf61e73a8af0-3cc7200c614mr10187444637.26.1786749085392; Fri, 14 Aug 2026 16:11:25 -0700 (PDT) Received: from Mac.localdomain ([12.162.234.74]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-320d61e1568sm8563607eec.11.2026.08.14.16.11.20 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Fri, 14 Aug 2026 16:11:24 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Andi Shyti , Peter Rosin , Nam Tran , Vadim Fedorenko , Richard Cochran , Andrew Lunn , "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Kees Cook , "Gustavo A . R . Silva" , 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 v5 5/7] ptp: ocp: Add profile-driven I2C topology support Date: Fri, 14 Aug 2026 16:10:53 -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" Some Time Card variants route board peripherals through a PCA9546 mux, but their peripheral layout depends on the physical board revision. Add reusable software-node construction and client population helpers. Use per-device I2C notifications and serialized delayed work to wait for the root adapter, instantiate a selected profile, and retry dependencies that are not ready yet. Stop the work and remove the mux from a platform-bus unbind notifier before the controller driver deletes its root adapter. This prevents a new client from being created after i2c_del_adapter() has swept the adapter children. Board profiles are added separately. Signed-off-by: Ahmad Byagowi --- drivers/ptp/ptp_ocp.c | 718 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 681 insertions(+), 37 deletions(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 31a4c9dfe391..444b26a5e2a9 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -23,9 +23,12 @@ #include #include #include +#include #include #include =20 +#include + #define PCI_DEVICE_ID_META_TIMECARD 0x0400 =20 #define PCI_VENDOR_ID_CELESTICA 0x18d4 @@ -352,6 +355,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 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" +#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, @@ -422,6 +489,17 @@ 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; + struct notifier_block i2c_ctrl_notifier; + unsigned int i2c_retry_count; + bool i2c_root_present; + bool i2c_resources_ready; + bool i2c_notifier_registered; + bool i2c_ctrl_notifier_registered; int signals_nr; int freq_in_nr; }; @@ -452,6 +530,10 @@ static int ptp_ocp_signal_from_perout(struct ptp_ocp *= bp, 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); +static int ptp_ocp_i2c_ctrl_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 @@ -1998,7 +2080,7 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp, unsigned int = fields) fields &=3D ~OCP_EEPROM_SERIAL; if (!fields) goto out; - if (!bp->i2c_ctrl || !bp->eeprom_map) { + if (!READ_ONCE(bp->i2c_ctrl) || !bp->eeprom_map) { ret =3D -ENODEV; goto out; } @@ -2059,6 +2141,465 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp, unsigned in= t fields) return ret; } =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 to_i2c_adapter(dev); + if (!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 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; + unsigned int retries; + bool exhausted; + + if (!READ_ONCE(bp->i2c_resources_ready)) + return; + if (!READ_ONCE(bp->i2c_root_present)) + return; + + exhausted =3D bp->i2c_retry_count =3D=3D OCP_I2C_RETRY_MAX - 1; + if (bp->i2c_retry_count < OCP_I2C_RETRY_MAX) + bp->i2c_retry_count++; + retries =3D bp->i2c_retry_count; + if (retries >=3D OCP_I2C_RETRY_MAX) { + if (exhausted) + 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); + 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->i2c_profile) + return 0; + + adapter =3D ptp_ocp_i2c_root_adapter(i2c_ctrl); + if (!adapter) + return -EAGAIN; + + 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; + unsigned int retries; + int 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 bp->i2c_retry_count; + bp->i2c_retry_count =3D 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: + bp->i2c_retry_count =3D 0; + mutex_unlock(&bp->i2c_topology_lock); +} + static struct device * ptp_ocp_find_flash(struct ptp_ocp *bp) { @@ -2330,7 +2871,7 @@ ptp_ocp_register_i2c(struct ptp_ocp *bp, struct ocp_r= esource *r) if (IS_ERR(p)) return PTR_ERR(p); =20 - bp_assign_entry(bp, r, p); + WRITE_ONCE(bp->i2c_ctrl, p); =20 return 0; } @@ -4786,6 +5327,7 @@ ptp_ocp_device_init(struct ptp_ocp *bp, struct pci_de= v *pdev) 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; @@ -4915,6 +5457,7 @@ ptp_ocp_detach_sysfs(struct ptp_ocp *bp) { struct device *dev =3D &bp->dev; =20 + sysfs_remove_link(&dev->kobj, "i2c"); sysfs_remove_link(&dev->kobj, "ptp"); sysfs_remove_link(&dev->kobj, "pps"); } @@ -4922,8 +5465,19 @@ ptp_ocp_detach_sysfs(struct ptp_ocp *bp) static void ptp_ocp_detach(struct ptp_ocp *bp) { + struct platform_device *i2c_ctrl; int i; =20 + if (bp->i2c_ctrl_notifier_registered) { + bus_unregister_notifier(&platform_bus_type, + &bp->i2c_ctrl_notifier); + bp->i2c_ctrl_notifier_registered =3D false; + } + 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); @@ -4955,8 +5509,13 @@ ptp_ocp_detach(struct ptp_ocp *bp) for (i =3D 0; i < __PORT_COUNT; i++) if (bp->port[i].line !=3D -1) serial8250_unregister_port(bp->port[i].line); - platform_device_unregister(bp->spi_flash); - platform_device_unregister(bp->i2c_ctrl); + if (bp->spi_flash) + platform_device_unregister(bp->spi_flash); + i2c_ctrl =3D READ_ONCE(bp->i2c_ctrl); + if (i2c_ctrl) { + WRITE_ONCE(bp->i2c_ctrl, NULL); + platform_device_unregister(i2c_ctrl); + } if (bp->i2c_clk) clk_hw_unregister_fixed_rate(bp->i2c_clk); if (bp->n_irqs) @@ -5138,6 +5697,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. @@ -5152,9 +5712,32 @@ 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; + bp->i2c_ctrl_notifier.notifier_call =3D + ptp_ocp_i2c_ctrl_notifier_call; + err =3D bus_register_notifier(&platform_bus_type, + &bp->i2c_ctrl_notifier); + if (err) { + dev_err(&pdev->dev, + "failed to register I2C controller notifier: %d\n", + err); + goto out; + } + bp->i2c_ctrl_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)) { @@ -5248,46 +5831,115 @@ 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 platform_device *i2c_ctrl =3D READ_ONCE(bp->i2c_ctrl); + struct device *parent =3D adapter->dev.parent; + + if (!parent) + return false; + if (i2c_ctrl && parent =3D=3D &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_ctrl_notifier_call(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct ptp_ocp *bp =3D container_of(nb, struct ptp_ocp, + i2c_ctrl_notifier); + struct platform_device *i2c_ctrl =3D READ_ONCE(bp->i2c_ctrl); + struct device *dev =3D data; + + if (action !=3D BUS_NOTIFY_UNBIND_DRIVER || !i2c_ctrl || + dev !=3D &i2c_ctrl->dev) + return 0; + + /* + * Stop topology creation before the controller driver's remove path + * deletes its root adapter. Otherwise a newly-created mux client can + * pin that adapter after i2c_del_adapter() has swept its children. + */ + WRITE_ONCE(bp->i2c_root_present, false); + cancel_delayed_work_sync(&bp->i2c_work); + mutex_lock(&bp->i2c_topology_lock); + ptp_ocp_i2c_remove_mux(bp); + bp->i2c_retry_count =3D 0; + mutex_unlock(&bp->i2c_topology_lock); + sysfs_remove_link(&bp->dev.kobj, "i2c"); + + return 0; +} + 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; + + 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) { @@ -5301,21 +5953,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(); @@ -5326,7 +5971,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 Mon Sep 28 23:50:55 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 D573D3EFD22 for ; Fri, 14 Aug 2026 23:11: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=1786749094; cv=none; b=W4xqxcIcNbleOm2R6N6ssINSXMjdmoOFvuJIGFQjEqEI7TD9i4TlaWx1vKgiy3aE5eimiavzkxE5DzzYPniKJGtD2vBifXtg9WfEcWpbuCq3A+GR4STiE/7ZO+Ej63qWr97QTg+wJ5GNi4Q5wk6KhqB47Pz0Kp1gHIBQqpw0umw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786749094; c=relaxed/simple; bh=4KC/H4o52CopchtrhVy8iaV3lRYZ5/qmXJGbc4O4nok=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ggZv3QOHk53gc34GD7IxhMCcnMivgyrArpz95Z4WWDXeUsahoGiQqy/kh8sK/Bu/ntCFBHUNNA6DwGJz7nIBHdsQFrpCW1YLaeerUDRtz7v7tjUGmbe1kARS4sLF9vVYMPQGzDmTDGJjje2paBxXew7RxAy9AAgRiM1l+vgddR0= 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=ZZYCXXk2; 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="ZZYCXXk2" Received: by mail-pj1-f52.google.com with SMTP id 98e67ed59e1d1-3900e39d935so1563705a91.0 for ; Fri, 14 Aug 2026 16:11:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786749091; x=1787353891; 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=qr19CEkBHhB/7joCmqGqeg7ZqlAJoQBl4UCNBJQTQMo=; b=ZZYCXXk2cjblWEewHKIf0MSZdhxOwI3Rx6DmiFchvJTcy6ViAlbK9BQD4Zp4N1t6kn J9w8gOMmg96+T1wZfC1Uh2NFJSafaqkizr+JlXPceP2NDr6FM98869FSrP8VlP3WJFFy WcSqyNQFh1YdFf0XKJfBiptGUhNiN5NRMaob5HKbvJ3N1lJRIH1Nhl+gaGSwfFoqfFfS YyxL8MOscr6ZV6XHU/fwQkFkQgjohcvWCo60rtW3QQ1f9UD7fHUnq7KsFMfkigEcb/7/ 1mD+BCFJt//H4z9XMAP7wYXc5Rpza/H9S9sdd7e0vkqyc76ANWttncddBWJij36IUZkG AOAw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786749091; x=1787353891; 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=qr19CEkBHhB/7joCmqGqeg7ZqlAJoQBl4UCNBJQTQMo=; b=lqwCDG7HnO1KUlvpqQgdxJENWlUpHo2HvpCSPCTeJuKcK7q5ah3RVvcBnAB4dqByTm g/ctm+EGeoJF7yxILWT+Yca3B3zK1QeaCw1j53ObPy0c/zWBivXazLLGNb+GyTOkhLuf ko+9C+U4ISqT3Va+hsTjVJ4jtArrnb1j6T5b8Y1UKX08Wr4rJboyKB978ldlhO2iL8Jl uxS9tD7U/E3N7B3+5h3JcIZEmahTKsMdE0puOj8xSYq7HppPRA7J1IpDj1H7QnbLviVY xN4r67KIncp6Oum+omU9qd1cJQ3hPjy5t99Wsk9zuIjlSeTRJ+jtTT7R2YpyJE9ZATTQ g0aw== X-Forwarded-Encrypted: i=1; AHgh+Ro3MjIZbE2y3YhyXQI+5n1ojvougUl+epPIfvIKFnD5Cll2uArXt5IHdZXD+q60MJ97AgZaJBkVbLfQQ94=@vger.kernel.org X-Gm-Message-State: AOJu0Yw50sN9VhPJhIUqTyXAXgpjcPVMGud60BPL48CO4kfPiobvO3d9 dCCFooUEQd7xvSBP8O0QNb0GzIiyCC3KwoSjL9vffG4xVeu00U1tJeyC X-Gm-Gg: AR+sD12me5WZNfGUQUSCIEqM0eXlplCJhp8SESyNk7ZeHgixzuJViryINrxPC91OQiE 9giaP0XT/60lBNJgR8A9EGd3qwFXv9ug+DV0Zuhyp/MO4IqJHfIyZqDbuiXZ2mk4kWP3KA4p+bE CGtxVobOir4VTDlfs0Y/kGnHn7jVUo6g87DmTXaIaMeEr4eaQ6wVGlMzfCOxgTtwgvqxFFuSO68 vcs/MBmFd9K+hZG5JRvyxaEHQQNFZ38xgb1aQI7mUNmpcJx7CWxmoRJNjLHjIenWaphlRL6Z2ju v2IlTlwEiWvOjaINgJ0rKmF6TiW+afG6QseD7CjPTwt0oSGFkKIXf4rT2Ai2uF1eZHRBFaICeBJ aozbix3OcEgopzoOBwe1f07+tgDtIAsyqo9K5o8KRf+RhVstl/YklqHjPsWcZudX3c34wTXB7f0 5o5iH40OoUIniVTG9yhd0SDGLGQpCeZ1Pk70Cluf8WFX9Ekh7zd3I3rvJqe4bIPWLb2u1xD9X0X BEnKg== X-Received: by 2002:a17:90b:2d07:b0:381:3b5d:30f4 with SMTP id 98e67ed59e1d1-3933bd18856mr10717536a91.1.1786749091160; Fri, 14 Aug 2026 16:11:31 -0700 (PDT) Received: from Mac.localdomain ([12.162.234.74]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-320d61e1568sm8563607eec.11.2026.08.14.16.11.25 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Fri, 14 Aug 2026 16:11:30 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Andi Shyti , Peter Rosin , Nam Tran , Vadim Fedorenko , Richard Cochran , Andrew Lunn , "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Kees Cook , "Gustavo A . R . Silva" , 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 v5 6/7] ptp: ocp: Add R4006 I2C peripheral topology Date: Fri, 14 Aug 2026 16:10:54 -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 boards place sensors and an IS32FL3207 LED controller behind a four-channel PCA9546 mux. Describe the LM75B, SHT3x, and ICP10100 sensors and the five RGB LED groups. The BNO08x at address 0x4a is omitted because it has no upstream driver; the LM75B using the same address is on a different mux channel. Read the board ID independently of the serial EEPROM and select this profile from the R4006 prefix. Production IDs may append a board revision, while unknown board IDs remain untouched. Signed-off-by: Ahmad Byagowi --- drivers/ptp/ptp_ocp.c | 97 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 444b26a5e2a9..5ae50ad83bda 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -578,6 +578,71 @@ static struct ptp_ocp_eeprom_map art_eeprom_map[] =3D { { } }; =20 +/* + * The BNO08x at 0x4a on channel 3 has no upstream Linux driver, so omit i= t. + * The LM75B at the same address is a separate device on mux channel 0. + */ +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; \ @@ -2178,6 +2243,20 @@ ptp_ocp_i2c_supported(struct ptp_ocp *bp) bp->pdev->device =3D=3D PCI_DEVICE_ID_CELESTICA_TIMECARD); } =20 +static const struct ptp_ocp_i2c_profile * +ptp_ocp_i2c_select_profile(struct ptp_ocp *bp) +{ + /* Pairs with field publication in ptp_ocp_read_eeprom(). */ + if (!smp_load_acquire(&bp->has_board_id)) + return NULL; + + /* Production R4006 board IDs may carry a revision suffix. */ + if (!memcmp(bp->board_id, "R4006", 5)) + return &ptp_ocp_r4006_profile; + + return NULL; +} + static int ptp_ocp_i2c_init_nodes(struct ptp_ocp *bp) { @@ -2496,13 +2575,29 @@ ptp_ocp_i2c_populate_topology(struct ptp_ocp *bp, =20 if (!READ_ONCE(bp->i2c_root_present)) return 0; - if (!ptp_ocp_i2c_supported(bp) || !bp->i2c_profile) + if (!ptp_ocp_i2c_supported(bp) || !bp->eeprom_map) return 0; =20 adapter =3D ptp_ocp_i2c_root_adapter(i2c_ctrl); if (!adapter) return -EAGAIN; =20 + /* Pairs with field publication in ptp_ocp_read_eeprom(). */ + if (!smp_load_acquire(&bp->has_board_id)) { + ret =3D ptp_ocp_read_eeprom(bp, OCP_EEPROM_BOARD_ID); + if (ret) + goto out_put_adapter; + } + /* Pairs with field publication in ptp_ocp_read_eeprom(). */ + if (!smp_load_acquire(&bp->has_board_id)) { + 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; --=20 2.50.1 (Apple Git-155) From nobody Mon Sep 28 23:50:55 2026 Received: from mail-pg1-f182.google.com (mail-pg1-f182.google.com [209.85.215.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 C0AEF3F326D for ; Fri, 14 Aug 2026 23:11:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786749098; cv=none; b=n3URXcD5UQTYTEU7rDsX4PLGiLTbTa/O9rb+vbUiBpQf26o63QpFJ04KUoKA823l4DFxhyBTqN2//HeJyqIpzTfuPN7chspXBJuHs9LNVBlfUHDrHL6fk2j31MQrBTIwVLxxF8D6OH8rXZO2Burbsp/PndXjjus0zUZac90WNOw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786749098; c=relaxed/simple; bh=YlvvGTa9EnahPCY2nskii4xpnenwqPbUOJnmp8L6yUU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=hf8D+7KZd2ssYzGOFQwAFZlrGTududSI1O8iwwVHcF6gYJ4I71pwU4DHahMoeYxYZmJJKb4ZYJmVjm6Erx04DUUFmO2r12yYK7pTV3ex0J9koeVU+eO909HNUka9nRL+r008VLMnPZucXvASuUG10exWERg/5944AWVHeWbzJu0= 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=k/uhdm/q; arc=none smtp.client-ip=209.85.215.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="k/uhdm/q" Received: by mail-pg1-f182.google.com with SMTP id 41be03b00d2f7-cbedf6a6fe8so1097239a12.3 for ; Fri, 14 Aug 2026 16:11:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786749096; x=1787353896; 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=03zKDu7qMZtz3mj6zlwpsyLOBqCiVsWKdbf7HTBPFNI=; b=k/uhdm/qUK9inlscuCWlPyDjQCcfwr8MFrK+9AUFaNM1WbgP9BEX3liTpexR3BJAxF atAcrdZbiorzu4cVtI19Ryahz8Mc8tL5ukrXJXypW8I+ByBXNZrO8GO2jt5Jrk8JxYcs Ow1WN1JuQWn81rVeNqqxA1lYxvoKEeuiFdff+LNvQsQ6CQncUQJN1nrCP37MNbPtTdsP dxTRyk0t3hhMfwG3AmRoa1dA9w5AbZBQNWL9Ld6fGo98hX6v3gzVTsh39m8YxV6lr19m PaEwDauXUKCxOvqaVXPpvL3v5FQKVuN3bxdGCogEJvC94pIMuT9PEH0ZUiI+MRkehcj1 AvJg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786749096; x=1787353896; 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=03zKDu7qMZtz3mj6zlwpsyLOBqCiVsWKdbf7HTBPFNI=; b=sEPdbDBQHC+wuZj1Qq1oPXMWGbvWyB7aAKA6TTHgPL76YD0QetCjZoo28gpmcwNo5Q T2f22zoN2f45UjoUfSgwWrz4E2vM4CWgNEiJnJf96IVewwTITwO9iXG0EFI7t7hVlOj2 ZLSAOlQBeAA7EZaB7bzLOU2c+AutryZXz5CHCqjU0rGCUyOyohyxboW1+oQe2fhg3Gpq tz4dC1GiLjk/f5qnX3S+WDjpTbjMSyOIv7r1QdSRI7hmBcNgWkcZ4rZDt5d072aOIQH0 9pcfnUpyBRY6KJaptF7cYWKmsySPrjoJiUeJtdLRUysOTXGONbd3u1Cb0LrrtO+bs5sK ijVQ== X-Forwarded-Encrypted: i=1; AHgh+Rof481qwl3hgqXpY0ec5hWpmsxvDjPxDI+esKQ4QQsVpAjd2hRl8T4aWPegVBfGdkhEbn07Vr0M5PKypNo=@vger.kernel.org X-Gm-Message-State: AOJu0Yzq/iusbML++O4UvTN64U9BWNst9GStzYxZs8xMS/wWoXIFHq/U 2M6+lyrmwL8G8qKSdXmhQ5DTYSOAyAWh+O3kp7YqxgefbgTR2tihDYu3 X-Gm-Gg: AR+sD10bppreI8xHITMZlZ5c2/KPAt+jcR3ic3EzYQkXBWDvQyTgD6hdXXyx5/HGpBM qKPiXmYLZtCSAgBlsq6GQvHYQ59xAlX5+r/ksCrR3BswsxRMBTYfb7dVjahHM+qXhCbCVL9hMJM pZwKbLIrnyN64GehbD00NOpWoEj0EVfz1JZMffno5/Tfzl3u8wvfZxWAG1WEzsYKeaaT3P2vJOf cAt6uB/buaLD9/F1YBJEfAjGmwkJvyHF0bVxW5WHYqb1Cf5sYu2s5q31XLjPGZsNIC9rtlAc29a 3kUQhkdPKYeJKLlWaWJQbZwoOWznkJR6szADcUQuoE/LCjLa6R/iIURjMxM+uj86IMZCmFjMZsp 3zibkCehE5Hfv+1sJTUSIirDyPPNTlYAzjIgTFmEsV7lhRo9q7IwLaoqQv7zj+sb41gjG76Gc1M P9JEynjUCe4AdggocCQfquRQ97Kh5dQkWfFAyTHW4Q3th5wpI3tVnR1nCH0iuJKGLj2cZaaMgMQ vY1sQ== X-Received: by 2002:a05:6a20:3ca3:b0:3c3:b226:f165 with SMTP id adf61e73a8af0-3cc71fb5d5bmr11525983637.35.1786749095785; Fri, 14 Aug 2026 16:11:35 -0700 (PDT) Received: from Mac.localdomain ([12.162.234.74]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-320d61e1568sm8563607eec.11.2026.08.14.16.11.31 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Fri, 14 Aug 2026 16:11:35 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Andi Shyti , Peter Rosin , Nam Tran , Vadim Fedorenko , Richard Cochran , Andrew Lunn , "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Kees Cook , "Gustavo A . R . Silva" , 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 v5 7/7] ptp: ocp: Add Time Card V9 I2C peripheral topology Date: Fri, 14 Aug 2026 16:10:55 -0700 Message-ID: <1b0ad989c6290a400bd642f9fb2d26d965bc02c8.1786747084.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 boards use the same mux infrastructure with a BME280, a BNO055, and an IS32FL3207 at a different address. Describe six RGB groups. The first two expose the GNSS1 and GNSS2 status LEDs on outputs 0 through 5; the remaining four expose the SMA indicator LEDs on outputs 6 through 17. Select the profile only when the fixed-width board ID exactly matches TIMECARD-V9 with zero or erased-byte padding. Older revisions share the detectable I2C devices but not the V9 LED wiring. Signed-off-by: Ahmad Byagowi --- drivers/ptp/ptp_ocp.c | 87 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 5ae50ad83bda..5100278f164d 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -643,6 +643,73 @@ static const struct ptp_ocp_i2c_profile ptp_ocp_r4006_= profile =3D { .led_max_microamp =3D 8150, }; =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_v9_sensors) <=3D + OCP_I2C_MAX_SENSOR_COUNT); +static_assert(ARRAY_SIZE(ptp_ocp_v9_leds) <=3D OCP_I2C_MAX_LED_COUNT); + +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; \ @@ -2243,6 +2310,22 @@ ptp_ocp_i2c_supported(struct ptp_ocp *bp) bp->pdev->device =3D=3D PCI_DEVICE_ID_CELESTICA_TIMECARD); } =20 +static bool +ptp_ocp_board_id_matches(const u8 *board_id, const char *id) +{ + size_t len =3D strlen(id); + unsigned int i; + + if (len > OCP_BOARD_ID_LEN || memcmp(board_id, id, len)) + return false; + + for (i =3D len; i < OCP_BOARD_ID_LEN; i++) + if (board_id[i] !=3D 0 && board_id[i] !=3D 0xff) + return false; + + return true; +} + static const struct ptp_ocp_i2c_profile * ptp_ocp_i2c_select_profile(struct ptp_ocp *bp) { @@ -2254,6 +2337,10 @@ ptp_ocp_i2c_select_profile(struct ptp_ocp *bp) if (!memcmp(bp->board_id, "R4006", 5)) return &ptp_ocp_r4006_profile; =20 + /* Older revisions share V9's I2C devices but not its LED wiring. */ + if (ptp_ocp_board_id_matches(bp->board_id, "TIMECARD-V9")) + return &ptp_ocp_v9_profile; + return NULL; } =20 --=20 2.50.1 (Apple Git-155)