From nobody Fri Oct 2 07:45:25 2026 Received: from mail-pg1-f173.google.com (mail-pg1-f173.google.com [209.85.215.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 165D9437114 for ; Mon, 3 Aug 2026 20:50:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.173 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785790234; cv=none; b=EgYXQjekncdzADmvf4UKkmOwUNf+9rTpVFIt1uDFdrIZxeTqEM+8ZLtV0Kn2DfKpfrKV8KOVts6QeHQi+dSitnWeq5yxt1JkOQi6IopALx0BGFTvRVh2cmZ5fiOVZc++78X2JuCcFMpdoLmJER7FfAYq15XNtO3XRAaBc8GAwSQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785790234; c=relaxed/simple; bh=bOdUwZOm3el4ZTYOz7a8pzdsGE5dfIEAW/tE8GDGbGI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Ag41VyokMvI8NTFWLPFOXWIT1qxKvW+xecdet/whe6MGYslmk4dLyxz0keZUHNulIcHrxHbnDsFmae8/jDS6yE8lBDQNOr2cczkE68Y0vuKiM09k02Bz3J57Q/OJVHsJZx+i/t9KsSuH5CfksONgg7S86LT8njvm3S2SacXwqN8= 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=H26LeBb4; arc=none smtp.client-ip=209.85.215.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="H26LeBb4" Received: by mail-pg1-f173.google.com with SMTP id 41be03b00d2f7-c9b373d5af0so2973961a12.2 for ; Mon, 03 Aug 2026 13:50:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785790221; x=1786395021; 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=sPNeOdiz6uWEqmYyRrHHYQLDd/67o6llgv5ZaCAb9Bk=; b=H26LeBb4DOsYCGZFn+YADYc4qGgmOKuZNMZMy48DzhSe+69w6d0aNQ1oxIde14pKYQ QPkYek93pQxiQhXZ4Vif8MXIjFqCS5w4hi9Ee/whd7n8F6kSR9kFU5UmFg7Tz8PYxWv8 xF/yQmV5HRzxbhW/9B85LEMgZJGXRdp+cvwLFwBNSxyQiq8/Y9/eY46Sq+txwsF8JgY+ zg3YU6kyur0CTBWUSHrW6bZC1PvX+K9rO075FCuwazTVaJvGpS9a488ikndaBS971okv hwNhLfwo2ywT7L4RcVDxZbCzyta34jVMdzZjiBWfg0SGy39l6RfZ5jtsZRh+/tU4X/wR F/3g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785790221; x=1786395021; 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=sPNeOdiz6uWEqmYyRrHHYQLDd/67o6llgv5ZaCAb9Bk=; b=otzSbFQlvgI/FtzJ/wJMzGQjHD6CDQhcKQhxs8xDsxH5ejbM/qM6GX9a/ENNyf9jw8 UG3bnuNPKQOatC1i5v/cbmC0HL3RxLlLo/eoCXUnlwmy14N2pwwX+Xasgb7DYA7R4K7l G6vd5VXbTnzlGAiSMVhMP27USCN2id7/DVt9ZGyLr9VEXTh+oBea8q91WF69g8yvfKHO BT5DPOurFIHAffrCt2CT5sr0etjHrdV9xr5f7Bxm7NJPmJkHhS/dX9l0Z2hosS9YCiel eZkxFNiV3NXkR7zKCzTTvUcHQvHbtQ7Kl9YPZgIqLJ4oRUsN2l1R70c5i8BwAZhvR8l4 h4JA== X-Forwarded-Encrypted: i=1; AHgh+Rq611VSPRftjFDu7ttsw9KoummDqcH/R+K2GGuXJw99BAlRmdGxf12746aKqas7Qw4N3K+vVcQ1BVwPXN4=@vger.kernel.org X-Gm-Message-State: AOJu0YxW0JpaRGaIjGzv4Z+4qbsR1hNoJM6iBTdAtePqdDjVhW/AKLVP psYjuGt5rHw4/mb8TyjSzywM5CiquYlOXXj29HgGpnbUjI0AMk3y+WtX X-Gm-Gg: AR+sD11kRPcAIJRgUhtY7+iNBgRbub2cj17m1L5kD/WvbPzsJ1DIr3KSh1NfQX1NZkV rF0UtawZB9UZe+KXL+95KQoul8J2uscL7tRXjVdHRaau79Jj342rLaR2p6nGBlRtBK2TgO5/Sgl 5nLUmdORtWIPIEOBYQ9yU+i6aRnNKHq/7EPQWePBwPle7Kxux+J7cwRxEw3weVse+W7VcFURgox aMCvmCe6qPx1gFAKaQR4w7CJzc4G5NsNpnUgaKszzdIgXZf/qJoUi+E04FyMq9707/EH5moaHm+ ZFvAwghQXD4jY8n7M79n8rr16dttoYTLRlRNTJC5xp/FZf+GyEAOmlTBH/lomc8MsVD0bcVoDJ/ BJVWAjBO4RQhE6oLlfHWmC/H7u/BU8HXFDktjkFPbdu1m+Rpviov7qMbWPJa1LtlS+hFjqsWdoQ aJgQWajFEm3Ig6Dpw2c06mTZqfAyMVKpLrRiQXSDxF1HnBOfOmIzfzM50kBmTN0C+ZdJE= X-Received: by 2002:a05:6a21:1fc4:b0:3c3:c185:f334 with SMTP id adf61e73a8af0-3c92a95073cmr13560180637.66.1785790221259; Mon, 03 Aug 2026 13:50:21 -0700 (PDT) Received: from Mac.net ([12.162.234.74]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-13fab4d10bbsm33112324c88.11.2026.08.03.13.50.17 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Mon, 03 Aug 2026 13:50:20 -0700 (PDT) From: Ahmad Byagowi To: linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, 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-hardening@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFC net-next v2 1/6] dt-bindings: leds: Add IS32FL3207 controller Date: Mon, 3 Aug 2026 13:50:06 -0700 Message-ID: <20260803205011.1249-2-ahmadexp@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20260803205011.1249-1-ahmadexp@gmail.com> References: <20260803205011.1249-1-ahmadexp@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The IS32FL3207 is an 18-channel constant-current LED controller with per-output PWM and current scaling. Describe I2C addressing, power and shutdown controls, RISET, and individual or multicolor output groupings. Require board current limits for each output. Signed-off-by: Ahmad Byagowi --- .../bindings/leds/issi,is32fl3207.yaml | 208 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 214 insertions(+) create mode 100644 Documentation/devicetree/bindings/leds/issi,is32fl3207.= yaml diff --git a/Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml b/= Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml new file mode 100644 index 000000000..ffb305e9a --- /dev/null +++ b/Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml @@ -0,0 +1,208 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/issi,is32fl3207.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Lumissil IS32FL3207 18-channel LED controller + +maintainers: + - Ahmad Byagowi + +description: | + The IS32FL3207 is an I2C LED controller with 18 individually controlled + constant-current outputs. Each output has 8-bit current scaling. PWM + brightness control uses the controller's default 8-bit, 62 kHz mode. + Outputs may be exposed as individual LEDs or grouped as multicolor LEDs. + + The data sheet is available at: + https://www.lumissil.com/assets/pdf/core/IS32FL3207_DS.pdf + +properties: + compatible: + const: issi,is32fl3207 + + reg: + enum: [0x34, 0x35, 0x36, 0x37] + + vcc-supply: + description: Regulator providing power to the VCC pin. + + enable-gpios: + maxItems: 1 + description: GPIO connected to SDB, asserted to enable the controller. + + issi,riset-ohms: + minimum: 2000 + maximum: 76500000 + description: | + Resistance in ohms of the external RISET resistor. The resistor sets= the + maximum output current according to IOUT(MAX) =3D 76.5 / RISET amper= es. + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + "^led@[0-9a-f]+$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 17 + description: LED output number. + + led-max-microamp: + minimum: 1 + maximum: 38250 + + color: + not: + enum: [8, 9] + + max-brightness: + minimum: 1 + maximum: 255 + + default-state: + description: + The controller is reset during initialization, so an existing st= ate + cannot be preserved. + enum: ["on", "off"] + + default-brightness: + maximum: 255 + + required: + - reg + - led-max-microamp + + "^multi-led@[0-9a-f]+$": + type: object + $ref: leds-class-multicolor.yaml# + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 17 + description: Lowest-numbered LED output used by the group. + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + max-brightness: + minimum: 1 + maximum: 255 + + default-state: + description: + The controller is reset during initialization, so an existing st= ate + cannot be preserved. + enum: ["on", "off"] + + default-brightness: + maximum: 255 + + patternProperties: + "^led@[0-9a-f]+$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 17 + description: LED output number. + + led-max-microamp: + minimum: 1 + maximum: 38250 + + color: + not: + enum: [8, 9] + + max-brightness: false + + default-state: false + + default-brightness: false + + retain-state-shutdown: false + + required: + - reg + - color + - led-max-microamp + + required: + - reg + - "#address-cells" + - "#size-cells" + +required: + - compatible + - reg + - issi,riset-ohms + - "#address-cells" + - "#size-cells" + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + led-controller@34 { + compatible =3D "issi,is32fl3207"; + reg =3D <0x34>; + #address-cells =3D <1>; + #size-cells =3D <0>; + + vcc-supply =3D <&led_3v3>; + enable-gpios =3D <&gpio0 4 GPIO_ACTIVE_HIGH>; + issi,riset-ohms =3D <4700>; + + multi-led@0 { + reg =3D <0>; + color =3D ; + function =3D LED_FUNCTION_STATUS; + #address-cells =3D <1>; + #size-cells =3D <0>; + + led@0 { + reg =3D <0>; + color =3D ; + led-max-microamp =3D <8100>; + }; + + led@1 { + reg =3D <1>; + color =3D ; + led-max-microamp =3D <8100>; + }; + + led@2 { + reg =3D <2>; + color =3D ; + led-max-microamp =3D <8100>; + }; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 932ea1db0..744296f1c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13828,6 +13828,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 Fri Oct 2 07:45:25 2026 Received: from mail-pg1-f174.google.com (mail-pg1-f174.google.com [209.85.215.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3AF12433032 for ; Mon, 3 Aug 2026 20:50:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785790230; cv=none; b=Nz+RWHGpPO1MaPJWlv8V0TBt0/2/gL1pFOF/t/Teu1kXwBAj8NNQjry2yPMndJDUkfH9u+NzRZA4y4iYmGevYYKs34tJmtnjXUnoBk0cuWAfiNk4X5hoXb8wYqBfYaD7T2v8GMYKMHedA/lSM/LXlwx+SGFKjtl5qgM00WUp2VM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785790230; c=relaxed/simple; bh=E6AIf4QTV+f9CymIs34y9i6oz54pEi/ksj5d4y9J3rs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=sNrlQBgpNhYeQAkVkxvDdEZB/74Rek+Wx5k5U7a0f3DzpYhSn/lyrK3K1LEVuaz5rEbUT8rrvY9yVVLBotAHTEfwxoKz6ooTeu05Lx2Hyj02OldhXiUbBfUWuyyjUS+Zret4XqBfUWm2UNIITNaA60QB+tDlF61SI86gOru64Z0= 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=UfOLdiZ1; arc=none smtp.client-ip=209.85.215.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="UfOLdiZ1" Received: by mail-pg1-f174.google.com with SMTP id 41be03b00d2f7-ca957432c7fso2606375a12.1 for ; Mon, 03 Aug 2026 13:50:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785790226; x=1786395026; 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=SJVZwJQnFx3Bw+TQgOysVbru6CqjE8f7xqxUI0NqoII=; b=UfOLdiZ1ToG9Xo1TB/ahW291H+wnScr9/XEtEVhk5vAT176HmBhYF17qAVwT0PFHDD hH6EjTfqAcO9fQ8ymMdyWsoIL6+gpcKJijeMvWjzSITe0Q1tCI4SNsrxpclDL36zD9sU ancCzL4BOQDMPp2Gy0QKOSCfInE+9ztWeL++/4BsA4f1VL9cRNuv3aVKfqdtgxSUy9tw 5OpmlR/exDVVgD1d4F1o5EiFgCzkNWiocYxGISzDrJHpjuNoCYxniyEJygmncWMQ6G6r OPkeXjLMR1WMYRBFEqP2yz8Y741g6Ic6+934ha4mDeMrkKILhnpZTp+fmEiZOnzKtCdP vtDg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785790226; x=1786395026; 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=SJVZwJQnFx3Bw+TQgOysVbru6CqjE8f7xqxUI0NqoII=; b=rq4uQfuQ32f74mp4l8nhLi2z4Nnz5UBKfXQcKHj9nxlb305PTsPMTn9wD8QdYO/Hhm EVd3dF69zbyokF/yaEnReC92fSJorceoY/fEOcUzh7hSzLGVdFaEWgIxdM8vL4tXSgW0 8pgV3IPfCIXWXlwbZsUW2t1iQjNcykFSva9c/ZaY3YpkiIoRQIZZUU9yhQfM4J2kIMRD +N7jDL+l1Y9izYVsTXo6g7NkiC4/oXux3Zmno1rHn3VZmBzPSaBWv4/OfZkotFVbBUEa kQEny9BZfHIsYVKBm76ZnkPFEdNNtyv5Aj6csZcfLg09v1aFdZs4yc9nXc3HXzoWIjd7 Irfg== X-Forwarded-Encrypted: i=1; AHgh+RqoXLKOyLE0cUDBiMPh22EHl6Gd4Qhpfnw67+58IwqZuiEViJjSJqfjsvfhDX5+rrW0dsLQP9IFWpHZ4EE=@vger.kernel.org X-Gm-Message-State: AOJu0YwoQnAwsUzuyQbrqrZaAazya5UT3PdS6kns+2QEwqSr579qWnvX MW6UJ4k3XSN5Zk4t/bDumF62sdUAaYnV2EPjWVCDSyt+F6p75esj8/Bf X-Gm-Gg: AR+sD128EJG9K6sTE+SS+HQOXv+bOvLfGlvRO1QW/cAYrl8HH8IgheEl34hnTXsE6JE TbYgNodnRJMPlJDfJ90Abdl3bcOuzfBGIuC4mt5wNJdqJmjFbYHM9q203x6kiTOQAfWlLjQFL7k hcuAefP1U8wldNLdkbTrJWmwhvSrCOLZgaZYeeMhQOde8sCMI/YL1c5IYrE9KPNFgxjhz+X50AD vlzwtrWRy0TGJMS1hjmx6MAOgmW+OF2UG+QhuRAUgkVRKcuxUKHBUpTtmJcpqilTs41ErLIyGRB 7LaRZq0t69jYxsHdGRrqosNvSRedR8ruNa+TAzQnie/ghlxAy3vTEL8aIUdKHjVCP4joQOudn93 fgtDwlHa6+Z5x1GRZnGs972T+nUPAayLfFxPnufNXqd3IMvbJJO+9+l8m/KvWjYW6GbfVLguh/O CER6Ak+cmVtWWW5FT2Ii1mWNub9gnFsRdbqv6ETjXNv3RLrMBCe3vHxC5a1IeyXUOYEKo= X-Received: by 2002:a05:6a20:734c:b0:3c0:b3f7:e5e5 with SMTP id adf61e73a8af0-3c92a52c2cbmr10819802637.25.1785790226338; Mon, 03 Aug 2026 13:50:26 -0700 (PDT) Received: from Mac.net ([12.162.234.74]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-13fab4d10bbsm33112324c88.11.2026.08.03.13.50.21 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Mon, 03 Aug 2026 13:50:24 -0700 (PDT) From: Ahmad Byagowi To: linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, 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-hardening@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFC net-next v2 2/6] leds: rgb: Add IS32FL3207 controller driver Date: Mon, 3 Aug 2026 13:50:07 -0700 Message-ID: <20260803205011.1249-3-ahmadexp@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20260803205011.1249-1-ahmadexp@gmail.com> References: <20260803205011.1249-1-ahmadexp@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add a generic I2C driver for the Lumissil IS32FL3207 18-channel LED controller. Expose individual and multicolor LEDs through the LED class. Use the default 8-bit 62 kHz PWM mode, derive output current from RISET, and enforce each output current limit with the scaling registers. Honor initial brightness and retained shutdown state. Handle an optional supply and enable GPIO, and serialize controller-wide update latches. Signed-off-by: Ahmad Byagowi --- MAINTAINERS | 1 + drivers/leds/rgb/Kconfig | 11 + drivers/leds/rgb/Makefile | 1 + drivers/leds/rgb/leds-is32fl3207.c | 580 +++++++++++++++++++++++++++++ 4 files changed, 593 insertions(+) create mode 100644 drivers/leds/rgb/leds-is32fl3207.c diff --git a/MAINTAINERS b/MAINTAINERS index 744296f1c..8f2386fd5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13833,6 +13833,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 6e9ab5f60..c896be131 100644 --- a/drivers/leds/rgb/Kconfig +++ b/drivers/leds/rgb/Kconfig @@ -14,6 +14,17 @@ config LEDS_GROUP_MULTICOLOR To compile this driver as a module, choose M here: the module will be called leds-group-multicolor. =20 +config LEDS_IS32FL3207 + tristate "LED support for ISSI IS32FL3207" + depends on I2C + select REGMAP_I2C + help + Say Y here to include support for the Lumissil IS32FL3207 + 18-channel I2C LED controller. + + To compile this driver as a module, choose M here: the module will + be called leds-is32fl3207. + config LEDS_KTD202X tristate "LED support for KTD202x Chips" depends on I2C diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile index cc0f2df66..228923e8b 100644 --- a/drivers/leds/rgb/Makefile +++ b/drivers/leds/rgb/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 =20 obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) +=3D leds-group-multicolor.o +obj-$(CONFIG_LEDS_IS32FL3207) +=3D leds-is32fl3207.o obj-$(CONFIG_LEDS_KTD202X) +=3D leds-ktd202x.o obj-$(CONFIG_LEDS_LP5812) +=3D leds-lp5812.o obj-$(CONFIG_LEDS_LP5860_CORE) +=3D leds-lp5860-core.o diff --git a/drivers/leds/rgb/leds-is32fl3207.c b/drivers/leds/rgb/leds-is3= 2fl3207.c new file mode 100644 index 000000000..08843da96 --- /dev/null +++ b/drivers/leds/rgb/leds-is32fl3207.c @@ -0,0 +1,580 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ISSI IS32FL3207 LED controller driver + * + * Copyright 2026 Ahmad Byagowi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define IS32FL3207_NUM_CHANNELS 18 +#define IS32FL3207_MAX_BRIGHTNESS 255 + +#define IS32FL3207_REG_CONTROL 0x00 +#define IS32FL3207_REG_PWM_LOW(channel) (0x01 + 2 * (channel)) +#define IS32FL3207_REG_PWM_UPDATE 0x49 +#define IS32FL3207_REG_SCALING(channel) (0x4a + (channel)) +#define IS32FL3207_REG_GLOBAL_CURRENT 0x6e +#define IS32FL3207_REG_RESET 0x7f + +#define IS32FL3207_CONTROL_ENABLE BIT(0) +#define IS32FL3207_GLOBAL_CURRENT_MAX 0xff + +/* IOUT(MAX) in microamperes =3D 76,500,000 / RISET in ohms. */ +#define IS32FL3207_CURRENT_NUMERATOR 76500000ULL + +struct is32fl3207; + +struct is32fl3207_led { + struct is32fl3207 *chip; + struct led_classdev *led_cdev; + union { + struct led_classdev cdev; + struct led_classdev_mc mcdev; + }; + unsigned int channel; +}; + +struct is32fl3207 { + struct device *dev; + struct regmap *regmap; + struct gpio_desc *enable_gpio; + struct mutex lock; /* Serializes controller register updates. */ + unsigned long channels[BITS_TO_LONGS(IS32FL3207_NUM_CHANNELS)]; + u32 output_max_microamp; + unsigned int num_leds; + struct is32fl3207_led leds[] __counted_by(num_leds); +}; + +static int is32fl3207_parse_led_properties(struct is32fl3207 *chip, + struct fwnode_handle *fwnode, + unsigned int *max_brightness, + unsigned int *brightness) +{ + enum led_default_state default_state; + u32 value; + int ret; + + *max_brightness =3D IS32FL3207_MAX_BRIGHTNESS; + if (fwnode_property_present(fwnode, "max-brightness")) { + ret =3D fwnode_property_read_u32(fwnode, "max-brightness", &value); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read maximum brightness for %pfw\n", + fwnode); + if (!value || value > IS32FL3207_MAX_BRIGHTNESS) + return dev_err_probe(chip->dev, -EINVAL, + "invalid maximum brightness %u for %pfw\n", + value, fwnode); + + *max_brightness =3D value; + } + + value =3D *max_brightness; + if (fwnode_property_present(fwnode, "default-brightness")) { + ret =3D fwnode_property_read_u32(fwnode, "default-brightness", + &value); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read default brightness for %pfw\n", + fwnode); + if (value > *max_brightness) + return dev_err_probe(chip->dev, -EINVAL, + "invalid default brightness %u for %pfw\n", + value, fwnode); + } + + default_state =3D led_init_default_state_get(fwnode); + if (default_state =3D=3D LEDS_DEFSTATE_KEEP) + return dev_err_probe(chip->dev, -EINVAL, + "default state keep is not supported for %pfw\n", + fwnode); + + *brightness =3D default_state =3D=3D LEDS_DEFSTATE_ON ? value : LED_OFF; + return 0; +} + +static int is32fl3207_validate_component(struct is32fl3207 *chip, + struct fwnode_handle *fwnode) +{ + static const char * const unsupported[] =3D { + "default-brightness", + "default-state", + "max-brightness", + "retain-state-shutdown", + }; + unsigned int i; + + for (i =3D 0; i < ARRAY_SIZE(unsupported); i++) + if (fwnode_property_present(fwnode, unsupported[i])) + return dev_err_probe(chip->dev, -EINVAL, + "%s is not supported for component %pfw\n", + unsupported[i], fwnode); + + return 0; +} + +static int is32fl3207_write_channels(struct is32fl3207 *chip, + const struct mc_subled *subleds, + unsigned int num_channels) +{ + unsigned int i; + int ret; + + guard(mutex)(&chip->lock); + + for (i =3D 0; i < num_channels; i++) { + ret =3D regmap_write(chip->regmap, + IS32FL3207_REG_PWM_LOW(subleds[i].channel), + subleds[i].brightness); + if (ret) + return ret; + } + + return regmap_write(chip->regmap, IS32FL3207_REG_PWM_UPDATE, 0); +} + +static int is32fl3207_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct is32fl3207_led *led =3D container_of(cdev, struct is32fl3207_led, + cdev); + struct mc_subled subled =3D { + .brightness =3D brightness, + .channel =3D led->channel, + }; + + return is32fl3207_write_channels(led->chip, &subled, 1); +} + +static int is32fl3207_mc_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mcdev =3D lcdev_to_mccdev(cdev); + struct is32fl3207_led *led =3D container_of(mcdev, struct is32fl3207_led, + mcdev); + + led_mc_calc_color_components(mcdev, brightness); + + return is32fl3207_write_channels(led->chip, mcdev->subled_info, + mcdev->num_colors); +} + +static int is32fl3207_configure_channel(struct is32fl3207 *chip, + struct fwnode_handle *fwnode, + unsigned int *channel) +{ + u64 scaling; + u32 max_microamp; + u32 reg; + int ret; + + ret =3D fwnode_property_read_u32(fwnode, "reg", ®); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read channel for %pfw\n", fwnode); + + if (reg >=3D IS32FL3207_NUM_CHANNELS) + return dev_err_probe(chip->dev, -EINVAL, + "channel %u is out of range\n", reg); + + if (test_bit(reg, chip->channels)) + return dev_err_probe(chip->dev, -EINVAL, + "channel %u is used more than once\n", reg); + + ret =3D fwnode_property_read_u32(fwnode, "led-max-microamp", + &max_microamp); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read current limit for channel %u\n", + reg); + + if (!max_microamp || max_microamp > chip->output_max_microamp) + return dev_err_probe(chip->dev, -EINVAL, + "invalid current limit %u uA for channel %u\n", + max_microamp, reg); + + /* GCC is fixed at 0xff, so use each output's scaling register. */ + scaling =3D div_u64((u64)max_microamp * 256 * 256, + (u64)chip->output_max_microamp * + IS32FL3207_GLOBAL_CURRENT_MAX); + if (!scaling) + return dev_err_probe(chip->dev, -EINVAL, + "current limit %u uA is below channel %u resolution\n", + max_microamp, reg); + + scaling =3D min_t(u64, scaling, 0xff); + 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; + + ret =3D is32fl3207_parse_led_properties(chip, fwnode, + &max_brightness, &brightness); + if (ret) + return ret; + + if (fwnode_property_present(fwnode, "color")) { + ret =3D fwnode_property_read_u32(fwnode, "color", &color); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read color for %pfw\n", + fwnode); + if (color >=3D LED_COLOR_ID_MAX || color =3D=3D LED_COLOR_ID_MULTI || + color =3D=3D LED_COLOR_ID_RGB) + return dev_err_probe(chip->dev, -EINVAL, + "invalid single LED color %u\n", color); + } + + ret =3D is32fl3207_configure_channel(chip, fwnode, &led->channel); + if (ret) + return ret; + led->chip =3D chip; + led->led_cdev =3D &led->cdev; + led->cdev.brightness =3D brightness; + led->cdev.max_brightness =3D max_brightness; + led->cdev.brightness_set_blocking =3D is32fl3207_brightness_set; + + ret =3D is32fl3207_brightness_set(&led->cdev, brightness); + if (ret) + return ret; + + return devm_led_classdev_register_ext(chip->dev, &led->cdev, &init_data); +} + +static int is32fl3207_register_multicolor(struct is32fl3207 *chip, + struct fwnode_handle *fwnode, + struct is32fl3207_led *led) +{ + struct led_init_data init_data =3D { + .devicename =3D dev_name(chip->dev), + .devname_mandatory =3D true, + .fwnode =3D fwnode, + }; + struct mc_subled *subleds; + DECLARE_BITMAP(color_map, LED_COLOR_ID_MAX); + unsigned int max_brightness; + unsigned int brightness; + unsigned int count; + unsigned int i =3D 0; + u32 group_color; + u32 group_reg; + unsigned int first_channel =3D IS32FL3207_NUM_CHANNELS; + int ret; + + ret =3D is32fl3207_parse_led_properties(chip, fwnode, + &max_brightness, &brightness); + if (ret) + return ret; + + ret =3D fwnode_property_read_u32(fwnode, "color", &group_color); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read color for %pfw\n", fwnode); + + if (group_color !=3D LED_COLOR_ID_RGB && + group_color !=3D LED_COLOR_ID_MULTI) + return dev_err_probe(chip->dev, -EINVAL, + "invalid multicolor LED color %u\n", group_color); + + ret =3D fwnode_property_read_u32(fwnode, "reg", &group_reg); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read group index for %pfw\n", fwnode); + + count =3D fwnode_get_child_node_count(fwnode); + if (!count || count > LED_COLOR_ID_MAX) + return dev_err_probe(chip->dev, -EINVAL, + "invalid component count %u for %pfw\n", + count, fwnode); + + subleds =3D devm_kcalloc(chip->dev, count, sizeof(*subleds), GFP_KERNEL); + if (!subleds) + return -ENOMEM; + bitmap_zero(color_map, LED_COLOR_ID_MAX); + + fwnode_for_each_child_node_scoped(fwnode, child) { + u32 color; + + ret =3D is32fl3207_validate_component(chip, child); + if (ret) + return ret; + + ret =3D fwnode_property_read_u32(child, "color", &color); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read color for %pfw\n", + child); + + if (color >=3D LED_COLOR_ID_MAX || color =3D=3D LED_COLOR_ID_MULTI || + color =3D=3D LED_COLOR_ID_RGB) + return dev_err_probe(chip->dev, -EINVAL, + "invalid component color %u\n", color); + if (test_and_set_bit(color, color_map)) + return dev_err_probe(chip->dev, -EINVAL, + "component color %u is used more than once\n", + color); + + ret =3D is32fl3207_configure_channel(chip, child, + &subleds[i].channel); + if (ret) + return ret; + + subleds[i].color_index =3D color; + subleds[i].intensity =3D max_brightness; + subleds[i].max_intensity =3D 0; + first_channel =3D min(first_channel, subleds[i].channel); + i++; + } + + if (group_reg !=3D first_channel) + return dev_err_probe(chip->dev, -EINVAL, + "group index %u does not match first channel %u\n", + group_reg, first_channel); + + led->chip =3D chip; + led->led_cdev =3D &led->mcdev.led_cdev; + led->mcdev.num_colors =3D count; + led->mcdev.subled_info =3D subleds; + led->mcdev.led_cdev.brightness =3D brightness; + led->mcdev.led_cdev.max_brightness =3D max_brightness; + led->mcdev.led_cdev.brightness_set_blocking =3D + is32fl3207_mc_brightness_set; + + led_mc_calc_color_components(&led->mcdev, brightness); + + ret =3D is32fl3207_write_channels(chip, subleds, count); + if (ret) + return ret; + + return devm_led_classdev_multicolor_register_ext(chip->dev, &led->mcdev, + &init_data); +} + +static int is32fl3207_hw_init(struct is32fl3207 *chip) +{ + u8 scaling[IS32FL3207_NUM_CHANNELS]; + u8 pwm[2 * IS32FL3207_NUM_CHANNELS] =3D { }; + int ret; + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, + IS32FL3207_CONTROL_ENABLE); + if (ret) + return ret; + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_RESET, 0); + if (ret) + return ret; + usleep_range(200, 300); + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, + IS32FL3207_CONTROL_ENABLE); + if (ret) + return ret; + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_GLOBAL_CURRENT, + IS32FL3207_GLOBAL_CURRENT_MAX); + if (ret) + return ret; + + memset(scaling, 0xff, sizeof(scaling)); + ret =3D regmap_bulk_write(chip->regmap, IS32FL3207_REG_SCALING(0), + scaling, sizeof(scaling)); + if (ret) + return ret; + + ret =3D regmap_bulk_write(chip->regmap, IS32FL3207_REG_PWM_LOW(0), pwm, + sizeof(pwm)); + if (ret) + return ret; + + return regmap_write(chip->regmap, IS32FL3207_REG_PWM_UPDATE, 0); +} + +static void is32fl3207_disable(void *data) +{ + struct is32fl3207 *chip =3D data; + + guard(mutex)(&chip->lock); + regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, 0); + if (chip->enable_gpio) + gpiod_set_value_cansleep(chip->enable_gpio, 0); +} + +static const struct regmap_config is32fl3207_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 8, + .max_register =3D IS32FL3207_REG_RESET, +}; + +static int is32fl3207_probe(struct i2c_client *client) +{ + struct device *dev =3D &client->dev; + struct is32fl3207 *chip; + unsigned int count; + unsigned int i =3D 0; + u32 riset_ohms; + int ret; + + count =3D device_get_child_node_count(dev); + if (!count || count > IS32FL3207_NUM_CHANNELS) + return dev_err_probe(dev, -EINVAL, + "invalid LED count %u\n", count); + + chip =3D devm_kzalloc(dev, struct_size(chip, leds, count), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev =3D dev; + chip->num_leds =3D count; + i2c_set_clientdata(client, chip); + + ret =3D device_property_read_u32(dev, "issi,riset-ohms", &riset_ohms); + if (ret) + return dev_err_probe(dev, ret, "failed to read RISET value\n"); + + if (riset_ohms < 2000) + return dev_err_probe(dev, -EINVAL, + "RISET value %u is below 2000 ohms\n", + riset_ohms); + + chip->output_max_microamp =3D div_u64(IS32FL3207_CURRENT_NUMERATOR, + riset_ohms); + if (!chip->output_max_microamp) + return dev_err_probe(dev, -EINVAL, + "RISET value %u is too large\n", riset_ohms); + + ret =3D devm_regulator_get_enable_optional(dev, "vcc"); + if (ret && ret !=3D -ENODEV) + return dev_err_probe(dev, ret, "failed to enable VCC regulator\n"); + + chip->enable_gpio =3D devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_HIGH); + if (IS_ERR(chip->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(chip->enable_gpio), + "failed to get enable GPIO\n"); + + if (chip->enable_gpio) + usleep_range(1000, 2000); + + chip->regmap =3D devm_regmap_init_i2c(client, &is32fl3207_regmap_config); + if (IS_ERR(chip->regmap)) + return dev_err_probe(dev, PTR_ERR(chip->regmap), + "failed to allocate register map\n"); + + ret =3D devm_mutex_init(dev, &chip->lock); + if (ret) + return ret; + + ret =3D devm_add_action_or_reset(dev, is32fl3207_disable, chip); + if (ret) + return ret; + + ret =3D is32fl3207_hw_init(chip); + if (ret) + return dev_err_probe(dev, ret, "failed to initialize controller\n"); + + device_for_each_child_node_scoped(dev, child) { + struct is32fl3207_led *led =3D &chip->leds[i]; + + if (fwnode_get_child_node_count(child)) + ret =3D is32fl3207_register_multicolor(chip, child, led); + else + ret =3D is32fl3207_register_single(chip, child, led); + if (ret) + return ret; + + i++; + } + + return 0; +} + +static void is32fl3207_shutdown(struct i2c_client *client) +{ + struct is32fl3207 *chip =3D i2c_get_clientdata(client); + bool retain_state =3D false; + unsigned int i; + + for (i =3D 0; i < chip->num_leds; i++) + if (chip->leds[i].led_cdev->flags & LED_RETAIN_AT_SHUTDOWN) { + retain_state =3D true; + break; + } + + if (!retain_state) { + is32fl3207_disable(chip); + return; + } + + for (i =3D 0; i < chip->num_leds; i++) { + struct led_classdev *cdev =3D chip->leds[i].led_cdev; + + if (cdev->flags & LED_RETAIN_AT_SHUTDOWN) + continue; + if (cdev->flags & LED_MULTI_COLOR) + is32fl3207_mc_brightness_set(cdev, LED_OFF); + else + is32fl3207_brightness_set(cdev, LED_OFF); + } +} + +static const struct of_device_id is32fl3207_of_match[] =3D { + { .compatible =3D "issi,is32fl3207" }, + { } +}; +MODULE_DEVICE_TABLE(of, is32fl3207_of_match); + +static const struct i2c_device_id is32fl3207_id[] =3D { + { .name =3D "is32fl3207" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, is32fl3207_id); + +static struct i2c_driver is32fl3207_driver =3D { + .driver =3D { + .name =3D "is32fl3207", + .of_match_table =3D is32fl3207_of_match, + }, + .probe =3D is32fl3207_probe, + .shutdown =3D is32fl3207_shutdown, + .id_table =3D is32fl3207_id, +}; +module_i2c_driver(is32fl3207_driver); + +MODULE_AUTHOR("Ahmad Byagowi "); +MODULE_DESCRIPTION("Lumissil IS32FL3207 LED controller driver"); +MODULE_LICENSE("GPL"); --=20 2.50.1 (Apple Git-155) From nobody Fri Oct 2 07:45:25 2026 Received: from mail-pj1-f46.google.com (mail-pj1-f46.google.com [209.85.216.46]) (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 83A2042EEDA for ; Mon, 3 Aug 2026 20:50:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785790234; cv=none; b=VX5QhRMeCaju/05Y6feTKyxTI4gKg5q5ESQ8pFo7Cj1wgzlkM65MOAXqxH70DKetLjTBGXsDf3MgFFaMOY2exwFiKF2iQiWiPH67os4PxJPni4YczECkn6jnnbCZTihpqkHMS5kpT7yNapoJ/gJZDpLEaYt4t9FK2RslplPS4lY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785790234; c=relaxed/simple; bh=Mv2fB5QTMgzygfKi4oX9eQwPWNNkjLy2vCUnRHb4K0E=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=YZl6sJYUM9s2m6t1Z6x23kzeULOoAR6fttFQF9nMMz7lUn/ALz0Sr0HtDi1Kl2oYYqvP0EiG5CufkLdhgON+wJXmN8UapBWN1W8rC4MjiKTEdz4dyxLKMqNcSCBynO/WCM+HJxoE3IViqHd79rtxjsUSlR5y+1/Ha3Ph8aN49b4= 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=R5/NJ34K; arc=none smtp.client-ip=209.85.216.46 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="R5/NJ34K" Received: by mail-pj1-f46.google.com with SMTP id 98e67ed59e1d1-384930ca5e2so3258460a91.3 for ; Mon, 03 Aug 2026 13:50:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785790230; x=1786395030; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=jXjf3n6y2o2sUp9ziyNWD3SU5x1Q1/QeHjG5pG2QUDY=; b=R5/NJ34KbgbSSSwP/mL7/dZ/mxx+64Oj39jF8LRMthrCM21JJAEe9emil60hag8QPu nVAiKqVQ5JkM893S+WpcxRS8TZpKxim9FquWzc9SyaIB8Ijh3lXCr4+vDyq5CerCcgrw LZg75+PfFwImIuT/JAOZMFSdKyMb1rhviawBF7V73lMvl2JUbcNchdVxqde8bhe1tbj5 rrxbhvvh1dTpyHdVhQoC6rY6jHOmNsDuR14gQXJqsZVfEK4Oly3LWWQz68rIgapkToDf vfDnp5I4EsbeeViocd1l3xMKGjNGzs4YBBUPH/xeeWOd6tLHBhih0ZjLxtZaiDDd+FLk C2+w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785790230; x=1786395030; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=jXjf3n6y2o2sUp9ziyNWD3SU5x1Q1/QeHjG5pG2QUDY=; b=mc2M0uh31lVu+OmaaPx6o7yKBY3Nf6EnhPRkNyCXUl5Gn7cJVSgP6r2i5eVY++97OP qpeQqie4+2e6pbbsVu7E2SndY2KsL5JkFMIuwE78z/4aMpp0FzlFlWj1QQsEeDuS66fs kRPJUDvBBowjJYWxrFFZNrR+B7FSRIVfPw8nUCuHAi14uVSusgSoift5NtAg7d0nXQ+M kRFoVm5XgBAMmgHxsBCtyU1L9ghPYl/np4+RBIYdoNAd18hyXBoumXindeJG6qWZKZN/ S512HPodIAPQqWWplLg+GUavCGABe+Ct0RYRBI878FiZovWRqiObQE0sJGX6rKQa7YA6 NXhA== X-Forwarded-Encrypted: i=1; AHgh+RrrBUZps3vdbXx4ELlTX17eEVgF8DmtgQ2kqU42Z1BTIuTbGeoUgwdfimigcBMe5vTdyDeGwWfuBN+0zro=@vger.kernel.org X-Gm-Message-State: AOJu0YwRZcl7m3y2FZ6R81/7+bofrItnR1FuXkPxStORcw9nTvQBa3AZ /XDLiQUb+CMEbj5tUlYynbfxAoLoyqNGhi11gUFU7PvPWMWFFSNh3yC7 X-Gm-Gg: AR+sD126ekOMz7/PCZof44ErOAHv+3CzkCUY1161UJAkhSEiYZLD4FGp32hK6qT+j9/ /qcN52B5eASyJBWqWANSVjocpEhGvNJn1171JbCRKmhqYt9QapgUMhkkyS8CGyao8ub9DY2JomS 3WTlLYwGqPyNjKh8yzDCJnbXZj3IHXUnWLLdvbw38+1lumGFwC6V5T9zzg4gwPFRXBdX/2LRtdd KXFE9jKpOB9UPl/dF0yGMJpTOmKIUlpR61KnzRqPLTDHKz38obgFRUPAfjMrrRg/s6T8mJL/5sM 46kdS+SlHrydo69/Kvba7UJ4osKltJMNCiQEoFDN1GCLBpyl7iF899frgzCWiQEPK24CSXAG4Au 3Mwl2VmhcKIJGuXEEcpcPVSD0I262lBR1sDXkxoeY+NpS2Dl/ztv6hT+8UVwCO6scRu1oAOgjDe gdObe0nkkjuWzdwXFNIytC9T7qSLvIy+Xm/WJYr09Up0Qtr0oZKSGB2URflfApoo56jtNLICfZE W5oCQ== X-Received: by 2002:a17:90b:53c7:b0:38d:a150:ee04 with SMTP id 98e67ed59e1d1-38fbc4ef153mr9786906a91.30.1785790229767; Mon, 03 Aug 2026 13:50:29 -0700 (PDT) Received: from Mac.net ([12.162.234.74]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-13fab4d10bbsm33112324c88.11.2026.08.03.13.50.26 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Mon, 03 Aug 2026 13:50:28 -0700 (PDT) From: Ahmad Byagowi To: linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, 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-hardening@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFC net-next v2 3/6] i2c: mux: Propagate software nodes to channel adapters Date: Mon, 3 Aug 2026 13:50:08 -0700 Message-ID: <20260803205011.1249-4-ahmadexp@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20260803205011.1249-1-ahmadexp@gmail.com> References: <20260803205011.1249-1-ahmadexp@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Device Tree channel nodes are associated with the adapters created by i2c-mux, but equivalent software-node descriptions are not. Find the software-node child whose reg value matches the channel and assign it to the new adapter. Keep a reference for the adapter lifetime so downstream clients can find the channel by firmware node. Signed-off-by: Ahmad Byagowi --- drivers/i2c/i2c-mux.c | 55 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c index 681a201c2..1e587ea4e 100644 --- a/drivers/i2c/i2c-mux.c +++ b/drivers/i2c/i2c-mux.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include =20 @@ -264,6 +265,49 @@ static const struct i2c_lock_operations i2c_parent_loc= k_ops =3D { .unlock_bus =3D i2c_parent_unlock_bus, }; =20 +static struct fwnode_handle * +i2c_mux_get_channel_swnode(struct i2c_mux_core *muxc, u32 chan_id) +{ + struct fwnode_handle *dev_node =3D dev_fwnode(muxc->dev); + struct fwnode_handle *mux_node, *child =3D NULL; + u32 reg; + + if (!is_software_node(dev_node)) + return NULL; + + if (muxc->arbitrator) + mux_node =3D fwnode_get_named_child_node(dev_node, "i2c-arb"); + else if (muxc->gate) + mux_node =3D fwnode_get_named_child_node(dev_node, "i2c-gate"); + else + mux_node =3D fwnode_get_named_child_node(dev_node, "i2c-mux"); + + if (mux_node) { + /* A "reg" property indicates an old-style firmware entry. */ + if (!fwnode_property_read_u32(mux_node, "reg", ®)) { + fwnode_handle_put(mux_node); + mux_node =3D NULL; + } + } + + if (!mux_node) + mux_node =3D fwnode_handle_get(dev_node); + else if (muxc->arbitrator || muxc->gate) + child =3D fwnode_handle_get(mux_node); + + if (!child) { + fwnode_for_each_child_node(mux_node, child) { + if (fwnode_property_read_u32(child, "reg", ®)) + continue; + if (chan_id =3D=3D reg) + break; + } + } + + fwnode_handle_put(mux_node); + return child; +} + int i2c_mux_add_adapter(struct i2c_mux_core *muxc, u32 force_nr, u32 chan_id) { @@ -364,6 +408,12 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, =20 priv->adap.dev.of_node =3D child; of_node_put(mux_node); + } else { + struct fwnode_handle *fwnode; + + fwnode =3D i2c_mux_get_channel_swnode(muxc, chan_id); + if (fwnode) + device_set_node(&priv->adap.dev, fwnode); } =20 /* @@ -408,6 +458,8 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, return 0; =20 err_free_priv: + if (is_software_node(dev_fwnode(&priv->adap.dev))) + fwnode_handle_put(dev_fwnode(&priv->adap.dev)); kfree(priv); return ret; } @@ -420,6 +472,7 @@ void i2c_mux_del_adapters(struct i2c_mux_core *muxc) while (muxc->num_adapters) { struct i2c_adapter *adap =3D muxc->adapter[--muxc->num_adapters]; struct i2c_mux_priv *priv =3D adap->algo_data; + struct fwnode_handle *fwnode =3D dev_fwnode(&adap->dev); struct device_node *np =3D adap->dev.of_node; =20 muxc->adapter[muxc->num_adapters] =3D NULL; @@ -430,6 +483,8 @@ void i2c_mux_del_adapters(struct i2c_mux_core *muxc) =20 sysfs_remove_link(&priv->adap.dev.kobj, "mux_device"); i2c_del_adapter(adap); + if (is_software_node(fwnode)) + fwnode_handle_put(fwnode); of_node_put(np); kfree(priv); } --=20 2.50.1 (Apple Git-155) From nobody Fri Oct 2 07:45:25 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 CBBBF4334A2 for ; Mon, 3 Aug 2026 20:50:33 +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=1785790235; cv=none; b=KhFcIVVCOG3qswKbu7QhqcnF4YaqMnc6WWtpSEu0lNWNTlwjJFjfM+ZNYpNmi90w/bzdQahIK+bt8tWsf7bdouUtE2WDVu4iFW+0Vhx/AzJ7pQqrBwdJcPCNzVWhl0tZ237L/iJF0gGNSYZ1Zvm9hqMHk3Opn+oxOww4m/OICic= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785790235; c=relaxed/simple; bh=PuTxehw/+oG0R+AUeP1S0jJfWF/Fu/L6ssD+zcOM2zY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=rrvj0WkUqykNYDMXxN1cT3+l/ZL0zaeiegGHePSNkinVfEqH37zvc06fWQUIMVBZsfkAvS9G7GHazwQu1bayEWXQZjtacvou20FHwfYZ4dRRJyvIpNM57MSRPoEfXulVbwKEI5RtXbayhi1PthghJeyxc7Ekr1KaW2O+t1/7mYQ= 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=VblUMTIK; 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="VblUMTIK" Received: by mail-pj1-f52.google.com with SMTP id 98e67ed59e1d1-38e3efab7e0so267608a91.0 for ; Mon, 03 Aug 2026 13:50:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785790233; x=1786395033; 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=gcnfcYJ16OlC/FCeHSIUYdGv8WlSuwcbHtR9yqY3EXk=; b=VblUMTIKXDgt/FYOzdKV8CxFYreBf1imFvjbTciNdXpEVoYT6evIVDoFVcVa8TxNLb Os8VC7HNGV1zToIQuTx38zg3bOGGdUhbdKsMYiNdGYbRVnRDq/nzrVieSr4WAhB5a62j WGUBH9SnpdQRu2JTsrIK0oKeqb+uC6im13PGHNusT1nuDdvF6KOi/Hx4aZmTc9uJHGjF wWNXpBTWY2OtEnigx80RLf4xXLV56ZHuIhgWKiY/v7RXoLlhuxj1yiKuMrxF289M5gEs P/qWGkkxnVh8BNrMWlwHUrfNy4+4Z+nkfbKvCh6NTv7J6gbvtGh3KzKhscZluPuKuDmA Wc0w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785790233; x=1786395033; 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=gcnfcYJ16OlC/FCeHSIUYdGv8WlSuwcbHtR9yqY3EXk=; b=Qgd589UKqkRWlOGjJjymG9g39T+uujTm1jfSjKJ7RS0sjXw2v8h0H/Kd7ZEWkn3uyD csTc9Sszm/njUzHiv97PTbppQLKnvmzXYMxp2U2HWr/RM0yb32QJ84CaqDd5Vj06jeWO Eu/m/Rw3Hht09iZQKYtdGV8+5saPtpHohqbvFYUv8AOKMuBKvqTiuEFbjrdVSV30CVJn G9rcnzsOBvcrSfgy9gLBm7iZlvedtX++Z2DHQuUju507CZ04VsrWlW6DeZCpDtu+D2O+ Fy6NQ1OcvhRST329VSfLL2OQV8dYrFFAv468FJrDeey89QXQA3HnsGG9n0LPSv8wikj1 +T8A== X-Forwarded-Encrypted: i=1; AHgh+RrpDR8YwoDnQidUOBYJ4gfy5kgpP6m4KuHfaHHk7Y1DmD0sMXFfqAsu7303mxRPKM86j76qcS52lZp0lFM=@vger.kernel.org X-Gm-Message-State: AOJu0YyvOQRpKnpQ/SBUW76BytWl9ur2/dunKeyFLbvJvTaraOghICDB vFyawYqpONTSmQTqRQyvWM2od+fbq0cJsEfZbirjQ4aEdfYxvhG9pU3r X-Gm-Gg: AR+sD10g4dJKlg0GHwDG61Tw1EEYWXxD+p9GUr2E4NXXJyuHQxb79FAxTEcQCPWJpGe 00nSSZlP5ZmcKaIzYzMuI2e5Jbm9EvN0VfbP4Dj5xZbPeOp537aNWmIJA60VbJlRmvyNgNPXXxm NacVsVNtz9V4N6mQvw4Dj7BUO26m/hkKvYCAqDOTfQiXU9sJVzR3qtscuYCR32vzOFwLFeHN4ZB 7LbsliVwn72zDFhHxLQjDxBUYT/ZzK60uhXLRMDtdzH04FYsNLNzbinvBDmKZcZ+q73XraKuqLU RnK3RFO40IH/jzM9XOjq0H975NKx772FyJP6d7PbWi4XttcLV8AHLL21lgQ5IFaJxGOro6yJcqQ wW1V2rCOADz82bkTytGk56qFYhG4NtmAbawGXJg9N3QJ4/SAM8yaifdK+DHu+GAeBAESlqdy2cK 3eD8UNJHyCbBc6rrrgMPD3g6HzwjeWfaMKKvNw3JE5u06FbIrRuHXutBuD4ZFatWfvYS8= X-Received: by 2002:a17:90a:d885:b0:38e:9784:dd47 with SMTP id 98e67ed59e1d1-38febf7117amr885915a91.1.1785790233222; Mon, 03 Aug 2026 13:50:33 -0700 (PDT) Received: from Mac.net ([12.162.234.74]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-13fab4d10bbsm33112324c88.11.2026.08.03.13.50.29 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Mon, 03 Aug 2026 13:50:32 -0700 (PDT) From: Ahmad Byagowi To: linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, 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-hardening@vger.kernel.org, linux-kernel@vger.kernel.org, stable@vger.kernel.org Subject: [RFC net-next v2 4/6] ptp: ocp: Validate EEPROM board IDs Date: Mon, 3 Aug 2026 13:50:09 -0700 Message-ID: <20260803205011.1249-5-ahmadexp@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20260803205011.1249-1-ahmadexp@gmail.com> References: <20260803205011.1249-1-ahmadexp@gmail.com> 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 EEPROM board ID is a fixed 13-byte field. It is stored without room for a terminator and passed to devlink as a C string. An erased EEPROM therefore exposes 0xff bytes and can make devlink read beyond the field while formatting board.id. Reserve a trailing byte, classify erased and malformed contents, and publish board.id only when the field contains printable text with valid padding. Continue reporting the serial number when the board ID is absent. Fixes: 0cfcdd1ebcfe1a9b262f6ad8419580720dc843c4 ("ptp: ocp: add nvmem inter= face for accessing eeprom") Cc: stable@vger.kernel.org Signed-off-by: Ahmad Byagowi --- drivers/ptp/ptp_ocp.c | 69 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 35e911f1a..cec936bd1 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -347,6 +347,13 @@ struct ptp_ocp_serial_port { #define OCP_SIGNAL_NUM 4 #define OCP_FREQ_NUM 4 =20 +enum ptp_ocp_board_id_state { + OCP_BOARD_ID_UNREAD, + OCP_BOARD_ID_VALID, + OCP_BOARD_ID_ERASED, + OCP_BOARD_ID_INVALID, +}; + enum { PORT_GNSS, PORT_GNSS2, @@ -401,8 +408,9 @@ struct ptp_ocp { bool fw_loader; u8 fw_tag; u16 fw_version; - u8 board_id[OCP_BOARD_ID_LEN]; + char board_id[OCP_BOARD_ID_LEN + 1]; u8 serial[OCP_SERIAL_LEN]; + enum ptp_ocp_board_id_state board_id_state; bool has_eeprom_data; u32 pps_req_map; int flash_start; @@ -472,18 +480,23 @@ struct ptp_ocp_eeprom_map { .len =3D sizeof_field(struct ptp_ocp, member), \ .bp_offset =3D offsetof(struct ptp_ocp, member) =20 +#define EEPROM_ENTRY_LEN(addr, member, entry_len) \ + .off =3D addr, \ + .len =3D entry_len, \ + .bp_offset =3D offsetof(struct ptp_ocp, member) + #define BP_MAP_ENTRY_ADDR(bp, map) ({ \ (void *)((uintptr_t)(bp) + (map)->bp_offset); \ }) =20 static struct ptp_ocp_eeprom_map fb_eeprom_map[] =3D { - { EEPROM_ENTRY(0x43, board_id) }, + { EEPROM_ENTRY_LEN(0x43, board_id, OCP_BOARD_ID_LEN) }, { EEPROM_ENTRY(0x00, serial), .tag =3D "mac" }, { } }; =20 static struct ptp_ocp_eeprom_map art_eeprom_map[] =3D { - { EEPROM_ENTRY(0x200 + 0x43, board_id) }, + { EEPROM_ENTRY_LEN(0x200 + 0x43, board_id, OCP_BOARD_ID_LEN) }, { EEPROM_ENTRY(0x200 + 0x63, serial) }, { } }; @@ -1969,6 +1982,52 @@ ptp_ocp_nvmem_device_put(struct nvmem_device **nvmem= p) *nvmemp =3D NULL; } =20 +static enum ptp_ocp_board_id_state +ptp_ocp_classify_board_id(char *board_id) +{ + bool all_zero =3D true; + bool all_ones =3D true; + bool terminated =3D false; + unsigned int i; + + board_id[OCP_BOARD_ID_LEN] =3D '\0'; + for (i =3D 0; i < OCP_BOARD_ID_LEN; i++) { + u8 value =3D board_id[i]; + + all_zero &=3D value =3D=3D 0; + all_ones &=3D value =3D=3D 0xff; + } + + if (all_zero || all_ones) { + board_id[0] =3D '\0'; + return OCP_BOARD_ID_ERASED; + } + + for (i =3D 0; i < OCP_BOARD_ID_LEN; i++) { + u8 value =3D board_id[i]; + + if (terminated) { + if (value) + goto invalid; + continue; + } + + if (!value) { + terminated =3D true; + continue; + } + if (value < 0x20 || value > 0x7e) + goto invalid; + } + + if (board_id[0]) + return OCP_BOARD_ID_VALID; + +invalid: + board_id[0] =3D '\0'; + return OCP_BOARD_ID_INVALID; +} + static void ptp_ocp_read_eeprom(struct ptp_ocp *bp) { @@ -2001,6 +2060,7 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp) goto fail; } =20 + bp->board_id_state =3D ptp_ocp_classify_board_id(bp->board_id); bp->has_eeprom_data =3D true; =20 out: @@ -2177,6 +2237,9 @@ ptp_ocp_devlink_info_get(struct devlink *devlink, str= uct devlink_info_req *req, if (err) return err; =20 + if (bp->board_id_state !=3D OCP_BOARD_ID_VALID) + return 0; + err =3D devlink_info_version_fixed_put(req, DEVLINK_INFO_VERSION_GENERIC_BOARD_ID, bp->board_id); --=20 2.50.1 (Apple Git-155) From nobody Fri Oct 2 07:45:25 2026 Received: from mail-pj1-f42.google.com (mail-pj1-f42.google.com [209.85.216.42]) (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 CC0C54399CF for ; Mon, 3 Aug 2026 20:50:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785790241; cv=none; b=RsQIV5HdLkcdUVaGJo1Vs2a5/JzZwHLowwx7Z1SEa2QNaN5ZG/L7ydFtyIBZso8weZhn7YYobliEg8vpxw9NduMqb7G9fuKHeANcaTDXVSrg4ge6CGgfNQcm52MEXl3zYk1erKgTwxmYUCZb+wi+so5juCl+EtV2KNKLFBBiL9E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785790241; c=relaxed/simple; bh=dmP+q87qicjyW18ozyL+ogXe7/5/GdMXTvpy0iRKLrU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fHnAc0ba14fbrBz6Iipop84YFglK8hjRgNhL9+aGn/lsyCQoSbmbAc6Rsa0eOpDBSc8eZqJ1aQSIYtltgzRbPfhgCfu7lnbHNRAkLAdkYSqUo8wIPPBvogpTXl7c0DihllRNaR+6Xy/lOt2tSLT53gnyHYLqgPWk/WtN09RlzP4= 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=QP9+TDFW; arc=none smtp.client-ip=209.85.216.42 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="QP9+TDFW" Received: by mail-pj1-f42.google.com with SMTP id 98e67ed59e1d1-38e08baf860so3375409a91.2 for ; Mon, 03 Aug 2026 13:50:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785790237; x=1786395037; 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=qqCB45N3f0im5p2UcDZQfhCmgK03i2WHE7t+nKif+84=; b=QP9+TDFWwpnMScPIEKDq0JratEqHl/LJDADQzlHjQdwfuCeOHVsjg4L8sdexItlnca 8cJKUlC3WRsMdsgO5rM0lC8d5DT2LrM111TT0BOjeY6MVabE8clw9FpolW2cFIXSDGk7 aO2TB9xyFEJ5JLyWx3lVK9A+LXhbvBQsfgaym07z3EayMqqa4Czr+yWIBhXxPmAUbbUt U4t82dhF0+4R/X6MRY5CPsSFZ+J+UXYzNbY5AJIKfuhqrUVzmhojH7htE+lPksfDZWEp oLjEEF5hTJvRyTo6pKRaeQzBOTTeO+Rwkn5TYWhJbZDSW7xZIGt2QSBxSIhzlZe6hkJI zkog== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785790237; x=1786395037; 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=qqCB45N3f0im5p2UcDZQfhCmgK03i2WHE7t+nKif+84=; b=igZ8dUsFpgBZO+Y6/G7uo+4H0yWcfCYBx4cg/VduOMi+2bqHdjzTYa+9Jch1dgzYdE 8aIGMAKcGwxKAZZaGFTCJMZaWEorMFLl07SNMr4igIYRnXBC7HpJO/KlDtwzUzEMznPK e+LZEUFUsj7X84+9jQV6Gko+72/4EbaRVf3kzulgPwfPRybjZE5KwG9ZDhMFDDs3IGyf CWwR0JgirX7Lvyhg39XHwv8wO0o3734Fe6CSW0pkHwlnXtP7ac+bjz8Avb5XBkbRUl4k KjWIDrH2wWzVxGlTJ5SblJqI1Ctmw/doBvnNVGgugc0fnLZR0pVGXsVmEhwzEKD3u9JO etYQ== X-Forwarded-Encrypted: i=1; AHgh+RqjQUeTUhMUm5DaeknsjzeBhE4CdN58Cb1TcOPNCGADL2qM//Uw3KU0U51Y3JXanpbCZjjTNIuq/LHE6Qk=@vger.kernel.org X-Gm-Message-State: AOJu0YwhkQZ4FtnbDw8M2imsprAWzWoQK/N9PC24QWYPx8t7zGn5fJLf NbMs4oPkYR3cVl5gRRkI6p3xsk4RPZ6jadgYHKCiYvLYf/Aany50u9hN X-Gm-Gg: AR+sD10FMgZtOeqE0+OaPxOfGGLeroO9L1M7zSeNlvdR+STtyEwZYA5Dsd1qSB95KHG ok/tObFcngM0SrrTXdlETNLvarws213I2KjOWf5S4SgPBj7XiRKQDBm2uSUWuXYJfeB3Lf9a5gq +8Z3aqnRgPu6ldwOwMMTkaRzaRq2C74PNmovLXTZ6t730bWLLxMbFSSeHxFYUxtejOznvaBw/W8 IoSlP47Jjk2xR4bGTjdngUSKtuNrfnvBrzHm68RExBtDilhzUTzvbC9Hq69PURSmj505s97gBPs 5P2shsREusRfyiRBmbtT+FCKq7YkEv0mAvBPpcLWmcGtOUACnOlNWVXZqv0lGyqNST/nKkceZ+k Zc3NE9gDQ7md+/5jGw/txkArgBm3IfG7fdbSTduWdfvbNR7BHcHL51q2ImTYXmBpzkQy7KEyW0W g7RksDEKaIh9QAuNENCFGErr5Iods10aa3Ln9X2AGm9bTOhbA62JcCFBXtDJ0Ua4GcXChl X-Received: by 2002:a17:90b:3f44:b0:38f:decc:33c7 with SMTP id 98e67ed59e1d1-38fdecc3428mr3756451a91.42.1785790236904; Mon, 03 Aug 2026 13:50:36 -0700 (PDT) Received: from Mac.net ([12.162.234.74]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-13fab4d10bbsm33112324c88.11.2026.08.03.13.50.33 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Mon, 03 Aug 2026 13:50:36 -0700 (PDT) From: Ahmad Byagowi To: linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, 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-hardening@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFC net-next v2 5/6] ptp: ocp: Add R4006 I2C peripheral topology Date: Mon, 3 Aug 2026 13:50:10 -0700 Message-ID: <20260803205011.1249-6-ahmadexp@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20260803205011.1249-1-ahmadexp@gmail.com> References: <20260803205011.1249-1-ahmadexp@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" R4006 cards place a PCA9546 mux behind the FPGA I2C controller. The mux exposes three LM75B temperature sensors, an SHT3x humidity sensor, an ICP10100 pressure sensor, and an IS32FL3207 controller for the GNSS and SMA LEDs. Describe the per-card topology with software nodes and instantiate standard I2C clients after their adapters appear. Use a reusable board profile selected only when the validated EEPROM ID begins with "R4006". PCI IDs are shared by multiple PCB revisions and cannot safely select LED wiring. Leave the channel containing the BNO08x empty because no upstream driver exists. Retry asynchronous population with a bounded budget. Serialize population against controller and mux driver bind/unbind, remove the mux when the root adapter disappears, and support adapter reappearance. Limit the existing i2c sysfs link to the root adapter. Signed-off-by: Ahmad Byagowi --- drivers/ptp/ptp_ocp.c | 736 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 693 insertions(+), 43 deletions(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index cec936bd1..70ba47096 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -21,10 +21,14 @@ #include #include #include +#include #include +#include #include #include =20 +#include + #define PCI_DEVICE_ID_META_TIMECARD 0x0400 =20 #define PCI_VENDOR_ID_CELESTICA 0x18d4 @@ -347,6 +351,17 @@ struct ptp_ocp_serial_port { #define OCP_SIGNAL_NUM 4 #define OCP_FREQ_NUM 4 =20 +#define OCP_I2C_MUX_CHANNELS 4 +#define OCP_I2C_MAX_SENSOR_COUNT 5 +#define OCP_I2C_MAX_LED_COUNT 5 +#define OCP_I2C_MAX_LED_COMPONENT_COUNT (3 * OCP_I2C_MAX_LED_COUNT) +#define OCP_I2C_MUX_NAME_LEN 32 +#define OCP_I2C_RETRY_MAX 10 +#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) + enum ptp_ocp_board_id_state { OCP_BOARD_ID_UNREAD, OCP_BOARD_ID_VALID, @@ -354,6 +369,51 @@ enum ptp_ocp_board_id_state { OCP_BOARD_ID_INVALID, }; =20 +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_address; +}; + +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, @@ -410,6 +470,7 @@ struct ptp_ocp { u16 fw_version; char board_id[OCP_BOARD_ID_LEN + 1]; u8 serial[OCP_SERIAL_LEN]; + struct mutex eeprom_lock; /* Serializes EEPROM discovery. */ enum ptp_ocp_board_id_state board_id_state; bool has_eeprom_data; u32 pps_req_map; @@ -422,6 +483,14 @@ 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 delayed_work i2c_work; + struct notifier_block i2c_notifier; + atomic_t i2c_retry_count; + bool i2c_root_present; + bool i2c_resources_ready; + bool i2c_notifier_registered; int signals_nr; int freq_in_nr; }; @@ -452,6 +521,8 @@ static int ptp_ocp_signal_from_perout(struct ptp_ocp *b= p, int gen, struct ptp_perout_request *req); static int ptp_ocp_signal_enable(void *priv, u32 req, bool enable); static int ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_= nr); +static int ptp_ocp_i2c_notifier_call(struct notifier_block *nb, + unsigned long action, void *data); =20 static int ptp_ocp_art_board_init(struct ptp_ocp *bp, struct ocp_resource = *r); =20 @@ -501,6 +572,65 @@ static struct ptp_ocp_eeprom_map art_eeprom_map[] =3D { { } }; =20 +/* Channel 3's BNO08x at 0x4a has no upstream Linux driver, so omit it. */ +static const struct ptp_ocp_i2c_device ptp_ocp_r4006_sensors[] =3D { + { "temperature@48", "national,lm75b", "lm75b", 0, 0x48 }, + { "temperature@49", "national,lm75b", "lm75b", 0, 0x49 }, + { "temperature@4a", "national,lm75b", "lm75b", 0, 0x4a }, + { "humidity@44", "sensirion,sht3x", "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_address =3D 0x34, +}; + #define bp_assign_entry(bp, res, val) ({ \ uintptr_t addr =3D (uintptr_t)(bp) + (res)->bp_offset; \ *(typeof(val) *)addr =3D val; \ @@ -1961,8 +2091,8 @@ ptp_ocp_nvmem_match(struct device *dev, const void *d= ata) return 0; =20 while ((dev =3D dev->parent)) - if (dev->driver && !strcmp(dev->driver->name, KBUILD_MODNAME)) - return info->bp =3D=3D dev_get_drvdata(dev); + if (dev =3D=3D &info->bp->pdev->dev) + return 1; return 0; } =20 @@ -1982,6 +2112,12 @@ ptp_ocp_nvmem_device_put(struct nvmem_device **nvmem= p) *nvmemp =3D NULL; } =20 +static bool +ptp_ocp_has_eeprom_data(struct ptp_ocp *bp) +{ + return smp_load_acquire(&bp->has_eeprom_data); +} + static enum ptp_ocp_board_id_state ptp_ocp_classify_board_id(char *board_id) { @@ -2036,11 +2172,11 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp) const void *tag; int ret; =20 - if (!bp->i2c_ctrl) - return; - tag =3D NULL; nvmem =3D NULL; + mutex_lock(&bp->eeprom_lock); + if (!bp->i2c_ctrl || ptp_ocp_has_eeprom_data(bp)) + goto out; =20 for (map =3D bp->eeprom_map; map->len; map++) { if (map->tag !=3D tag) { @@ -2061,10 +2197,13 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp) } =20 bp->board_id_state =3D ptp_ocp_classify_board_id(bp->board_id); - bp->has_eeprom_data =3D true; + + /* Publish the EEPROM fields before readers observe valid data. */ + smp_store_release(&bp->has_eeprom_data, true); =20 out: ptp_ocp_nvmem_device_put(&nvmem); + mutex_unlock(&bp->eeprom_lock); return; =20 fail: @@ -2072,6 +2211,467 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp) goto out; } =20 +static int +ptp_ocp_i2c_adapter_match(struct device *dev, const void *data) +{ + return !!i2c_verify_adapter(dev); +} + +static struct i2c_adapter * +ptp_ocp_i2c_root_adapter(struct ptp_ocp *bp) +{ + struct i2c_adapter *adapter; + struct device *dev; + + if (!bp->i2c_ctrl) + return NULL; + + dev =3D device_find_child(&bp->i2c_ctrl->dev, NULL, + ptp_ocp_i2c_adapter_match); + if (!dev) + return NULL; + + adapter =3D i2c_verify_adapter(dev); + if (!adapter || !try_module_get(adapter->owner)) { + put_device(dev); + return NULL; + } + + /* The device reference from device_find_child() is owned by the caller. = */ + return adapter; +} + +static const struct ptp_ocp_i2c_profile * +ptp_ocp_i2c_select_profile(struct ptp_ocp *bp) +{ + /* PCI IDs identify FPGA images, not a unique PCB revision. */ + if (!((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))) + return NULL; + + if (!ptp_ocp_has_eeprom_data(bp)) + return NULL; + if (bp->board_id_state !=3D OCP_BOARD_ID_VALID) + return NULL; + + if (!strncmp(bp->board_id, "R4006", 5)) + return &ptp_ocp_r4006_profile; + + return NULL; +} + +static int +ptp_ocp_i2c_init_nodes(struct ptp_ocp *bp) +{ + static const char * const channel_names[] =3D { + "i2c@0", "i2c@1", "i2c@2", "i2c@3", + }; + static const char * const output_names[] =3D { + "led@0", "led@1", "led@2", "led@3", "led@4", "led@5", + "led@6", "led@7", "led@8", "led@9", "led@a", "led@b", + "led@c", "led@d", "led@e", "led@f", "led@10", "led@11", + }; + static const u32 colors[] =3D { + LED_COLOR_ID_RED, LED_COLOR_ID_GREEN, LED_COLOR_ID_BLUE, + }; + const struct ptp_ocp_i2c_profile *profile =3D bp->i2c_profile; + struct device *dev =3D &bp->pdev->dev; + struct ptp_ocp_i2c_topology *topology; + unsigned int component =3D 0; + unsigned int node =3D 0; + unsigned int expected_nodes; + unsigned int i, j, prop; + int ret; + + if (!profile) + return -EINVAL; + if (bp->i2c_topology && bp->i2c_topology->registered) + return 0; + if (bp->i2c_topology) { + topology =3D bp->i2c_topology; + goto register_nodes; + } + if (WARN_ON(profile->sensor_count > OCP_I2C_MAX_SENSOR_COUNT || + profile->led_count > OCP_I2C_MAX_LED_COUNT)) + 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", "nxp,pca9546"); + topology->mux_props[1] =3D PROPERTY_ENTRY_U32("reg", 0x70); + 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]; + topology->sensor_props[i][0] =3D + PROPERTY_ENTRY_STRING("compatible", sensor->compatible); + topology->sensor_props[i][1] =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[1]; + 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", 4700); + 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", 8150); + 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 dev_err_probe(dev, ret, + "failed to register %s firmware nodes\n", + profile->name); + + topology->registered =3D true; + return 0; +} + +static int +ptp_ocp_i2c_add_device(struct ptp_ocp *bp, struct i2c_adapter *adapter, + const struct software_node *node, const char *type, + u16 address) +{ + struct fwnode_handle *fwnode =3D software_node_fwnode(node); + struct i2c_board_info info =3D { }; + struct i2c_client *client; + + client =3D i2c_find_device_by_fwnode(fwnode); + if (client) { + put_device(&client->dev); + return 0; + } + + strscpy(info.type, type, sizeof(info.type)); + info.addr =3D address; + info.fwnode =3D fwnode; + client =3D i2c_new_client_device(adapter, &info); + if (IS_ERR(client)) + return dev_err_probe(&bp->pdev->dev, PTR_ERR(client), + "failed to add %s at %d-00%02x\n", + type, i2c_adapter_id(adapter), address); + + return 0; +} + +static 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(bp, adapter, node, sensor->type, + sensor->address); + if (err && !ret) + ret =3D err; + } + + if (channel =3D=3D 1) { + err =3D ptp_ocp_i2c_add_device(bp, 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 (!READ_ONCE(bp->i2c_resources_ready)) + return; + if (!READ_ONCE(bp->i2c_root_present)) + return; + + atomic_set(&bp->i2c_retry_count, 0); + mod_delayed_work(system_wq, &bp->i2c_work, 1); +} + +static void +ptp_ocp_i2c_retry(struct ptp_ocp *bp) +{ + if (!READ_ONCE(bp->i2c_resources_ready)) + return; + if (!READ_ONCE(bp->i2c_root_present)) + return; + if (atomic_inc_return(&bp->i2c_retry_count) > OCP_I2C_RETRY_MAX) + return; + + mod_delayed_work(system_wq, &bp->i2c_work, HZ); +} + +static void +ptp_ocp_i2c_populate_topology(struct ptp_ocp *bp, + struct platform_device *i2c_ctrl, + bool *retry) +{ + const struct software_node *node; + struct i2c_adapter *adapter; + struct fwnode_handle *fwnode; + struct i2c_client *mux; + unsigned int channel; + int ret; + + if (!READ_ONCE(bp->i2c_root_present)) { + ptp_ocp_i2c_remove_mux(bp); + return; + } + if (!device_is_registered(&i2c_ctrl->dev) || + !device_is_bound(&i2c_ctrl->dev)) { + *retry =3D true; + return; + } + if (!bp->eeprom_map) + return; + + adapter =3D ptp_ocp_i2c_root_adapter(bp); + if (!adapter) { + *retry =3D true; + return; + } + + if (!ptp_ocp_has_eeprom_data(bp)) + ptp_ocp_read_eeprom(bp); + if (!ptp_ocp_has_eeprom_data(bp)) { + *retry =3D true; + 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) { + *retry =3D true; + goto out_put_adapter; + } + + node =3D &bp->i2c_topology->mux_node; + ret =3D ptp_ocp_i2c_add_device(bp, adapter, node, "pca9546", 0x70); + if (ret) { + *retry =3D true; + goto out_put_adapter; + } + + fwnode =3D software_node_fwnode(&bp->i2c_topology->mux_node); + mux =3D i2c_find_device_by_fwnode(fwnode); + if (!mux) { + *retry =3D true; + goto out_put_adapter; + } + + device_lock(&mux->dev); + if (!device_is_registered(&mux->dev) || !device_is_bound(&mux->dev)) { + *retry =3D true; + goto out_unlock_mux; + } + + for (channel =3D 0; channel < OCP_I2C_MUX_CHANNELS; channel++) { + ret =3D ptp_ocp_i2c_populate_channel(bp, channel); + if (ret) + *retry =3D true; + } + +out_unlock_mux: + device_unlock(&mux->dev); + put_device(&mux->dev); +out_put_adapter: + i2c_put_adapter(adapter); +} + +static void +ptp_ocp_i2c_work(struct work_struct *work) +{ + struct ptp_ocp *bp =3D container_of(work, struct ptp_ocp, i2c_work.work); + struct platform_device *i2c_ctrl; + bool retry =3D false; + + /* Pair with resource publication after registration. */ + if (!smp_load_acquire(&bp->i2c_resources_ready)) + return; + + i2c_ctrl =3D bp->i2c_ctrl; + if (!i2c_ctrl) { + ptp_ocp_i2c_retry(bp); + return; + } + + device_lock(&i2c_ctrl->dev); + ptp_ocp_i2c_populate_topology(bp, i2c_ctrl, &retry); + device_unlock(&i2c_ctrl->dev); + + if (retry) + ptp_ocp_i2c_retry(bp); + else + atomic_set(&bp->i2c_retry_count, 0); +} + +static void +ptp_ocp_i2c_unregister(struct ptp_ocp *bp) +{ + struct device *i2c_ctrl_dev =3D NULL; + + WRITE_ONCE(bp->i2c_resources_ready, false); + WRITE_ONCE(bp->i2c_root_present, false); + disable_delayed_work_sync(&bp->i2c_work); + if (bp->i2c_ctrl) { + i2c_ctrl_dev =3D &bp->i2c_ctrl->dev; + device_lock(i2c_ctrl_dev); + } + if (!bp->i2c_topology) { + bp->i2c_profile =3D NULL; + goto out_unlock; + } + + if (bp->i2c_topology->registered) { + ptp_ocp_i2c_remove_mux(bp); + software_node_unregister_node_group(bp->i2c_topology->node_group); + bp->i2c_topology->registered =3D false; + } + + bp->i2c_topology =3D NULL; + bp->i2c_profile =3D NULL; + +out_unlock: + if (i2c_ctrl_dev) + device_unlock(i2c_ctrl_dev); +} + static struct device * ptp_ocp_find_flash(struct ptp_ocp *bp) { @@ -2226,9 +2826,9 @@ ptp_ocp_devlink_info_get(struct devlink *devlink, str= uct devlink_info_req *req, if (err) return err; =20 - if (!bp->has_eeprom_data) { + if (!ptp_ocp_has_eeprom_data(bp)) { ptp_ocp_read_eeprom(bp); - if (!bp->has_eeprom_data) + if (!ptp_ocp_has_eeprom_data(bp)) return 0; } =20 @@ -3819,7 +4419,7 @@ serialnum_show(struct device *dev, struct device_attr= ibute *attr, char *buf) { struct ptp_ocp *bp =3D dev_get_drvdata(dev); =20 - if (!bp->has_eeprom_data) + if (!ptp_ocp_has_eeprom_data(bp)) ptp_ocp_read_eeprom(bp); =20 return sysfs_emit(buf, "%pM\n", bp->serial); @@ -4790,6 +5390,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; @@ -4928,6 +5529,11 @@ ptp_ocp_detach(struct ptp_ocp *bp) { int i; =20 + if (bp->i2c_notifier_registered) { + bus_unregister_notifier(&i2c_bus_type, &bp->i2c_notifier); + bp->i2c_notifier_registered =3D false; + } + ptp_ocp_i2c_unregister(bp); ptp_ocp_debugfs_remove_device(bp); ptp_ocp_detach_sysfs(bp); ptp_ocp_attr_group_del(bp); @@ -5142,6 +5748,8 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_= device_id *id) goto out_disable; =20 INIT_DELAYED_WORK(&bp->sync_work, ptp_ocp_sync_work); + INIT_DELAYED_WORK(&bp->i2c_work, ptp_ocp_i2c_work); + atomic_set(&bp->i2c_retry_count, 0); =20 /* compat mode. * Older FPGA firmware only returns 2 irq's. @@ -5156,9 +5764,20 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci= _device_id *id) bp->n_irqs =3D err; pci_set_master(pdev); =20 + bp->i2c_notifier.notifier_call =3D ptp_ocp_i2c_notifier_call; + err =3D bus_register_notifier(&i2c_bus_type, &bp->i2c_notifier); + if (err) { + dev_err(&pdev->dev, "failed to register I2C notifier: %d\n", err); + goto out; + } + bp->i2c_notifier_registered =3D true; + err =3D ptp_ocp_register_resources(bp, id->driver_data); if (err) goto out; + /* Publish all board resources before I2C topology work can run. */ + smp_store_release(&bp->i2c_resources_ready, true); + ptp_ocp_i2c_kick(bp); =20 bp->ptp =3D ptp_clock_register(&bp->ptp_info, &pdev->dev); if (IS_ERR(bp->ptp)) { @@ -5252,46 +5871,85 @@ static struct pci_driver ptp_ocp_driver =3D { .shutdown =3D ptp_ocp_remove, }; =20 +static bool +ptp_ocp_i2c_is_child(struct ptp_ocp *bp, struct device *child) +{ + struct device *dev =3D child; + + while (dev) { + if (dev =3D=3D &bp->pdev->dev) + return true; + dev =3D dev->parent; + } + + return false; +} + +static bool +ptp_ocp_i2c_is_root(struct ptp_ocp *bp, struct i2c_adapter *adapter) +{ + struct device *parent =3D adapter->dev.parent; + + if (!parent) + return false; + if (bp->i2c_ctrl && parent =3D=3D &bp->i2c_ctrl->dev) + return true; + + /* The adapter can notify before ptp_ocp_register_i2c() stores it. */ + return parent->parent =3D=3D &bp->pdev->dev; +} + static int ptp_ocp_i2c_notifier_call(struct notifier_block *nb, unsigned long action, void *data) { - struct device *dev, *child =3D data; - struct ptp_ocp *bp; - bool add; + struct i2c_adapter *adapter; + struct i2c_client *client; + struct device *child =3D data; + struct ptp_ocp *bp =3D container_of(nb, struct ptp_ocp, i2c_notifier); + + if (!ptp_ocp_i2c_is_child(bp, child)) + return 0; =20 switch (action) { case BUS_NOTIFY_ADD_DEVICE: - case BUS_NOTIFY_DEL_DEVICE: - add =3D action =3D=3D BUS_NOTIFY_ADD_DEVICE; - break; - default: + adapter =3D i2c_verify_adapter(child); + if (!adapter) + return 0; + if (ptp_ocp_i2c_is_root(bp, adapter)) { + WRITE_ONCE(bp->i2c_root_present, true); + ptp_ocp_symlink(bp, child, "i2c"); + } + ptp_ocp_i2c_kick(bp); return 0; - } =20 - if (!i2c_verify_adapter(child)) + case BUS_NOTIFY_DEL_DEVICE: + adapter =3D i2c_verify_adapter(child); + if (!adapter) + return 0; + if (ptp_ocp_i2c_is_root(bp, adapter)) { + WRITE_ONCE(bp->i2c_root_present, false); + sysfs_remove_link(&bp->dev.kobj, "i2c"); + mod_delayed_work(system_wq, &bp->i2c_work, 0); + } 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) { @@ -5305,21 +5963,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(); @@ -5330,7 +5981,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 Fri Oct 2 07:45:25 2026 Received: from mail-pj1-f48.google.com (mail-pj1-f48.google.com [209.85.216.48]) (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 2348F437114 for ; Mon, 3 Aug 2026 20:50:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785790243; cv=none; b=rkOI9UJo/mdNaBMSjdsO6XdYXs+7e2DR+rrykKLPii3Rfoq970j6IGctL2OxEfk26eCy0FrX6UrF+nMx/1m+2WzjaXFpqLUG5Qo74gJibwjDDfHPR/c1y/z5v9q1jrKV3L+ywJRk0JwsIHna+MstKZ8SC3XqFcUJ68D2J7wDjNg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785790243; c=relaxed/simple; bh=XrAFZS+IhJxi+kxMlYidEjTvpsk2txu5b59dVurg6+0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=H9TzwKLpRMtZOBF+yyoSFfQkCfyrL8Z9Fr4zUx7APaPvL0E/E+kaoeCSch/Im+gzM0OoA9yk6iCtHKWbPej7meN/RsLSJ5MTqg8RuLP8S92s4l6o6Q8u/aZjcsO2Tud49BeyPCv0mZ1kxN/Tn9SsVvThh9fvM56tV9CKLfHiQ0I= 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=Ujv/X5aa; arc=none smtp.client-ip=209.85.216.48 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="Ujv/X5aa" Received: by mail-pj1-f48.google.com with SMTP id 98e67ed59e1d1-38e69bdb0fcso2684051a91.1 for ; Mon, 03 Aug 2026 13:50:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785790240; x=1786395040; 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=rDd38SZH0rN9ZI+VsvQIWz/trEy5IHBaWNCDHbcZ/KY=; b=Ujv/X5aazGxrcUKwL53R8k9kPLYxWDrtLARF80Vt7DjUu74eMfl3GBeCCQHJh+ggCN lrGxREyzJ0wDbInQlbGONxJ5GfT7HxG+MFL24GadBnDrYprS0PimjBK1DIRzYiFljk1H srJY72leZeHEUwm2/DdSKuUPRwndeTHg3GEjcSMF1SV0Q1A98SjxOd0bM/q8PkOhK9If zKC7ttcB/VEbDkqQqtZ84/R14NmclJlDnHHeD5HxacDAh2VQR3JCqdf6U1fXmOaaeYQX AqFq6TYpGOwk44CwhmiltnjDxqByQmB/e9K8MlTo9yUY5c86FDiZfYo8dSaJHCOURz+W x1yA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785790240; x=1786395040; 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=rDd38SZH0rN9ZI+VsvQIWz/trEy5IHBaWNCDHbcZ/KY=; b=kCA92s/CoYSvpVnAwDqyAlJ4xnas7hylC3DYoumoR5W02D8gDflPDgYaT0cl1UDzrc iagtPTcC6MiXoFgcaw1qupCnMBjlPw8JTOEsQDmYmjkvNnoGYkPAYI8Qn1gXu7M9cNWH Cwb8EQ4ff+6F9mN8PibW1xOqogZUpcFtPaTLEbBKVi8g0oOMXXjCd+XgIp6XDW+7AAl/ EAUNYaQ4GafeMGSrCbL9HSI0dzCkGyYglzyiN/UlzdnAZJGn2532lQv76I9Ah/tPq7D7 sF91K0dwdCycd99p15RbnDsCarP9gNWiVIdNtMZt7siyCdGJB7XSPffZpvD7i06GOVOn mrdQ== X-Forwarded-Encrypted: i=1; AHgh+Rp8vOLB5/DWdp3aof4Vs41mq1jAiT32K0NgFDoG8VvPofnuITbUIyOt6Jfuwe/fFqqNr4fSy+QMZxhakeo=@vger.kernel.org X-Gm-Message-State: AOJu0Yy++gIN+YR87sVNt6t0gheVQRJT50/ozN8p6k/juMGUuWakBV0l BCHbKAF6SubXj7nDfKzkB5xM8RRytnO/LxRfF6nyLALGWedQsGW95EeR X-Gm-Gg: AR+sD13EhiWDgRX7wSkgTCJm67UkB9TisX0/dgKmxXh38QeT4Cxx3Rd5jbFEDPzSDWW CCQ8oMXR28lkbB33cxiMgaFtX9TZbm3ZR0LCFK9QcEPrl46vL7wvEYXuze+EaqzCkxwAl9AlHQd 8SQ19Up+6QAEpcqstOxgO3IEZC02tXhQpnRyRYdfADviQPRq4X2uz79znFl7nFZqFNQRyY200OZ XWyZ6pYdtjC8Gmmqd3R1un168DFJOyRdjd7vudd/mPN+heaMjOf2c8+ZZWm1umW83jgzBtu51EL lB6dESEp//6MiubofGzAzqpk/qxEB0xXSYwk/6BzSWzmgctjKV1GFUMRAzRROG/3Ybj39+nvgzD S0snGCOxO1+FjAKfh0vebgnDBpKWT+LfUp1dau0Jetl5VxPQ8T+locCov0GEu1R5dHEvn+mg6UD Pv4SezHnYfuBJA59DUjZp+yi6Xx2bWvoeSYm88l2LtPZDVWZkGRjgOOYAbsiGEdNbzaPo= X-Received: by 2002:a17:90b:4c51:b0:38e:75f3:ad4d with SMTP id 98e67ed59e1d1-38fbc3f9ab6mr10445999a91.7.1785790240510; Mon, 03 Aug 2026 13:50:40 -0700 (PDT) Received: from Mac.net ([12.162.234.74]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-13fab4d10bbsm33112324c88.11.2026.08.03.13.50.37 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Mon, 03 Aug 2026 13:50:39 -0700 (PDT) From: Ahmad Byagowi To: linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, 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-hardening@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFC net-next v2 6/6] ptp: ocp: Add Time Card V9 I2C peripheral topology Date: Mon, 3 Aug 2026 13:50:11 -0700 Message-ID: <20260803205011.1249-7-ahmadexp@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20260803205011.1249-1-ahmadexp@gmail.com> References: <20260803205011.1249-1-ahmadexp@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Time Card V9 places a BME280 at 0x76, a BNO055 at 0x29, and an IS32FL3207 at 0x37 on PCA9546 channel 1. Describe GNSS1 and GNSS2 as RGB LEDs on outputs 0 through 5 and SMA1 through SMA4 on outputs 6 through 17. Select this profile only for the exact EEPROM ID "TIMECARD-V9". Earlier Time Card revisions expose the same I2C device population with different LED routing, so probing cannot distinguish them safely. Leave erased, malformed, and unknown board IDs unconfigured. Signed-off-by: Ahmad Byagowi --- drivers/ptp/ptp_ocp.c | 68 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 70ba47096..c8c28d792 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -353,7 +353,7 @@ struct ptp_ocp_serial_port { =20 #define OCP_I2C_MUX_CHANNELS 4 #define OCP_I2C_MAX_SENSOR_COUNT 5 -#define OCP_I2C_MAX_LED_COUNT 5 +#define OCP_I2C_MAX_LED_COUNT 6 #define OCP_I2C_MAX_LED_COMPONENT_COUNT (3 * OCP_I2C_MAX_LED_COUNT) #define OCP_I2C_MUX_NAME_LEN 32 #define OCP_I2C_RETRY_MAX 10 @@ -617,9 +617,61 @@ static const struct ptp_ocp_led ptp_ocp_r4006_leds[] = =3D { }, }; =20 +static const struct ptp_ocp_i2c_device ptp_ocp_v9_sensors[] =3D { + { "pressure@76", "bosch,bme280", "bme280", 1, 0x76 }, + { "imu@29", "bosch,bno055", "bno055", 1, 0x29 }, +}; + +static const struct ptp_ocp_led ptp_ocp_v9_leds[] =3D { + { + .node_name =3D "multi-led@0", + .function =3D LED_FUNCTION_STATUS, + .function_enumerator =3D 1, + .has_function_enumerator =3D true, + .channel =3D { 0, 1, 2 }, + }, + { + .node_name =3D "multi-led@3", + .function =3D LED_FUNCTION_STATUS, + .function_enumerator =3D 2, + .has_function_enumerator =3D true, + .channel =3D { 3, 4, 5 }, + }, + { + .node_name =3D "multi-led@6", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 1, + .has_function_enumerator =3D true, + .channel =3D { 6, 7, 8 }, + }, + { + .node_name =3D "multi-led@9", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 2, + .has_function_enumerator =3D true, + .channel =3D { 9, 10, 11 }, + }, + { + .node_name =3D "multi-led@c", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 3, + .has_function_enumerator =3D true, + .channel =3D { 12, 13, 14 }, + }, + { + .node_name =3D "multi-led@f", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 4, + .has_function_enumerator =3D true, + .channel =3D { 15, 16, 17 }, + }, +}; + static_assert(ARRAY_SIZE(ptp_ocp_r4006_sensors) <=3D OCP_I2C_MAX_SENSOR_COUNT); static_assert(ARRAY_SIZE(ptp_ocp_r4006_leds) <=3D OCP_I2C_MAX_LED_COUNT); +static_assert(ARRAY_SIZE(ptp_ocp_v9_sensors) <=3D OCP_I2C_MAX_SENSOR_COUNT= ); +static_assert(ARRAY_SIZE(ptp_ocp_v9_leds) <=3D OCP_I2C_MAX_LED_COUNT); =20 static const struct ptp_ocp_i2c_profile ptp_ocp_r4006_profile =3D { .name =3D "r4006", @@ -631,6 +683,16 @@ static const struct ptp_ocp_i2c_profile ptp_ocp_r4006_= profile =3D { .led_address =3D 0x34, }; =20 +static const struct ptp_ocp_i2c_profile ptp_ocp_v9_profile =3D { + .name =3D "v9", + .sensors =3D ptp_ocp_v9_sensors, + .sensor_count =3D ARRAY_SIZE(ptp_ocp_v9_sensors), + .leds =3D ptp_ocp_v9_leds, + .led_count =3D ARRAY_SIZE(ptp_ocp_v9_leds), + .led_node_name =3D "led-controller@37", + .led_address =3D 0x37, +}; + #define bp_assign_entry(bp, res, val) ({ \ uintptr_t addr =3D (uintptr_t)(bp) + (res)->bp_offset; \ *(typeof(val) *)addr =3D val; \ @@ -2259,6 +2321,10 @@ ptp_ocp_i2c_select_profile(struct ptp_ocp *bp) if (!strncmp(bp->board_id, "R4006", 5)) return &ptp_ocp_r4006_profile; =20 + /* Older revisions share V9's I2C devices but not its LED wiring. */ + if (!strcmp(bp->board_id, "TIMECARD-V9")) + return &ptp_ocp_v9_profile; + return NULL; } =20 --=20 2.50.1 (Apple Git-155)