From nobody Tue Sep 29 04:08:41 2026 Received: from mail-pl1-f178.google.com (mail-pl1-f178.google.com [209.85.214.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0A32737B40C for ; Wed, 12 Aug 2026 14:17:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786544244; cv=none; b=rmQlucpblRlCbczDjQ+c95e5qYZgNFd2aBre4gCU2n06mvDxsTy8cCc5LiOFs5OHjIGmEq2Lm70a7gP3RCNVhLikIf2oSBuqA+PWzt4ZO8/mr4mPcoIwGj0gd7cVYOrfagKZ1mPjxck40GiI1gkESA4HrUM4aAfOiJQAzDyeh2M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786544244; c=relaxed/simple; bh=w4D3Tw/qGODOSquheRTqS2zaE5vY/ibskA7/ntNW+8E=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=uGC/tga2Cso2NTQe6YtB5tXkzYzMalERBbR1EUGm57+EEPm4gyntqvV/8ylQ1AKJz+GL+0ETxU4PUTAe8I0GllGv1Uzjr6WVhmgFV/uzWtHa9HCSu8AsWUH1o+mSQBzg5dHNiOky5M/WjWj7lNxx5YbEljaLu4Z1l+5Q0Ro4mi8= 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=PnAAKYOt; arc=none smtp.client-ip=209.85.214.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="PnAAKYOt" Received: by mail-pl1-f178.google.com with SMTP id d9443c01a7336-2d004f135b1so15557045ad.3 for ; Wed, 12 Aug 2026 07:17:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786544242; x=1787149042; 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=c7gjSQ0r/UdoBWSwg7nk7c7n8PtdywkD8czofZ+H0lU=; b=PnAAKYOtz7Mz1ciZOlBRlxFv384+MX9DHgmiJsWBiy17KK0V4LTJJ+eXDA9q+HNtqt bRJDuY5g26goQ02awKjr8yXOdU0qkV253pSuPQzYQ4zng0UPySTqmYH/fuFhDSveezIO ZmajdNNSzOcQv5jqulaN4TkWYuUWuxvj3blGom1xNFY19ZtRAJhTX2+eMp9tRgqbm23/ nN7kdzSZhhK/cKc/KvxPS3dp8AMIJmyV7pzt9xXomg2rIIX4NC+4YaXmtfVCoMLqJ1Tp +M+13rupziVn2HoC0EhCkF9ngbrH8qqRTG4VOMbQbhhuFwVs5gycgbjijdNgP+Y29v5q c4Rg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786544242; x=1787149042; 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=c7gjSQ0r/UdoBWSwg7nk7c7n8PtdywkD8czofZ+H0lU=; b=CuvVia0AUrZvmyskfkGUcSOWeEXchmuF0cx48aJX4e59vrReAM+bsvVLHUyIJgy6pY 5wLU4udcm3P6Msup3+PA1CQOolZcWADt5r5KlQVqeNF+3aESkJ8M1VjK23KpoBf6QFLu 8Jep5XpvWphDD0YPI3hMf7bvP1NjXLnrp3Gnpyh7tiPB/eiQT3/2JS/X25FDSw1838zz zJXlV3NhhxCthSIcWoUE8r7duCtrZHVs9gGPKuW/FvmFw2dDNy5ovwlcQtlbfdUfo6KZ HvgiEjxbnXgZ/DeIzsCG5ib3WndK14neIWGnhdZlg/O05L8bBMJ/So6Q4CYbfZmOPEzK JHwA== X-Forwarded-Encrypted: i=1; AHgh+RrxcKGL4+2gnn5hQgdtoKmaYZa/h9RVfSGZ6MFifpVrCuON2Z1JZswPXcF7ezcw59bm4otToq3q8QRpqPo=@vger.kernel.org X-Gm-Message-State: AOJu0YxkOe7sa3OFu1zXCy2eWgqDeYdt43Yt9GKYtg2s0RCAJJkmF/tn 5dzciWWsCOIpDs+ipibL2hWdpl/Vl55ddLXBEfZNYez6AS7hLhIa9sZt X-Gm-Gg: AR+sD12U1QTc7cVBa6nogbP0ACN1NdIYYHY3AseqkE3O0o63x5r8dvhuievtS+0VEEH S0tJLAI7B9LYb3bg04QX12DJPWa22pra0IE4ZYTk4XYzurwPFBhwowy1HFzgpbO7XfiHduQ+uqF vN51BPQ6hMlmRlvZzvGApJjW7C2hyPr6qfcd8S0AaM0qpGljKW8I1853fmQ0lumP++Q0TuDSBbu LTKgcutgpEaZi7NyukkqbGa2SSnHIGtJDHnC9xGOE9Fa+pYecYtFnPR0/nzNULEoNp5wUJPJuLb gHxNuXW06/7tvcYvIfpUfcf4Om5JDy8qROOII2zvMfMf1bkoRpU9mK/Fljkly3KtJhguFEN1VLA MY/bOah6Jz36cuSIbczfagp1ZoHtgDgCzwuqqDag7JnhE9+kKuVKapG080zN23ak2FcLbXYlzan OL03wg9z+PcUqkZ1lwSCu8MLgdz/BpWOI1qMxvBgX3VFIpEhdAAn/9M1bEK8VwodNrEN0gPFWqh /i/8kEdOgk38bvQDK7iwkn5fZvf3saD+AT8eV1HBu8azUJqGYBhXdivxoxd X-Received: by 2002:a17:90b:254c:b0:380:83fc:4315 with SMTP id 98e67ed59e1d1-393014a8c62mr6658843a91.21.1786544241876; Wed, 12 Aug 2026 07:17:21 -0700 (PDT) Received: from Mac.localdomain (198-27-228-154.fiber.dynamic.sonic.net. [198.27.228.154]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-31cf361d57asm11963071eec.6.2026.08.12.07.17.19 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 12 Aug 2026 07:17:20 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Nam Tran , Kees Cook , "Gustavo A. R. Silva" , Peter Rosin , Andi Shyti , Richard Cochran , Vadim Fedorenko , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hardening@vger.kernel.org, Krzysztof Kozlowski Subject: [PATCH net-next v4 1/5] dt-bindings: leds: Add IS32FL3207 controller Date: Wed, 12 Aug 2026 07:17:11 -0700 Message-ID: <361f85124583adba9aae710179af910c328a747b.1786543681.git.ahmadexp@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The IS32FL3207 is an 18-channel constant-current LED controller with per-output PWM and current scaling. Describe I2C addressing, power and shutdown controls, RISET, and individual or multicolor output groupings. Require board current limits for each output. Signed-off-by: Ahmad Byagowi Reviewed-by: Krzysztof Kozlowski --- .../bindings/leds/issi,is32fl3207.yaml | 251 ++++++++++++++++++ MAINTAINERS | 6 + 2 files changed, 257 insertions(+) create mode 100644 Documentation/devicetree/bindings/leds/issi,is32fl3207.= yaml diff --git a/Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml b/= Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml new file mode 100644 index 000000000000..4ee9db964ecc --- /dev/null +++ b/Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml @@ -0,0 +1,251 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/issi,is32fl3207.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Lumissil IS32FL3207 18-channel LED controller + +maintainers: + - Ahmad Byagowi + +description: | + The IS32FL3207 is an I2C LED controller with 18 individually controlled + constant-current outputs. Each output has 8-bit current scaling. PWM + brightness control uses the controller's default 8-bit, 62 kHz mode. + Outputs may be exposed as individual LEDs or grouped as multicolor LEDs. + + The data sheet is available at: + https://www.lumissil.com/assets/pdf/core/IS32FL3207_DS.pdf + +properties: + compatible: + const: issi,is32fl3207 + + reg: + enum: [0x34, 0x35, 0x36, 0x37] + + vcc-supply: + description: Regulator providing power to the VCC pin. + + enable-gpios: + maxItems: 1 + description: GPIO connected to SDB, asserted to enable the controller. + + issi,riset-ohms: + minimum: 2000 + maximum: 76500000 + description: | + Resistance in ohms of the external RISET resistor. The resistor sets= the + maximum output current according to IOUT(MAX) =3D 76.5 / RISET amper= es. + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + "^led@[0-9a-f]+$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 17 + description: | + Zero-based LED output number. Values 0 through 17 correspond to + OUT1 through OUT18. + + led-max-microamp: + minimum: 1 + maximum: 38250 + + color: + oneOf: + - minimum: 0 + maximum: 7 + - minimum: 10 + maximum: 14 + + max-brightness: + minimum: 1 + maximum: 255 + + default-state: + description: + The controller is reset during initialization, so an existing st= ate + cannot be preserved. + enum: ["on", "off"] + + default-brightness: + maximum: 255 + description: | + Brightness used when default-state is "on". This value must not + exceed max-brightness when max-brightness is specified. + + anyOf: + - required: + - function + - required: + - color + + required: + - reg + - led-max-microamp + + "^multi-led@[0-9a-f]$": + type: object + $ref: leds-class-multicolor.yaml# + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 15 + description: | + Zero-based lowest-numbered LED output used by the group. Values 0 + through 15 correspond to OUT1 through OUT16. A group based at + channel 15 (OUT16) may include channels 16 and 17 (OUT17 and OUT= 18). + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + max-brightness: + minimum: 1 + maximum: 255 + + default-state: + description: + The controller is reset during initialization, so an existing st= ate + cannot be preserved. + enum: ["on", "off"] + + default-brightness: + maximum: 255 + description: | + Brightness used when default-state is "on". This value must not + exceed max-brightness when max-brightness is specified. + + patternProperties: + "^led@[0-9a-f]+$": + type: object + $ref: common.yaml# + unevaluatedProperties: false + + properties: + reg: + minimum: 0 + maximum: 17 + description: | + Zero-based LED output number. Values 0 through 17 correspond= to + OUT1 through OUT18. + + led-max-microamp: + minimum: 1 + maximum: 38250 + + color: + oneOf: + - minimum: 0 + maximum: 7 + - minimum: 10 + maximum: 14 + + max-brightness: false + + default-state: false + + default-brightness: false + + retain-state-shutdown: false + + required: + - reg + - color + - led-max-microamp + + anyOf: + - required: [led@0] + - required: [led@1] + - required: [led@2] + - required: [led@3] + - required: [led@4] + - required: [led@5] + - required: [led@6] + - required: [led@7] + - required: [led@8] + - required: [led@9] + - required: [led@a] + - required: [led@b] + - required: [led@c] + - required: [led@d] + - required: [led@e] + - required: [led@f] + + required: + - reg + - "#address-cells" + - "#size-cells" + +required: + - compatible + - reg + - issi,riset-ohms + - "#address-cells" + - "#size-cells" + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + led-controller@34 { + compatible =3D "issi,is32fl3207"; + reg =3D <0x34>; + #address-cells =3D <1>; + #size-cells =3D <0>; + + vcc-supply =3D <&led_3v3>; + enable-gpios =3D <&gpio0 4 GPIO_ACTIVE_HIGH>; + issi,riset-ohms =3D <4700>; + + multi-led@0 { + reg =3D <0>; + color =3D ; + function =3D LED_FUNCTION_STATUS; + #address-cells =3D <1>; + #size-cells =3D <0>; + + led@0 { + reg =3D <0>; + color =3D ; + led-max-microamp =3D <8100>; + }; + + led@1 { + reg =3D <1>; + color =3D ; + led-max-microamp =3D <8100>; + }; + + led@2 { + reg =3D <2>; + color =3D ; + led-max-microamp =3D <8100>; + }; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index e0e7fae5b92f..af4b63b9f1ef 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13822,6 +13822,12 @@ S: Maintained F: Documentation/filesystems/isofs.rst F: fs/isofs/ =20 +ISSI IS32FL3207 LED DRIVER +M: Ahmad Byagowi +L: linux-leds@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml + IT87 HARDWARE MONITORING DRIVER M: Jean Delvare L: linux-hwmon@vger.kernel.org --=20 2.50.1 (Apple Git-155) From nobody Tue Sep 29 04:08:41 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 84901442FD8 for ; Wed, 12 Aug 2026 14:17:25 +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=1786544254; cv=none; b=hvapdKsPc3mF8DQalTPzSGYyLoOViDeYdcDrr9NaLUH0rGkQuBOob05t5Zjx10Yo4VQGTrquTszvCwSBPP1QFUY/A6zlBNRbhKPeLsk8mFQwcEIZH4V260Y851w7gkBnG2nQbaRmLJWo61zoCSHNlTlolE8E93ZNVCT1zO/dT98= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786544254; c=relaxed/simple; bh=Qzkt6ubFU7Lm5tN4JSKqFUKRqVdl0YbbtUDuGDG8ZSc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=PyTCSsiVCS7LOmpJz66iju0so41yyvJ+BR83M4I4hRykAIhSuuBjnNfpSmxrowcZEycYadDPGBRYddi7jmsyGYjy7sxf94gony/3mUcBdPTuv52/80DjlCpNzj3SzSc88vMxRINM3NjTWJDab0pm/P1LFXaVVYpQJDrDszk0CRE= 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=IMu4Iq7v; 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="IMu4Iq7v" Received: by mail-pj1-f42.google.com with SMTP id 98e67ed59e1d1-38dfe910e9dso1195585a91.3 for ; Wed, 12 Aug 2026 07:17:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786544245; x=1787149045; 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=dwleNJS62wayJ8G+qNlO/sozTmsgj8sqpxyjqe0c2qA=; b=IMu4Iq7v1xk+3c+Ma5HudBAUNqXLZuzZkchoKwo3v4mZJUemOAUBq5YbHatp7BYbx7 ABTwAPV7vjGVxBjSQhYF2Z3d+OgOeYQlJw1GloE3m1UkrE/XF4XsQFcR18Y5BJYtml5d lPVqZEj9TtmdGq4fzh6FbfGvSiixF1IZr9MmyBJnMR9lMS2GXTRLWjnH4qa+oLl/zY1t OBRl85YJZ+JeH125vzo5hFFhBitX8S1Z5hNk05vqBcHYLXNSsPLSQPVisr1a4znz72mI FPa9axL7ugwEqnF3+l1HJ6++EB/GTqRRZtf+zxYzu+FjQC8XqaZJXEm1iEF4ScQB7QXj 0sKg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786544245; x=1787149045; 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=dwleNJS62wayJ8G+qNlO/sozTmsgj8sqpxyjqe0c2qA=; b=Jeb6+X92p9pxiNtp7f/YrlLk8kXSMX7S5HiHlvwhLHG2vwANcSwiHURfmZb14SRR0g tIl4dfT6jIEb7SaO4G8rNRsESkY2THASutaoj4ZPXdyIMmYOpOKttd/j5xFgaImqCcpJ B2uAhsO8zsMtp7sGez/d8qB92kNlYS1V7sDKtpjbePD1N9JsWugasgKMn2WcUIeArwZ/ KKNCiV7LGwSiZuWhNJOLkd1Cn98GkosRFp3KN+/mT3kugsi1T1ga1dQqc+1RMhJepgtK 0Xud3KVJPM+rfg/is9IYMQwCDvsFiffhGeyUw0k5tc+Ontu/M8Yld/xY/GMtjpXLdv5S lofA== X-Forwarded-Encrypted: i=1; AHgh+RqhkTFIi6QIJ5R0PZqsld8yehuAqxXT14n7wm2++mMsoQC2g3XtRfHnXgtMikuHKIrHfdUbzd13ZeI2Q64=@vger.kernel.org X-Gm-Message-State: AOJu0YzQiiXRxvFeSogYvRGQk1X0p1jpFEBhe6Z+20Qu1uh6BkghP7Nw NqRS/7QmVlfl3hWO/hlhAe/7UaG0LPN3ke9jU3k62lID+GkcXlFwYFzU X-Gm-Gg: AR+sD13h/TP586EgWmsoHhwQwXqo1Izmws+qOi93ueRgPs+7zSCiRJqUvSIZeNj2Ec/ 0Ud6nwD+sowNDZNhbX184rfGogfxc4ImNmtLJlPs9HtTqerLceGKgav3E58aIBL/eboUfVOtBWx 8a4yxpU/9fCZRCpvn4/TrhuiOxUMqawANVmEQ4+ZhMPpILSFAwe0BpG6XSXHoNM9qHL+iVtYOB9 rF6I5KEklsmAJJmrz+txOxrZgfCGNynHk31GDmH+HC/+CHuQKT7L7unFC5nSp0KeSme5zjG9usu a0avgBIf2yYQXX1xETnu1ogICr8m1rq/CM7yt42zg84HTw3y+WGLOWe3L5ZLqo45y5NwK2S9ZLI EzBFScokz+Glzw26DOAAY4vE6bH7babig3YGbaGuv2zU2tC8qoIZLlzqgNTkbLXwKEPAaVY10WX 4bRfTA8YWha5IiIjZyfBykZhBMpSDWgc50g9DQPIIck5OKI8/bXJ3pp+xErdutnouTmH5G3NtnO zxx/yrnEP5Z21YYkuqEr9xNq5b/RuieoWJ9mGxLdi2LYn0PIw== X-Received: by 2002:a17:90b:1dc5:b0:38e:9eb2:9d43 with SMTP id 98e67ed59e1d1-39301414638mr6246217a91.16.1786544244781; Wed, 12 Aug 2026 07:17:24 -0700 (PDT) Received: from Mac.localdomain (198-27-228-154.fiber.dynamic.sonic.net. [198.27.228.154]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-31cf361d57asm11963071eec.6.2026.08.12.07.17.22 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 12 Aug 2026 07:17:23 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Nam Tran , Kees Cook , "Gustavo A. R. Silva" , Peter Rosin , Andi Shyti , Richard Cochran , Vadim Fedorenko , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hardening@vger.kernel.org Subject: [PATCH net-next v4 2/5] leds: is32fl3207: Add controller driver Date: Wed, 12 Aug 2026 07:17:12 -0700 Message-ID: X-Mailer: git-send-email 2.50.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add an I2C driver for the Lumissil IS32FL3207 18-channel LED controller. Expose individual and multicolor LEDs through the LED class. Use the default 8-bit, 62 kHz PWM mode, derive output current from RISET, and enforce each output current limit with the scaling registers. Serialize scaling, PWM, and controller-wide update operations. Keep the controller shut down while clearing retained current settings, resetting the device, and registering every LED. Enable outputs only after all channel limits and initial brightness values are programmed. Handle an optional supply and enable GPIO, and honor retained shutdown state. Signed-off-by: Ahmad Byagowi --- MAINTAINERS | 1 + drivers/leds/rgb/Kconfig | 11 + drivers/leds/rgb/Makefile | 1 + drivers/leds/rgb/leds-is32fl3207.c | 691 +++++++++++++++++++++++++++++ 4 files changed, 704 insertions(+) create mode 100644 drivers/leds/rgb/leds-is32fl3207.c diff --git a/MAINTAINERS b/MAINTAINERS index af4b63b9f1ef..0c101c54a5e2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13827,6 +13827,7 @@ M: Ahmad Byagowi L: linux-leds@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml +F: drivers/leds/rgb/leds-is32fl3207.c =20 IT87 HARDWARE MONITORING DRIVER M: Jean Delvare diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig index 6e9ab5f60714..c896be1318dc 100644 --- a/drivers/leds/rgb/Kconfig +++ b/drivers/leds/rgb/Kconfig @@ -14,6 +14,17 @@ config LEDS_GROUP_MULTICOLOR To compile this driver as a module, choose M here: the module will be called leds-group-multicolor. =20 +config LEDS_IS32FL3207 + tristate "LED support for ISSI IS32FL3207" + depends on I2C + select REGMAP_I2C + help + Say Y here to include support for the Lumissil IS32FL3207 + 18-channel I2C LED controller. + + To compile this driver as a module, choose M here: the module will + be called leds-is32fl3207. + config LEDS_KTD202X tristate "LED support for KTD202x Chips" depends on I2C diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile index cc0f2df66286..228923e8bb11 100644 --- a/drivers/leds/rgb/Makefile +++ b/drivers/leds/rgb/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 =20 obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) +=3D leds-group-multicolor.o +obj-$(CONFIG_LEDS_IS32FL3207) +=3D leds-is32fl3207.o obj-$(CONFIG_LEDS_KTD202X) +=3D leds-ktd202x.o obj-$(CONFIG_LEDS_LP5812) +=3D leds-lp5812.o obj-$(CONFIG_LEDS_LP5860_CORE) +=3D leds-lp5860-core.o diff --git a/drivers/leds/rgb/leds-is32fl3207.c b/drivers/leds/rgb/leds-is3= 2fl3207.c new file mode 100644 index 000000000000..42899b55fc59 --- /dev/null +++ b/drivers/leds/rgb/leds-is32fl3207.c @@ -0,0 +1,691 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ISSI IS32FL3207 LED controller driver + * + * Copyright 2026 Ahmad Byagowi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define IS32FL3207_NUM_CHANNELS 18 +#define IS32FL3207_MAX_BRIGHTNESS 255 + +#define IS32FL3207_REG_CONTROL 0x00 +#define IS32FL3207_REG_PWM_LOW(channel) (0x01 + 2 * (channel)) +#define IS32FL3207_REG_PWM_UPDATE 0x49 +#define IS32FL3207_REG_SCALING(channel) (0x4a + (channel)) +#define IS32FL3207_REG_GLOBAL_CURRENT 0x6e +#define IS32FL3207_REG_RESET 0x7f + +#define IS32FL3207_CONTROL_ENABLE BIT(0) +#define IS32FL3207_GLOBAL_CURRENT_MAX 0xff + +/* IOUT(MAX) in microamperes =3D 76,500,000 / RISET in ohms. */ +#define IS32FL3207_CURRENT_NUMERATOR 76500000ULL + +struct is32fl3207; + +struct is32fl3207_led { + struct is32fl3207 *chip; + struct led_classdev *led_cdev; + union { + struct led_classdev cdev; + struct led_classdev_mc mcdev; + }; + unsigned int channel; +}; + +struct is32fl3207 { + struct device *dev; + struct regmap *regmap; + struct gpio_desc *enable_gpio; + struct mutex lock; /* Serializes controller register updates. */ + unsigned long channels[BITS_TO_LONGS(IS32FL3207_NUM_CHANNELS)]; + u32 output_max_microamp; + unsigned int num_leds; + struct is32fl3207_led leds[] __counted_by(num_leds); +}; + +static int is32fl3207_parse_led_properties(struct is32fl3207 *chip, + struct fwnode_handle *fwnode, + unsigned int *max_brightness, + unsigned int *brightness) +{ + enum led_default_state default_state; + u32 value; + int ret; + + *max_brightness =3D IS32FL3207_MAX_BRIGHTNESS; + if (fwnode_property_present(fwnode, "max-brightness")) { + ret =3D fwnode_property_read_u32(fwnode, "max-brightness", + &value); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read maximum brightness for %pfw\n", + fwnode); + if (!value || value > IS32FL3207_MAX_BRIGHTNESS) + return dev_err_probe(chip->dev, -EINVAL, + "invalid maximum brightness %u for %pfw\n", + value, fwnode); + + *max_brightness =3D value; + } + + value =3D *max_brightness; + if (fwnode_property_present(fwnode, "default-brightness")) { + ret =3D fwnode_property_read_u32(fwnode, "default-brightness", + &value); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read default brightness for %pfw\n", + fwnode); + if (value > *max_brightness) + return dev_err_probe(chip->dev, -EINVAL, + "invalid default brightness %u for %pfw\n", + value, fwnode); + } + + default_state =3D led_init_default_state_get(fwnode); + if (default_state =3D=3D LEDS_DEFSTATE_KEEP) + return dev_err_probe(chip->dev, -EINVAL, + "default state keep is not supported for %pfw\n", + fwnode); + + *brightness =3D default_state =3D=3D LEDS_DEFSTATE_ON ? value : LED_OFF; + return 0; +} + +static int is32fl3207_validate_component(struct is32fl3207 *chip, + struct fwnode_handle *fwnode) +{ + static const char * const unsupported[] =3D { + "default-brightness", + "default-state", + "max-brightness", + "retain-state-shutdown", + }; + unsigned int i; + + for (i =3D 0; i < ARRAY_SIZE(unsupported); i++) + if (fwnode_property_present(fwnode, unsupported[i])) + return dev_err_probe(chip->dev, -EINVAL, + "%s is not supported for component %pfw\n", + unsupported[i], fwnode); + + return 0; +} + +static int is32fl3207_write_channels(struct is32fl3207 *chip, + const struct mc_subled *subleds, + unsigned int num_channels) +{ + unsigned int i; + int ret; + + guard(mutex)(&chip->lock); + + for (i =3D 0; i < num_channels; i++) { + ret =3D regmap_write(chip->regmap, + IS32FL3207_REG_PWM_LOW(subleds[i].channel), + subleds[i].brightness); + if (ret) + return ret; + } + + return regmap_write(chip->regmap, IS32FL3207_REG_PWM_UPDATE, 0); +} + +static int is32fl3207_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct is32fl3207_led *led =3D container_of(cdev, struct is32fl3207_led, + cdev); + struct mc_subled subled =3D { + .brightness =3D brightness, + .channel =3D led->channel, + }; + + return is32fl3207_write_channels(led->chip, &subled, 1); +} + +static int is32fl3207_mc_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mcdev =3D lcdev_to_mccdev(cdev); + struct is32fl3207_led *led =3D container_of(mcdev, struct is32fl3207_led, + mcdev); + + led_mc_calc_color_components(mcdev, brightness); + + return is32fl3207_write_channels(led->chip, mcdev->subled_info, + mcdev->num_colors); +} + +static int is32fl3207_configure_channel(struct is32fl3207 *chip, + struct fwnode_handle *fwnode, + unsigned int *channel) +{ + u64 scaling; + u32 max_microamp; + u32 reg; + int ret; + + ret =3D fwnode_property_read_u32(fwnode, "reg", ®); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read channel for %pfw\n", + fwnode); + + if (reg >=3D IS32FL3207_NUM_CHANNELS) + return dev_err_probe(chip->dev, -EINVAL, + "channel %u is out of range\n", reg); + + if (test_bit(reg, chip->channels)) + return dev_err_probe(chip->dev, -EINVAL, + "channel %u is used more than once\n", + reg); + + ret =3D fwnode_property_read_u32(fwnode, "led-max-microamp", + &max_microamp); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read current limit for channel %u\n", + reg); + + if (!max_microamp || max_microamp > chip->output_max_microamp) + return dev_err_probe(chip->dev, -EINVAL, + "invalid current limit %u uA for channel %u\n", + max_microamp, reg); + + /* GCC is fixed at 0xff, so use each output's scaling register. */ + scaling =3D div_u64((u64)max_microamp * 256 * 256, + (u64)chip->output_max_microamp * + IS32FL3207_GLOBAL_CURRENT_MAX); + if (!scaling) + return dev_err_probe(chip->dev, -EINVAL, + "current limit %u uA is below channel %u resolution\n", + max_microamp, reg); + + scaling =3D min_t(u64, scaling, 0xff); + + guard(mutex)(&chip->lock); + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_SCALING(reg), + (unsigned int)scaling); + if (ret) + return ret; + + set_bit(reg, chip->channels); + *channel =3D reg; + + return 0; +} + +static int is32fl3207_register_single(struct is32fl3207 *chip, + struct fwnode_handle *fwnode, + struct is32fl3207_led *led) +{ + struct led_init_data init_data =3D { + .devicename =3D dev_name(chip->dev), + .devname_mandatory =3D true, + .fwnode =3D fwnode, + }; + unsigned int max_brightness; + unsigned int brightness; + u32 color; + int ret; + + if (!fwnode_property_present(fwnode, "function") && + !fwnode_property_present(fwnode, "color")) + return dev_err_probe(chip->dev, -EINVAL, + "single LED %pfw requires function or color\n", + fwnode); + + ret =3D is32fl3207_parse_led_properties(chip, fwnode, + &max_brightness, &brightness); + if (ret) + return ret; + + if (fwnode_property_present(fwnode, "color")) { + ret =3D fwnode_property_read_u32(fwnode, "color", &color); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read color for %pfw\n", + fwnode); + if (color >=3D LED_COLOR_ID_MAX || color =3D=3D LED_COLOR_ID_MULTI || + color =3D=3D LED_COLOR_ID_RGB) + return dev_err_probe(chip->dev, -EINVAL, + "invalid single LED color %u\n", + color); + } + + ret =3D is32fl3207_configure_channel(chip, fwnode, &led->channel); + if (ret) + return ret; + led->chip =3D chip; + led->led_cdev =3D &led->cdev; + led->cdev.brightness =3D brightness; + led->cdev.max_brightness =3D max_brightness; + led->cdev.brightness_set_blocking =3D is32fl3207_brightness_set; + + ret =3D is32fl3207_brightness_set(&led->cdev, brightness); + if (ret) + return ret; + + return devm_led_classdev_register_ext(chip->dev, &led->cdev, + &init_data); +} + +static int is32fl3207_register_multicolor(struct is32fl3207 *chip, + struct fwnode_handle *fwnode, + struct is32fl3207_led *led) +{ + struct led_init_data init_data =3D { + .devicename =3D dev_name(chip->dev), + .devname_mandatory =3D true, + .fwnode =3D fwnode, + }; + struct mc_subled *subleds; + DECLARE_BITMAP(color_map, LED_COLOR_ID_MAX); + unsigned int max_brightness; + unsigned int brightness; + unsigned int count; + unsigned int i =3D 0; + u32 group_color; + u32 group_reg; + unsigned int first_channel =3D IS32FL3207_NUM_CHANNELS; + int ret; + + ret =3D is32fl3207_parse_led_properties(chip, fwnode, + &max_brightness, &brightness); + if (ret) + return ret; + + ret =3D fwnode_property_read_u32(fwnode, "color", &group_color); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read color for %pfw\n", fwnode); + + if (group_color !=3D LED_COLOR_ID_RGB && + group_color !=3D LED_COLOR_ID_MULTI) + return dev_err_probe(chip->dev, -EINVAL, + "invalid multicolor LED color %u\n", + group_color); + + ret =3D fwnode_property_read_u32(fwnode, "reg", &group_reg); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read group index for %pfw\n", + fwnode); + + count =3D fwnode_get_child_node_count(fwnode); + if (!count || count > LED_COLOR_ID_MAX) + return dev_err_probe(chip->dev, -EINVAL, + "invalid component count %u for %pfw\n", + count, fwnode); + + subleds =3D devm_kcalloc(chip->dev, count, sizeof(*subleds), GFP_KERNEL); + if (!subleds) + return -ENOMEM; + bitmap_zero(color_map, LED_COLOR_ID_MAX); + + fwnode_for_each_child_node_scoped(fwnode, child) { + u32 color; + + ret =3D is32fl3207_validate_component(chip, child); + if (ret) + return ret; + + ret =3D fwnode_property_read_u32(child, "color", &color); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read color for %pfw\n", + child); + + if (color >=3D LED_COLOR_ID_MAX || color =3D=3D LED_COLOR_ID_MULTI || + color =3D=3D LED_COLOR_ID_RGB) + return dev_err_probe(chip->dev, -EINVAL, + "invalid component color %u\n", + color); + if (test_and_set_bit(color, color_map)) + return dev_err_probe(chip->dev, -EINVAL, + "component color %u is used more than once\n", + color); + + ret =3D is32fl3207_configure_channel(chip, child, + &subleds[i].channel); + if (ret) + return ret; + + subleds[i].color_index =3D color; + subleds[i].intensity =3D max_brightness; + subleds[i].max_intensity =3D 0; + first_channel =3D min(first_channel, subleds[i].channel); + i++; + } + + if (group_reg !=3D first_channel) + return dev_err_probe(chip->dev, -EINVAL, + "group index %u does not match first channel %u\n", + group_reg, first_channel); + + led->chip =3D chip; + led->led_cdev =3D &led->mcdev.led_cdev; + led->mcdev.num_colors =3D count; + led->mcdev.subled_info =3D subleds; + led->mcdev.led_cdev.brightness =3D brightness; + led->mcdev.led_cdev.max_brightness =3D max_brightness; + led->mcdev.led_cdev.brightness_set_blocking =3D + is32fl3207_mc_brightness_set; + + led_mc_calc_color_components(&led->mcdev, brightness); + + ret =3D is32fl3207_write_channels(chip, subleds, count); + if (ret) + return ret; + + return devm_led_classdev_multicolor_register_ext(chip->dev, &led->mcdev, + &init_data); +} + +static int is32fl3207_register_led(struct is32fl3207 *chip, + struct fwnode_handle *fwnode, + struct is32fl3207_led *led) +{ + unsigned int count =3D fwnode_get_child_node_count(fwnode); + bool has_color =3D fwnode_property_present(fwnode, "color"); + u32 color =3D LED_COLOR_ID_MAX; + int ret; + + if (has_color) { + ret =3D fwnode_property_read_u32(fwnode, "color", &color); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to read color for %pfw\n", + fwnode); + } + + if (color =3D=3D LED_COLOR_ID_RGB || color =3D=3D LED_COLOR_ID_MULTI) { + if (!count) + return dev_err_probe(chip->dev, -EINVAL, + "multicolor LED %pfw has no components\n", + fwnode); + + return is32fl3207_register_multicolor(chip, fwnode, led); + } + + if (count) + return dev_err_probe(chip->dev, -EINVAL, + "single LED %pfw must not have components\n", + fwnode); + + return is32fl3207_register_single(chip, fwnode, led); +} + +static int is32fl3207_prepare_reset(struct is32fl3207 *chip) +{ + u8 scaling[IS32FL3207_NUM_CHANNELS] =3D { }; + int ret; + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, 0); + if (ret) + return ret; + + return regmap_bulk_write(chip->regmap, + IS32FL3207_REG_SCALING(0), scaling, + sizeof(scaling)); +} + +static int is32fl3207_hw_init(struct is32fl3207 *chip) +{ + u8 scaling[IS32FL3207_NUM_CHANNELS] =3D { }; + u8 pwm[2 * IS32FL3207_NUM_CHANNELS] =3D { }; + int disable_ret; + int ret; + + /* Reset requires normal operation (SSD =3D 1). */ + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, + IS32FL3207_CONTROL_ENABLE); + if (ret) + return ret; + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_RESET, 0); + if (ret) + return ret; + usleep_range(200, 300); + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, 0); + if (ret) + return ret; + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_GLOBAL_CURRENT, + IS32FL3207_GLOBAL_CURRENT_MAX); + if (ret) + return ret; + + ret =3D regmap_bulk_write(chip->regmap, IS32FL3207_REG_SCALING(0), + scaling, sizeof(scaling)); + if (ret) + return ret; + + ret =3D regmap_bulk_write(chip->regmap, IS32FL3207_REG_PWM_LOW(0), pwm, + sizeof(pwm)); + if (ret) + return ret; + + /* PWM data can be latched only in normal operation. */ + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, + IS32FL3207_CONTROL_ENABLE); + if (ret) + return ret; + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_PWM_UPDATE, 0); + disable_ret =3D regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, 0); + + return ret ?: disable_ret; +} + +static int is32fl3207_enable(struct is32fl3207 *chip) +{ + int ret; + + guard(mutex)(&chip->lock); + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, + IS32FL3207_CONTROL_ENABLE); + if (ret) + return ret; + + ret =3D regmap_write(chip->regmap, IS32FL3207_REG_PWM_UPDATE, 0); + if (ret) + regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, 0); + + return ret; +} + +static void is32fl3207_disable(void *data) +{ + struct is32fl3207 *chip =3D data; + + guard(mutex)(&chip->lock); + regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, 0); + if (chip->enable_gpio) + gpiod_set_value_cansleep(chip->enable_gpio, 0); +} + +static const struct regmap_config is32fl3207_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 8, + .max_register =3D IS32FL3207_REG_RESET, +}; + +static int is32fl3207_probe(struct i2c_client *client) +{ + struct device *dev =3D &client->dev; + struct is32fl3207 *ddata; + unsigned int count; + unsigned int i =3D 0; + u32 riset_ohms; + int ret; + + count =3D device_get_child_node_count(dev); + if (!count || count > IS32FL3207_NUM_CHANNELS) + return dev_err_probe(dev, -EINVAL, + "invalid LED count %u\n", count); + + ddata =3D devm_kzalloc(dev, struct_size(ddata, leds, count), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + ddata->dev =3D dev; + ddata->num_leds =3D count; + i2c_set_clientdata(client, ddata); + + ret =3D device_property_read_u32(dev, "issi,riset-ohms", &riset_ohms); + if (ret) + return dev_err_probe(dev, ret, "failed to read RISET value\n"); + + if (riset_ohms < 2000) + return dev_err_probe(dev, -EINVAL, + "RISET value %u is below 2000 ohms\n", + riset_ohms); + + ddata->output_max_microamp =3D div_u64(IS32FL3207_CURRENT_NUMERATOR, + riset_ohms); + if (!ddata->output_max_microamp) + return dev_err_probe(dev, -EINVAL, + "RISET value %u is too large\n", + riset_ohms); + + ddata->enable_gpio =3D devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(ddata->enable_gpio)) + return dev_err_probe(dev, PTR_ERR(ddata->enable_gpio), + "failed to get enable GPIO\n"); + + ddata->regmap =3D devm_regmap_init_i2c(client, + &is32fl3207_regmap_config); + if (IS_ERR(ddata->regmap)) + return dev_err_probe(dev, PTR_ERR(ddata->regmap), + "failed to allocate register map\n"); + + ret =3D devm_mutex_init(dev, &ddata->lock); + if (ret) + return ret; + + ret =3D devm_regulator_get_enable_optional(dev, "vcc"); + if (ret && ret !=3D -ENODEV) + return dev_err_probe(dev, ret, + "failed to enable VCC regulator\n"); + + ret =3D devm_add_action_or_reset(dev, is32fl3207_disable, ddata); + if (ret) + return ret; + + /* Let VCC settle while SDB keeps the outputs disabled. */ + usleep_range(1000, 2000); + + /* + * Registers remain accessible with SDB low. Clear retained scaling + * before releasing hardware shutdown. + */ + ret =3D is32fl3207_prepare_reset(ddata); + if (ret) + return dev_err_probe(dev, ret, + "failed to prepare controller reset\n"); + + if (ddata->enable_gpio) + gpiod_set_value_cansleep(ddata->enable_gpio, 1); + + /* Allow the SDB release and I2C interface reset to settle. */ + usleep_range(1000, 2000); + + ret =3D is32fl3207_hw_init(ddata); + if (ret) + return dev_err_probe(dev, ret, + "failed to initialize controller\n"); + + device_for_each_child_node_scoped(dev, child) { + struct is32fl3207_led *led =3D &ddata->leds[i]; + + ret =3D is32fl3207_register_led(ddata, child, led); + if (ret) + return ret; + + i++; + } + + ret =3D is32fl3207_enable(ddata); + if (ret) + return dev_err_probe(dev, ret, "failed to enable controller\n"); + + return 0; +} + +static void is32fl3207_shutdown(struct i2c_client *client) +{ + struct is32fl3207 *chip =3D i2c_get_clientdata(client); + bool retain_state =3D false; + unsigned int i; + + for (i =3D 0; i < chip->num_leds; i++) + if (chip->leds[i].led_cdev->flags & LED_RETAIN_AT_SHUTDOWN) { + retain_state =3D true; + break; + } + + if (!retain_state) { + is32fl3207_disable(chip); + return; + } + + for (i =3D 0; i < chip->num_leds; i++) { + struct led_classdev *cdev =3D chip->leds[i].led_cdev; + + if (cdev->flags & LED_RETAIN_AT_SHUTDOWN) + continue; + if (cdev->flags & LED_MULTI_COLOR) + is32fl3207_mc_brightness_set(cdev, LED_OFF); + else + is32fl3207_brightness_set(cdev, LED_OFF); + } +} + +static const struct of_device_id is32fl3207_of_match[] =3D { + { .compatible =3D "issi,is32fl3207" }, + { } +}; +MODULE_DEVICE_TABLE(of, is32fl3207_of_match); + +static const struct i2c_device_id is32fl3207_id[] =3D { + { .name =3D "is32fl3207" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, is32fl3207_id); + +static struct i2c_driver is32fl3207_driver =3D { + .driver =3D { + .name =3D "is32fl3207", + .of_match_table =3D is32fl3207_of_match, + }, + .probe =3D is32fl3207_probe, + .shutdown =3D is32fl3207_shutdown, + .id_table =3D is32fl3207_id, +}; +module_i2c_driver(is32fl3207_driver); + +MODULE_AUTHOR("Ahmad Byagowi "); +MODULE_DESCRIPTION("Lumissil IS32FL3207 LED controller driver"); +MODULE_LICENSE("GPL"); --=20 2.50.1 (Apple Git-155) From nobody Tue Sep 29 04:08:41 2026 Received: from mail-pg1-f175.google.com (mail-pg1-f175.google.com [209.85.215.175]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5F51E43B6C9 for ; Wed, 12 Aug 2026 14:17:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786544253; cv=none; b=BaDMc+dDd0dz8kkge/Z9XrjlEwnLNXPk4T8vhJsTkogN40rN4YlKiTAAQPSwW3MfZqhAdfE6pbzC8w3nXz1t3VweYPuQ6AMu5vlF1HGj+B20WGHyc5pcVdupzmjVhhReN7Mm5hPFxDyuwv+HpzIycUn3CS+Fzi2J+aau5QuDmS8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786544253; c=relaxed/simple; bh=TMJAnG8b4psorsvMOOOR3YdZzaqaqHlSJnvqJmht61Y=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mT4BYPvMcgL+MaSpkcLPEdujXd/x58M23hl79NAc3PgDwc3Th9ZCOITGop3HL9N93juY7Bc18EdYRTHlSVaWuniy/zJq1l+53tQK8ddlbyPByd3myfBKhOejyNGC0sqlab6bH6VQzGVm5eXID8wTssXtEaMvz8X11VxapUmijgs= 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=rSN8aq1O; arc=none smtp.client-ip=209.85.215.175 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="rSN8aq1O" Received: by mail-pg1-f175.google.com with SMTP id 41be03b00d2f7-ca88130e09aso627370a12.3 for ; Wed, 12 Aug 2026 07:17:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786544248; x=1787149048; 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=CiwN7fMyor0aHM1D7i8wQ5RI5PPro03COVrr2/AFUGU=; b=rSN8aq1OBO3CaDMuvy9LtomCaSAd5F5oiH3VqSFaySpywPPzx+IlEwaWaE0W6VAS/V nCprzmgvpmoF2X5PT7d2qqT/7yfLYG9kqSzhD47mQyYXOzLbXXFQMvOG0egone3L4+FN 7DzO1oERXDl/pQaHMcsGyDIaH61U5I/Tawqa+hLtx3QGD2fQLS8cI4ux6nqUMw6/nIDI J5r3BA1h+8RpjVpNebPVipkZ13BrJDy70UJdxWprcpB87QdzkBM52aw+aUDlRC1VCafI zh8JB4fqpWPNCfwDpnu+Q8u0tmx9kC+VARo3mQAUQY3JKYr9vgjqWw5JVAtGHeE9nHBI FXxw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786544248; x=1787149048; 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=CiwN7fMyor0aHM1D7i8wQ5RI5PPro03COVrr2/AFUGU=; b=abOpw9LH2zaOLi2MsPinp4ptoxh7I8UFzghMxBpzQC9JXy0MfUKuNdX5ySxD8th7f2 eRpDR+CReyE/OngYyEWqqKrSgV75jXmtq/LnSYeWKQEDq9DLL1J0sheubC/9raBacqL6 qOPHdfkepj90iAbylE+VhMN8G/dIxcNNV7vKPkgrlMOjz2PBp4LgMUvb8YRL+mOria1t cY9M2dB9BufRpGHQykQlYMnRlDh8HRiBA4/lXFPFfDbfQjqMEkWfty9v1r66xuHEah6g L0/gZC/cDtzg8tuS2+Ef9S+7r/14wPp1Ft9d4l9tprLfesgDgLSsrnaXaAOo0bqRSCDX O4Jg== X-Forwarded-Encrypted: i=1; AHgh+RrH0l7FblD0SPVKld88J/V8lHA5cCxzZtiwvRvar6vTSsEckt0Jt3rpPzN6CnL/8Mzdobew7nZLrgahgck=@vger.kernel.org X-Gm-Message-State: AOJu0YyniMG7clsJ4Fn9TCL19wUqNPZ5K1Pe4ylsCKvg4ZJv0OFvg3MV DpkTVudjCl8IDHvHg4qNe49NvZUJYXnQcNtdpd5sFePlOkMOYQy5oJv9 X-Gm-Gg: AR+sD110+Z3evqpfjtdBC/vgb2IkOF2b2ZtUQlFBRWOKnRbL3iVep8sfkFvxN+nPWy/ K9HuzszHAc9Ikriy+A56y6spWhWAfrrpJH3JXCIKt0VqTZqD8E9QyxY37xS5gSfJFVg41mZI/h4 /PosnqMHg6q6WPv1LslkIpvgexdDnZK821BV1isbeM31XP/BKkUSuDOhFXZaSUyHvAgIJeK4x/X HuhSQLXTYQNQdl7NG/6y5FJ4H+LaAeMVULvenM436O9rvSXl41y+D2T79CyrXYaGCzq+FZ81nBe FkW90RTUcHDPnWMuXibpwzxDHDEgJPRQN9ISm5hA6jxup536mR5ygjCcNUFTaOJT3/s9xPHzsIy gNj1Z+4eklntQ7DcaZdCd8NKJKRuwEJF7xTmZSMtNRzmBgnRJTT+k0BvNGaK5jRHY1DcPnRyM3a b1QxCQqAMADZ+XYzGPVjR0CaGitPK0dNuPSE7cUIRsxjaUzuMD2BdakuUYgPMaBHkQYnt6GPuGW NaHDGlcaQL5UNCT6cq8jLODqTI5JM9+6UNml78fdSsw+9xHQg== X-Received: by 2002:a05:6a20:2587:b0:3c3:c3fb:2473 with SMTP id adf61e73a8af0-3cc3f731173mr5685375637.20.1786544247452; Wed, 12 Aug 2026 07:17:27 -0700 (PDT) Received: from Mac.localdomain (198-27-228-154.fiber.dynamic.sonic.net. [198.27.228.154]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-31cf361d57asm11963071eec.6.2026.08.12.07.17.24 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 12 Aug 2026 07:17:25 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Nam Tran , Kees Cook , "Gustavo A. R. Silva" , Peter Rosin , Andi Shyti , Richard Cochran , Vadim Fedorenko , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hardening@vger.kernel.org Subject: [PATCH net-next v4 3/5] i2c: mux: Propagate software nodes to channel adapters Date: Wed, 12 Aug 2026 07:17:13 -0700 Message-ID: X-Mailer: git-send-email 2.50.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Device Tree channel nodes are associated with the adapters created by i2c-mux, but equivalent software-node descriptions are not. Find the software-node child whose reg value matches the channel and 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 | 71 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c index 681a201c239b..040284236275 100644 --- a/drivers/i2c/i2c-mux.c +++ b/drivers/i2c/i2c-mux.c @@ -20,11 +20,13 @@ */ =20 #include +#include #include #include #include #include #include +#include #include #include =20 @@ -33,6 +35,7 @@ struct i2c_mux_priv { struct i2c_adapter adap; struct i2c_algorithm algo; struct i2c_mux_core *muxc; + struct fwnode_handle *swnode; u32 chan_id; }; =20 @@ -264,10 +267,60 @@ static const struct i2c_lock_operations i2c_parent_lo= ck_ops =3D { .unlock_bus =3D i2c_parent_unlock_bus, }; =20 +static struct fwnode_handle * +i2c_mux_get_channel_swnode(struct i2c_mux_core *muxc, u32 chan_id) +{ + struct fwnode_handle *dev_node =3D dev_fwnode(muxc->dev); + struct fwnode_handle *mux_node, *child =3D NULL; + u32 reg; + + /* A software node supplementing ACPI is the secondary fwnode. */ + if (!is_software_node(dev_node)) { + if (IS_ERR_OR_NULL(dev_node)) + return NULL; + dev_node =3D dev_node->secondary; + } + if (!is_software_node(dev_node)) + return NULL; + + if (muxc->arbitrator) + mux_node =3D fwnode_get_named_child_node(dev_node, "i2c-arb"); + else if (muxc->gate) + mux_node =3D fwnode_get_named_child_node(dev_node, "i2c-gate"); + else + mux_node =3D fwnode_get_named_child_node(dev_node, "i2c-mux"); + + if (mux_node) { + /* A "reg" property indicates an old-style firmware entry. */ + if (!fwnode_property_read_u32(mux_node, "reg", ®)) { + fwnode_handle_put(mux_node); + mux_node =3D NULL; + } + } + + if (!mux_node) + mux_node =3D fwnode_handle_get(dev_node); + else if (muxc->arbitrator || muxc->gate) + child =3D fwnode_handle_get(mux_node); + + if (!child) { + fwnode_for_each_child_node(mux_node, child) { + if (fwnode_property_read_u32(child, "reg", ®)) + continue; + if (chan_id =3D=3D reg) + break; + } + } + + fwnode_handle_put(mux_node); + return child; +} + int i2c_mux_add_adapter(struct i2c_mux_core *muxc, u32 force_nr, u32 chan_id) { struct i2c_adapter *parent =3D muxc->parent; + struct fwnode_handle *channel_node =3D NULL; struct i2c_mux_priv *priv; char symlink_name[20]; int ret; @@ -324,8 +377,8 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, priv->adap.lock_ops =3D &i2c_parent_lock_ops; =20 /* - * Try to populate the mux adapter's of_node, expands to - * nothing if !CONFIG_OF. + * Associate the mux adapter with its DT or software-node channel. + * DT support expands to nothing if !CONFIG_OF. */ if (muxc->dev->of_node) { struct device_node *dev_node =3D muxc->dev->of_node; @@ -364,6 +417,8 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, =20 priv->adap.dev.of_node =3D child; of_node_put(mux_node); + } else { + channel_node =3D i2c_mux_get_channel_swnode(muxc, chan_id); } =20 /* @@ -374,6 +429,16 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, ACPI_COMPANION(muxc->dev), chan_id); =20 + if (channel_node) { + ret =3D device_add_software_node(&priv->adap.dev, + to_software_node(channel_node)); + if (!ret) + priv->swnode =3D channel_node; + fwnode_handle_put(channel_node); + if (ret) + goto err_free_priv; + } + if (force_nr) { priv->adap.nr =3D force_nr; ret =3D i2c_add_numbered_adapter(&priv->adap); @@ -408,6 +473,7 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc, return 0; =20 err_free_priv: + device_remove_software_node(&priv->adap.dev); kfree(priv); return ret; } @@ -430,6 +496,7 @@ void i2c_mux_del_adapters(struct i2c_mux_core *muxc) =20 sysfs_remove_link(&priv->adap.dev.kobj, "mux_device"); i2c_del_adapter(adap); + fwnode_handle_put(priv->swnode); of_node_put(np); kfree(priv); } --=20 2.50.1 (Apple Git-155) From nobody Tue Sep 29 04:08:41 2026 Received: from mail-pl1-f169.google.com (mail-pl1-f169.google.com [209.85.214.169]) (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 E2944453A3B for ; Wed, 12 Aug 2026 14:17:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786544255; cv=none; b=a+jVnZepaybcD+eSHqMQ6stDP9N9YSqOwfwW7BETiS8RNSmPU9FI2koMemSIsTZ5eZfFMcou4fICe53Ov0lc65ojJNL93XU+g0rCul4eMYKCr+OWisu6RRjwj4EvHyYSublSn5dQFTHSQVUK70whZi+btwioHc+WykZ0hufq17Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786544255; c=relaxed/simple; bh=qbT29I2CzZWeVZnhnEyPFxOtc1aecRPQo1/N7wBb2Hs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=bWrNsqFFygp6NUcAZdenB/JnU0YQntaWV+4dxF5eBt97r46J0xhHbhEWjYNuTdbAu/wIYUWdQ18hktR+/5gfIvK6SAfd8Yieovpc8A4CveUqagLzBs7/VOGoXnzBx1Ct/g/nx/0q9UCoEVIAtXZt8Zav5DBSXPtzbFr1r1w47Pw= 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=dZYoOw3E; arc=none smtp.client-ip=209.85.214.169 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="dZYoOw3E" Received: by mail-pl1-f169.google.com with SMTP id d9443c01a7336-2cfff5f88dbso11507525ad.3 for ; Wed, 12 Aug 2026 07:17:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786544250; x=1787149050; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=moijbzDzsmh5XF7idfs/D6eyKTrAZoDD9bA5sLCOiyM=; b=dZYoOw3EZ5wXng3+bps8s3tq1jAWnXB/XZ2Tu0ZZF8CmT/tWT3i8ncMNo5gxkZOxPG mo6lOinMhQsg2UxwVTbKerNxc5AMZyF4dyWEtqJZ+7A074nMhQSoN5QWZ3GpDrPT2YBk E5E1cYWsaRcfgp03zUjRy/EP2wWEeoqStRSiOcGgC8ketNV65G32v7Ba2cfLO/uon8YR jYw0/FxCq8aSy2bGNuc5FoxTaEe6La4lHtKid4Rw8pOKUgSH5q6/w1JuwKgAcxPi6g8F T8eW4olOhzRY+qopkC/cQXvysTGdB5R5TLSR9ZNkO65M9mFcN0NpOGzraIS/RWCzJ3WA uvLw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786544250; x=1787149050; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=moijbzDzsmh5XF7idfs/D6eyKTrAZoDD9bA5sLCOiyM=; b=L+fbkERXz3gQPj6FdkI4fhNLsIQ9JGM7Ib2pzqJur5dp21jlDpEFqZ3+VLYk9n2Yq4 Og/MxiKmo85px+GJLUKRI1XMwSxMtNmKBKdGyT0W8NJ5X475UjbJcQfM4ZTtB18vjeVX 5YoBPmqpZ3QFbp31MLYI1O6IuCmrZCoieqQ/lY8zEh9NSmDa9i9LtYPIP2CBn6hLNout yuiZFhw+KGyg7VURf+ox3kHtK1yBjaL/DHFe/JECJpQDs+8C8UjAdvwGboHuWCHPvQTQ 2gUTV6dbNfsq01hvjSRvcCkoDkZVyDgdEyRqHZ//yt3nuqZ3U/J5miu4fglk6rirncvX iuxQ== X-Forwarded-Encrypted: i=1; AHgh+RrIbttCQTMDM8jlNctjx9QyBPPwvLE1zGxQGeLVoGZx+W/nr+NO/CR81XWXHbgjTP3mBiho7Xz3flwtaac=@vger.kernel.org X-Gm-Message-State: AOJu0YxbjRdnHUBY7dqRuVuS9Fntq2WJoZwnpzyc6dlBP0Zhq7Qh5WtX xzJxHZShaK9Cpm/TD+98vQ+OU8YISyQB1J9vGg2tzKksg+JdK3ZoB5Hy X-Gm-Gg: AR+sD13x4WIEgHWR2p1cyqdnqAZcv7bsSJaa0zkhdaRmtv3SEVnUwGQXTrywXHzdWon aKO821UAHW7MDPAsTZ9h4XNkEsb1JFjDNWhfGw3hGf92uEW93IQQAYKuSELrMKRoOaBllfidbyj k7v+QPl119G2Dw/hVIC3EVW3kJ+b8NLift9m+48Dq4LX2wThlk4vR/ol3TULgWdYuK0FEObKpb9 v5Eevp1Xd/yAgRK4+rHU6TuRMPIYq56U+XSMjgkp6nBHkJ6or1pLAtNNzcaSYWgYqCAy4XscoXS bEbFT5l3opJ6nPzLnIWofAX54F3CUfiD8WtF+uWpGPBvdr5Phi1IdKDqzYJUMa8k1x8+BsiCJcZ wpB+9U4a61/FWEWfhORi1SBKKbIibjNmzwjCpdnC9Cg+Lly/v9m6OGC4MT8TmQV2lr69xTuvjgj 7eYy3VNzldnZUzFAh9y1DTav0jiAEpaQubfnWpROteqf/jcDj3a09QPUVFDbh32iI9plap9+yLq WbwAJQmo0jj1B/eSHnYdB3aP6dstn/cXlSkugPsYUv9TA+BkQ== X-Received: by 2002:a05:6a20:7283:b0:3c3:875d:705e with SMTP id adf61e73a8af0-3cc3f71c5b8mr5501726637.19.1786544249608; Wed, 12 Aug 2026 07:17:29 -0700 (PDT) Received: from Mac.localdomain (198-27-228-154.fiber.dynamic.sonic.net. [198.27.228.154]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-31cf361d57asm11963071eec.6.2026.08.12.07.17.27 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 12 Aug 2026 07:17:28 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Nam Tran , Kees Cook , "Gustavo A. R. Silva" , Peter Rosin , Andi Shyti , Richard Cochran , Vadim Fedorenko , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hardening@vger.kernel.org Subject: [PATCH net-next v4 4/5] ptp: ocp: Add R4006 I2C peripheral topology Date: Wed, 12 Aug 2026 07:17:14 -0700 Message-ID: <9b73c41a9f94482227af5879b11747668fe9af51.1786543681.git.ahmadexp@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" R4006 cards place a PCA9546 mux behind the FPGA I2C controller. The mux exposes three LM75B temperature sensors, an SHT3x humidity sensor, an ICP10100 pressure sensor, and an IS32FL3207 controller for the GNSS and SMA LEDs. Describe the per-card topology with software nodes and instantiate standard I2C clients after their adapters appear. Keep the mux channel, RISET value, and per-output current limit in the board profile, and validate every profile index before constructing nodes. Select the profile only on supported Time Card PCI devices whose fixed-width EEPROM ID contains printable text, valid zero or 0xff padding, and an R4006 prefix. Leave erased, malformed, and unknown IDs unconfigured without changing the EEPROM data. Leave the channel containing the BNO08x empty because no upstream driver exists. Serialize topology changes with a private mutex and stable device references. Retry transient setup failures, report exhaustion once, and continue low-rate recovery so late adapter or client availability can still complete setup. Signed-off-by: Ahmad Byagowi --- drivers/ptp/ptp_ocp.c | 810 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 764 insertions(+), 46 deletions(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 3d26ec1f7b9e..f2c2979daaf1 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -21,10 +21,14 @@ #include #include #include +#include #include +#include #include #include =20 +#include + #define PCI_DEVICE_ID_META_TIMECARD 0x0400 =20 #define PCI_VENDOR_ID_CELESTICA 0x18d4 @@ -347,6 +351,70 @@ struct ptp_ocp_serial_port { #define OCP_SIGNAL_NUM 4 #define OCP_FREQ_NUM 4 =20 +#define OCP_I2C_MUX_CHANNELS 4 +#define OCP_I2C_MAX_SENSOR_COUNT 5 +#define OCP_I2C_MAX_LED_COUNT 5 +#define OCP_I2C_MAX_LED_COMPONENT_COUNT (3 * OCP_I2C_MAX_LED_COUNT) +#define OCP_I2C_MUX_NAME_LEN 32 +#define OCP_I2C_MUX_COMPATIBLE "nxp,pca9546" +#define OCP_I2C_MUX_TYPE "pca9546" +#define OCP_I2C_MUX_ADDRESS 0x70 +#define OCP_I2C_RETRY_MAX 10 +#define OCP_I2C_RECOVERY_SECS 30 +#define OCP_I2C_MAX_NODE_COUNT (1 + OCP_I2C_MUX_CHANNELS + \ + OCP_I2C_MAX_SENSOR_COUNT + 1 + \ + OCP_I2C_MAX_LED_COUNT + \ + OCP_I2C_MAX_LED_COMPONENT_COUNT) + +struct ptp_ocp_i2c_device { + const char *node_name; + const char *compatible; + const char *type; + u8 channel; + u8 address; +}; + +struct ptp_ocp_led { + const char *node_name; + const char *function; + u8 function_enumerator; + bool has_function_enumerator; + u8 channel[3]; +}; + +struct ptp_ocp_i2c_profile { + const char *name; + const struct ptp_ocp_i2c_device *sensors; + unsigned int sensor_count; + const struct ptp_ocp_led *leds; + unsigned int led_count; + const char *led_node_name; + u8 led_mux_channel; + u8 led_address; + u32 led_riset_ohms; + u32 led_max_microamp; +}; + +struct ptp_ocp_i2c_topology { + char mux_name[OCP_I2C_MUX_NAME_LEN]; + struct software_node mux_node; + struct property_entry mux_props[6]; + struct software_node channel_nodes[OCP_I2C_MUX_CHANNELS]; + struct property_entry channel_props[OCP_I2C_MUX_CHANNELS][4]; + struct software_node sensor_nodes[OCP_I2C_MAX_SENSOR_COUNT]; + struct property_entry sensor_props[OCP_I2C_MAX_SENSOR_COUNT][3]; + struct software_node led_node; + struct property_entry led_props[6]; + struct software_node led_nodes[OCP_I2C_MAX_LED_COUNT]; + struct property_entry led_group_props[OCP_I2C_MAX_LED_COUNT][7]; + struct software_node component_nodes[OCP_I2C_MAX_LED_COMPONENT_COUNT]; + struct property_entry + component_props[OCP_I2C_MAX_LED_COMPONENT_COUNT][4]; + const struct software_node *node_group[OCP_I2C_MAX_NODE_COUNT + 1]; + const struct ptp_ocp_i2c_profile *profile; + bool registered; +}; + enum { PORT_GNSS, PORT_GNSS2, @@ -403,6 +471,7 @@ struct ptp_ocp { u16 fw_version; u8 board_id[OCP_BOARD_ID_LEN]; u8 serial[OCP_SERIAL_LEN]; + struct mutex eeprom_lock; /* Serializes EEPROM discovery. */ bool has_eeprom_data; u32 pps_req_map; int flash_start; @@ -414,6 +483,15 @@ struct ptp_ocp { const struct ocp_sma_op *sma_op; struct dpll_device *dpll; dpll_tracker tracker; + const struct ptp_ocp_i2c_profile *i2c_profile; + struct ptp_ocp_i2c_topology *i2c_topology; + struct mutex i2c_topology_lock; /* Serializes topology updates. */ + struct delayed_work i2c_work; + struct notifier_block i2c_notifier; + atomic_t i2c_retry_count; + bool i2c_root_present; + bool i2c_resources_ready; + bool i2c_notifier_registered; int signals_nr; int freq_in_nr; }; @@ -444,6 +522,8 @@ static int ptp_ocp_signal_from_perout(struct ptp_ocp *b= p, int gen, struct ptp_perout_request *req); static int ptp_ocp_signal_enable(void *priv, u32 req, bool enable); static int ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_= nr); +static int ptp_ocp_i2c_notifier_call(struct notifier_block *nb, + unsigned long action, void *data); =20 static int ptp_ocp_art_board_init(struct ptp_ocp *bp, struct ocp_resource = *r); =20 @@ -488,6 +568,68 @@ static struct ptp_ocp_eeprom_map art_eeprom_map[] =3D { { } }; =20 +/* Channel 3's BNO08x at 0x4a has no upstream Linux driver, so omit it. */ +static const struct ptp_ocp_i2c_device ptp_ocp_r4006_sensors[] =3D { + { "temperature@48", "national,lm75b", "lm75b", 0, 0x48 }, + { "temperature@49", "national,lm75b", "lm75b", 0, 0x49 }, + { "temperature@4a", "national,lm75b", "lm75b", 0, 0x4a }, + { "humidity@44", NULL, "sht3x", 1, 0x44 }, + { "pressure@63", "invensense,icp10100", "icp10100", 2, 0x63 }, +}; + +static const struct ptp_ocp_led ptp_ocp_r4006_leds[] =3D { + { + .node_name =3D "multi-led@c", + .function =3D LED_FUNCTION_STATUS, + .channel =3D { 13, 12, 14 }, + }, + { + .node_name =3D "multi-led@6", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 1, + .has_function_enumerator =3D true, + .channel =3D { 7, 6, 8 }, + }, + { + .node_name =3D "multi-led@9", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 2, + .has_function_enumerator =3D true, + .channel =3D { 10, 9, 11 }, + }, + { + .node_name =3D "multi-led@0", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 3, + .has_function_enumerator =3D true, + .channel =3D { 1, 0, 2 }, + }, + { + .node_name =3D "multi-led@3", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 4, + .has_function_enumerator =3D true, + .channel =3D { 4, 3, 5 }, + }, +}; + +static_assert(ARRAY_SIZE(ptp_ocp_r4006_sensors) <=3D + OCP_I2C_MAX_SENSOR_COUNT); +static_assert(ARRAY_SIZE(ptp_ocp_r4006_leds) <=3D OCP_I2C_MAX_LED_COUNT); + +static const struct ptp_ocp_i2c_profile ptp_ocp_r4006_profile =3D { + .name =3D "r4006", + .sensors =3D ptp_ocp_r4006_sensors, + .sensor_count =3D ARRAY_SIZE(ptp_ocp_r4006_sensors), + .leds =3D ptp_ocp_r4006_leds, + .led_count =3D ARRAY_SIZE(ptp_ocp_r4006_leds), + .led_node_name =3D "led-controller@34", + .led_mux_channel =3D 1, + .led_address =3D 0x34, + .led_riset_ohms =3D 4700, + .led_max_microamp =3D 8150, +}; + #define bp_assign_entry(bp, res, val) ({ \ uintptr_t addr =3D (uintptr_t)(bp) + (res)->bp_offset; \ *(typeof(val) *)addr =3D val; \ @@ -1969,19 +2111,29 @@ ptp_ocp_nvmem_device_put(struct nvmem_device **nvme= mp) *nvmemp =3D NULL; } =20 -static void +static bool +ptp_ocp_has_eeprom_data(struct ptp_ocp *bp) +{ + return smp_load_acquire(&bp->has_eeprom_data); +} + +static int ptp_ocp_read_eeprom(struct ptp_ocp *bp) { const struct ptp_ocp_eeprom_map *map; struct nvmem_device *nvmem; const void *tag; - int ret; - - if (!bp->i2c_ctrl) - return; + int ret =3D 0; =20 tag =3D NULL; nvmem =3D NULL; + mutex_lock(&bp->eeprom_lock); + if (ptp_ocp_has_eeprom_data(bp)) + goto out; + if (!bp->i2c_ctrl || !bp->eeprom_map) { + ret =3D -ENODEV; + goto out; + } =20 for (map =3D bp->eeprom_map; map->len; map++) { if (map->tag !=3D tag) { @@ -1997,21 +2149,536 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp) } ret =3D nvmem_device_read(nvmem, map->off, map->len, BP_MAP_ENTRY_ADDR(bp, map)); - if (ret !=3D map->len) + if (ret !=3D map->len) { + if (ret >=3D 0) + ret =3D -EIO; goto fail; + } } =20 - bp->has_eeprom_data =3D true; + /* Publish the EEPROM fields before readers observe valid data. */ + smp_store_release(&bp->has_eeprom_data, true); + ret =3D 0; =20 out: ptp_ocp_nvmem_device_put(&nvmem); - return; + mutex_unlock(&bp->eeprom_lock); + return ret; =20 fail: - dev_err(&bp->pdev->dev, "could not read eeprom: %d\n", ret); goto out; } =20 +static int +ptp_ocp_i2c_adapter_match(struct device *dev, const void *data) +{ + return !!i2c_verify_adapter(dev); +} + +static struct i2c_adapter * +ptp_ocp_i2c_root_adapter(struct platform_device *i2c_ctrl) +{ + struct i2c_adapter *adapter; + struct device *dev; + + dev =3D device_find_child(&i2c_ctrl->dev, NULL, + ptp_ocp_i2c_adapter_match); + if (!dev) + return NULL; + + adapter =3D i2c_verify_adapter(dev); + if (!adapter || !try_module_get(adapter->owner)) { + put_device(dev); + return NULL; + } + + /* The caller owns the reference returned by device_find_child(). */ + return adapter; +} + +static bool +ptp_ocp_i2c_supported(struct ptp_ocp *bp) +{ + /* PCI IDs identify FPGA images, not a unique PCB revision. */ + return (bp->pdev->vendor =3D=3D PCI_VENDOR_ID_META && + bp->pdev->device =3D=3D PCI_DEVICE_ID_META_TIMECARD) || + (bp->pdev->vendor =3D=3D PCI_VENDOR_ID_CELESTICA && + bp->pdev->device =3D=3D PCI_DEVICE_ID_CELESTICA_TIMECARD); +} + +static bool +ptp_ocp_board_id_valid(const u8 *board_id, size_t *text_len) +{ + unsigned int len; + + for (len =3D 0; len < OCP_BOARD_ID_LEN; len++) + if (board_id[len] < 0x20 || board_id[len] > 0x7e) + break; + + if (!len) + return false; + + *text_len =3D len; + for (; len < OCP_BOARD_ID_LEN; len++) + if (board_id[len] !=3D 0 && board_id[len] !=3D 0xff) + return false; + + return true; +} + +static const struct ptp_ocp_i2c_profile * +ptp_ocp_i2c_select_profile(struct ptp_ocp *bp) +{ + static const char r4006_id[] =3D "R4006"; + size_t board_id_len; + + if (!ptp_ocp_has_eeprom_data(bp)) + return NULL; + + if (!ptp_ocp_board_id_valid(bp->board_id, &board_id_len)) + return NULL; + + if (board_id_len >=3D sizeof(r4006_id) - 1 && + !memcmp(bp->board_id, r4006_id, sizeof(r4006_id) - 1)) + return &ptp_ocp_r4006_profile; + + return NULL; +} + +static int +ptp_ocp_i2c_init_nodes(struct ptp_ocp *bp) +{ + static const char * const channel_names[] =3D { + "i2c@0", "i2c@1", "i2c@2", "i2c@3", + }; + static const char * const output_names[] =3D { + "led@0", "led@1", "led@2", "led@3", "led@4", "led@5", + "led@6", "led@7", "led@8", "led@9", "led@a", "led@b", + "led@c", "led@d", "led@e", "led@f", "led@10", "led@11", + }; + static const u32 colors[] =3D { + LED_COLOR_ID_RED, LED_COLOR_ID_GREEN, LED_COLOR_ID_BLUE, + }; + const struct ptp_ocp_i2c_profile *profile =3D bp->i2c_profile; + struct device *dev =3D &bp->pdev->dev; + struct ptp_ocp_i2c_topology *topology; + unsigned int component =3D 0; + unsigned int node =3D 0; + unsigned int expected_nodes; + unsigned int i, j, prop; + int ret; + + if (!profile) + return -EINVAL; + if (bp->i2c_topology && bp->i2c_topology->registered) + return 0; + if (bp->i2c_topology) { + topology =3D bp->i2c_topology; + goto register_nodes; + } + if (profile->sensor_count > OCP_I2C_MAX_SENSOR_COUNT || + profile->led_count > OCP_I2C_MAX_LED_COUNT || + profile->led_mux_channel >=3D OCP_I2C_MUX_CHANNELS) + return -EINVAL; + + for (i =3D 0; i < profile->sensor_count; i++) + if (profile->sensors[i].channel >=3D OCP_I2C_MUX_CHANNELS) + return -EINVAL; + + for (i =3D 0; i < profile->led_count; i++) + for (j =3D 0; j < ARRAY_SIZE(profile->leds[i].channel); j++) + if (profile->leds[i].channel[j] >=3D + ARRAY_SIZE(output_names)) + return -EINVAL; + + topology =3D devm_kzalloc(dev, sizeof(*topology), GFP_KERNEL); + if (!topology) + return -ENOMEM; + + topology->profile =3D profile; + snprintf(topology->mux_name, sizeof(topology->mux_name), + "ocp%d-%s-mux", bp->id, profile->name); + topology->mux_node.name =3D topology->mux_name; + topology->mux_node.properties =3D topology->mux_props; + topology->mux_props[0] =3D + PROPERTY_ENTRY_STRING("compatible", OCP_I2C_MUX_COMPATIBLE); + topology->mux_props[1] =3D + PROPERTY_ENTRY_U32("reg", OCP_I2C_MUX_ADDRESS); + topology->mux_props[2] =3D + PROPERTY_ENTRY_BOOL("i2c-mux-idle-disconnect"); + topology->mux_props[3] =3D PROPERTY_ENTRY_U32("#address-cells", 1); + topology->mux_props[4] =3D PROPERTY_ENTRY_U32("#size-cells", 0); + topology->node_group[node++] =3D &topology->mux_node; + + for (i =3D 0; i < OCP_I2C_MUX_CHANNELS; i++) { + topology->channel_nodes[i].name =3D channel_names[i]; + topology->channel_nodes[i].parent =3D &topology->mux_node; + topology->channel_nodes[i].properties =3D + topology->channel_props[i]; + topology->channel_props[i][0] =3D PROPERTY_ENTRY_U32("reg", i); + topology->channel_props[i][1] =3D + PROPERTY_ENTRY_U32("#address-cells", 1); + topology->channel_props[i][2] =3D + PROPERTY_ENTRY_U32("#size-cells", 0); + topology->node_group[node++] =3D &topology->channel_nodes[i]; + } + + for (i =3D 0; i < profile->sensor_count; i++) { + const struct ptp_ocp_i2c_device *sensor; + + sensor =3D &profile->sensors[i]; + topology->sensor_nodes[i].name =3D sensor->node_name; + topology->sensor_nodes[i].parent =3D + &topology->channel_nodes[sensor->channel]; + topology->sensor_nodes[i].properties =3D + topology->sensor_props[i]; + prop =3D 0; + if (sensor->compatible) + topology->sensor_props[i][prop++] =3D + PROPERTY_ENTRY_STRING("compatible", + sensor->compatible); + topology->sensor_props[i][prop] =3D + PROPERTY_ENTRY_U32("reg", sensor->address); + topology->node_group[node++] =3D &topology->sensor_nodes[i]; + } + + topology->led_node.name =3D profile->led_node_name; + topology->led_node.parent =3D + &topology->channel_nodes[profile->led_mux_channel]; + topology->led_node.properties =3D topology->led_props; + topology->led_props[0] =3D + PROPERTY_ENTRY_STRING("compatible", "issi,is32fl3207"); + topology->led_props[1] =3D + PROPERTY_ENTRY_U32("reg", profile->led_address); + topology->led_props[2] =3D + PROPERTY_ENTRY_U32("issi,riset-ohms", + profile->led_riset_ohms); + topology->led_props[3] =3D PROPERTY_ENTRY_U32("#address-cells", 1); + topology->led_props[4] =3D PROPERTY_ENTRY_U32("#size-cells", 0); + topology->node_group[node++] =3D &topology->led_node; + + for (i =3D 0; i < profile->led_count; i++) { + const struct ptp_ocp_led *led =3D &profile->leds[i]; + u32 group_reg; + + group_reg =3D min3(led->channel[0], led->channel[1], + led->channel[2]); + topology->led_nodes[i].name =3D led->node_name; + topology->led_nodes[i].parent =3D &topology->led_node; + topology->led_nodes[i].properties =3D + topology->led_group_props[i]; + prop =3D 0; + topology->led_group_props[i][prop++] =3D + PROPERTY_ENTRY_U32("reg", group_reg); + topology->led_group_props[i][prop++] =3D + PROPERTY_ENTRY_U32("color", LED_COLOR_ID_RGB); + topology->led_group_props[i][prop++] =3D + PROPERTY_ENTRY_STRING("function", led->function); + if (led->has_function_enumerator) + topology->led_group_props[i][prop++] =3D + PROPERTY_ENTRY_U32("function-enumerator", + led->function_enumerator); + topology->led_group_props[i][prop++] =3D + PROPERTY_ENTRY_U32("#address-cells", 1); + topology->led_group_props[i][prop] =3D + PROPERTY_ENTRY_U32("#size-cells", 0); + topology->node_group[node++] =3D &topology->led_nodes[i]; + + for (j =3D 0; j < ARRAY_SIZE(led->channel); j++, component++) { + u8 channel =3D led->channel[j]; + + topology->component_nodes[component].name =3D + output_names[channel]; + topology->component_nodes[component].parent =3D + &topology->led_nodes[i]; + topology->component_nodes[component].properties =3D + topology->component_props[component]; + topology->component_props[component][0] =3D + PROPERTY_ENTRY_U32("reg", channel); + topology->component_props[component][1] =3D + PROPERTY_ENTRY_U32("color", colors[j]); + topology->component_props[component][2] =3D + PROPERTY_ENTRY_U32("led-max-microamp", + profile->led_max_microamp); + topology->node_group[node++] =3D + &topology->component_nodes[component]; + } + } + + expected_nodes =3D 1 + OCP_I2C_MUX_CHANNELS + + profile->sensor_count + 1 + profile->led_count + + 3 * profile->led_count; + if (WARN_ON(node !=3D expected_nodes)) { + devm_kfree(dev, topology); + return -EINVAL; + } + + bp->i2c_topology =3D topology; + +register_nodes: + ret =3D software_node_register_node_group(topology->node_group); + if (ret) + return ret; + + topology->registered =3D true; + return 0; +} + +static int +ptp_ocp_i2c_add_device(struct i2c_adapter *adapter, + const struct software_node *node, + const char *type, u16 address) +{ + struct fwnode_handle *fwnode =3D software_node_fwnode(node); + struct i2c_board_info info =3D { }; + struct i2c_client *client; + + client =3D i2c_find_device_by_fwnode(fwnode); + if (client) { + put_device(&client->dev); + return 0; + } + + strscpy(info.type, type, sizeof(info.type)); + info.addr =3D address; + info.fwnode =3D fwnode; + client =3D i2c_new_client_device(adapter, &info); + if (IS_ERR(client)) + return PTR_ERR(client); + + return 0; +} + +static int +ptp_ocp_i2c_populate_channel(struct ptp_ocp *bp, unsigned int channel) +{ + struct ptp_ocp_i2c_topology *topology =3D bp->i2c_topology; + const struct ptp_ocp_i2c_profile *profile =3D topology->profile; + const struct software_node *node; + struct fwnode_handle *fwnode; + struct i2c_adapter *adapter; + unsigned int i; + int err, ret =3D 0; + + fwnode =3D software_node_fwnode(&topology->channel_nodes[channel]); + adapter =3D i2c_get_adapter_by_fwnode(fwnode); + if (!adapter) + return -EAGAIN; + + for (i =3D 0; i < profile->sensor_count; i++) { + const struct ptp_ocp_i2c_device *sensor; + + sensor =3D &profile->sensors[i]; + if (sensor->channel !=3D channel) + continue; + + node =3D &topology->sensor_nodes[i]; + err =3D ptp_ocp_i2c_add_device(adapter, node, sensor->type, + sensor->address); + if (err && !ret) + ret =3D err; + } + + if (channel =3D=3D profile->led_mux_channel) { + err =3D ptp_ocp_i2c_add_device(adapter, &topology->led_node, + "is32fl3207", + profile->led_address); + if (err && !ret) + ret =3D err; + } + + i2c_put_adapter(adapter); + return ret; +} + +static void +ptp_ocp_i2c_remove_mux(struct ptp_ocp *bp) +{ + struct fwnode_handle *fwnode; + struct i2c_client *client; + + if (!bp->i2c_topology || !bp->i2c_topology->registered) + return; + + fwnode =3D software_node_fwnode(&bp->i2c_topology->mux_node); + client =3D i2c_find_device_by_fwnode(fwnode); + if (!client) + return; + + i2c_unregister_device(client); + put_device(&client->dev); +} + +static void +ptp_ocp_i2c_kick(struct ptp_ocp *bp) +{ + if (!ptp_ocp_i2c_supported(bp)) + return; + if (!READ_ONCE(bp->i2c_resources_ready)) + return; + if (!READ_ONCE(bp->i2c_root_present)) + return; + + mod_delayed_work(system_wq, &bp->i2c_work, 1); +} + +static void +ptp_ocp_i2c_retry(struct ptp_ocp *bp, int error) +{ + unsigned long delay =3D HZ; + int retries; + + if (!READ_ONCE(bp->i2c_resources_ready)) + return; + if (!READ_ONCE(bp->i2c_root_present)) + return; + + retries =3D atomic_inc_return(&bp->i2c_retry_count); + if (retries >=3D OCP_I2C_RETRY_MAX) { + if (retries =3D=3D OCP_I2C_RETRY_MAX) { + dev_err(&bp->pdev->dev, + "I2C topology failed after %d attempts: %pe; " + "retrying every %d seconds\n", + OCP_I2C_RETRY_MAX, ERR_PTR(error), + OCP_I2C_RECOVERY_SECS); + } else { + atomic_set(&bp->i2c_retry_count, OCP_I2C_RETRY_MAX); + dev_err_ratelimited(&bp->pdev->dev, + "I2C topology setup still failing: %pe\n", + ERR_PTR(error)); + } + delay =3D OCP_I2C_RECOVERY_SECS * HZ; + } + + /* Preserve a faster rerun queued by an I2C bus notification. */ + queue_delayed_work(system_wq, &bp->i2c_work, delay); +} + +static int +ptp_ocp_i2c_populate_topology(struct ptp_ocp *bp, + struct platform_device *i2c_ctrl) +{ + const struct software_node *node; + struct i2c_adapter *adapter; + unsigned int channel; + int err, ret =3D 0; + + if (!READ_ONCE(bp->i2c_root_present)) + return 0; + if (!ptp_ocp_i2c_supported(bp) || !bp->eeprom_map) + return 0; + + adapter =3D ptp_ocp_i2c_root_adapter(i2c_ctrl); + if (!adapter) + return -EAGAIN; + + if (!ptp_ocp_has_eeprom_data(bp)) { + ret =3D ptp_ocp_read_eeprom(bp); + if (ret) + goto out_put_adapter; + } + if (!ptp_ocp_has_eeprom_data(bp)) { + ret =3D -EAGAIN; + goto out_put_adapter; + } + if (!bp->i2c_profile) + bp->i2c_profile =3D ptp_ocp_i2c_select_profile(bp); + if (!bp->i2c_profile) + goto out_put_adapter; + + ret =3D ptp_ocp_i2c_init_nodes(bp); + if (ret) + goto out_put_adapter; + + node =3D &bp->i2c_topology->mux_node; + ret =3D ptp_ocp_i2c_add_device(adapter, node, OCP_I2C_MUX_TYPE, + OCP_I2C_MUX_ADDRESS); + if (ret) + goto out_put_adapter; + + for (channel =3D 0; channel < OCP_I2C_MUX_CHANNELS; channel++) { + err =3D ptp_ocp_i2c_populate_channel(bp, channel); + if (err && !ret) + ret =3D err; + } + +out_put_adapter: + i2c_put_adapter(adapter); + return ret; +} + +static void +ptp_ocp_i2c_work(struct work_struct *work) +{ + struct ptp_ocp *bp =3D container_of(work, struct ptp_ocp, i2c_work.work); + struct platform_device *i2c_ctrl; + struct device *i2c_ctrl_dev; + int retries, ret =3D 0; + + /* Pair with resource publication after registration. */ + if (!smp_load_acquire(&bp->i2c_resources_ready)) + return; + if (!ptp_ocp_i2c_supported(bp)) + return; + + mutex_lock(&bp->i2c_topology_lock); + if (!READ_ONCE(bp->i2c_resources_ready) || + !READ_ONCE(bp->i2c_root_present)) { + mutex_unlock(&bp->i2c_topology_lock); + return; + } + + i2c_ctrl =3D READ_ONCE(bp->i2c_ctrl); + if (!i2c_ctrl) { + ret =3D -EAGAIN; + goto out_unlock; + } + + i2c_ctrl_dev =3D get_device(&i2c_ctrl->dev); + ret =3D ptp_ocp_i2c_populate_topology(bp, i2c_ctrl); + put_device(i2c_ctrl_dev); + +out_unlock: + mutex_unlock(&bp->i2c_topology_lock); + + if (ret) { + ptp_ocp_i2c_retry(bp, ret); + return; + } + + retries =3D atomic_xchg(&bp->i2c_retry_count, 0); + if (retries >=3D OCP_I2C_RETRY_MAX) + dev_info(&bp->pdev->dev, "I2C topology setup recovered\n"); +} + +static void +ptp_ocp_i2c_unregister(struct ptp_ocp *bp) +{ + struct ptp_ocp_i2c_topology *topology; + + WRITE_ONCE(bp->i2c_resources_ready, false); + WRITE_ONCE(bp->i2c_root_present, false); + disable_delayed_work_sync(&bp->i2c_work); + mutex_lock(&bp->i2c_topology_lock); + topology =3D bp->i2c_topology; + if (!topology) { + bp->i2c_profile =3D NULL; + goto out; + } + + if (topology->registered) { + ptp_ocp_i2c_remove_mux(bp); + software_node_unregister_node_group(topology->node_group); + topology->registered =3D false; + } + + bp->i2c_topology =3D NULL; + bp->i2c_profile =3D NULL; + +out: + mutex_unlock(&bp->i2c_topology_lock); +} + static struct device * ptp_ocp_find_flash(struct ptp_ocp *bp) { @@ -2166,9 +2833,9 @@ ptp_ocp_devlink_info_get(struct devlink *devlink, str= uct devlink_info_req *req, if (err) return err; =20 - if (!bp->has_eeprom_data) { + if (!ptp_ocp_has_eeprom_data(bp)) { ptp_ocp_read_eeprom(bp); - if (!bp->has_eeprom_data) + if (!ptp_ocp_has_eeprom_data(bp)) return 0; } =20 @@ -3758,7 +4425,7 @@ serialnum_show(struct device *dev, struct device_attr= ibute *attr, char *buf) { struct ptp_ocp *bp =3D dev_get_drvdata(dev); =20 - if (!bp->has_eeprom_data) + if (!ptp_ocp_has_eeprom_data(bp)) ptp_ocp_read_eeprom(bp); =20 return sysfs_emit(buf, "%pM\n", bp->serial); @@ -4729,6 +5396,8 @@ ptp_ocp_device_init(struct ptp_ocp *bp, struct pci_de= v *pdev) =20 bp->ptp_info =3D ptp_ocp_clock_info; spin_lock_init(&bp->lock); + mutex_init(&bp->eeprom_lock); + mutex_init(&bp->i2c_topology_lock); =20 for (i =3D 0; i < __PORT_COUNT; i++) bp->port[i].line =3D -1; @@ -4867,6 +5536,11 @@ ptp_ocp_detach(struct ptp_ocp *bp) { int i; =20 + if (bp->i2c_notifier_registered) { + bus_unregister_notifier(&i2c_bus_type, &bp->i2c_notifier); + bp->i2c_notifier_registered =3D false; + } + ptp_ocp_i2c_unregister(bp); ptp_ocp_debugfs_remove_device(bp); ptp_ocp_detach_sysfs(bp); ptp_ocp_attr_group_del(bp); @@ -5081,6 +5755,8 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_= device_id *id) goto out_disable; =20 INIT_DELAYED_WORK(&bp->sync_work, ptp_ocp_sync_work); + INIT_DELAYED_WORK(&bp->i2c_work, ptp_ocp_i2c_work); + atomic_set(&bp->i2c_retry_count, 0); =20 /* compat mode. * Older FPGA firmware only returns 2 irq's. @@ -5095,9 +5771,21 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci= _device_id *id) bp->n_irqs =3D err; pci_set_master(pdev); =20 + bp->i2c_notifier.notifier_call =3D ptp_ocp_i2c_notifier_call; + err =3D bus_register_notifier(&i2c_bus_type, &bp->i2c_notifier); + if (err) { + dev_err(&pdev->dev, + "failed to register I2C notifier: %d\n", err); + goto out; + } + bp->i2c_notifier_registered =3D true; + err =3D ptp_ocp_register_resources(bp, id->driver_data); if (err) goto out; + /* Publish all board resources before I2C topology work can run. */ + smp_store_release(&bp->i2c_resources_ready, true); + ptp_ocp_i2c_kick(bp); =20 bp->ptp =3D ptp_clock_register(&bp->ptp_info, &pdev->dev); if (IS_ERR(bp->ptp)) { @@ -5191,46 +5879,84 @@ static struct pci_driver ptp_ocp_driver =3D { .shutdown =3D ptp_ocp_remove, }; =20 +static bool +ptp_ocp_i2c_is_child(struct ptp_ocp *bp, struct device *child) +{ + struct device *dev =3D child; + + while (dev) { + if (dev =3D=3D &bp->pdev->dev) + return true; + dev =3D dev->parent; + } + + return false; +} + +static bool +ptp_ocp_i2c_is_root(struct ptp_ocp *bp, struct i2c_adapter *adapter) +{ + struct device *parent =3D adapter->dev.parent; + + if (!parent) + return false; + if (bp->i2c_ctrl && parent =3D=3D &bp->i2c_ctrl->dev) + return true; + + /* The adapter can notify before ptp_ocp_register_i2c() stores it. */ + return parent->parent =3D=3D &bp->pdev->dev; +} + static int ptp_ocp_i2c_notifier_call(struct notifier_block *nb, unsigned long action, void *data) { - struct device *dev, *child =3D data; - struct ptp_ocp *bp; - bool add; + struct i2c_adapter *adapter; + struct i2c_client *client; + struct device *child =3D data; + struct ptp_ocp *bp =3D container_of(nb, struct ptp_ocp, i2c_notifier); + + if (!ptp_ocp_i2c_is_child(bp, child)) + return 0; =20 switch (action) { case BUS_NOTIFY_ADD_DEVICE: - case BUS_NOTIFY_DEL_DEVICE: - add =3D action =3D=3D BUS_NOTIFY_ADD_DEVICE; - break; - default: + adapter =3D i2c_verify_adapter(child); + if (!adapter) + return 0; + if (ptp_ocp_i2c_is_root(bp, adapter)) { + WRITE_ONCE(bp->i2c_root_present, true); + ptp_ocp_symlink(bp, child, "i2c"); + } + ptp_ocp_i2c_kick(bp); return 0; - } =20 - if (!i2c_verify_adapter(child)) + case BUS_NOTIFY_DEL_DEVICE: + adapter =3D i2c_verify_adapter(child); + if (!adapter) + return 0; + if (ptp_ocp_i2c_is_root(bp, adapter)) { + WRITE_ONCE(bp->i2c_root_present, false); + sysfs_remove_link(&bp->dev.kobj, "i2c"); + } return 0; =20 - dev =3D child; - while ((dev =3D dev->parent)) - if (dev->driver && !strcmp(dev->driver->name, KBUILD_MODNAME)) - goto found; - return 0; + case BUS_NOTIFY_BOUND_DRIVER: + client =3D i2c_verify_client(child); + if (!client || client->addr !=3D 0x50) + return 0; + if (!ptp_ocp_i2c_is_root(bp, client->adapter)) + return 0; + ptp_ocp_i2c_kick(bp); + return 0; =20 -found: - bp =3D dev_get_drvdata(dev); - if (add) - ptp_ocp_symlink(bp, child, "i2c"); - else - sysfs_remove_link(&bp->dev.kobj, "i2c"); + default: + return 0; + } =20 return 0; } =20 -static struct notifier_block ptp_ocp_i2c_notifier =3D { - .notifier_call =3D ptp_ocp_i2c_notifier_call, -}; - static int __init ptp_ocp_init(void) { @@ -5244,21 +5970,14 @@ ptp_ocp_init(void) if (err) goto out; =20 - what =3D "i2c notifier"; - err =3D bus_register_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier); - if (err) - goto out_notifier; - what =3D "ptp_ocp driver"; err =3D pci_register_driver(&ptp_ocp_driver); if (err) - goto out_register; + goto out_class; =20 return 0; =20 -out_register: - bus_unregister_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier); -out_notifier: +out_class: class_unregister(&timecard_class); out: ptp_ocp_debugfs_fini(); @@ -5269,7 +5988,6 @@ ptp_ocp_init(void) static void __exit ptp_ocp_fini(void) { - bus_unregister_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier); pci_unregister_driver(&ptp_ocp_driver); class_unregister(&timecard_class); ptp_ocp_debugfs_fini(); --=20 2.50.1 (Apple Git-155) From nobody Tue Sep 29 04:08:41 2026 Received: from mail-pl1-f174.google.com (mail-pl1-f174.google.com [209.85.214.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 CF2AA4562BB for ; Wed, 12 Aug 2026 14:17:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786544256; cv=none; b=WTTmLMdMB4I4hHWpkr8+DM/7PcF8sCrTDtjQrjcHFwLVKdDmkEuwi7zGMyH6a5vRb7gCGI+r46jI5YwSxF5NxVrZkCEHCMxGu+9JuNVObqFTlP09jbrqV1dY9U5oIwPGl2UhNxGbFxAi8RoqpDokyVeUdRkOgb5SXt0mXkQV8Ug= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786544256; c=relaxed/simple; bh=GfzBwg4ZHphT0HU4JIpIzgvGvublxLx3VU0uduAtHN8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mTA+s3O/wuzj0x6MR3wL42JVQKx1hV0Cmvo9ivjl4dmM650hF6crfCDiWofB+oCmPOuCCCKNp4hgmvB2TrqcRs3jbWkN+3ZfXWcpc4MlU5SrU1hn2Ho7+CwPpKuS0b0eHUjODCrvzB+TOc2zQhXN+L24n9I6cD+g1ddBMO3NilA= 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=J9PEs6sS; arc=none smtp.client-ip=209.85.214.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="J9PEs6sS" Received: by mail-pl1-f174.google.com with SMTP id d9443c01a7336-2cab973140bso18790505ad.3 for ; Wed, 12 Aug 2026 07:17:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786544252; x=1787149052; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=/70XqHwKtpkb8Nem1mvPAp3age5VnZSIpQXUrCBp0Sw=; b=J9PEs6sSN6TRv7rae6EJnMJV1fJFkMR5LYzm4yAseH7WWu7D6IEGn4UMhMFbnBnNEw 3LD4Y5sRVjilCXbsYDSTG8bNqYf7R/2mp9h7FCTLFSehkAKqjPteDBJY+DDCPN/OkMPQ 67DJ8HDWqbdEFE+flPmJHuFFa5V1nXHtqGD8KSfx4vzZ2+Y9nI2bMbjG97XoKZ+u7KIT dhHZsIrfYfHSvcAOYJg+Zkga6+y4BYRTZXvoamsqw7DRc/l/lOFl9cq59v05q9OmMlxi O71c3KI97tJsjpKj8L5aXBb1CQORjUjGjCzcoeLecuw4nAaCAhCjzvtajUCLEAZeImmm 0SEQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786544252; x=1787149052; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=/70XqHwKtpkb8Nem1mvPAp3age5VnZSIpQXUrCBp0Sw=; b=Q4s+a8Bl9qCnGk7Uo87qUHu9OwcUFYdHa1jEBUKbVQYOWi2BW4OTDLzihJY/Xxle+X cKaaGnT2WClKt0UxBx94yoOZe6xd1i0oiQ/wprtwlkxcvy8bCoz/mkwl8PVyz89qqlhr FiiHJI06s8exFjbrCpevvl5kG+FIkxd+T0wyYgbaCJrdnI0I5/IG4/dHLEO1J+6noD7N ZY9wBZmbRC+AGJe1mHA3CfN2KQC9Y+hOSxxA1k6zih/k//k1ZmDrwNjdquVShmIySjLb ZFqR1dVvwyNIBJZfI/gxQ38jV9F48CyLugRFvWkCyFQsYozq+vwCwPa/uKXL50Efoo/0 WYpg== X-Forwarded-Encrypted: i=1; AHgh+RqABIlAg9gzNRmW0szphoDDncT1vWsI/CwFGXqbmDsqlfkQKjZT+imLIbm+2+Y6XPML1KwBQ0w3w3URO+o=@vger.kernel.org X-Gm-Message-State: AOJu0YxYs4r3lRtSNgVmUN3CK/8YTLbF6DmcjW2i+T1hcboXJ5NDj6lM cwcacFbdrR6BAOlmovs7wYp6F8ITvZdmkWnBVaM0RYIgBXY2CKAvmiON X-Gm-Gg: AR+sD12xAckohaZpQh7795m+ICF6EBfO4HneW8l5KweDiXL3SrG1eUAISkEJKhp9oYg ylWyWBqJB72Plgh0DWZfi/ShUZOkDFKXSKHGCrwEStLkQqHilFVUA5kCaUUW/Jpe+BbN2jpEAF6 12QGhNABkx0e6cDZEFzlwSoau4+v6vM5efVwHzVBEYdcE/UVyHL5yRYaphB7/hJpt8Y2YzbAe6g csBLd07dlyvYaMIa4Jn8Gd24IoipwRu926npTjoLzi/uUvvUxChMp8iaCCJKnQzs2OTH9rhFHuO 5A6Q/bW+QT45smmy84AEpik2yk19pSQjzAqycwUtqdIXRep/TWWvO/jDgPObyd4CMi6Ht2xi8c1 3MkIBO8Bx5oOl6N++gLo90j3//yjhjoHSTqULt0uEcJplTZ3d4lKV0SwQbspI3GsgCI0vSWtWTL wP8kcSfA+YFBz4EV693gev0GdZtIpc1iBMesJ9XJerl2y0xQFO3BGnv4UjZvegF5MchHHrzfdre 3oEuvCvXMUUuAc0GKv21I6ct9CZyPC7XgnWYEBYp86pHM9w50g3mvA6Jwtm X-Received: by 2002:a17:90b:3802:b0:38d:e0c4:c955 with SMTP id 98e67ed59e1d1-393014f2d24mr5947349a91.15.1786544251973; Wed, 12 Aug 2026 07:17:31 -0700 (PDT) Received: from Mac.localdomain (198-27-228-154.fiber.dynamic.sonic.net. [198.27.228.154]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-31cf361d57asm11963071eec.6.2026.08.12.07.17.29 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 12 Aug 2026 07:17:30 -0700 (PDT) From: Ahmad Byagowi To: netdev@vger.kernel.org Cc: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Nam Tran , Kees Cook , "Gustavo A. R. Silva" , Peter Rosin , Andi Shyti , Richard Cochran , Vadim Fedorenko , Andrew Lunn , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-hardening@vger.kernel.org Subject: [PATCH net-next v4 5/5] ptp: ocp: Add Time Card V9 I2C peripheral topology Date: Wed, 12 Aug 2026 07:17:15 -0700 Message-ID: <1db6580bc69f73f7b0273b0444484b1f391c8616.1786543681.git.ahmadexp@gmail.com> X-Mailer: git-send-email 2.50.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Time Card V9 places a BME280 at 0x76, a BNO055 at 0x29, and an IS32FL3207 at 0x37 on PCA9546 channel 1. Describe GNSS1 and GNSS2 as RGB LEDs on outputs 0 through 5 and SMA1 through SMA4 on outputs 6 through 17. The board uses a 4.7 kohm RISET resistor, sequential red, green, and blue output wiring, and an 8100 uA per-output limit. Select this profile only for the exact fixed-width EEPROM ID "TIMECARD-V9". Earlier revisions expose the same I2C devices with different LED routing, so probing cannot distinguish them safely. Leave erased, malformed, and unknown board IDs unconfigured. Signed-off-by: Ahmad Byagowi --- drivers/ptp/ptp_ocp.c | 73 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index f2c2979daaf1..a843198796ab 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -353,7 +353,7 @@ struct ptp_ocp_serial_port { =20 #define OCP_I2C_MUX_CHANNELS 4 #define OCP_I2C_MAX_SENSOR_COUNT 5 -#define OCP_I2C_MAX_LED_COUNT 5 +#define OCP_I2C_MAX_LED_COUNT 6 #define OCP_I2C_MAX_LED_COMPONENT_COUNT (3 * OCP_I2C_MAX_LED_COUNT) #define OCP_I2C_MUX_NAME_LEN 32 #define OCP_I2C_MUX_COMPATIBLE "nxp,pca9546" @@ -613,9 +613,61 @@ static const struct ptp_ocp_led ptp_ocp_r4006_leds[] = =3D { }, }; =20 +static const struct ptp_ocp_i2c_device ptp_ocp_v9_sensors[] =3D { + { "pressure@76", "bosch,bme280", "bme280", 1, 0x76 }, + { "imu@29", "bosch,bno055", "bno055", 1, 0x29 }, +}; + +static const struct ptp_ocp_led ptp_ocp_v9_leds[] =3D { + { + .node_name =3D "multi-led@0", + .function =3D LED_FUNCTION_STATUS, + .function_enumerator =3D 1, + .has_function_enumerator =3D true, + .channel =3D { 0, 1, 2 }, + }, + { + .node_name =3D "multi-led@3", + .function =3D LED_FUNCTION_STATUS, + .function_enumerator =3D 2, + .has_function_enumerator =3D true, + .channel =3D { 3, 4, 5 }, + }, + { + .node_name =3D "multi-led@6", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 1, + .has_function_enumerator =3D true, + .channel =3D { 6, 7, 8 }, + }, + { + .node_name =3D "multi-led@9", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 2, + .has_function_enumerator =3D true, + .channel =3D { 9, 10, 11 }, + }, + { + .node_name =3D "multi-led@c", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 3, + .has_function_enumerator =3D true, + .channel =3D { 12, 13, 14 }, + }, + { + .node_name =3D "multi-led@f", + .function =3D LED_FUNCTION_INDICATOR, + .function_enumerator =3D 4, + .has_function_enumerator =3D true, + .channel =3D { 15, 16, 17 }, + }, +}; + static_assert(ARRAY_SIZE(ptp_ocp_r4006_sensors) <=3D OCP_I2C_MAX_SENSOR_COUNT); static_assert(ARRAY_SIZE(ptp_ocp_r4006_leds) <=3D OCP_I2C_MAX_LED_COUNT); +static_assert(ARRAY_SIZE(ptp_ocp_v9_sensors) <=3D OCP_I2C_MAX_SENSOR_COUNT= ); +static_assert(ARRAY_SIZE(ptp_ocp_v9_leds) <=3D OCP_I2C_MAX_LED_COUNT); =20 static const struct ptp_ocp_i2c_profile ptp_ocp_r4006_profile =3D { .name =3D "r4006", @@ -630,6 +682,19 @@ static const struct ptp_ocp_i2c_profile ptp_ocp_r4006_= profile =3D { .led_max_microamp =3D 8150, }; =20 +static const struct ptp_ocp_i2c_profile ptp_ocp_v9_profile =3D { + .name =3D "v9", + .sensors =3D ptp_ocp_v9_sensors, + .sensor_count =3D ARRAY_SIZE(ptp_ocp_v9_sensors), + .leds =3D ptp_ocp_v9_leds, + .led_count =3D ARRAY_SIZE(ptp_ocp_v9_leds), + .led_node_name =3D "led-controller@37", + .led_mux_channel =3D 1, + .led_address =3D 0x37, + .led_riset_ohms =3D 4700, + .led_max_microamp =3D 8100, +}; + #define bp_assign_entry(bp, res, val) ({ \ uintptr_t addr =3D (uintptr_t)(bp) + (res)->bp_offset; \ *(typeof(val) *)addr =3D val; \ @@ -2230,6 +2295,7 @@ static const struct ptp_ocp_i2c_profile * ptp_ocp_i2c_select_profile(struct ptp_ocp *bp) { static const char r4006_id[] =3D "R4006"; + static const char v9_id[] =3D "TIMECARD-V9"; size_t board_id_len; =20 if (!ptp_ocp_has_eeprom_data(bp)) @@ -2242,6 +2308,11 @@ ptp_ocp_i2c_select_profile(struct ptp_ocp *bp) !memcmp(bp->board_id, r4006_id, sizeof(r4006_id) - 1)) return &ptp_ocp_r4006_profile; =20 + /* Older revisions share V9's I2C devices but not its LED wiring. */ + if (board_id_len =3D=3D sizeof(v9_id) - 1 && + !memcmp(bp->board_id, v9_id, sizeof(v9_id) - 1)) + return &ptp_ocp_v9_profile; + return NULL; } =20 --=20 2.50.1 (Apple Git-155)