AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/11 05:53 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "LEDS_CLASS_MULTICOLOR",
    "LEDS_IS32FL3207",
    "I2C_MUX",
    "PTP_1588_CLOCK_OCP"
  ],
  "FocusSymbols": [
    "i2c_mux_add_adapter",
    "is32fl3207_probe",
    "is32fl3207_brightness_set",
    "is32fl3207_mc_brightness_set"
  ],
  "KMSANReasoning": "The patch series introduces a new LED driver (`leds-is32fl3207.c`) and modifies the `ptp_ocp` driver to use it via I2C. It also adds software node support to `i2c-mux.c`. All memory allocations introduced by these changes use zero-initializing functions (`devm_kzalloc`, `devm_kcalloc`) or are explicitly zero-initialized on the stack (e.g., `u8 scaling[...] = { };`). The changes do not introduce any new kernel structures that are copied to user space, nor do they alter control flow or bounds checking in a way that could expose uninitialized memory. Any potential bugs introduced by these changes, such as out-of-bounds accesses or use-after-free, would be effectively caught by standard KASAN and other standard bug detectors. Therefore, a dedicated KMSAN fuzzing session is not justified.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch introduces a new I2C LED driver (leds-is32fl3207) and modifies the core I2C mux logic (i2c-mux.c) to support software nodes. It also modifies the ptp_ocp PCI driver to instantiate an I2C topology using this new LED driver. While the ptp_ocp driver itself is for specific hardware not emulated in QEMU, the new I2C LED driver and the core I2C mux changes are reachable and can be fuzzed via sysfs I2C device instantiation or software node injection. Therefore, the patch contains functional changes to reachable core kernel logic and drivers that are worth fuzzing.",
  "WorthFuzzing": true
}

1/1 2026/08/11 05:53 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 3eeff6deacc895e05e8263c0f0fd7fddae5280ac\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Aug 11 05:53:56 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml b/Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml\nnew file mode 100644\nindex 0000000000000..4ee9db964eccc\n--- /dev/null\n+++ b/Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml\n@@ -0,0 +1,251 @@\n+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)\n+%YAML 1.2\n+---\n+$id: http://devicetree.org/schemas/leds/issi,is32fl3207.yaml#\n+$schema: http://devicetree.org/meta-schemas/core.yaml#\n+\n+title: Lumissil IS32FL3207 18-channel LED controller\n+\n+maintainers:\n+  - Ahmad Byagowi \u003cahmadexp@gmail.com\u003e\n+\n+description: |\n+  The IS32FL3207 is an I2C LED controller with 18 individually controlled\n+  constant-current outputs. Each output has 8-bit current scaling. PWM\n+  brightness control uses the controller's default 8-bit, 62 kHz mode.\n+  Outputs may be exposed as individual LEDs or grouped as multicolor LEDs.\n+\n+  The data sheet is available at:\n+    https://www.lumissil.com/assets/pdf/core/IS32FL3207_DS.pdf\n+\n+properties:\n+  compatible:\n+    const: issi,is32fl3207\n+\n+  reg:\n+    enum: [0x34, 0x35, 0x36, 0x37]\n+\n+  vcc-supply:\n+    description: Regulator providing power to the VCC pin.\n+\n+  enable-gpios:\n+    maxItems: 1\n+    description: GPIO connected to SDB, asserted to enable the controller.\n+\n+  issi,riset-ohms:\n+    minimum: 2000\n+    maximum: 76500000\n+    description: |\n+      Resistance in ohms of the external RISET resistor. The resistor sets the\n+      maximum output current according to IOUT(MAX) = 76.5 / RISET amperes.\n+\n+  \"#address-cells\":\n+    const: 1\n+\n+  \"#size-cells\":\n+    const: 0\n+\n+patternProperties:\n+  \"^led@[0-9a-f]+$\":\n+    type: object\n+    $ref: common.yaml#\n+    unevaluatedProperties: false\n+\n+    properties:\n+      reg:\n+        minimum: 0\n+        maximum: 17\n+        description: |\n+          Zero-based LED output number. Values 0 through 17 correspond to\n+          OUT1 through OUT18.\n+\n+      led-max-microamp:\n+        minimum: 1\n+        maximum: 38250\n+\n+      color:\n+        oneOf:\n+          - minimum: 0\n+            maximum: 7\n+          - minimum: 10\n+            maximum: 14\n+\n+      max-brightness:\n+        minimum: 1\n+        maximum: 255\n+\n+      default-state:\n+        description:\n+          The controller is reset during initialization, so an existing state\n+          cannot be preserved.\n+        enum: [\"on\", \"off\"]\n+\n+      default-brightness:\n+        maximum: 255\n+        description: |\n+          Brightness used when default-state is \"on\". This value must not\n+          exceed max-brightness when max-brightness is specified.\n+\n+    anyOf:\n+      - required:\n+          - function\n+      - required:\n+          - color\n+\n+    required:\n+      - reg\n+      - led-max-microamp\n+\n+  \"^multi-led@[0-9a-f]$\":\n+    type: object\n+    $ref: leds-class-multicolor.yaml#\n+    unevaluatedProperties: false\n+\n+    properties:\n+      reg:\n+        minimum: 0\n+        maximum: 15\n+        description: |\n+          Zero-based lowest-numbered LED output used by the group. Values 0\n+          through 15 correspond to OUT1 through OUT16. A group based at\n+          channel 15 (OUT16) may include channels 16 and 17 (OUT17 and OUT18).\n+\n+      \"#address-cells\":\n+        const: 1\n+\n+      \"#size-cells\":\n+        const: 0\n+\n+      max-brightness:\n+        minimum: 1\n+        maximum: 255\n+\n+      default-state:\n+        description:\n+          The controller is reset during initialization, so an existing state\n+          cannot be preserved.\n+        enum: [\"on\", \"off\"]\n+\n+      default-brightness:\n+        maximum: 255\n+        description: |\n+          Brightness used when default-state is \"on\". This value must not\n+          exceed max-brightness when max-brightness is specified.\n+\n+    patternProperties:\n+      \"^led@[0-9a-f]+$\":\n+        type: object\n+        $ref: common.yaml#\n+        unevaluatedProperties: false\n+\n+        properties:\n+          reg:\n+            minimum: 0\n+            maximum: 17\n+            description: |\n+              Zero-based LED output number. Values 0 through 17 correspond to\n+              OUT1 through OUT18.\n+\n+          led-max-microamp:\n+            minimum: 1\n+            maximum: 38250\n+\n+          color:\n+            oneOf:\n+              - minimum: 0\n+                maximum: 7\n+              - minimum: 10\n+                maximum: 14\n+\n+          max-brightness: false\n+\n+          default-state: false\n+\n+          default-brightness: false\n+\n+          retain-state-shutdown: false\n+\n+        required:\n+          - reg\n+          - color\n+          - led-max-microamp\n+\n+    anyOf:\n+      - required: [led@0]\n+      - required: [led@1]\n+      - required: [led@2]\n+      - required: [led@3]\n+      - required: [led@4]\n+      - required: [led@5]\n+      - required: [led@6]\n+      - required: [led@7]\n+      - required: [led@8]\n+      - required: [led@9]\n+      - required: [led@a]\n+      - required: [led@b]\n+      - required: [led@c]\n+      - required: [led@d]\n+      - required: [led@e]\n+      - required: [led@f]\n+\n+    required:\n+      - reg\n+      - \"#address-cells\"\n+      - \"#size-cells\"\n+\n+required:\n+  - compatible\n+  - reg\n+  - issi,riset-ohms\n+  - \"#address-cells\"\n+  - \"#size-cells\"\n+\n+additionalProperties: false\n+\n+examples:\n+  - |\n+    #include \u003cdt-bindings/gpio/gpio.h\u003e\n+    #include \u003cdt-bindings/leds/common.h\u003e\n+\n+    i2c {\n+        #address-cells = \u003c1\u003e;\n+        #size-cells = \u003c0\u003e;\n+\n+        led-controller@34 {\n+            compatible = \"issi,is32fl3207\";\n+            reg = \u003c0x34\u003e;\n+            #address-cells = \u003c1\u003e;\n+            #size-cells = \u003c0\u003e;\n+\n+            vcc-supply = \u003c\u0026led_3v3\u003e;\n+            enable-gpios = \u003c\u0026gpio0 4 GPIO_ACTIVE_HIGH\u003e;\n+            issi,riset-ohms = \u003c4700\u003e;\n+\n+            multi-led@0 {\n+                reg = \u003c0\u003e;\n+                color = \u003cLED_COLOR_ID_RGB\u003e;\n+                function = LED_FUNCTION_STATUS;\n+                #address-cells = \u003c1\u003e;\n+                #size-cells = \u003c0\u003e;\n+\n+                led@0 {\n+                    reg = \u003c0\u003e;\n+                    color = \u003cLED_COLOR_ID_RED\u003e;\n+                    led-max-microamp = \u003c8100\u003e;\n+                };\n+\n+                led@1 {\n+                    reg = \u003c1\u003e;\n+                    color = \u003cLED_COLOR_ID_GREEN\u003e;\n+                    led-max-microamp = \u003c8100\u003e;\n+                };\n+\n+                led@2 {\n+                    reg = \u003c2\u003e;\n+                    color = \u003cLED_COLOR_ID_BLUE\u003e;\n+                    led-max-microamp = \u003c8100\u003e;\n+                };\n+            };\n+        };\n+    };\n+...\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex a82b6ec9c5674..ce4132a754d74 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -13822,6 +13822,13 @@ S:\tMaintained\n F:\tDocumentation/filesystems/isofs.rst\n F:\tfs/isofs/\n \n+ISSI IS32FL3207 LED DRIVER\n+M:\tAhmad Byagowi \u003cahmadexp@gmail.com\u003e\n+L:\tlinux-leds@vger.kernel.org\n+S:\tMaintained\n+F:\tDocumentation/devicetree/bindings/leds/issi,is32fl3207.yaml\n+F:\tdrivers/leds/rgb/leds-is32fl3207.c\n+\n IT87 HARDWARE MONITORING DRIVER\n M:\tJean Delvare \u003cjdelvare@suse.com\u003e\n L:\tlinux-hwmon@vger.kernel.org\ndiff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c\nindex 681a201c239ba..8b54bb13c613e 100644\n--- a/drivers/i2c/i2c-mux.c\n+++ b/drivers/i2c/i2c-mux.c\n@@ -20,11 +20,13 @@\n  */\n \n #include \u003clinux/acpi.h\u003e\n+#include \u003clinux/err.h\u003e\n #include \u003clinux/i2c.h\u003e\n #include \u003clinux/i2c-mux.h\u003e\n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/module.h\u003e\n #include \u003clinux/of.h\u003e\n+#include \u003clinux/property.h\u003e\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/sysfs.h\u003e\n \n@@ -264,6 +266,55 @@ static const struct i2c_lock_operations i2c_parent_lock_ops = {\n \t.unlock_bus =  i2c_parent_unlock_bus,\n };\n \n+static struct fwnode_handle *\n+i2c_mux_get_channel_swnode(struct i2c_mux_core *muxc, u32 chan_id)\n+{\n+\tstruct fwnode_handle *dev_node = dev_fwnode(muxc-\u003edev);\n+\tstruct fwnode_handle *mux_node, *child = NULL;\n+\tu32 reg;\n+\n+\t/* A software node supplementing ACPI is the secondary fwnode. */\n+\tif (!is_software_node(dev_node)) {\n+\t\tif (IS_ERR_OR_NULL(dev_node))\n+\t\t\treturn NULL;\n+\t\tdev_node = dev_node-\u003esecondary;\n+\t}\n+\tif (!is_software_node(dev_node))\n+\t\treturn NULL;\n+\n+\tif (muxc-\u003earbitrator)\n+\t\tmux_node = fwnode_get_named_child_node(dev_node, \"i2c-arb\");\n+\telse if (muxc-\u003egate)\n+\t\tmux_node = fwnode_get_named_child_node(dev_node, \"i2c-gate\");\n+\telse\n+\t\tmux_node = fwnode_get_named_child_node(dev_node, \"i2c-mux\");\n+\n+\tif (mux_node) {\n+\t\t/* A \"reg\" property indicates an old-style firmware entry. */\n+\t\tif (!fwnode_property_read_u32(mux_node, \"reg\", \u0026reg)) {\n+\t\t\tfwnode_handle_put(mux_node);\n+\t\t\tmux_node = NULL;\n+\t\t}\n+\t}\n+\n+\tif (!mux_node)\n+\t\tmux_node = fwnode_handle_get(dev_node);\n+\telse if (muxc-\u003earbitrator || muxc-\u003egate)\n+\t\tchild = fwnode_handle_get(mux_node);\n+\n+\tif (!child) {\n+\t\tfwnode_for_each_child_node(mux_node, child) {\n+\t\t\tif (fwnode_property_read_u32(child, \"reg\", \u0026reg))\n+\t\t\t\tcontinue;\n+\t\t\tif (chan_id == reg)\n+\t\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tfwnode_handle_put(mux_node);\n+\treturn child;\n+}\n+\n int i2c_mux_add_adapter(struct i2c_mux_core *muxc,\n \t\t\tu32 force_nr, u32 chan_id)\n {\n@@ -324,8 +375,8 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc,\n \t\tpriv-\u003eadap.lock_ops = \u0026i2c_parent_lock_ops;\n \n \t/*\n-\t * Try to populate the mux adapter's of_node, expands to\n-\t * nothing if !CONFIG_OF.\n+\t * Associate the mux adapter with its DT or software-node channel.\n+\t * DT support expands to nothing if !CONFIG_OF.\n \t */\n \tif (muxc-\u003edev-\u003eof_node) {\n \t\tstruct device_node *dev_node = muxc-\u003edev-\u003eof_node;\n@@ -364,6 +415,18 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc,\n \n \t\tpriv-\u003eadap.dev.of_node = child;\n \t\tof_node_put(mux_node);\n+\t} else {\n+\t\tstruct fwnode_handle *fwnode;\n+\t\tconst struct software_node *swnode;\n+\n+\t\tfwnode = i2c_mux_get_channel_swnode(muxc, chan_id);\n+\t\tif (fwnode) {\n+\t\t\tswnode = to_software_node(fwnode);\n+\t\t\tret = device_add_software_node(\u0026priv-\u003eadap.dev, swnode);\n+\t\t\tfwnode_handle_put(fwnode);\n+\t\t\tif (ret)\n+\t\t\t\tgoto err_free_priv;\n+\t\t}\n \t}\n \n \t/*\n@@ -408,6 +471,7 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc,\n \treturn 0;\n \n err_free_priv:\n+\tdevice_remove_software_node(\u0026priv-\u003eadap.dev);\n \tkfree(priv);\n \treturn ret;\n }\n@@ -429,6 +493,7 @@ void i2c_mux_del_adapters(struct i2c_mux_core *muxc)\n \t\tsysfs_remove_link(\u0026muxc-\u003edev-\u003ekobj, symlink_name);\n \n \t\tsysfs_remove_link(\u0026priv-\u003eadap.dev.kobj, \"mux_device\");\n+\t\tdevice_remove_software_node(\u0026priv-\u003eadap.dev);\n \t\ti2c_del_adapter(adap);\n \t\tof_node_put(np);\n \t\tkfree(priv);\ndiff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig\nindex 6e9ab5f60714b..c896be1318dcc 100644\n--- a/drivers/leds/rgb/Kconfig\n+++ b/drivers/leds/rgb/Kconfig\n@@ -14,6 +14,17 @@ config LEDS_GROUP_MULTICOLOR\n \t  To compile this driver as a module, choose M here: the module\n \t  will be called leds-group-multicolor.\n \n+config LEDS_IS32FL3207\n+\ttristate \"LED support for ISSI IS32FL3207\"\n+\tdepends on I2C\n+\tselect REGMAP_I2C\n+\thelp\n+\t  Say Y here to include support for the Lumissil IS32FL3207\n+\t  18-channel I2C LED controller.\n+\n+\t  To compile this driver as a module, choose M here: the module will\n+\t  be called leds-is32fl3207.\n+\n config LEDS_KTD202X\n \ttristate \"LED support for KTD202x Chips\"\n \tdepends on I2C\ndiff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile\nindex cc0f2df662869..228923e8bb119 100644\n--- a/drivers/leds/rgb/Makefile\n+++ b/drivers/leds/rgb/Makefile\n@@ -1,6 +1,7 @@\n # SPDX-License-Identifier: GPL-2.0\n \n obj-$(CONFIG_LEDS_GROUP_MULTICOLOR)\t+= leds-group-multicolor.o\n+obj-$(CONFIG_LEDS_IS32FL3207)\t\t+= leds-is32fl3207.o\n obj-$(CONFIG_LEDS_KTD202X)\t\t+= leds-ktd202x.o\n obj-$(CONFIG_LEDS_LP5812)\t\t+= leds-lp5812.o\n obj-$(CONFIG_LEDS_LP5860_CORE)\t\t+= leds-lp5860-core.o\ndiff --git a/drivers/leds/rgb/leds-is32fl3207.c b/drivers/leds/rgb/leds-is32fl3207.c\nnew file mode 100644\nindex 0000000000000..c4e4a114a16e1\n--- /dev/null\n+++ b/drivers/leds/rgb/leds-is32fl3207.c\n@@ -0,0 +1,687 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * ISSI IS32FL3207 LED controller driver\n+ *\n+ * Copyright 2026 Ahmad Byagowi\n+ */\n+\n+#include \u003clinux/bitmap.h\u003e\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/gpio/consumer.h\u003e\n+#include \u003clinux/i2c.h\u003e\n+#include \u003clinux/led-class-multicolor.h\u003e\n+#include \u003clinux/math64.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/property.h\u003e\n+#include \u003clinux/regmap.h\u003e\n+#include \u003clinux/regulator/consumer.h\u003e\n+#include \u003clinux/string.h\u003e\n+\n+#include \u003cdt-bindings/leds/common.h\u003e\n+\n+#define IS32FL3207_NUM_CHANNELS\t\t18\n+#define IS32FL3207_MAX_BRIGHTNESS\t255\n+\n+#define IS32FL3207_REG_CONTROL\t\t0x00\n+#define IS32FL3207_REG_PWM_LOW(channel)\t(0x01 + 2 * (channel))\n+#define IS32FL3207_REG_PWM_UPDATE\t0x49\n+#define IS32FL3207_REG_SCALING(channel)\t(0x4a + (channel))\n+#define IS32FL3207_REG_GLOBAL_CURRENT\t0x6e\n+#define IS32FL3207_REG_RESET\t\t0x7f\n+\n+#define IS32FL3207_CONTROL_ENABLE\tBIT(0)\n+#define IS32FL3207_GLOBAL_CURRENT_MAX\t0xff\n+\n+/* IOUT(MAX) in microamperes = 76,500,000 / RISET in ohms. */\n+#define IS32FL3207_CURRENT_NUMERATOR\t76500000ULL\n+\n+struct is32fl3207;\n+\n+struct is32fl3207_led {\n+\tstruct is32fl3207 *chip;\n+\tstruct led_classdev *led_cdev;\n+\tunion {\n+\t\tstruct led_classdev cdev;\n+\t\tstruct led_classdev_mc mcdev;\n+\t};\n+\tunsigned int channel;\n+};\n+\n+struct is32fl3207 {\n+\tstruct device *dev;\n+\tstruct regmap *regmap;\n+\tstruct gpio_desc *enable_gpio;\n+\tstruct mutex lock; /* Serializes controller register updates. */\n+\tunsigned long channels[BITS_TO_LONGS(IS32FL3207_NUM_CHANNELS)];\n+\tu32 output_max_microamp;\n+\tunsigned int num_leds;\n+\tstruct is32fl3207_led leds[] __counted_by(num_leds);\n+};\n+\n+static int is32fl3207_parse_led_properties(struct is32fl3207 *chip,\n+\t\t\t\t\t   struct fwnode_handle *fwnode,\n+\t\t\t\t\t   unsigned int *max_brightness,\n+\t\t\t\t\t   unsigned int *brightness)\n+{\n+\tenum led_default_state default_state;\n+\tu32 value;\n+\tint ret;\n+\n+\t*max_brightness = IS32FL3207_MAX_BRIGHTNESS;\n+\tif (fwnode_property_present(fwnode, \"max-brightness\")) {\n+\t\tret = fwnode_property_read_u32(fwnode, \"max-brightness\",\n+\t\t\t\t\t       \u0026value);\n+\t\tif (ret)\n+\t\t\treturn dev_err_probe(chip-\u003edev, ret,\n+\t\t\t\t\t     \"failed to read maximum brightness for %pfw\\n\",\n+\t\t\t\t\t     fwnode);\n+\t\tif (!value || value \u003e IS32FL3207_MAX_BRIGHTNESS)\n+\t\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t\t     \"invalid maximum brightness %u for %pfw\\n\",\n+\t\t\t\t\t     value, fwnode);\n+\n+\t\t*max_brightness = value;\n+\t}\n+\n+\tvalue = *max_brightness;\n+\tif (fwnode_property_present(fwnode, \"default-brightness\")) {\n+\t\tret = fwnode_property_read_u32(fwnode, \"default-brightness\",\n+\t\t\t\t\t       \u0026value);\n+\t\tif (ret)\n+\t\t\treturn dev_err_probe(chip-\u003edev, ret,\n+\t\t\t\t\t     \"failed to read default brightness for %pfw\\n\",\n+\t\t\t\t\t     fwnode);\n+\t\tif (value \u003e *max_brightness)\n+\t\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t\t     \"invalid default brightness %u for %pfw\\n\",\n+\t\t\t\t\t     value, fwnode);\n+\t}\n+\n+\tdefault_state = led_init_default_state_get(fwnode);\n+\tif (default_state == LEDS_DEFSTATE_KEEP)\n+\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t     \"default state keep is not supported for %pfw\\n\",\n+\t\t\t\t     fwnode);\n+\n+\t*brightness = default_state == LEDS_DEFSTATE_ON ? value : LED_OFF;\n+\treturn 0;\n+}\n+\n+static int is32fl3207_validate_component(struct is32fl3207 *chip,\n+\t\t\t\t\t struct fwnode_handle *fwnode)\n+{\n+\tstatic const char * const unsupported[] = {\n+\t\t\"default-brightness\",\n+\t\t\"default-state\",\n+\t\t\"max-brightness\",\n+\t\t\"retain-state-shutdown\",\n+\t};\n+\tunsigned int i;\n+\n+\tfor (i = 0; i \u003c ARRAY_SIZE(unsupported); i++)\n+\t\tif (fwnode_property_present(fwnode, unsupported[i]))\n+\t\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t\t     \"%s is not supported for component %pfw\\n\",\n+\t\t\t\t\t     unsupported[i], fwnode);\n+\n+\treturn 0;\n+}\n+\n+static int is32fl3207_write_channels(struct is32fl3207 *chip,\n+\t\t\t\t     const struct mc_subled *subleds,\n+\t\t\t\t     unsigned int num_channels)\n+{\n+\tunsigned int i;\n+\tint ret;\n+\n+\tguard(mutex)(\u0026chip-\u003elock);\n+\n+\tfor (i = 0; i \u003c num_channels; i++) {\n+\t\tret = regmap_write(chip-\u003eregmap,\n+\t\t\t\t   IS32FL3207_REG_PWM_LOW(subleds[i].channel),\n+\t\t\t\t   subleds[i].brightness);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn regmap_write(chip-\u003eregmap, IS32FL3207_REG_PWM_UPDATE, 0);\n+}\n+\n+static int is32fl3207_brightness_set(struct led_classdev *cdev,\n+\t\t\t\t     enum led_brightness brightness)\n+{\n+\tstruct is32fl3207_led *led = container_of(cdev, struct is32fl3207_led,\n+\t\t\t\t\t\t     cdev);\n+\tstruct mc_subled subled = {\n+\t\t.brightness = brightness,\n+\t\t.channel = led-\u003echannel,\n+\t};\n+\n+\treturn is32fl3207_write_channels(led-\u003echip, \u0026subled, 1);\n+}\n+\n+static int is32fl3207_mc_brightness_set(struct led_classdev *cdev,\n+\t\t\t\t\tenum led_brightness brightness)\n+{\n+\tstruct led_classdev_mc *mcdev = lcdev_to_mccdev(cdev);\n+\tstruct is32fl3207_led *led = container_of(mcdev, struct is32fl3207_led,\n+\t\t\t\t\t\t     mcdev);\n+\n+\tled_mc_calc_color_components(mcdev, brightness);\n+\n+\treturn is32fl3207_write_channels(led-\u003echip, mcdev-\u003esubled_info,\n+\t\t\t\t\t    mcdev-\u003enum_colors);\n+}\n+\n+static int is32fl3207_configure_channel(struct is32fl3207 *chip,\n+\t\t\t\t\tstruct fwnode_handle *fwnode,\n+\t\t\t\t\tunsigned int *channel)\n+{\n+\tu64 scaling;\n+\tu32 max_microamp;\n+\tu32 reg;\n+\tint ret;\n+\n+\tret = fwnode_property_read_u32(fwnode, \"reg\", \u0026reg);\n+\tif (ret)\n+\t\treturn dev_err_probe(chip-\u003edev, ret,\n+\t\t\t\t     \"failed to read channel for %pfw\\n\",\n+\t\t\t\t     fwnode);\n+\n+\tif (reg \u003e= IS32FL3207_NUM_CHANNELS)\n+\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t     \"channel %u is out of range\\n\", reg);\n+\n+\tif (test_bit(reg, chip-\u003echannels))\n+\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t     \"channel %u is used more than once\\n\",\n+\t\t\t\t     reg);\n+\n+\tret = fwnode_property_read_u32(fwnode, \"led-max-microamp\",\n+\t\t\t\t       \u0026max_microamp);\n+\tif (ret)\n+\t\treturn dev_err_probe(chip-\u003edev, ret,\n+\t\t\t\t     \"failed to read current limit for channel %u\\n\",\n+\t\t\t\t     reg);\n+\n+\tif (!max_microamp || max_microamp \u003e chip-\u003eoutput_max_microamp)\n+\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t     \"invalid current limit %u uA for channel %u\\n\",\n+\t\t\t\t     max_microamp, reg);\n+\n+\t/* GCC is fixed at 0xff, so use each output's scaling register. */\n+\tscaling = div_u64((u64)max_microamp * 256 * 256,\n+\t\t\t  (u64)chip-\u003eoutput_max_microamp *\n+\t\t\t  IS32FL3207_GLOBAL_CURRENT_MAX);\n+\tif (!scaling)\n+\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t     \"current limit %u uA is below channel %u resolution\\n\",\n+\t\t\t\t     max_microamp, reg);\n+\n+\tscaling = min_t(u64, scaling, 0xff);\n+\n+\tguard(mutex)(\u0026chip-\u003elock);\n+\tret = regmap_write(chip-\u003eregmap, IS32FL3207_REG_SCALING(reg),\n+\t\t\t   (unsigned int)scaling);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tset_bit(reg, chip-\u003echannels);\n+\t*channel = reg;\n+\n+\treturn 0;\n+}\n+\n+static int is32fl3207_register_single(struct is32fl3207 *chip,\n+\t\t\t\t      struct fwnode_handle *fwnode,\n+\t\t\t\t      struct is32fl3207_led *led)\n+{\n+\tstruct led_init_data init_data = {\n+\t\t.devicename = dev_name(chip-\u003edev),\n+\t\t.devname_mandatory = true,\n+\t\t.fwnode = fwnode,\n+\t};\n+\tunsigned int max_brightness;\n+\tunsigned int brightness;\n+\tu32 color;\n+\tint ret;\n+\n+\tif (!fwnode_property_present(fwnode, \"function\") \u0026\u0026\n+\t    !fwnode_property_present(fwnode, \"color\"))\n+\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t     \"single LED %pfw requires function or color\\n\",\n+\t\t\t\t     fwnode);\n+\n+\tret = is32fl3207_parse_led_properties(chip, fwnode,\n+\t\t\t\t\t      \u0026max_brightness, \u0026brightness);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (fwnode_property_present(fwnode, \"color\")) {\n+\t\tret = fwnode_property_read_u32(fwnode, \"color\", \u0026color);\n+\t\tif (ret)\n+\t\t\treturn dev_err_probe(chip-\u003edev, ret,\n+\t\t\t\t\t     \"failed to read color for %pfw\\n\",\n+\t\t\t\t\t     fwnode);\n+\t\tif (color \u003e= LED_COLOR_ID_MAX || color == LED_COLOR_ID_MULTI ||\n+\t\t    color == LED_COLOR_ID_RGB)\n+\t\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t\t     \"invalid single LED color %u\\n\",\n+\t\t\t\t\t     color);\n+\t}\n+\n+\tret = is32fl3207_configure_channel(chip, fwnode, \u0026led-\u003echannel);\n+\tif (ret)\n+\t\treturn ret;\n+\tled-\u003echip = chip;\n+\tled-\u003eled_cdev = \u0026led-\u003ecdev;\n+\tled-\u003ecdev.brightness = brightness;\n+\tled-\u003ecdev.max_brightness = max_brightness;\n+\tled-\u003ecdev.brightness_set_blocking = is32fl3207_brightness_set;\n+\n+\tret = is32fl3207_brightness_set(\u0026led-\u003ecdev, brightness);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn devm_led_classdev_register_ext(chip-\u003edev, \u0026led-\u003ecdev,\n+\t\t\t\t\t      \u0026init_data);\n+}\n+\n+static int is32fl3207_register_multicolor(struct is32fl3207 *chip,\n+\t\t\t\t\t  struct fwnode_handle *fwnode,\n+\t\t\t\t\t  struct is32fl3207_led *led)\n+{\n+\tstruct led_init_data init_data = {\n+\t\t.devicename = dev_name(chip-\u003edev),\n+\t\t.devname_mandatory = true,\n+\t\t.fwnode = fwnode,\n+\t};\n+\tstruct mc_subled *subleds;\n+\tDECLARE_BITMAP(color_map, LED_COLOR_ID_MAX);\n+\tunsigned int max_brightness;\n+\tunsigned int brightness;\n+\tunsigned int count;\n+\tunsigned int i = 0;\n+\tu32 group_color;\n+\tu32 group_reg;\n+\tunsigned int first_channel = IS32FL3207_NUM_CHANNELS;\n+\tint ret;\n+\n+\tret = is32fl3207_parse_led_properties(chip, fwnode,\n+\t\t\t\t\t      \u0026max_brightness, \u0026brightness);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = fwnode_property_read_u32(fwnode, \"color\", \u0026group_color);\n+\tif (ret)\n+\t\treturn dev_err_probe(chip-\u003edev, ret,\n+\t\t\t\t     \"failed to read color for %pfw\\n\", fwnode);\n+\n+\tif (group_color != LED_COLOR_ID_RGB \u0026\u0026\n+\t    group_color != LED_COLOR_ID_MULTI)\n+\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t     \"invalid multicolor LED color %u\\n\",\n+\t\t\t\t     group_color);\n+\n+\tret = fwnode_property_read_u32(fwnode, \"reg\", \u0026group_reg);\n+\tif (ret)\n+\t\treturn dev_err_probe(chip-\u003edev, ret,\n+\t\t\t\t     \"failed to read group index for %pfw\\n\",\n+\t\t\t\t     fwnode);\n+\n+\tcount = fwnode_get_child_node_count(fwnode);\n+\tif (!count || count \u003e LED_COLOR_ID_MAX)\n+\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t     \"invalid component count %u for %pfw\\n\",\n+\t\t\t\t     count, fwnode);\n+\n+\tsubleds = devm_kcalloc(chip-\u003edev, count, sizeof(*subleds), GFP_KERNEL);\n+\tif (!subleds)\n+\t\treturn -ENOMEM;\n+\tbitmap_zero(color_map, LED_COLOR_ID_MAX);\n+\n+\tfwnode_for_each_child_node_scoped(fwnode, child) {\n+\t\tu32 color;\n+\n+\t\tret = is32fl3207_validate_component(chip, child);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tret = fwnode_property_read_u32(child, \"color\", \u0026color);\n+\t\tif (ret)\n+\t\t\treturn dev_err_probe(chip-\u003edev, ret,\n+\t\t\t\t\t     \"failed to read color for %pfw\\n\",\n+\t\t\t\t\t     child);\n+\n+\t\tif (color \u003e= LED_COLOR_ID_MAX || color == LED_COLOR_ID_MULTI ||\n+\t\t    color == LED_COLOR_ID_RGB)\n+\t\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t\t     \"invalid component color %u\\n\",\n+\t\t\t\t\t     color);\n+\t\tif (test_and_set_bit(color, color_map))\n+\t\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t\t     \"component color %u is used more than once\\n\",\n+\t\t\t\t\t     color);\n+\n+\t\tret = is32fl3207_configure_channel(chip, child,\n+\t\t\t\t\t\t   \u0026subleds[i].channel);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tsubleds[i].color_index = color;\n+\t\tsubleds[i].intensity = max_brightness;\n+\t\tsubleds[i].max_intensity = 0;\n+\t\tfirst_channel = min(first_channel, subleds[i].channel);\n+\t\ti++;\n+\t}\n+\n+\tif (group_reg != first_channel)\n+\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t     \"group index %u does not match first channel %u\\n\",\n+\t\t\t\t     group_reg, first_channel);\n+\n+\tled-\u003echip = chip;\n+\tled-\u003eled_cdev = \u0026led-\u003emcdev.led_cdev;\n+\tled-\u003emcdev.num_colors = count;\n+\tled-\u003emcdev.subled_info = subleds;\n+\tled-\u003emcdev.led_cdev.brightness = brightness;\n+\tled-\u003emcdev.led_cdev.max_brightness = max_brightness;\n+\tled-\u003emcdev.led_cdev.brightness_set_blocking =\n+\t\tis32fl3207_mc_brightness_set;\n+\n+\tled_mc_calc_color_components(\u0026led-\u003emcdev, brightness);\n+\n+\tret = is32fl3207_write_channels(chip, subleds, count);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn devm_led_classdev_multicolor_register_ext(chip-\u003edev, \u0026led-\u003emcdev,\n+\t\t\t\t\t\t     \u0026init_data);\n+}\n+\n+static int is32fl3207_register_led(struct is32fl3207 *chip,\n+\t\t\t\t   struct fwnode_handle *fwnode,\n+\t\t\t\t   struct is32fl3207_led *led)\n+{\n+\tunsigned int count = fwnode_get_child_node_count(fwnode);\n+\tbool has_color = fwnode_property_present(fwnode, \"color\");\n+\tu32 color = LED_COLOR_ID_MAX;\n+\tint ret;\n+\n+\tif (has_color) {\n+\t\tret = fwnode_property_read_u32(fwnode, \"color\", \u0026color);\n+\t\tif (ret)\n+\t\t\treturn dev_err_probe(chip-\u003edev, ret,\n+\t\t\t\t\t     \"failed to read color for %pfw\\n\",\n+\t\t\t\t\t     fwnode);\n+\t}\n+\n+\tif (color == LED_COLOR_ID_RGB || color == LED_COLOR_ID_MULTI) {\n+\t\tif (!count)\n+\t\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t\t     \"multicolor LED %pfw has no components\\n\",\n+\t\t\t\t\t     fwnode);\n+\n+\t\treturn is32fl3207_register_multicolor(chip, fwnode, led);\n+\t}\n+\n+\tif (count)\n+\t\treturn dev_err_probe(chip-\u003edev, -EINVAL,\n+\t\t\t\t     \"single LED %pfw must not have components\\n\",\n+\t\t\t\t     fwnode);\n+\n+\treturn is32fl3207_register_single(chip, fwnode, led);\n+}\n+\n+static int is32fl3207_prepare_reset(struct is32fl3207 *chip)\n+{\n+\tu8 scaling[IS32FL3207_NUM_CHANNELS] = { };\n+\tint ret;\n+\n+\tret = regmap_write(chip-\u003eregmap, IS32FL3207_REG_CONTROL, 0);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn regmap_bulk_write(chip-\u003eregmap,\n+\t\t\t\t IS32FL3207_REG_SCALING(0), scaling,\n+\t\t\t\t sizeof(scaling));\n+}\n+\n+static int is32fl3207_hw_init(struct is32fl3207 *chip)\n+{\n+\tu8 scaling[IS32FL3207_NUM_CHANNELS] = { };\n+\tu8 pwm[2 * IS32FL3207_NUM_CHANNELS] = { };\n+\tint disable_ret;\n+\tint ret;\n+\n+\t/* Reset requires normal operation (SSD = 1). */\n+\tret = regmap_write(chip-\u003eregmap, IS32FL3207_REG_CONTROL,\n+\t\t\t   IS32FL3207_CONTROL_ENABLE);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = regmap_write(chip-\u003eregmap, IS32FL3207_REG_RESET, 0);\n+\tif (ret)\n+\t\treturn ret;\n+\tusleep_range(200, 300);\n+\n+\tret = regmap_write(chip-\u003eregmap, IS32FL3207_REG_CONTROL, 0);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = regmap_write(chip-\u003eregmap, IS32FL3207_REG_GLOBAL_CURRENT,\n+\t\t\t   IS32FL3207_GLOBAL_CURRENT_MAX);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = regmap_bulk_write(chip-\u003eregmap, IS32FL3207_REG_SCALING(0),\n+\t\t\t\tscaling, sizeof(scaling));\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = regmap_bulk_write(chip-\u003eregmap, IS32FL3207_REG_PWM_LOW(0), pwm,\n+\t\t\t\tsizeof(pwm));\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* PWM data can be latched only in normal operation. */\n+\tret = regmap_write(chip-\u003eregmap, IS32FL3207_REG_CONTROL,\n+\t\t\t   IS32FL3207_CONTROL_ENABLE);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = regmap_write(chip-\u003eregmap, IS32FL3207_REG_PWM_UPDATE, 0);\n+\tdisable_ret = regmap_write(chip-\u003eregmap, IS32FL3207_REG_CONTROL, 0);\n+\n+\treturn ret ?: disable_ret;\n+}\n+\n+static int is32fl3207_enable(struct is32fl3207 *chip)\n+{\n+\tint ret;\n+\n+\tguard(mutex)(\u0026chip-\u003elock);\n+\n+\tret = regmap_write(chip-\u003eregmap, IS32FL3207_REG_CONTROL,\n+\t\t\t   IS32FL3207_CONTROL_ENABLE);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = regmap_write(chip-\u003eregmap, IS32FL3207_REG_PWM_UPDATE, 0);\n+\tif (ret)\n+\t\tregmap_write(chip-\u003eregmap, IS32FL3207_REG_CONTROL, 0);\n+\n+\treturn ret;\n+}\n+\n+static void is32fl3207_disable(void *data)\n+{\n+\tstruct is32fl3207 *chip = data;\n+\n+\tguard(mutex)(\u0026chip-\u003elock);\n+\tregmap_write(chip-\u003eregmap, IS32FL3207_REG_CONTROL, 0);\n+\tif (chip-\u003eenable_gpio)\n+\t\tgpiod_set_value_cansleep(chip-\u003eenable_gpio, 0);\n+}\n+\n+static const struct regmap_config is32fl3207_regmap_config = {\n+\t.reg_bits = 8,\n+\t.val_bits = 8,\n+\t.max_register = IS32FL3207_REG_RESET,\n+};\n+\n+static int is32fl3207_probe(struct i2c_client *client)\n+{\n+\tstruct device *dev = \u0026client-\u003edev;\n+\tstruct is32fl3207 *ddata;\n+\tunsigned int count;\n+\tunsigned int i = 0;\n+\tu32 riset_ohms;\n+\tint ret;\n+\n+\tcount = device_get_child_node_count(dev);\n+\tif (!count || count \u003e IS32FL3207_NUM_CHANNELS)\n+\t\treturn dev_err_probe(dev, -EINVAL,\n+\t\t\t\t     \"invalid LED count %u\\n\", count);\n+\n+\tddata = devm_kzalloc(dev, struct_size(ddata, leds, count), GFP_KERNEL);\n+\tif (!ddata)\n+\t\treturn -ENOMEM;\n+\n+\tddata-\u003edev = dev;\n+\tddata-\u003enum_leds = count;\n+\ti2c_set_clientdata(client, ddata);\n+\n+\tret = device_property_read_u32(dev, \"issi,riset-ohms\", \u0026riset_ohms);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret, \"failed to read RISET value\\n\");\n+\n+\tif (riset_ohms \u003c 2000)\n+\t\treturn dev_err_probe(dev, -EINVAL,\n+\t\t\t\t     \"RISET value %u is below 2000 ohms\\n\",\n+\t\t\t\t     riset_ohms);\n+\n+\tddata-\u003eoutput_max_microamp = div_u64(IS32FL3207_CURRENT_NUMERATOR,\n+\t\t\t\t\t     riset_ohms);\n+\tif (!ddata-\u003eoutput_max_microamp)\n+\t\treturn dev_err_probe(dev, -EINVAL,\n+\t\t\t\t     \"RISET value %u is too large\\n\",\n+\t\t\t\t     riset_ohms);\n+\n+\tddata-\u003eenable_gpio = devm_gpiod_get_optional(dev, \"enable\",\n+\t\t\t\t\t\t     GPIOD_OUT_LOW);\n+\tif (IS_ERR(ddata-\u003eenable_gpio))\n+\t\treturn dev_err_probe(dev, PTR_ERR(ddata-\u003eenable_gpio),\n+\t\t\t\t     \"failed to get enable GPIO\\n\");\n+\n+\tddata-\u003eregmap = devm_regmap_init_i2c(client,\n+\t\t\t\t\t     \u0026is32fl3207_regmap_config);\n+\tif (IS_ERR(ddata-\u003eregmap))\n+\t\treturn dev_err_probe(dev, PTR_ERR(ddata-\u003eregmap),\n+\t\t\t\t     \"failed to allocate register map\\n\");\n+\n+\tret = devm_mutex_init(dev, \u0026ddata-\u003elock);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = devm_regulator_get_enable_optional(dev, \"vcc\");\n+\tif (ret \u0026\u0026 ret != -ENODEV)\n+\t\treturn dev_err_probe(dev, ret,\n+\t\t\t\t     \"failed to enable VCC regulator\\n\");\n+\n+\tret = devm_add_action_or_reset(dev, is32fl3207_disable, ddata);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/* Let VCC settle while SDB keeps the outputs disabled. */\n+\tusleep_range(1000, 2000);\n+\n+\tret = is32fl3207_prepare_reset(ddata);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret,\n+\t\t\t\t     \"failed to prepare controller reset\\n\");\n+\n+\tif (ddata-\u003eenable_gpio)\n+\t\tgpiod_set_value_cansleep(ddata-\u003eenable_gpio, 1);\n+\n+\t/* Allow the SDB release and I2C interface reset to settle. */\n+\tusleep_range(1000, 2000);\n+\n+\tret = is32fl3207_hw_init(ddata);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret,\n+\t\t\t\t     \"failed to initialize controller\\n\");\n+\n+\tdevice_for_each_child_node_scoped(dev, child) {\n+\t\tstruct is32fl3207_led *led = \u0026ddata-\u003eleds[i];\n+\n+\t\tret = is32fl3207_register_led(ddata, child, led);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\ti++;\n+\t}\n+\n+\tret = is32fl3207_enable(ddata);\n+\tif (ret)\n+\t\treturn dev_err_probe(dev, ret, \"failed to enable controller\\n\");\n+\n+\treturn 0;\n+}\n+\n+static void is32fl3207_shutdown(struct i2c_client *client)\n+{\n+\tstruct is32fl3207 *chip = i2c_get_clientdata(client);\n+\tbool retain_state = false;\n+\tunsigned int i;\n+\n+\tfor (i = 0; i \u003c chip-\u003enum_leds; i++)\n+\t\tif (chip-\u003eleds[i].led_cdev-\u003eflags \u0026 LED_RETAIN_AT_SHUTDOWN) {\n+\t\t\tretain_state = true;\n+\t\t\tbreak;\n+\t\t}\n+\n+\tif (!retain_state) {\n+\t\tis32fl3207_disable(chip);\n+\t\treturn;\n+\t}\n+\n+\tfor (i = 0; i \u003c chip-\u003enum_leds; i++) {\n+\t\tstruct led_classdev *cdev = chip-\u003eleds[i].led_cdev;\n+\n+\t\tif (cdev-\u003eflags \u0026 LED_RETAIN_AT_SHUTDOWN)\n+\t\t\tcontinue;\n+\t\tif (cdev-\u003eflags \u0026 LED_MULTI_COLOR)\n+\t\t\tis32fl3207_mc_brightness_set(cdev, LED_OFF);\n+\t\telse\n+\t\t\tis32fl3207_brightness_set(cdev, LED_OFF);\n+\t}\n+}\n+\n+static const struct of_device_id is32fl3207_of_match[] = {\n+\t{ .compatible = \"issi,is32fl3207\" },\n+\t{ }\n+};\n+MODULE_DEVICE_TABLE(of, is32fl3207_of_match);\n+\n+static const struct i2c_device_id is32fl3207_id[] = {\n+\t{ .name = \"is32fl3207\" },\n+\t{ }\n+};\n+MODULE_DEVICE_TABLE(i2c, is32fl3207_id);\n+\n+static struct i2c_driver is32fl3207_driver = {\n+\t.driver = {\n+\t\t.name = \"is32fl3207\",\n+\t\t.of_match_table = is32fl3207_of_match,\n+\t},\n+\t.probe = is32fl3207_probe,\n+\t.shutdown = is32fl3207_shutdown,\n+\t.id_table = is32fl3207_id,\n+};\n+module_i2c_driver(is32fl3207_driver);\n+\n+MODULE_AUTHOR(\"Ahmad Byagowi \u003cahmadexp@gmail.com\u003e\");\n+MODULE_DESCRIPTION(\"Lumissil IS32FL3207 LED controller driver\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c\nindex 3d26ec1f7b9ec..a843198796ab3 100644\n--- a/drivers/ptp/ptp_ocp.c\n+++ b/drivers/ptp/ptp_ocp.c\n@@ -21,10 +21,14 @@\n #include \u003cnet/devlink.h\u003e\n #include \u003clinux/i2c.h\u003e\n #include \u003clinux/mtd/mtd.h\u003e\n+#include \u003clinux/mutex.h\u003e\n #include \u003clinux/nvmem-consumer.h\u003e\n+#include \u003clinux/property.h\u003e\n #include \u003clinux/crc16.h\u003e\n #include \u003clinux/dpll.h\u003e\n \n+#include \u003cdt-bindings/leds/common.h\u003e\n+\n #define PCI_DEVICE_ID_META_TIMECARD\t\t0x0400\n \n #define PCI_VENDOR_ID_CELESTICA\t\t\t0x18d4\n@@ -347,6 +351,70 @@ struct ptp_ocp_serial_port {\n #define OCP_SIGNAL_NUM\t\t\t4\n #define OCP_FREQ_NUM\t\t\t4\n \n+#define OCP_I2C_MUX_CHANNELS\t\t4\n+#define OCP_I2C_MAX_SENSOR_COUNT\t5\n+#define OCP_I2C_MAX_LED_COUNT\t\t6\n+#define OCP_I2C_MAX_LED_COMPONENT_COUNT\t(3 * OCP_I2C_MAX_LED_COUNT)\n+#define OCP_I2C_MUX_NAME_LEN\t\t32\n+#define OCP_I2C_MUX_COMPATIBLE\t\t\"nxp,pca9546\"\n+#define OCP_I2C_MUX_TYPE\t\t\"pca9546\"\n+#define OCP_I2C_MUX_ADDRESS\t\t0x70\n+#define OCP_I2C_RETRY_MAX\t\t10\n+#define OCP_I2C_RECOVERY_SECS\t\t30\n+#define OCP_I2C_MAX_NODE_COUNT\t\t(1 + OCP_I2C_MUX_CHANNELS + \\\n+\t\t\t\t\t OCP_I2C_MAX_SENSOR_COUNT + 1 + \\\n+\t\t\t\t\t OCP_I2C_MAX_LED_COUNT + \\\n+\t\t\t\t\t OCP_I2C_MAX_LED_COMPONENT_COUNT)\n+\n+struct ptp_ocp_i2c_device {\n+\tconst char *node_name;\n+\tconst char *compatible;\n+\tconst char *type;\n+\tu8 channel;\n+\tu8 address;\n+};\n+\n+struct ptp_ocp_led {\n+\tconst char *node_name;\n+\tconst char *function;\n+\tu8 function_enumerator;\n+\tbool has_function_enumerator;\n+\tu8 channel[3];\n+};\n+\n+struct ptp_ocp_i2c_profile {\n+\tconst char *name;\n+\tconst struct ptp_ocp_i2c_device *sensors;\n+\tunsigned int sensor_count;\n+\tconst struct ptp_ocp_led *leds;\n+\tunsigned int led_count;\n+\tconst char *led_node_name;\n+\tu8 led_mux_channel;\n+\tu8 led_address;\n+\tu32 led_riset_ohms;\n+\tu32 led_max_microamp;\n+};\n+\n+struct ptp_ocp_i2c_topology {\n+\tchar mux_name[OCP_I2C_MUX_NAME_LEN];\n+\tstruct software_node mux_node;\n+\tstruct property_entry mux_props[6];\n+\tstruct software_node channel_nodes[OCP_I2C_MUX_CHANNELS];\n+\tstruct property_entry channel_props[OCP_I2C_MUX_CHANNELS][4];\n+\tstruct software_node sensor_nodes[OCP_I2C_MAX_SENSOR_COUNT];\n+\tstruct property_entry sensor_props[OCP_I2C_MAX_SENSOR_COUNT][3];\n+\tstruct software_node led_node;\n+\tstruct property_entry led_props[6];\n+\tstruct software_node led_nodes[OCP_I2C_MAX_LED_COUNT];\n+\tstruct property_entry led_group_props[OCP_I2C_MAX_LED_COUNT][7];\n+\tstruct software_node component_nodes[OCP_I2C_MAX_LED_COMPONENT_COUNT];\n+\tstruct property_entry\n+\t\tcomponent_props[OCP_I2C_MAX_LED_COMPONENT_COUNT][4];\n+\tconst struct software_node *node_group[OCP_I2C_MAX_NODE_COUNT + 1];\n+\tconst struct ptp_ocp_i2c_profile *profile;\n+\tbool registered;\n+};\n+\n enum {\n \tPORT_GNSS,\n \tPORT_GNSS2,\n@@ -403,6 +471,7 @@ struct ptp_ocp {\n \tu16\t\t\tfw_version;\n \tu8\t\t\tboard_id[OCP_BOARD_ID_LEN];\n \tu8\t\t\tserial[OCP_SERIAL_LEN];\n+\tstruct mutex\t\teeprom_lock; /* Serializes EEPROM discovery. */\n \tbool\t\t\thas_eeprom_data;\n \tu32\t\t\tpps_req_map;\n \tint\t\t\tflash_start;\n@@ -414,6 +483,15 @@ struct ptp_ocp {\n \tconst struct ocp_sma_op *sma_op;\n \tstruct dpll_device *dpll;\n \tdpll_tracker tracker;\n+\tconst struct ptp_ocp_i2c_profile *i2c_profile;\n+\tstruct ptp_ocp_i2c_topology *i2c_topology;\n+\tstruct mutex i2c_topology_lock; /* Serializes topology updates. */\n+\tstruct delayed_work i2c_work;\n+\tstruct notifier_block i2c_notifier;\n+\tatomic_t i2c_retry_count;\n+\tbool i2c_root_present;\n+\tbool i2c_resources_ready;\n+\tbool i2c_notifier_registered;\n \tint signals_nr;\n \tint freq_in_nr;\n };\n@@ -444,6 +522,8 @@ static int ptp_ocp_signal_from_perout(struct ptp_ocp *bp, int gen,\n \t\t\t\t      struct ptp_perout_request *req);\n static int ptp_ocp_signal_enable(void *priv, u32 req, bool enable);\n static int ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr);\n+static int ptp_ocp_i2c_notifier_call(struct notifier_block *nb,\n+\t\t\t\t     unsigned long action, void *data);\n \n static int ptp_ocp_art_board_init(struct ptp_ocp *bp, struct ocp_resource *r);\n \n@@ -488,6 +568,133 @@ static struct ptp_ocp_eeprom_map art_eeprom_map[] = {\n \t{ }\n };\n \n+/* Channel 3's BNO08x at 0x4a has no upstream Linux driver, so omit it. */\n+static const struct ptp_ocp_i2c_device ptp_ocp_r4006_sensors[] = {\n+\t{ \"temperature@48\", \"national,lm75b\", \"lm75b\", 0, 0x48 },\n+\t{ \"temperature@49\", \"national,lm75b\", \"lm75b\", 0, 0x49 },\n+\t{ \"temperature@4a\", \"national,lm75b\", \"lm75b\", 0, 0x4a },\n+\t{ \"humidity@44\", NULL, \"sht3x\", 1, 0x44 },\n+\t{ \"pressure@63\", \"invensense,icp10100\", \"icp10100\", 2, 0x63 },\n+};\n+\n+static const struct ptp_ocp_led ptp_ocp_r4006_leds[] = {\n+\t{\n+\t\t.node_name = \"multi-led@c\",\n+\t\t.function = LED_FUNCTION_STATUS,\n+\t\t.channel = { 13, 12, 14 },\n+\t},\n+\t{\n+\t\t.node_name = \"multi-led@6\",\n+\t\t.function = LED_FUNCTION_INDICATOR,\n+\t\t.function_enumerator = 1,\n+\t\t.has_function_enumerator = true,\n+\t\t.channel = { 7, 6, 8 },\n+\t},\n+\t{\n+\t\t.node_name = \"multi-led@9\",\n+\t\t.function = LED_FUNCTION_INDICATOR,\n+\t\t.function_enumerator = 2,\n+\t\t.has_function_enumerator = true,\n+\t\t.channel = { 10, 9, 11 },\n+\t},\n+\t{\n+\t\t.node_name = \"multi-led@0\",\n+\t\t.function = LED_FUNCTION_INDICATOR,\n+\t\t.function_enumerator = 3,\n+\t\t.has_function_enumerator = true,\n+\t\t.channel = { 1, 0, 2 },\n+\t},\n+\t{\n+\t\t.node_name = \"multi-led@3\",\n+\t\t.function = LED_FUNCTION_INDICATOR,\n+\t\t.function_enumerator = 4,\n+\t\t.has_function_enumerator = true,\n+\t\t.channel = { 4, 3, 5 },\n+\t},\n+};\n+\n+static const struct ptp_ocp_i2c_device ptp_ocp_v9_sensors[] = {\n+\t{ \"pressure@76\", \"bosch,bme280\", \"bme280\", 1, 0x76 },\n+\t{ \"imu@29\", \"bosch,bno055\", \"bno055\", 1, 0x29 },\n+};\n+\n+static const struct ptp_ocp_led ptp_ocp_v9_leds[] = {\n+\t{\n+\t\t.node_name = \"multi-led@0\",\n+\t\t.function = LED_FUNCTION_STATUS,\n+\t\t.function_enumerator = 1,\n+\t\t.has_function_enumerator = true,\n+\t\t.channel = { 0, 1, 2 },\n+\t},\n+\t{\n+\t\t.node_name = \"multi-led@3\",\n+\t\t.function = LED_FUNCTION_STATUS,\n+\t\t.function_enumerator = 2,\n+\t\t.has_function_enumerator = true,\n+\t\t.channel = { 3, 4, 5 },\n+\t},\n+\t{\n+\t\t.node_name = \"multi-led@6\",\n+\t\t.function = LED_FUNCTION_INDICATOR,\n+\t\t.function_enumerator = 1,\n+\t\t.has_function_enumerator = true,\n+\t\t.channel = { 6, 7, 8 },\n+\t},\n+\t{\n+\t\t.node_name = \"multi-led@9\",\n+\t\t.function = LED_FUNCTION_INDICATOR,\n+\t\t.function_enumerator = 2,\n+\t\t.has_function_enumerator = true,\n+\t\t.channel = { 9, 10, 11 },\n+\t},\n+\t{\n+\t\t.node_name = \"multi-led@c\",\n+\t\t.function = LED_FUNCTION_INDICATOR,\n+\t\t.function_enumerator = 3,\n+\t\t.has_function_enumerator = true,\n+\t\t.channel = { 12, 13, 14 },\n+\t},\n+\t{\n+\t\t.node_name = \"multi-led@f\",\n+\t\t.function = LED_FUNCTION_INDICATOR,\n+\t\t.function_enumerator = 4,\n+\t\t.has_function_enumerator = true,\n+\t\t.channel = { 15, 16, 17 },\n+\t},\n+};\n+\n+static_assert(ARRAY_SIZE(ptp_ocp_r4006_sensors) \u003c=\n+\t      OCP_I2C_MAX_SENSOR_COUNT);\n+static_assert(ARRAY_SIZE(ptp_ocp_r4006_leds) \u003c= OCP_I2C_MAX_LED_COUNT);\n+static_assert(ARRAY_SIZE(ptp_ocp_v9_sensors) \u003c= OCP_I2C_MAX_SENSOR_COUNT);\n+static_assert(ARRAY_SIZE(ptp_ocp_v9_leds) \u003c= OCP_I2C_MAX_LED_COUNT);\n+\n+static const struct ptp_ocp_i2c_profile ptp_ocp_r4006_profile = {\n+\t.name = \"r4006\",\n+\t.sensors = ptp_ocp_r4006_sensors,\n+\t.sensor_count = ARRAY_SIZE(ptp_ocp_r4006_sensors),\n+\t.leds = ptp_ocp_r4006_leds,\n+\t.led_count = ARRAY_SIZE(ptp_ocp_r4006_leds),\n+\t.led_node_name = \"led-controller@34\",\n+\t.led_mux_channel = 1,\n+\t.led_address = 0x34,\n+\t.led_riset_ohms = 4700,\n+\t.led_max_microamp = 8150,\n+};\n+\n+static const struct ptp_ocp_i2c_profile ptp_ocp_v9_profile = {\n+\t.name = \"v9\",\n+\t.sensors = ptp_ocp_v9_sensors,\n+\t.sensor_count = ARRAY_SIZE(ptp_ocp_v9_sensors),\n+\t.leds = ptp_ocp_v9_leds,\n+\t.led_count = ARRAY_SIZE(ptp_ocp_v9_leds),\n+\t.led_node_name = \"led-controller@37\",\n+\t.led_mux_channel = 1,\n+\t.led_address = 0x37,\n+\t.led_riset_ohms = 4700,\n+\t.led_max_microamp = 8100,\n+};\n+\n #define bp_assign_entry(bp, res, val) ({\t\t\t\t\\\n \tuintptr_t addr = (uintptr_t)(bp) + (res)-\u003ebp_offset;\t\t\\\n \t*(typeof(val) *)addr = val;\t\t\t\t\t\\\n@@ -1969,19 +2176,29 @@ ptp_ocp_nvmem_device_put(struct nvmem_device **nvmemp)\n \t*nvmemp = NULL;\n }\n \n-static void\n+static bool\n+ptp_ocp_has_eeprom_data(struct ptp_ocp *bp)\n+{\n+\treturn smp_load_acquire(\u0026bp-\u003ehas_eeprom_data);\n+}\n+\n+static int\n ptp_ocp_read_eeprom(struct ptp_ocp *bp)\n {\n \tconst struct ptp_ocp_eeprom_map *map;\n \tstruct nvmem_device *nvmem;\n \tconst void *tag;\n-\tint ret;\n-\n-\tif (!bp-\u003ei2c_ctrl)\n-\t\treturn;\n+\tint ret = 0;\n \n \ttag = NULL;\n \tnvmem = NULL;\n+\tmutex_lock(\u0026bp-\u003eeeprom_lock);\n+\tif (ptp_ocp_has_eeprom_data(bp))\n+\t\tgoto out;\n+\tif (!bp-\u003ei2c_ctrl || !bp-\u003eeeprom_map) {\n+\t\tret = -ENODEV;\n+\t\tgoto out;\n+\t}\n \n \tfor (map = bp-\u003eeeprom_map; map-\u003elen; map++) {\n \t\tif (map-\u003etag != tag) {\n@@ -1997,21 +2214,542 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp)\n \t\t}\n \t\tret = nvmem_device_read(nvmem, map-\u003eoff, map-\u003elen,\n \t\t\t\t\tBP_MAP_ENTRY_ADDR(bp, map));\n-\t\tif (ret != map-\u003elen)\n+\t\tif (ret != map-\u003elen) {\n+\t\t\tif (ret \u003e= 0)\n+\t\t\t\tret = -EIO;\n \t\t\tgoto fail;\n+\t\t}\n \t}\n \n-\tbp-\u003ehas_eeprom_data = true;\n+\t/* Publish the EEPROM fields before readers observe valid data. */\n+\tsmp_store_release(\u0026bp-\u003ehas_eeprom_data, true);\n+\tret = 0;\n \n out:\n \tptp_ocp_nvmem_device_put(\u0026nvmem);\n-\treturn;\n+\tmutex_unlock(\u0026bp-\u003eeeprom_lock);\n+\treturn ret;\n \n fail:\n-\tdev_err(\u0026bp-\u003epdev-\u003edev, \"could not read eeprom: %d\\n\", ret);\n \tgoto out;\n }\n \n+static int\n+ptp_ocp_i2c_adapter_match(struct device *dev, const void *data)\n+{\n+\treturn !!i2c_verify_adapter(dev);\n+}\n+\n+static struct i2c_adapter *\n+ptp_ocp_i2c_root_adapter(struct platform_device *i2c_ctrl)\n+{\n+\tstruct i2c_adapter *adapter;\n+\tstruct device *dev;\n+\n+\tdev = device_find_child(\u0026i2c_ctrl-\u003edev, NULL,\n+\t\t\t\tptp_ocp_i2c_adapter_match);\n+\tif (!dev)\n+\t\treturn NULL;\n+\n+\tadapter = i2c_verify_adapter(dev);\n+\tif (!adapter || !try_module_get(adapter-\u003eowner)) {\n+\t\tput_device(dev);\n+\t\treturn NULL;\n+\t}\n+\n+\t/* The caller owns the reference returned by device_find_child(). */\n+\treturn adapter;\n+}\n+\n+static bool\n+ptp_ocp_i2c_supported(struct ptp_ocp *bp)\n+{\n+\t/* PCI IDs identify FPGA images, not a unique PCB revision. */\n+\treturn (bp-\u003epdev-\u003evendor == PCI_VENDOR_ID_META \u0026\u0026\n+\t\tbp-\u003epdev-\u003edevice == PCI_DEVICE_ID_META_TIMECARD) ||\n+\t       (bp-\u003epdev-\u003evendor == PCI_VENDOR_ID_CELESTICA \u0026\u0026\n+\t\tbp-\u003epdev-\u003edevice == PCI_DEVICE_ID_CELESTICA_TIMECARD);\n+}\n+\n+static bool\n+ptp_ocp_board_id_valid(const u8 *board_id, size_t *text_len)\n+{\n+\tunsigned int len;\n+\n+\tfor (len = 0; len \u003c OCP_BOARD_ID_LEN; len++)\n+\t\tif (board_id[len] \u003c 0x20 || board_id[len] \u003e 0x7e)\n+\t\t\tbreak;\n+\n+\tif (!len)\n+\t\treturn false;\n+\n+\t*text_len = len;\n+\tfor (; len \u003c OCP_BOARD_ID_LEN; len++)\n+\t\tif (board_id[len] != 0 \u0026\u0026 board_id[len] != 0xff)\n+\t\t\treturn false;\n+\n+\treturn true;\n+}\n+\n+static const struct ptp_ocp_i2c_profile *\n+ptp_ocp_i2c_select_profile(struct ptp_ocp *bp)\n+{\n+\tstatic const char r4006_id[] = \"R4006\";\n+\tstatic const char v9_id[] = \"TIMECARD-V9\";\n+\tsize_t board_id_len;\n+\n+\tif (!ptp_ocp_has_eeprom_data(bp))\n+\t\treturn NULL;\n+\n+\tif (!ptp_ocp_board_id_valid(bp-\u003eboard_id, \u0026board_id_len))\n+\t\treturn NULL;\n+\n+\tif (board_id_len \u003e= sizeof(r4006_id) - 1 \u0026\u0026\n+\t    !memcmp(bp-\u003eboard_id, r4006_id, sizeof(r4006_id) - 1))\n+\t\treturn \u0026ptp_ocp_r4006_profile;\n+\n+\t/* Older revisions share V9's I2C devices but not its LED wiring. */\n+\tif (board_id_len == sizeof(v9_id) - 1 \u0026\u0026\n+\t    !memcmp(bp-\u003eboard_id, v9_id, sizeof(v9_id) - 1))\n+\t\treturn \u0026ptp_ocp_v9_profile;\n+\n+\treturn NULL;\n+}\n+\n+static int\n+ptp_ocp_i2c_init_nodes(struct ptp_ocp *bp)\n+{\n+\tstatic const char * const channel_names[] = {\n+\t\t\"i2c@0\", \"i2c@1\", \"i2c@2\", \"i2c@3\",\n+\t};\n+\tstatic const char * const output_names[] = {\n+\t\t\"led@0\", \"led@1\", \"led@2\", \"led@3\", \"led@4\", \"led@5\",\n+\t\t\"led@6\", \"led@7\", \"led@8\", \"led@9\", \"led@a\", \"led@b\",\n+\t\t\"led@c\", \"led@d\", \"led@e\", \"led@f\", \"led@10\", \"led@11\",\n+\t};\n+\tstatic const u32 colors[] = {\n+\t\tLED_COLOR_ID_RED, LED_COLOR_ID_GREEN, LED_COLOR_ID_BLUE,\n+\t};\n+\tconst struct ptp_ocp_i2c_profile *profile = bp-\u003ei2c_profile;\n+\tstruct device *dev = \u0026bp-\u003epdev-\u003edev;\n+\tstruct ptp_ocp_i2c_topology *topology;\n+\tunsigned int component = 0;\n+\tunsigned int node = 0;\n+\tunsigned int expected_nodes;\n+\tunsigned int i, j, prop;\n+\tint ret;\n+\n+\tif (!profile)\n+\t\treturn -EINVAL;\n+\tif (bp-\u003ei2c_topology \u0026\u0026 bp-\u003ei2c_topology-\u003eregistered)\n+\t\treturn 0;\n+\tif (bp-\u003ei2c_topology) {\n+\t\ttopology = bp-\u003ei2c_topology;\n+\t\tgoto register_nodes;\n+\t}\n+\tif (profile-\u003esensor_count \u003e OCP_I2C_MAX_SENSOR_COUNT ||\n+\t    profile-\u003eled_count \u003e OCP_I2C_MAX_LED_COUNT ||\n+\t    profile-\u003eled_mux_channel \u003e= OCP_I2C_MUX_CHANNELS)\n+\t\treturn -EINVAL;\n+\n+\tfor (i = 0; i \u003c profile-\u003esensor_count; i++)\n+\t\tif (profile-\u003esensors[i].channel \u003e= OCP_I2C_MUX_CHANNELS)\n+\t\t\treturn -EINVAL;\n+\n+\tfor (i = 0; i \u003c profile-\u003eled_count; i++)\n+\t\tfor (j = 0; j \u003c ARRAY_SIZE(profile-\u003eleds[i].channel); j++)\n+\t\t\tif (profile-\u003eleds[i].channel[j] \u003e=\n+\t\t\t    ARRAY_SIZE(output_names))\n+\t\t\t\treturn -EINVAL;\n+\n+\ttopology = devm_kzalloc(dev, sizeof(*topology), GFP_KERNEL);\n+\tif (!topology)\n+\t\treturn -ENOMEM;\n+\n+\ttopology-\u003eprofile = profile;\n+\tsnprintf(topology-\u003emux_name, sizeof(topology-\u003emux_name),\n+\t\t \"ocp%d-%s-mux\", bp-\u003eid, profile-\u003ename);\n+\ttopology-\u003emux_node.name = topology-\u003emux_name;\n+\ttopology-\u003emux_node.properties = topology-\u003emux_props;\n+\ttopology-\u003emux_props[0] =\n+\t\tPROPERTY_ENTRY_STRING(\"compatible\", OCP_I2C_MUX_COMPATIBLE);\n+\ttopology-\u003emux_props[1] =\n+\t\tPROPERTY_ENTRY_U32(\"reg\", OCP_I2C_MUX_ADDRESS);\n+\ttopology-\u003emux_props[2] =\n+\t\tPROPERTY_ENTRY_BOOL(\"i2c-mux-idle-disconnect\");\n+\ttopology-\u003emux_props[3] = PROPERTY_ENTRY_U32(\"#address-cells\", 1);\n+\ttopology-\u003emux_props[4] = PROPERTY_ENTRY_U32(\"#size-cells\", 0);\n+\ttopology-\u003enode_group[node++] = \u0026topology-\u003emux_node;\n+\n+\tfor (i = 0; i \u003c OCP_I2C_MUX_CHANNELS; i++) {\n+\t\ttopology-\u003echannel_nodes[i].name = channel_names[i];\n+\t\ttopology-\u003echannel_nodes[i].parent = \u0026topology-\u003emux_node;\n+\t\ttopology-\u003echannel_nodes[i].properties =\n+\t\t\ttopology-\u003echannel_props[i];\n+\t\ttopology-\u003echannel_props[i][0] = PROPERTY_ENTRY_U32(\"reg\", i);\n+\t\ttopology-\u003echannel_props[i][1] =\n+\t\t\tPROPERTY_ENTRY_U32(\"#address-cells\", 1);\n+\t\ttopology-\u003echannel_props[i][2] =\n+\t\t\tPROPERTY_ENTRY_U32(\"#size-cells\", 0);\n+\t\ttopology-\u003enode_group[node++] = \u0026topology-\u003echannel_nodes[i];\n+\t}\n+\n+\tfor (i = 0; i \u003c profile-\u003esensor_count; i++) {\n+\t\tconst struct ptp_ocp_i2c_device *sensor;\n+\n+\t\tsensor = \u0026profile-\u003esensors[i];\n+\t\ttopology-\u003esensor_nodes[i].name = sensor-\u003enode_name;\n+\t\ttopology-\u003esensor_nodes[i].parent =\n+\t\t\t\u0026topology-\u003echannel_nodes[sensor-\u003echannel];\n+\t\ttopology-\u003esensor_nodes[i].properties =\n+\t\t\ttopology-\u003esensor_props[i];\n+\t\tprop = 0;\n+\t\tif (sensor-\u003ecompatible)\n+\t\t\ttopology-\u003esensor_props[i][prop++] =\n+\t\t\t\tPROPERTY_ENTRY_STRING(\"compatible\",\n+\t\t\t\t\t\t      sensor-\u003ecompatible);\n+\t\ttopology-\u003esensor_props[i][prop] =\n+\t\t\tPROPERTY_ENTRY_U32(\"reg\", sensor-\u003eaddress);\n+\t\ttopology-\u003enode_group[node++] = \u0026topology-\u003esensor_nodes[i];\n+\t}\n+\n+\ttopology-\u003eled_node.name = profile-\u003eled_node_name;\n+\ttopology-\u003eled_node.parent =\n+\t\t\u0026topology-\u003echannel_nodes[profile-\u003eled_mux_channel];\n+\ttopology-\u003eled_node.properties = topology-\u003eled_props;\n+\ttopology-\u003eled_props[0] =\n+\t\tPROPERTY_ENTRY_STRING(\"compatible\", \"issi,is32fl3207\");\n+\ttopology-\u003eled_props[1] =\n+\t\tPROPERTY_ENTRY_U32(\"reg\", profile-\u003eled_address);\n+\ttopology-\u003eled_props[2] =\n+\t\tPROPERTY_ENTRY_U32(\"issi,riset-ohms\",\n+\t\t\t\t   profile-\u003eled_riset_ohms);\n+\ttopology-\u003eled_props[3] = PROPERTY_ENTRY_U32(\"#address-cells\", 1);\n+\ttopology-\u003eled_props[4] = PROPERTY_ENTRY_U32(\"#size-cells\", 0);\n+\ttopology-\u003enode_group[node++] = \u0026topology-\u003eled_node;\n+\n+\tfor (i = 0; i \u003c profile-\u003eled_count; i++) {\n+\t\tconst struct ptp_ocp_led *led = \u0026profile-\u003eleds[i];\n+\t\tu32 group_reg;\n+\n+\t\tgroup_reg = min3(led-\u003echannel[0], led-\u003echannel[1],\n+\t\t\t\t led-\u003echannel[2]);\n+\t\ttopology-\u003eled_nodes[i].name = led-\u003enode_name;\n+\t\ttopology-\u003eled_nodes[i].parent = \u0026topology-\u003eled_node;\n+\t\ttopology-\u003eled_nodes[i].properties =\n+\t\t\ttopology-\u003eled_group_props[i];\n+\t\tprop = 0;\n+\t\ttopology-\u003eled_group_props[i][prop++] =\n+\t\t\tPROPERTY_ENTRY_U32(\"reg\", group_reg);\n+\t\ttopology-\u003eled_group_props[i][prop++] =\n+\t\t\tPROPERTY_ENTRY_U32(\"color\", LED_COLOR_ID_RGB);\n+\t\ttopology-\u003eled_group_props[i][prop++] =\n+\t\t\tPROPERTY_ENTRY_STRING(\"function\", led-\u003efunction);\n+\t\tif (led-\u003ehas_function_enumerator)\n+\t\t\ttopology-\u003eled_group_props[i][prop++] =\n+\t\t\t\tPROPERTY_ENTRY_U32(\"function-enumerator\",\n+\t\t\t\t\t\t   led-\u003efunction_enumerator);\n+\t\ttopology-\u003eled_group_props[i][prop++] =\n+\t\t\tPROPERTY_ENTRY_U32(\"#address-cells\", 1);\n+\t\ttopology-\u003eled_group_props[i][prop] =\n+\t\t\tPROPERTY_ENTRY_U32(\"#size-cells\", 0);\n+\t\ttopology-\u003enode_group[node++] = \u0026topology-\u003eled_nodes[i];\n+\n+\t\tfor (j = 0; j \u003c ARRAY_SIZE(led-\u003echannel); j++, component++) {\n+\t\t\tu8 channel = led-\u003echannel[j];\n+\n+\t\t\ttopology-\u003ecomponent_nodes[component].name =\n+\t\t\t\toutput_names[channel];\n+\t\t\ttopology-\u003ecomponent_nodes[component].parent =\n+\t\t\t\t\u0026topology-\u003eled_nodes[i];\n+\t\t\ttopology-\u003ecomponent_nodes[component].properties =\n+\t\t\t\ttopology-\u003ecomponent_props[component];\n+\t\t\ttopology-\u003ecomponent_props[component][0] =\n+\t\t\t\tPROPERTY_ENTRY_U32(\"reg\", channel);\n+\t\t\ttopology-\u003ecomponent_props[component][1] =\n+\t\t\t\tPROPERTY_ENTRY_U32(\"color\", colors[j]);\n+\t\t\ttopology-\u003ecomponent_props[component][2] =\n+\t\t\t\tPROPERTY_ENTRY_U32(\"led-max-microamp\",\n+\t\t\t\t\t\t   profile-\u003eled_max_microamp);\n+\t\t\ttopology-\u003enode_group[node++] =\n+\t\t\t\t\u0026topology-\u003ecomponent_nodes[component];\n+\t\t}\n+\t}\n+\n+\texpected_nodes = 1 + OCP_I2C_MUX_CHANNELS +\n+\t\tprofile-\u003esensor_count + 1 + profile-\u003eled_count +\n+\t\t3 * profile-\u003eled_count;\n+\tif (WARN_ON(node != expected_nodes)) {\n+\t\tdevm_kfree(dev, topology);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tbp-\u003ei2c_topology = topology;\n+\n+register_nodes:\n+\tret = software_node_register_node_group(topology-\u003enode_group);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\ttopology-\u003eregistered = true;\n+\treturn 0;\n+}\n+\n+static int\n+ptp_ocp_i2c_add_device(struct i2c_adapter *adapter,\n+\t\t       const struct software_node *node,\n+\t\t       const char *type, u16 address)\n+{\n+\tstruct fwnode_handle *fwnode = software_node_fwnode(node);\n+\tstruct i2c_board_info info = { };\n+\tstruct i2c_client *client;\n+\n+\tclient = i2c_find_device_by_fwnode(fwnode);\n+\tif (client) {\n+\t\tput_device(\u0026client-\u003edev);\n+\t\treturn 0;\n+\t}\n+\n+\tstrscpy(info.type, type, sizeof(info.type));\n+\tinfo.addr = address;\n+\tinfo.fwnode = fwnode;\n+\tclient = i2c_new_client_device(adapter, \u0026info);\n+\tif (IS_ERR(client))\n+\t\treturn PTR_ERR(client);\n+\n+\treturn 0;\n+}\n+\n+static int\n+ptp_ocp_i2c_populate_channel(struct ptp_ocp *bp, unsigned int channel)\n+{\n+\tstruct ptp_ocp_i2c_topology *topology = bp-\u003ei2c_topology;\n+\tconst struct ptp_ocp_i2c_profile *profile = topology-\u003eprofile;\n+\tconst struct software_node *node;\n+\tstruct fwnode_handle *fwnode;\n+\tstruct i2c_adapter *adapter;\n+\tunsigned int i;\n+\tint err, ret = 0;\n+\n+\tfwnode = software_node_fwnode(\u0026topology-\u003echannel_nodes[channel]);\n+\tadapter = i2c_get_adapter_by_fwnode(fwnode);\n+\tif (!adapter)\n+\t\treturn -EAGAIN;\n+\n+\tfor (i = 0; i \u003c profile-\u003esensor_count; i++) {\n+\t\tconst struct ptp_ocp_i2c_device *sensor;\n+\n+\t\tsensor = \u0026profile-\u003esensors[i];\n+\t\tif (sensor-\u003echannel != channel)\n+\t\t\tcontinue;\n+\n+\t\tnode = \u0026topology-\u003esensor_nodes[i];\n+\t\terr = ptp_ocp_i2c_add_device(adapter, node, sensor-\u003etype,\n+\t\t\t\t\t     sensor-\u003eaddress);\n+\t\tif (err \u0026\u0026 !ret)\n+\t\t\tret = err;\n+\t}\n+\n+\tif (channel == profile-\u003eled_mux_channel) {\n+\t\terr = ptp_ocp_i2c_add_device(adapter, \u0026topology-\u003eled_node,\n+\t\t\t\t\t     \"is32fl3207\",\n+\t\t\t\t\t     profile-\u003eled_address);\n+\t\tif (err \u0026\u0026 !ret)\n+\t\t\tret = err;\n+\t}\n+\n+\ti2c_put_adapter(adapter);\n+\treturn ret;\n+}\n+\n+static void\n+ptp_ocp_i2c_remove_mux(struct ptp_ocp *bp)\n+{\n+\tstruct fwnode_handle *fwnode;\n+\tstruct i2c_client *client;\n+\n+\tif (!bp-\u003ei2c_topology || !bp-\u003ei2c_topology-\u003eregistered)\n+\t\treturn;\n+\n+\tfwnode = software_node_fwnode(\u0026bp-\u003ei2c_topology-\u003emux_node);\n+\tclient = i2c_find_device_by_fwnode(fwnode);\n+\tif (!client)\n+\t\treturn;\n+\n+\ti2c_unregister_device(client);\n+\tput_device(\u0026client-\u003edev);\n+}\n+\n+static void\n+ptp_ocp_i2c_kick(struct ptp_ocp *bp)\n+{\n+\tif (!ptp_ocp_i2c_supported(bp))\n+\t\treturn;\n+\tif (!READ_ONCE(bp-\u003ei2c_resources_ready))\n+\t\treturn;\n+\tif (!READ_ONCE(bp-\u003ei2c_root_present))\n+\t\treturn;\n+\n+\tmod_delayed_work(system_wq, \u0026bp-\u003ei2c_work, 1);\n+}\n+\n+static void\n+ptp_ocp_i2c_retry(struct ptp_ocp *bp, int error)\n+{\n+\tunsigned long delay = HZ;\n+\tint retries;\n+\n+\tif (!READ_ONCE(bp-\u003ei2c_resources_ready))\n+\t\treturn;\n+\tif (!READ_ONCE(bp-\u003ei2c_root_present))\n+\t\treturn;\n+\n+\tretries = atomic_inc_return(\u0026bp-\u003ei2c_retry_count);\n+\tif (retries \u003e= OCP_I2C_RETRY_MAX) {\n+\t\tif (retries == OCP_I2C_RETRY_MAX) {\n+\t\t\tdev_err(\u0026bp-\u003epdev-\u003edev,\n+\t\t\t\t\"I2C topology failed after %d attempts: %pe; \"\n+\t\t\t\t\"retrying every %d seconds\\n\",\n+\t\t\t\tOCP_I2C_RETRY_MAX, ERR_PTR(error),\n+\t\t\t\tOCP_I2C_RECOVERY_SECS);\n+\t\t} else {\n+\t\t\tatomic_set(\u0026bp-\u003ei2c_retry_count, OCP_I2C_RETRY_MAX);\n+\t\t\tdev_err_ratelimited(\u0026bp-\u003epdev-\u003edev,\n+\t\t\t\t\t    \"I2C topology setup still failing: %pe\\n\",\n+\t\t\t\t\t    ERR_PTR(error));\n+\t\t}\n+\t\tdelay = OCP_I2C_RECOVERY_SECS * HZ;\n+\t}\n+\n+\t/* Preserve a faster rerun queued by an I2C bus notification. */\n+\tqueue_delayed_work(system_wq, \u0026bp-\u003ei2c_work, delay);\n+}\n+\n+static int\n+ptp_ocp_i2c_populate_topology(struct ptp_ocp *bp,\n+\t\t\t      struct platform_device *i2c_ctrl)\n+{\n+\tconst struct software_node *node;\n+\tstruct i2c_adapter *adapter;\n+\tunsigned int channel;\n+\tint err, ret = 0;\n+\n+\tif (!READ_ONCE(bp-\u003ei2c_root_present))\n+\t\treturn 0;\n+\tif (!ptp_ocp_i2c_supported(bp) || !bp-\u003eeeprom_map)\n+\t\treturn 0;\n+\n+\tadapter = ptp_ocp_i2c_root_adapter(i2c_ctrl);\n+\tif (!adapter)\n+\t\treturn -EAGAIN;\n+\n+\tif (!ptp_ocp_has_eeprom_data(bp)) {\n+\t\tret = ptp_ocp_read_eeprom(bp);\n+\t\tif (ret)\n+\t\t\tgoto out_put_adapter;\n+\t}\n+\tif (!ptp_ocp_has_eeprom_data(bp)) {\n+\t\tret = -EAGAIN;\n+\t\tgoto out_put_adapter;\n+\t}\n+\tif (!bp-\u003ei2c_profile)\n+\t\tbp-\u003ei2c_profile = ptp_ocp_i2c_select_profile(bp);\n+\tif (!bp-\u003ei2c_profile)\n+\t\tgoto out_put_adapter;\n+\n+\tret = ptp_ocp_i2c_init_nodes(bp);\n+\tif (ret)\n+\t\tgoto out_put_adapter;\n+\n+\tnode = \u0026bp-\u003ei2c_topology-\u003emux_node;\n+\tret = ptp_ocp_i2c_add_device(adapter, node, OCP_I2C_MUX_TYPE,\n+\t\t\t\t     OCP_I2C_MUX_ADDRESS);\n+\tif (ret)\n+\t\tgoto out_put_adapter;\n+\n+\tfor (channel = 0; channel \u003c OCP_I2C_MUX_CHANNELS; channel++) {\n+\t\terr = ptp_ocp_i2c_populate_channel(bp, channel);\n+\t\tif (err \u0026\u0026 !ret)\n+\t\t\tret = err;\n+\t}\n+\n+out_put_adapter:\n+\ti2c_put_adapter(adapter);\n+\treturn ret;\n+}\n+\n+static void\n+ptp_ocp_i2c_work(struct work_struct *work)\n+{\n+\tstruct ptp_ocp *bp = container_of(work, struct ptp_ocp, i2c_work.work);\n+\tstruct platform_device *i2c_ctrl;\n+\tstruct device *i2c_ctrl_dev;\n+\tint retries, ret = 0;\n+\n+\t/* Pair with resource publication after registration. */\n+\tif (!smp_load_acquire(\u0026bp-\u003ei2c_resources_ready))\n+\t\treturn;\n+\tif (!ptp_ocp_i2c_supported(bp))\n+\t\treturn;\n+\n+\tmutex_lock(\u0026bp-\u003ei2c_topology_lock);\n+\tif (!READ_ONCE(bp-\u003ei2c_resources_ready) ||\n+\t    !READ_ONCE(bp-\u003ei2c_root_present)) {\n+\t\tmutex_unlock(\u0026bp-\u003ei2c_topology_lock);\n+\t\treturn;\n+\t}\n+\n+\ti2c_ctrl = READ_ONCE(bp-\u003ei2c_ctrl);\n+\tif (!i2c_ctrl) {\n+\t\tret = -EAGAIN;\n+\t\tgoto out_unlock;\n+\t}\n+\n+\ti2c_ctrl_dev = get_device(\u0026i2c_ctrl-\u003edev);\n+\tret = ptp_ocp_i2c_populate_topology(bp, i2c_ctrl);\n+\tput_device(i2c_ctrl_dev);\n+\n+out_unlock:\n+\tmutex_unlock(\u0026bp-\u003ei2c_topology_lock);\n+\n+\tif (ret) {\n+\t\tptp_ocp_i2c_retry(bp, ret);\n+\t\treturn;\n+\t}\n+\n+\tretries = atomic_xchg(\u0026bp-\u003ei2c_retry_count, 0);\n+\tif (retries \u003e= OCP_I2C_RETRY_MAX)\n+\t\tdev_info(\u0026bp-\u003epdev-\u003edev, \"I2C topology setup recovered\\n\");\n+}\n+\n+static void\n+ptp_ocp_i2c_unregister(struct ptp_ocp *bp)\n+{\n+\tstruct ptp_ocp_i2c_topology *topology;\n+\n+\tWRITE_ONCE(bp-\u003ei2c_resources_ready, false);\n+\tWRITE_ONCE(bp-\u003ei2c_root_present, false);\n+\tdisable_delayed_work_sync(\u0026bp-\u003ei2c_work);\n+\tmutex_lock(\u0026bp-\u003ei2c_topology_lock);\n+\ttopology = bp-\u003ei2c_topology;\n+\tif (!topology) {\n+\t\tbp-\u003ei2c_profile = NULL;\n+\t\tgoto out;\n+\t}\n+\n+\tif (topology-\u003eregistered) {\n+\t\tptp_ocp_i2c_remove_mux(bp);\n+\t\tsoftware_node_unregister_node_group(topology-\u003enode_group);\n+\t\ttopology-\u003eregistered = false;\n+\t}\n+\n+\tbp-\u003ei2c_topology = NULL;\n+\tbp-\u003ei2c_profile = NULL;\n+\n+out:\n+\tmutex_unlock(\u0026bp-\u003ei2c_topology_lock);\n+}\n+\n static struct device *\n ptp_ocp_find_flash(struct ptp_ocp *bp)\n {\n@@ -2166,9 +2904,9 @@ ptp_ocp_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req,\n \tif (err)\n \t\treturn err;\n \n-\tif (!bp-\u003ehas_eeprom_data) {\n+\tif (!ptp_ocp_has_eeprom_data(bp)) {\n \t\tptp_ocp_read_eeprom(bp);\n-\t\tif (!bp-\u003ehas_eeprom_data)\n+\t\tif (!ptp_ocp_has_eeprom_data(bp))\n \t\t\treturn 0;\n \t}\n \n@@ -3758,7 +4496,7 @@ serialnum_show(struct device *dev, struct device_attribute *attr, char *buf)\n {\n \tstruct ptp_ocp *bp = dev_get_drvdata(dev);\n \n-\tif (!bp-\u003ehas_eeprom_data)\n+\tif (!ptp_ocp_has_eeprom_data(bp))\n \t\tptp_ocp_read_eeprom(bp);\n \n \treturn sysfs_emit(buf, \"%pM\\n\", bp-\u003eserial);\n@@ -4729,6 +5467,8 @@ ptp_ocp_device_init(struct ptp_ocp *bp, struct pci_dev *pdev)\n \n \tbp-\u003eptp_info = ptp_ocp_clock_info;\n \tspin_lock_init(\u0026bp-\u003elock);\n+\tmutex_init(\u0026bp-\u003eeeprom_lock);\n+\tmutex_init(\u0026bp-\u003ei2c_topology_lock);\n \n \tfor (i = 0; i \u003c __PORT_COUNT; i++)\n \t\tbp-\u003eport[i].line = -1;\n@@ -4867,6 +5607,11 @@ ptp_ocp_detach(struct ptp_ocp *bp)\n {\n \tint i;\n \n+\tif (bp-\u003ei2c_notifier_registered) {\n+\t\tbus_unregister_notifier(\u0026i2c_bus_type, \u0026bp-\u003ei2c_notifier);\n+\t\tbp-\u003ei2c_notifier_registered = false;\n+\t}\n+\tptp_ocp_i2c_unregister(bp);\n \tptp_ocp_debugfs_remove_device(bp);\n \tptp_ocp_detach_sysfs(bp);\n \tptp_ocp_attr_group_del(bp);\n@@ -5081,6 +5826,8 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)\n \t\tgoto out_disable;\n \n \tINIT_DELAYED_WORK(\u0026bp-\u003esync_work, ptp_ocp_sync_work);\n+\tINIT_DELAYED_WORK(\u0026bp-\u003ei2c_work, ptp_ocp_i2c_work);\n+\tatomic_set(\u0026bp-\u003ei2c_retry_count, 0);\n \n \t/* compat mode.\n \t * Older FPGA firmware only returns 2 irq's.\n@@ -5095,9 +5842,21 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)\n \tbp-\u003en_irqs = err;\n \tpci_set_master(pdev);\n \n+\tbp-\u003ei2c_notifier.notifier_call = ptp_ocp_i2c_notifier_call;\n+\terr = bus_register_notifier(\u0026i2c_bus_type, \u0026bp-\u003ei2c_notifier);\n+\tif (err) {\n+\t\tdev_err(\u0026pdev-\u003edev,\n+\t\t\t\"failed to register I2C notifier: %d\\n\", err);\n+\t\tgoto out;\n+\t}\n+\tbp-\u003ei2c_notifier_registered = true;\n+\n \terr = ptp_ocp_register_resources(bp, id-\u003edriver_data);\n \tif (err)\n \t\tgoto out;\n+\t/* Publish all board resources before I2C topology work can run. */\n+\tsmp_store_release(\u0026bp-\u003ei2c_resources_ready, true);\n+\tptp_ocp_i2c_kick(bp);\n \n \tbp-\u003eptp = ptp_clock_register(\u0026bp-\u003eptp_info, \u0026pdev-\u003edev);\n \tif (IS_ERR(bp-\u003eptp)) {\n@@ -5191,46 +5950,84 @@ static struct pci_driver ptp_ocp_driver = {\n \t.shutdown\t= ptp_ocp_remove,\n };\n \n+static bool\n+ptp_ocp_i2c_is_child(struct ptp_ocp *bp, struct device *child)\n+{\n+\tstruct device *dev = child;\n+\n+\twhile (dev) {\n+\t\tif (dev == \u0026bp-\u003epdev-\u003edev)\n+\t\t\treturn true;\n+\t\tdev = dev-\u003eparent;\n+\t}\n+\n+\treturn false;\n+}\n+\n+static bool\n+ptp_ocp_i2c_is_root(struct ptp_ocp *bp, struct i2c_adapter *adapter)\n+{\n+\tstruct device *parent = adapter-\u003edev.parent;\n+\n+\tif (!parent)\n+\t\treturn false;\n+\tif (bp-\u003ei2c_ctrl \u0026\u0026 parent == \u0026bp-\u003ei2c_ctrl-\u003edev)\n+\t\treturn true;\n+\n+\t/* The adapter can notify before ptp_ocp_register_i2c() stores it. */\n+\treturn parent-\u003eparent == \u0026bp-\u003epdev-\u003edev;\n+}\n+\n static int\n ptp_ocp_i2c_notifier_call(struct notifier_block *nb,\n \t\t\t  unsigned long action, void *data)\n {\n-\tstruct device *dev, *child = data;\n-\tstruct ptp_ocp *bp;\n-\tbool add;\n+\tstruct i2c_adapter *adapter;\n+\tstruct i2c_client *client;\n+\tstruct device *child = data;\n+\tstruct ptp_ocp *bp = container_of(nb, struct ptp_ocp, i2c_notifier);\n+\n+\tif (!ptp_ocp_i2c_is_child(bp, child))\n+\t\treturn 0;\n \n \tswitch (action) {\n \tcase BUS_NOTIFY_ADD_DEVICE:\n-\tcase BUS_NOTIFY_DEL_DEVICE:\n-\t\tadd = action == BUS_NOTIFY_ADD_DEVICE;\n-\t\tbreak;\n-\tdefault:\n+\t\tadapter = i2c_verify_adapter(child);\n+\t\tif (!adapter)\n+\t\t\treturn 0;\n+\t\tif (ptp_ocp_i2c_is_root(bp, adapter)) {\n+\t\t\tWRITE_ONCE(bp-\u003ei2c_root_present, true);\n+\t\t\tptp_ocp_symlink(bp, child, \"i2c\");\n+\t\t}\n+\t\tptp_ocp_i2c_kick(bp);\n \t\treturn 0;\n-\t}\n \n-\tif (!i2c_verify_adapter(child))\n+\tcase BUS_NOTIFY_DEL_DEVICE:\n+\t\tadapter = i2c_verify_adapter(child);\n+\t\tif (!adapter)\n+\t\t\treturn 0;\n+\t\tif (ptp_ocp_i2c_is_root(bp, adapter)) {\n+\t\t\tWRITE_ONCE(bp-\u003ei2c_root_present, false);\n+\t\t\tsysfs_remove_link(\u0026bp-\u003edev.kobj, \"i2c\");\n+\t\t}\n \t\treturn 0;\n \n-\tdev = child;\n-\twhile ((dev = dev-\u003eparent))\n-\t\tif (dev-\u003edriver \u0026\u0026 !strcmp(dev-\u003edriver-\u003ename, KBUILD_MODNAME))\n-\t\t\tgoto found;\n-\treturn 0;\n+\tcase BUS_NOTIFY_BOUND_DRIVER:\n+\t\tclient = i2c_verify_client(child);\n+\t\tif (!client || client-\u003eaddr != 0x50)\n+\t\t\treturn 0;\n+\t\tif (!ptp_ocp_i2c_is_root(bp, client-\u003eadapter))\n+\t\t\treturn 0;\n+\t\tptp_ocp_i2c_kick(bp);\n+\t\treturn 0;\n \n-found:\n-\tbp = dev_get_drvdata(dev);\n-\tif (add)\n-\t\tptp_ocp_symlink(bp, child, \"i2c\");\n-\telse\n-\t\tsysfs_remove_link(\u0026bp-\u003edev.kobj, \"i2c\");\n+\tdefault:\n+\t\treturn 0;\n+\t}\n \n \treturn 0;\n }\n \n-static struct notifier_block ptp_ocp_i2c_notifier = {\n-\t.notifier_call = ptp_ocp_i2c_notifier_call,\n-};\n-\n static int __init\n ptp_ocp_init(void)\n {\n@@ -5244,21 +6041,14 @@ ptp_ocp_init(void)\n \tif (err)\n \t\tgoto out;\n \n-\twhat = \"i2c notifier\";\n-\terr = bus_register_notifier(\u0026i2c_bus_type, \u0026ptp_ocp_i2c_notifier);\n-\tif (err)\n-\t\tgoto out_notifier;\n-\n \twhat = \"ptp_ocp driver\";\n \terr = pci_register_driver(\u0026ptp_ocp_driver);\n \tif (err)\n-\t\tgoto out_register;\n+\t\tgoto out_class;\n \n \treturn 0;\n \n-out_register:\n-\tbus_unregister_notifier(\u0026i2c_bus_type, \u0026ptp_ocp_i2c_notifier);\n-out_notifier:\n+out_class:\n \tclass_unregister(\u0026timecard_class);\n out:\n \tptp_ocp_debugfs_fini();\n@@ -5269,7 +6059,6 @@ ptp_ocp_init(void)\n static void __exit\n ptp_ocp_fini(void)\n {\n-\tbus_unregister_notifier(\u0026i2c_bus_type, \u0026ptp_ocp_i2c_notifier);\n \tpci_unregister_driver(\u0026ptp_ocp_driver);\n \tclass_unregister(\u0026timecard_class);\n \tptp_ocp_debugfs_fini();\n"
}

2/1 2026/08/11 05:53 agent patch-evaluator
1m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "LEDS_CLASS_MULTICOLOR",
    "LEDS_IS32FL3207",
    "I2C_MUX",
    "PTP_1588_CLOCK_OCP"
  ],
  "FocusSymbols": [
    "i2c_mux_add_adapter",
    "is32fl3207_probe",
    "is32fl3207_brightness_set",
    "is32fl3207_mc_brightness_set"
  ],
  "Reasoning": "The patch introduces a new I2C LED driver (leds-is32fl3207) and modifies the core I2C mux logic (i2c-mux.c) to support software nodes. It also modifies the ptp_ocp PCI driver to instantiate an I2C topology using this new LED driver. While the ptp_ocp driver itself is for specific hardware not emulated in QEMU, the new I2C LED driver and the core I2C mux changes are reachable and can be fuzzed via sysfs I2C device instantiation or software node injection. Therefore, the patch contains functional changes to reachable core kernel logic and drivers that are worth fuzzing.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 3eeff6deacc895e05e8263c0f0fd7fddae5280ac
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Aug 11 05:53:56 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml b/Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml
new file mode 100644
index 0000000000000..4ee9db964eccc
--- /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 <ahmadexp@gmail.com>
+
+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) = 76.5 / RISET amperes.
+
+  "#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 state
+          cannot be preserved.
+        enum: ["on", "off"]
+
+      default-brightness:
+        maximum: 255
+        description: |
+          Brightness used when default-state is "on". This value must not
+          exceed max-brightness when max-brightness is specified.
+
+    anyOf:
+      - required:
+          - function
+      - required:
+          - color
+
+    required:
+      - reg
+      - led-max-microamp
+
+  "^multi-led@[0-9a-f]$":
+    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 OUT18).
+
+      "#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 state
+          cannot be preserved.
+        enum: ["on", "off"]
+
+      default-brightness:
+        maximum: 255
+        description: |
+          Brightness used when default-state is "on". This value must not
+          exceed max-brightness when max-brightness is specified.
+
+    patternProperties:
+      "^led@[0-9a-f]+$":
+        type: object
+        $ref: common.yaml#
+        unevaluatedProperties: false
+
+        properties:
+          reg:
+            minimum: 0
+            maximum: 17
+            description: |
+              Zero-based LED output number. Values 0 through 17 correspond to
+              OUT1 through OUT18.
+
+          led-max-microamp:
+            minimum: 1
+            maximum: 38250
+
+          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 <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/leds/common.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        led-controller@34 {
+            compatible = "issi,is32fl3207";
+            reg = <0x34>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            vcc-supply = <&led_3v3>;
+            enable-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>;
+            issi,riset-ohms = <4700>;
+
+            multi-led@0 {
+                reg = <0>;
+                color = <LED_COLOR_ID_RGB>;
+                function = LED_FUNCTION_STATUS;
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                led@0 {
+                    reg = <0>;
+                    color = <LED_COLOR_ID_RED>;
+                    led-max-microamp = <8100>;
+                };
+
+                led@1 {
+                    reg = <1>;
+                    color = <LED_COLOR_ID_GREEN>;
+                    led-max-microamp = <8100>;
+                };
+
+                led@2 {
+                    reg = <2>;
+                    color = <LED_COLOR_ID_BLUE>;
+                    led-max-microamp = <8100>;
+                };
+            };
+        };
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index a82b6ec9c5674..ce4132a754d74 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13822,6 +13822,13 @@ S:	Maintained
 F:	Documentation/filesystems/isofs.rst
 F:	fs/isofs/
 
+ISSI IS32FL3207 LED DRIVER
+M:	Ahmad Byagowi <ahmadexp@gmail.com>
+L:	linux-leds@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml
+F:	drivers/leds/rgb/leds-is32fl3207.c
+
 IT87 HARDWARE MONITORING DRIVER
 M:	Jean Delvare <jdelvare@suse.com>
 L:	linux-hwmon@vger.kernel.org
diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c
index 681a201c239ba..8b54bb13c613e 100644
--- a/drivers/i2c/i2c-mux.c
+++ b/drivers/i2c/i2c-mux.c
@@ -20,11 +20,13 @@
  */
 
 #include <linux/acpi.h>
+#include <linux/err.h>
 #include <linux/i2c.h>
 #include <linux/i2c-mux.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/of.h>
+#include <linux/property.h>
 #include <linux/slab.h>
 #include <linux/sysfs.h>
 
@@ -264,6 +266,55 @@ static const struct i2c_lock_operations i2c_parent_lock_ops = {
 	.unlock_bus =  i2c_parent_unlock_bus,
 };
 
+static struct fwnode_handle *
+i2c_mux_get_channel_swnode(struct i2c_mux_core *muxc, u32 chan_id)
+{
+	struct fwnode_handle *dev_node = dev_fwnode(muxc->dev);
+	struct fwnode_handle *mux_node, *child = 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 = dev_node->secondary;
+	}
+	if (!is_software_node(dev_node))
+		return NULL;
+
+	if (muxc->arbitrator)
+		mux_node = fwnode_get_named_child_node(dev_node, "i2c-arb");
+	else if (muxc->gate)
+		mux_node = fwnode_get_named_child_node(dev_node, "i2c-gate");
+	else
+		mux_node = 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", &reg)) {
+			fwnode_handle_put(mux_node);
+			mux_node = NULL;
+		}
+	}
+
+	if (!mux_node)
+		mux_node = fwnode_handle_get(dev_node);
+	else if (muxc->arbitrator || muxc->gate)
+		child = fwnode_handle_get(mux_node);
+
+	if (!child) {
+		fwnode_for_each_child_node(mux_node, child) {
+			if (fwnode_property_read_u32(child, "reg", &reg))
+				continue;
+			if (chan_id == reg)
+				break;
+		}
+	}
+
+	fwnode_handle_put(mux_node);
+	return child;
+}
+
 int i2c_mux_add_adapter(struct i2c_mux_core *muxc,
 			u32 force_nr, u32 chan_id)
 {
@@ -324,8 +375,8 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc,
 		priv->adap.lock_ops = &i2c_parent_lock_ops;
 
 	/*
-	 * 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 = muxc->dev->of_node;
@@ -364,6 +415,18 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc,
 
 		priv->adap.dev.of_node = child;
 		of_node_put(mux_node);
+	} else {
+		struct fwnode_handle *fwnode;
+		const struct software_node *swnode;
+
+		fwnode = i2c_mux_get_channel_swnode(muxc, chan_id);
+		if (fwnode) {
+			swnode = to_software_node(fwnode);
+			ret = device_add_software_node(&priv->adap.dev, swnode);
+			fwnode_handle_put(fwnode);
+			if (ret)
+				goto err_free_priv;
+		}
 	}
 
 	/*
@@ -408,6 +471,7 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc,
 	return 0;
 
 err_free_priv:
+	device_remove_software_node(&priv->adap.dev);
 	kfree(priv);
 	return ret;
 }
@@ -429,6 +493,7 @@ void i2c_mux_del_adapters(struct i2c_mux_core *muxc)
 		sysfs_remove_link(&muxc->dev->kobj, symlink_name);
 
 		sysfs_remove_link(&priv->adap.dev.kobj, "mux_device");
+		device_remove_software_node(&priv->adap.dev);
 		i2c_del_adapter(adap);
 		of_node_put(np);
 		kfree(priv);
diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig
index 6e9ab5f60714b..c896be1318dcc 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.
 
+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 cc0f2df662869..228923e8bb119 100644
--- a/drivers/leds/rgb/Makefile
+++ b/drivers/leds/rgb/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
 obj-$(CONFIG_LEDS_GROUP_MULTICOLOR)	+= leds-group-multicolor.o
+obj-$(CONFIG_LEDS_IS32FL3207)		+= leds-is32fl3207.o
 obj-$(CONFIG_LEDS_KTD202X)		+= leds-ktd202x.o
 obj-$(CONFIG_LEDS_LP5812)		+= leds-lp5812.o
 obj-$(CONFIG_LEDS_LP5860_CORE)		+= leds-lp5860-core.o
diff --git a/drivers/leds/rgb/leds-is32fl3207.c b/drivers/leds/rgb/leds-is32fl3207.c
new file mode 100644
index 0000000000000..c4e4a114a16e1
--- /dev/null
+++ b/drivers/leds/rgb/leds-is32fl3207.c
@@ -0,0 +1,687 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ISSI IS32FL3207 LED controller driver
+ *
+ * Copyright 2026 Ahmad Byagowi
+ */
+
+#include <linux/bitmap.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/string.h>
+
+#include <dt-bindings/leds/common.h>
+
+#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 = 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 = IS32FL3207_MAX_BRIGHTNESS;
+	if (fwnode_property_present(fwnode, "max-brightness")) {
+		ret = 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 = value;
+	}
+
+	value = *max_brightness;
+	if (fwnode_property_present(fwnode, "default-brightness")) {
+		ret = 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 = led_init_default_state_get(fwnode);
+	if (default_state == LEDS_DEFSTATE_KEEP)
+		return dev_err_probe(chip->dev, -EINVAL,
+				     "default state keep is not supported for %pfw\n",
+				     fwnode);
+
+	*brightness = default_state == 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[] = {
+		"default-brightness",
+		"default-state",
+		"max-brightness",
+		"retain-state-shutdown",
+	};
+	unsigned int i;
+
+	for (i = 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 = 0; i < num_channels; i++) {
+		ret = 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 = container_of(cdev, struct is32fl3207_led,
+						     cdev);
+	struct mc_subled subled = {
+		.brightness = brightness,
+		.channel = 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 = lcdev_to_mccdev(cdev);
+	struct is32fl3207_led *led = 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 = fwnode_property_read_u32(fwnode, "reg", &reg);
+	if (ret)
+		return dev_err_probe(chip->dev, ret,
+				     "failed to read channel for %pfw\n",
+				     fwnode);
+
+	if (reg >= 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 = 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 = 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 = min_t(u64, scaling, 0xff);
+
+	guard(mutex)(&chip->lock);
+	ret = regmap_write(chip->regmap, IS32FL3207_REG_SCALING(reg),
+			   (unsigned int)scaling);
+	if (ret)
+		return ret;
+
+	set_bit(reg, chip->channels);
+	*channel = 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 = {
+		.devicename = dev_name(chip->dev),
+		.devname_mandatory = true,
+		.fwnode = 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 = is32fl3207_parse_led_properties(chip, fwnode,
+					      &max_brightness, &brightness);
+	if (ret)
+		return ret;
+
+	if (fwnode_property_present(fwnode, "color")) {
+		ret = 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 >= LED_COLOR_ID_MAX || color == LED_COLOR_ID_MULTI ||
+		    color == LED_COLOR_ID_RGB)
+			return dev_err_probe(chip->dev, -EINVAL,
+					     "invalid single LED color %u\n",
+					     color);
+	}
+
+	ret = is32fl3207_configure_channel(chip, fwnode, &led->channel);
+	if (ret)
+		return ret;
+	led->chip = chip;
+	led->led_cdev = &led->cdev;
+	led->cdev.brightness = brightness;
+	led->cdev.max_brightness = max_brightness;
+	led->cdev.brightness_set_blocking = is32fl3207_brightness_set;
+
+	ret = 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 = {
+		.devicename = dev_name(chip->dev),
+		.devname_mandatory = true,
+		.fwnode = 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 = 0;
+	u32 group_color;
+	u32 group_reg;
+	unsigned int first_channel = IS32FL3207_NUM_CHANNELS;
+	int ret;
+
+	ret = is32fl3207_parse_led_properties(chip, fwnode,
+					      &max_brightness, &brightness);
+	if (ret)
+		return ret;
+
+	ret = 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 != LED_COLOR_ID_RGB &&
+	    group_color != LED_COLOR_ID_MULTI)
+		return dev_err_probe(chip->dev, -EINVAL,
+				     "invalid multicolor LED color %u\n",
+				     group_color);
+
+	ret = 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 = 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 = 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 = is32fl3207_validate_component(chip, child);
+		if (ret)
+			return ret;
+
+		ret = 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 >= LED_COLOR_ID_MAX || color == LED_COLOR_ID_MULTI ||
+		    color == 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 = is32fl3207_configure_channel(chip, child,
+						   &subleds[i].channel);
+		if (ret)
+			return ret;
+
+		subleds[i].color_index = color;
+		subleds[i].intensity = max_brightness;
+		subleds[i].max_intensity = 0;
+		first_channel = min(first_channel, subleds[i].channel);
+		i++;
+	}
+
+	if (group_reg != 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 = chip;
+	led->led_cdev = &led->mcdev.led_cdev;
+	led->mcdev.num_colors = count;
+	led->mcdev.subled_info = subleds;
+	led->mcdev.led_cdev.brightness = brightness;
+	led->mcdev.led_cdev.max_brightness = max_brightness;
+	led->mcdev.led_cdev.brightness_set_blocking =
+		is32fl3207_mc_brightness_set;
+
+	led_mc_calc_color_components(&led->mcdev, brightness);
+
+	ret = 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 = fwnode_get_child_node_count(fwnode);
+	bool has_color = fwnode_property_present(fwnode, "color");
+	u32 color = LED_COLOR_ID_MAX;
+	int ret;
+
+	if (has_color) {
+		ret = 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 == LED_COLOR_ID_RGB || color == 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] = { };
+	int ret;
+
+	ret = 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] = { };
+	u8 pwm[2 * IS32FL3207_NUM_CHANNELS] = { };
+	int disable_ret;
+	int ret;
+
+	/* Reset requires normal operation (SSD = 1). */
+	ret = regmap_write(chip->regmap, IS32FL3207_REG_CONTROL,
+			   IS32FL3207_CONTROL_ENABLE);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(chip->regmap, IS32FL3207_REG_RESET, 0);
+	if (ret)
+		return ret;
+	usleep_range(200, 300);
+
+	ret = regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, 0);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(chip->regmap, IS32FL3207_REG_GLOBAL_CURRENT,
+			   IS32FL3207_GLOBAL_CURRENT_MAX);
+	if (ret)
+		return ret;
+
+	ret = regmap_bulk_write(chip->regmap, IS32FL3207_REG_SCALING(0),
+				scaling, sizeof(scaling));
+	if (ret)
+		return ret;
+
+	ret = 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 = regmap_write(chip->regmap, IS32FL3207_REG_CONTROL,
+			   IS32FL3207_CONTROL_ENABLE);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(chip->regmap, IS32FL3207_REG_PWM_UPDATE, 0);
+	disable_ret = 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 = regmap_write(chip->regmap, IS32FL3207_REG_CONTROL,
+			   IS32FL3207_CONTROL_ENABLE);
+	if (ret)
+		return ret;
+
+	ret = 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 = 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 = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = IS32FL3207_REG_RESET,
+};
+
+static int is32fl3207_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct is32fl3207 *ddata;
+	unsigned int count;
+	unsigned int i = 0;
+	u32 riset_ohms;
+	int ret;
+
+	count = 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 = devm_kzalloc(dev, struct_size(ddata, leds, count), GFP_KERNEL);
+	if (!ddata)
+		return -ENOMEM;
+
+	ddata->dev = dev;
+	ddata->num_leds = count;
+	i2c_set_clientdata(client, ddata);
+
+	ret = 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 = 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 = 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 = 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 = devm_mutex_init(dev, &ddata->lock);
+	if (ret)
+		return ret;
+
+	ret = devm_regulator_get_enable_optional(dev, "vcc");
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(dev, ret,
+				     "failed to enable VCC regulator\n");
+
+	ret = devm_add_action_or_reset(dev, is32fl3207_disable, ddata);
+	if (ret)
+		return ret;
+
+	/* Let VCC settle while SDB keeps the outputs disabled. */
+	usleep_range(1000, 2000);
+
+	ret = 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 = 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 = &ddata->leds[i];
+
+		ret = is32fl3207_register_led(ddata, child, led);
+		if (ret)
+			return ret;
+
+		i++;
+	}
+
+	ret = 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 = i2c_get_clientdata(client);
+	bool retain_state = false;
+	unsigned int i;
+
+	for (i = 0; i < chip->num_leds; i++)
+		if (chip->leds[i].led_cdev->flags & LED_RETAIN_AT_SHUTDOWN) {
+			retain_state = true;
+			break;
+		}
+
+	if (!retain_state) {
+		is32fl3207_disable(chip);
+		return;
+	}
+
+	for (i = 0; i < chip->num_leds; i++) {
+		struct led_classdev *cdev = 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[] = {
+	{ .compatible = "issi,is32fl3207" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, is32fl3207_of_match);
+
+static const struct i2c_device_id is32fl3207_id[] = {
+	{ .name = "is32fl3207" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, is32fl3207_id);
+
+static struct i2c_driver is32fl3207_driver = {
+	.driver = {
+		.name = "is32fl3207",
+		.of_match_table = is32fl3207_of_match,
+	},
+	.probe = is32fl3207_probe,
+	.shutdown = is32fl3207_shutdown,
+	.id_table = is32fl3207_id,
+};
+module_i2c_driver(is32fl3207_driver);
+
+MODULE_AUTHOR("Ahmad Byagowi <ahmadexp@gmail.com>");
+MODULE_DESCRIPTION("Lumissil IS32FL3207 LED controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c
index 3d26ec1f7b9ec..a843198796ab3 100644
--- a/drivers/ptp/ptp_ocp.c
+++ b/drivers/ptp/ptp_ocp.c
@@ -21,10 +21,14 @@
 #include <net/devlink.h>
 #include <linux/i2c.h>
 #include <linux/mtd/mtd.h>
+#include <linux/mutex.h>
 #include <linux/nvmem-consumer.h>
+#include <linux/property.h>
 #include <linux/crc16.h>
 #include <linux/dpll.h>
 
+#include <dt-bindings/leds/common.h>
+
 #define PCI_DEVICE_ID_META_TIMECARD		0x0400
 
 #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
 
+#define OCP_I2C_MUX_CHANNELS		4
+#define OCP_I2C_MAX_SENSOR_COUNT	5
+#define OCP_I2C_MAX_LED_COUNT		6
+#define OCP_I2C_MAX_LED_COMPONENT_COUNT	(3 * OCP_I2C_MAX_LED_COUNT)
+#define OCP_I2C_MUX_NAME_LEN		32
+#define OCP_I2C_MUX_COMPATIBLE		"nxp,pca9546"
+#define OCP_I2C_MUX_TYPE		"pca9546"
+#define OCP_I2C_MUX_ADDRESS		0x70
+#define OCP_I2C_RETRY_MAX		10
+#define OCP_I2C_RECOVERY_SECS		30
+#define OCP_I2C_MAX_NODE_COUNT		(1 + OCP_I2C_MUX_CHANNELS + \
+					 OCP_I2C_MAX_SENSOR_COUNT + 1 + \
+					 OCP_I2C_MAX_LED_COUNT + \
+					 OCP_I2C_MAX_LED_COMPONENT_COUNT)
+
+struct ptp_ocp_i2c_device {
+	const char *node_name;
+	const char *compatible;
+	const char *type;
+	u8 channel;
+	u8 address;
+};
+
+struct ptp_ocp_led {
+	const char *node_name;
+	const char *function;
+	u8 function_enumerator;
+	bool has_function_enumerator;
+	u8 channel[3];
+};
+
+struct ptp_ocp_i2c_profile {
+	const char *name;
+	const struct ptp_ocp_i2c_device *sensors;
+	unsigned int sensor_count;
+	const struct ptp_ocp_led *leds;
+	unsigned int led_count;
+	const char *led_node_name;
+	u8 led_mux_channel;
+	u8 led_address;
+	u32 led_riset_ohms;
+	u32 led_max_microamp;
+};
+
+struct ptp_ocp_i2c_topology {
+	char mux_name[OCP_I2C_MUX_NAME_LEN];
+	struct software_node mux_node;
+	struct property_entry mux_props[6];
+	struct software_node channel_nodes[OCP_I2C_MUX_CHANNELS];
+	struct property_entry channel_props[OCP_I2C_MUX_CHANNELS][4];
+	struct software_node sensor_nodes[OCP_I2C_MAX_SENSOR_COUNT];
+	struct property_entry sensor_props[OCP_I2C_MAX_SENSOR_COUNT][3];
+	struct software_node led_node;
+	struct property_entry led_props[6];
+	struct software_node led_nodes[OCP_I2C_MAX_LED_COUNT];
+	struct property_entry led_group_props[OCP_I2C_MAX_LED_COUNT][7];
+	struct software_node component_nodes[OCP_I2C_MAX_LED_COMPONENT_COUNT];
+	struct property_entry
+		component_props[OCP_I2C_MAX_LED_COMPONENT_COUNT][4];
+	const struct software_node *node_group[OCP_I2C_MAX_NODE_COUNT + 1];
+	const struct ptp_ocp_i2c_profile *profile;
+	bool registered;
+};
+
 enum {
 	PORT_GNSS,
 	PORT_GNSS2,
@@ -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 *bp, int gen,
 				      struct ptp_perout_request *req);
 static int ptp_ocp_signal_enable(void *priv, u32 req, bool enable);
 static int ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr);
+static int ptp_ocp_i2c_notifier_call(struct notifier_block *nb,
+				     unsigned long action, void *data);
 
 static int ptp_ocp_art_board_init(struct ptp_ocp *bp, struct ocp_resource *r);
 
@@ -488,6 +568,133 @@ static struct ptp_ocp_eeprom_map art_eeprom_map[] = {
 	{ }
 };
 
+/* 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[] = {
+	{ "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[] = {
+	{
+		.node_name = "multi-led@c",
+		.function = LED_FUNCTION_STATUS,
+		.channel = { 13, 12, 14 },
+	},
+	{
+		.node_name = "multi-led@6",
+		.function = LED_FUNCTION_INDICATOR,
+		.function_enumerator = 1,
+		.has_function_enumerator = true,
+		.channel = { 7, 6, 8 },
+	},
+	{
+		.node_name = "multi-led@9",
+		.function = LED_FUNCTION_INDICATOR,
+		.function_enumerator = 2,
+		.has_function_enumerator = true,
+		.channel = { 10, 9, 11 },
+	},
+	{
+		.node_name = "multi-led@0",
+		.function = LED_FUNCTION_INDICATOR,
+		.function_enumerator = 3,
+		.has_function_enumerator = true,
+		.channel = { 1, 0, 2 },
+	},
+	{
+		.node_name = "multi-led@3",
+		.function = LED_FUNCTION_INDICATOR,
+		.function_enumerator = 4,
+		.has_function_enumerator = true,
+		.channel = { 4, 3, 5 },
+	},
+};
+
+static const struct ptp_ocp_i2c_device ptp_ocp_v9_sensors[] = {
+	{ "pressure@76", "bosch,bme280", "bme280", 1, 0x76 },
+	{ "imu@29", "bosch,bno055", "bno055", 1, 0x29 },
+};
+
+static const struct ptp_ocp_led ptp_ocp_v9_leds[] = {
+	{
+		.node_name = "multi-led@0",
+		.function = LED_FUNCTION_STATUS,
+		.function_enumerator = 1,
+		.has_function_enumerator = true,
+		.channel = { 0, 1, 2 },
+	},
+	{
+		.node_name = "multi-led@3",
+		.function = LED_FUNCTION_STATUS,
+		.function_enumerator = 2,
+		.has_function_enumerator = true,
+		.channel = { 3, 4, 5 },
+	},
+	{
+		.node_name = "multi-led@6",
+		.function = LED_FUNCTION_INDICATOR,
+		.function_enumerator = 1,
+		.has_function_enumerator = true,
+		.channel = { 6, 7, 8 },
+	},
+	{
+		.node_name = "multi-led@9",
+		.function = LED_FUNCTION_INDICATOR,
+		.function_enumerator = 2,
+		.has_function_enumerator = true,
+		.channel = { 9, 10, 11 },
+	},
+	{
+		.node_name = "multi-led@c",
+		.function = LED_FUNCTION_INDICATOR,
+		.function_enumerator = 3,
+		.has_function_enumerator = true,
+		.channel = { 12, 13, 14 },
+	},
+	{
+		.node_name = "multi-led@f",
+		.function = LED_FUNCTION_INDICATOR,
+		.function_enumerator = 4,
+		.has_function_enumerator = true,
+		.channel = { 15, 16, 17 },
+	},
+};
+
+static_assert(ARRAY_SIZE(ptp_ocp_r4006_sensors) <=
+	      OCP_I2C_MAX_SENSOR_COUNT);
+static_assert(ARRAY_SIZE(ptp_ocp_r4006_leds) <= OCP_I2C_MAX_LED_COUNT);
+static_assert(ARRAY_SIZE(ptp_ocp_v9_sensors) <= OCP_I2C_MAX_SENSOR_COUNT);
+static_assert(ARRAY_SIZE(ptp_ocp_v9_leds) <= OCP_I2C_MAX_LED_COUNT);
+
+static const struct ptp_ocp_i2c_profile ptp_ocp_r4006_profile = {
+	.name = "r4006",
+	.sensors = ptp_ocp_r4006_sensors,
+	.sensor_count = ARRAY_SIZE(ptp_ocp_r4006_sensors),
+	.leds = ptp_ocp_r4006_leds,
+	.led_count = ARRAY_SIZE(ptp_ocp_r4006_leds),
+	.led_node_name = "led-controller@34",
+	.led_mux_channel = 1,
+	.led_address = 0x34,
+	.led_riset_ohms = 4700,
+	.led_max_microamp = 8150,
+};
+
+static const struct ptp_ocp_i2c_profile ptp_ocp_v9_profile = {
+	.name = "v9",
+	.sensors = ptp_ocp_v9_sensors,
+	.sensor_count = ARRAY_SIZE(ptp_ocp_v9_sensors),
+	.leds = ptp_ocp_v9_leds,
+	.led_count = ARRAY_SIZE(ptp_ocp_v9_leds),
+	.led_node_name = "led-controller@37",
+	.led_mux_channel = 1,
+	.led_address = 0x37,
+	.led_riset_ohms = 4700,
+	.led_max_microamp = 8100,
+};
+
 #define bp_assign_entry(bp, res, val) ({				\
 	uintptr_t addr = (uintptr_t)(bp) + (res)->bp_offset;		\
 	*(typeof(val) *)addr = val;					\
@@ -1969,19 +2176,29 @@ ptp_ocp_nvmem_device_put(struct nvmem_device **nvmemp)
 	*nvmemp = NULL;
 }
 
-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 = 0;
 
 	tag = NULL;
 	nvmem = NULL;
+	mutex_lock(&bp->eeprom_lock);
+	if (ptp_ocp_has_eeprom_data(bp))
+		goto out;
+	if (!bp->i2c_ctrl || !bp->eeprom_map) {
+		ret = -ENODEV;
+		goto out;
+	}
 
 	for (map = bp->eeprom_map; map->len; map++) {
 		if (map->tag != tag) {
@@ -1997,21 +2214,542 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp)
 		}
 		ret = nvmem_device_read(nvmem, map->off, map->len,
 					BP_MAP_ENTRY_ADDR(bp, map));
-		if (ret != map->len)
+		if (ret != map->len) {
+			if (ret >= 0)
+				ret = -EIO;
 			goto fail;
+		}
 	}
 
-	bp->has_eeprom_data = true;
+	/* Publish the EEPROM fields before readers observe valid data. */
+	smp_store_release(&bp->has_eeprom_data, true);
+	ret = 0;
 
 out:
 	ptp_ocp_nvmem_device_put(&nvmem);
-	return;
+	mutex_unlock(&bp->eeprom_lock);
+	return ret;
 
 fail:
-	dev_err(&bp->pdev->dev, "could not read eeprom: %d\n", ret);
 	goto out;
 }
 
+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 = device_find_child(&i2c_ctrl->dev, NULL,
+				ptp_ocp_i2c_adapter_match);
+	if (!dev)
+		return NULL;
+
+	adapter = 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 == PCI_VENDOR_ID_META &&
+		bp->pdev->device == PCI_DEVICE_ID_META_TIMECARD) ||
+	       (bp->pdev->vendor == PCI_VENDOR_ID_CELESTICA &&
+		bp->pdev->device == 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 = 0; len < OCP_BOARD_ID_LEN; len++)
+		if (board_id[len] < 0x20 || board_id[len] > 0x7e)
+			break;
+
+	if (!len)
+		return false;
+
+	*text_len = len;
+	for (; len < OCP_BOARD_ID_LEN; len++)
+		if (board_id[len] != 0 && board_id[len] != 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[] = "R4006";
+	static const char v9_id[] = "TIMECARD-V9";
+	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 >= sizeof(r4006_id) - 1 &&
+	    !memcmp(bp->board_id, r4006_id, sizeof(r4006_id) - 1))
+		return &ptp_ocp_r4006_profile;
+
+	/* Older revisions share V9's I2C devices but not its LED wiring. */
+	if (board_id_len == sizeof(v9_id) - 1 &&
+	    !memcmp(bp->board_id, v9_id, sizeof(v9_id) - 1))
+		return &ptp_ocp_v9_profile;
+
+	return NULL;
+}
+
+static int
+ptp_ocp_i2c_init_nodes(struct ptp_ocp *bp)
+{
+	static const char * const channel_names[] = {
+		"i2c@0", "i2c@1", "i2c@2", "i2c@3",
+	};
+	static const char * const output_names[] = {
+		"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[] = {
+		LED_COLOR_ID_RED, LED_COLOR_ID_GREEN, LED_COLOR_ID_BLUE,
+	};
+	const struct ptp_ocp_i2c_profile *profile = bp->i2c_profile;
+	struct device *dev = &bp->pdev->dev;
+	struct ptp_ocp_i2c_topology *topology;
+	unsigned int component = 0;
+	unsigned int node = 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 = 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 >= OCP_I2C_MUX_CHANNELS)
+		return -EINVAL;
+
+	for (i = 0; i < profile->sensor_count; i++)
+		if (profile->sensors[i].channel >= OCP_I2C_MUX_CHANNELS)
+			return -EINVAL;
+
+	for (i = 0; i < profile->led_count; i++)
+		for (j = 0; j < ARRAY_SIZE(profile->leds[i].channel); j++)
+			if (profile->leds[i].channel[j] >=
+			    ARRAY_SIZE(output_names))
+				return -EINVAL;
+
+	topology = devm_kzalloc(dev, sizeof(*topology), GFP_KERNEL);
+	if (!topology)
+		return -ENOMEM;
+
+	topology->profile = profile;
+	snprintf(topology->mux_name, sizeof(topology->mux_name),
+		 "ocp%d-%s-mux", bp->id, profile->name);
+	topology->mux_node.name = topology->mux_name;
+	topology->mux_node.properties = topology->mux_props;
+	topology->mux_props[0] =
+		PROPERTY_ENTRY_STRING("compatible", OCP_I2C_MUX_COMPATIBLE);
+	topology->mux_props[1] =
+		PROPERTY_ENTRY_U32("reg", OCP_I2C_MUX_ADDRESS);
+	topology->mux_props[2] =
+		PROPERTY_ENTRY_BOOL("i2c-mux-idle-disconnect");
+	topology->mux_props[3] = PROPERTY_ENTRY_U32("#address-cells", 1);
+	topology->mux_props[4] = PROPERTY_ENTRY_U32("#size-cells", 0);
+	topology->node_group[node++] = &topology->mux_node;
+
+	for (i = 0; i < OCP_I2C_MUX_CHANNELS; i++) {
+		topology->channel_nodes[i].name = channel_names[i];
+		topology->channel_nodes[i].parent = &topology->mux_node;
+		topology->channel_nodes[i].properties =
+			topology->channel_props[i];
+		topology->channel_props[i][0] = PROPERTY_ENTRY_U32("reg", i);
+		topology->channel_props[i][1] =
+			PROPERTY_ENTRY_U32("#address-cells", 1);
+		topology->channel_props[i][2] =
+			PROPERTY_ENTRY_U32("#size-cells", 0);
+		topology->node_group[node++] = &topology->channel_nodes[i];
+	}
+
+	for (i = 0; i < profile->sensor_count; i++) {
+		const struct ptp_ocp_i2c_device *sensor;
+
+		sensor = &profile->sensors[i];
+		topology->sensor_nodes[i].name = sensor->node_name;
+		topology->sensor_nodes[i].parent =
+			&topology->channel_nodes[sensor->channel];
+		topology->sensor_nodes[i].properties =
+			topology->sensor_props[i];
+		prop = 0;
+		if (sensor->compatible)
+			topology->sensor_props[i][prop++] =
+				PROPERTY_ENTRY_STRING("compatible",
+						      sensor->compatible);
+		topology->sensor_props[i][prop] =
+			PROPERTY_ENTRY_U32("reg", sensor->address);
+		topology->node_group[node++] = &topology->sensor_nodes[i];
+	}
+
+	topology->led_node.name = profile->led_node_name;
+	topology->led_node.parent =
+		&topology->channel_nodes[profile->led_mux_channel];
+	topology->led_node.properties = topology->led_props;
+	topology->led_props[0] =
+		PROPERTY_ENTRY_STRING("compatible", "issi,is32fl3207");
+	topology->led_props[1] =
+		PROPERTY_ENTRY_U32("reg", profile->led_address);
+	topology->led_props[2] =
+		PROPERTY_ENTRY_U32("issi,riset-ohms",
+				   profile->led_riset_ohms);
+	topology->led_props[3] = PROPERTY_ENTRY_U32("#address-cells", 1);
+	topology->led_props[4] = PROPERTY_ENTRY_U32("#size-cells", 0);
+	topology->node_group[node++] = &topology->led_node;
+
+	for (i = 0; i < profile->led_count; i++) {
+		const struct ptp_ocp_led *led = &profile->leds[i];
+		u32 group_reg;
+
+		group_reg = min3(led->channel[0], led->channel[1],
+				 led->channel[2]);
+		topology->led_nodes[i].name = led->node_name;
+		topology->led_nodes[i].parent = &topology->led_node;
+		topology->led_nodes[i].properties =
+			topology->led_group_props[i];
+		prop = 0;
+		topology->led_group_props[i][prop++] =
+			PROPERTY_ENTRY_U32("reg", group_reg);
+		topology->led_group_props[i][prop++] =
+			PROPERTY_ENTRY_U32("color", LED_COLOR_ID_RGB);
+		topology->led_group_props[i][prop++] =
+			PROPERTY_ENTRY_STRING("function", led->function);
+		if (led->has_function_enumerator)
+			topology->led_group_props[i][prop++] =
+				PROPERTY_ENTRY_U32("function-enumerator",
+						   led->function_enumerator);
+		topology->led_group_props[i][prop++] =
+			PROPERTY_ENTRY_U32("#address-cells", 1);
+		topology->led_group_props[i][prop] =
+			PROPERTY_ENTRY_U32("#size-cells", 0);
+		topology->node_group[node++] = &topology->led_nodes[i];
+
+		for (j = 0; j < ARRAY_SIZE(led->channel); j++, component++) {
+			u8 channel = led->channel[j];
+
+			topology->component_nodes[component].name =
+				output_names[channel];
+			topology->component_nodes[component].parent =
+				&topology->led_nodes[i];
+			topology->component_nodes[component].properties =
+				topology->component_props[component];
+			topology->component_props[component][0] =
+				PROPERTY_ENTRY_U32("reg", channel);
+			topology->component_props[component][1] =
+				PROPERTY_ENTRY_U32("color", colors[j]);
+			topology->component_props[component][2] =
+				PROPERTY_ENTRY_U32("led-max-microamp",
+						   profile->led_max_microamp);
+			topology->node_group[node++] =
+				&topology->component_nodes[component];
+		}
+	}
+
+	expected_nodes = 1 + OCP_I2C_MUX_CHANNELS +
+		profile->sensor_count + 1 + profile->led_count +
+		3 * profile->led_count;
+	if (WARN_ON(node != expected_nodes)) {
+		devm_kfree(dev, topology);
+		return -EINVAL;
+	}
+
+	bp->i2c_topology = topology;
+
+register_nodes:
+	ret = software_node_register_node_group(topology->node_group);
+	if (ret)
+		return ret;
+
+	topology->registered = 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 = software_node_fwnode(node);
+	struct i2c_board_info info = { };
+	struct i2c_client *client;
+
+	client = i2c_find_device_by_fwnode(fwnode);
+	if (client) {
+		put_device(&client->dev);
+		return 0;
+	}
+
+	strscpy(info.type, type, sizeof(info.type));
+	info.addr = address;
+	info.fwnode = fwnode;
+	client = 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 = bp->i2c_topology;
+	const struct ptp_ocp_i2c_profile *profile = topology->profile;
+	const struct software_node *node;
+	struct fwnode_handle *fwnode;
+	struct i2c_adapter *adapter;
+	unsigned int i;
+	int err, ret = 0;
+
+	fwnode = software_node_fwnode(&topology->channel_nodes[channel]);
+	adapter = i2c_get_adapter_by_fwnode(fwnode);
+	if (!adapter)
+		return -EAGAIN;
+
+	for (i = 0; i < profile->sensor_count; i++) {
+		const struct ptp_ocp_i2c_device *sensor;
+
+		sensor = &profile->sensors[i];
+		if (sensor->channel != channel)
+			continue;
+
+		node = &topology->sensor_nodes[i];
+		err = ptp_ocp_i2c_add_device(adapter, node, sensor->type,
+					     sensor->address);
+		if (err && !ret)
+			ret = err;
+	}
+
+	if (channel == profile->led_mux_channel) {
+		err = ptp_ocp_i2c_add_device(adapter, &topology->led_node,
+					     "is32fl3207",
+					     profile->led_address);
+		if (err && !ret)
+			ret = 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 = software_node_fwnode(&bp->i2c_topology->mux_node);
+	client = 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 = HZ;
+	int retries;
+
+	if (!READ_ONCE(bp->i2c_resources_ready))
+		return;
+	if (!READ_ONCE(bp->i2c_root_present))
+		return;
+
+	retries = atomic_inc_return(&bp->i2c_retry_count);
+	if (retries >= OCP_I2C_RETRY_MAX) {
+		if (retries == 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 = 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 = 0;
+
+	if (!READ_ONCE(bp->i2c_root_present))
+		return 0;
+	if (!ptp_ocp_i2c_supported(bp) || !bp->eeprom_map)
+		return 0;
+
+	adapter = ptp_ocp_i2c_root_adapter(i2c_ctrl);
+	if (!adapter)
+		return -EAGAIN;
+
+	if (!ptp_ocp_has_eeprom_data(bp)) {
+		ret = ptp_ocp_read_eeprom(bp);
+		if (ret)
+			goto out_put_adapter;
+	}
+	if (!ptp_ocp_has_eeprom_data(bp)) {
+		ret = -EAGAIN;
+		goto out_put_adapter;
+	}
+	if (!bp->i2c_profile)
+		bp->i2c_profile = ptp_ocp_i2c_select_profile(bp);
+	if (!bp->i2c_profile)
+		goto out_put_adapter;
+
+	ret = ptp_ocp_i2c_init_nodes(bp);
+	if (ret)
+		goto out_put_adapter;
+
+	node = &bp->i2c_topology->mux_node;
+	ret = ptp_ocp_i2c_add_device(adapter, node, OCP_I2C_MUX_TYPE,
+				     OCP_I2C_MUX_ADDRESS);
+	if (ret)
+		goto out_put_adapter;
+
+	for (channel = 0; channel < OCP_I2C_MUX_CHANNELS; channel++) {
+		err = ptp_ocp_i2c_populate_channel(bp, channel);
+		if (err && !ret)
+			ret = err;
+	}
+
+out_put_adapter:
+	i2c_put_adapter(adapter);
+	return ret;
+}
+
+static void
+ptp_ocp_i2c_work(struct work_struct *work)
+{
+	struct ptp_ocp *bp = container_of(work, struct ptp_ocp, i2c_work.work);
+	struct platform_device *i2c_ctrl;
+	struct device *i2c_ctrl_dev;
+	int retries, ret = 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 = READ_ONCE(bp->i2c_ctrl);
+	if (!i2c_ctrl) {
+		ret = -EAGAIN;
+		goto out_unlock;
+	}
+
+	i2c_ctrl_dev = get_device(&i2c_ctrl->dev);
+	ret = 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 = atomic_xchg(&bp->i2c_retry_count, 0);
+	if (retries >= 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 = bp->i2c_topology;
+	if (!topology) {
+		bp->i2c_profile = NULL;
+		goto out;
+	}
+
+	if (topology->registered) {
+		ptp_ocp_i2c_remove_mux(bp);
+		software_node_unregister_node_group(topology->node_group);
+		topology->registered = false;
+	}
+
+	bp->i2c_topology = NULL;
+	bp->i2c_profile = NULL;
+
+out:
+	mutex_unlock(&bp->i2c_topology_lock);
+}
+
 static struct device *
 ptp_ocp_find_flash(struct ptp_ocp *bp)
 {
@@ -2166,9 +2904,9 @@ ptp_ocp_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req,
 	if (err)
 		return err;
 
-	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;
 	}
 
@@ -3758,7 +4496,7 @@ serialnum_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
 	struct ptp_ocp *bp = dev_get_drvdata(dev);
 
-	if (!bp->has_eeprom_data)
+	if (!ptp_ocp_has_eeprom_data(bp))
 		ptp_ocp_read_eeprom(bp);
 
 	return sysfs_emit(buf, "%pM\n", bp->serial);
@@ -4729,6 +5467,8 @@ ptp_ocp_device_init(struct ptp_ocp *bp, struct pci_dev *pdev)
 
 	bp->ptp_info = ptp_ocp_clock_info;
 	spin_lock_init(&bp->lock);
+	mutex_init(&bp->eeprom_lock);
+	mutex_init(&bp->i2c_topology_lock);
 
 	for (i = 0; i < __PORT_COUNT; i++)
 		bp->port[i].line = -1;
@@ -4867,6 +5607,11 @@ ptp_ocp_detach(struct ptp_ocp *bp)
 {
 	int i;
 
+	if (bp->i2c_notifier_registered) {
+		bus_unregister_notifier(&i2c_bus_type, &bp->i2c_notifier);
+		bp->i2c_notifier_registered = 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 +5826,8 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 		goto out_disable;
 
 	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);
 
 	/* compat mode.
 	 * Older FPGA firmware only returns 2 irq's.
@@ -5095,9 +5842,21 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 	bp->n_irqs = err;
 	pci_set_master(pdev);
 
+	bp->i2c_notifier.notifier_call = ptp_ocp_i2c_notifier_call;
+	err = 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 = true;
+
 	err = 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);
 
 	bp->ptp = ptp_clock_register(&bp->ptp_info, &pdev->dev);
 	if (IS_ERR(bp->ptp)) {
@@ -5191,46 +5950,84 @@ static struct pci_driver ptp_ocp_driver = {
 	.shutdown	= ptp_ocp_remove,
 };
 
+static bool
+ptp_ocp_i2c_is_child(struct ptp_ocp *bp, struct device *child)
+{
+	struct device *dev = child;
+
+	while (dev) {
+		if (dev == &bp->pdev->dev)
+			return true;
+		dev = dev->parent;
+	}
+
+	return false;
+}
+
+static bool
+ptp_ocp_i2c_is_root(struct ptp_ocp *bp, struct i2c_adapter *adapter)
+{
+	struct device *parent = adapter->dev.parent;
+
+	if (!parent)
+		return false;
+	if (bp->i2c_ctrl && parent == &bp->i2c_ctrl->dev)
+		return true;
+
+	/* The adapter can notify before ptp_ocp_register_i2c() stores it. */
+	return parent->parent == &bp->pdev->dev;
+}
+
 static int
 ptp_ocp_i2c_notifier_call(struct notifier_block *nb,
 			  unsigned long action, void *data)
 {
-	struct device *dev, *child = data;
-	struct ptp_ocp *bp;
-	bool add;
+	struct i2c_adapter *adapter;
+	struct i2c_client *client;
+	struct device *child = data;
+	struct ptp_ocp *bp = container_of(nb, struct ptp_ocp, i2c_notifier);
+
+	if (!ptp_ocp_i2c_is_child(bp, child))
+		return 0;
 
 	switch (action) {
 	case BUS_NOTIFY_ADD_DEVICE:
-	case BUS_NOTIFY_DEL_DEVICE:
-		add = action == BUS_NOTIFY_ADD_DEVICE;
-		break;
-	default:
+		adapter = 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;
-	}
 
-	if (!i2c_verify_adapter(child))
+	case BUS_NOTIFY_DEL_DEVICE:
+		adapter = 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;
 
-	dev = child;
-	while ((dev = dev->parent))
-		if (dev->driver && !strcmp(dev->driver->name, KBUILD_MODNAME))
-			goto found;
-	return 0;
+	case BUS_NOTIFY_BOUND_DRIVER:
+		client = i2c_verify_client(child);
+		if (!client || client->addr != 0x50)
+			return 0;
+		if (!ptp_ocp_i2c_is_root(bp, client->adapter))
+			return 0;
+		ptp_ocp_i2c_kick(bp);
+		return 0;
 
-found:
-	bp = dev_get_drvdata(dev);
-	if (add)
-		ptp_ocp_symlink(bp, child, "i2c");
-	else
-		sysfs_remove_link(&bp->dev.kobj, "i2c");
+	default:
+		return 0;
+	}
 
 	return 0;
 }
 
-static struct notifier_block ptp_ocp_i2c_notifier = {
-	.notifier_call = ptp_ocp_i2c_notifier_call,
-};
-
 static int __init
 ptp_ocp_init(void)
 {
@@ -5244,21 +6041,14 @@ ptp_ocp_init(void)
 	if (err)
 		goto out;
 
-	what = "i2c notifier";
-	err = bus_register_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier);
-	if (err)
-		goto out_notifier;
-
 	what = "ptp_ocp driver";
 	err = pci_register_driver(&ptp_ocp_driver);
 	if (err)
-		goto out_register;
+		goto out_class;
 
 	return 0;
 
-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 +6059,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();

3/2 2026/08/11 05:53 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28110
							output: 43
							thoughts: 3090
						

Thoughts:
**Analysis of the Patch for Fuzzing Potential**

Okay, so I've got a patch here that's introducing a new LED controller driver, `issi,is32fl3207`, and making some modifications to `drivers/ptp/ptp_ocp.c`. The patch adds several files, including documentation and the driver source for the new LED, along with changes to core I2C mux logic. My initial thought is to assess whether this introduces new fuzzing opportunities.

First, I need to consider what's reachable. The `ptp_ocp.c` driver is for a specific PCI device, an Open Compute Project Time Card.  QEMU doesn't emulate this, so fuzzing that driver directly is likely a no-go, as per the documentation's guidelines on PCI drivers.

However, there's also an I2C LED driver, `leds-is32fl3207.c`. This is my primary area of focus. I2C drivers are generally reachable because they can be instantiated via device tree or sysfs (`new_device`). Syzkaller can fuzz I2C drivers by creating them via sysfs or by injecting I2C traffic if there's a mock I2C adapter, or using `vhci` for I2C. This is a very promising avenue.  I'll verify that `leds-is32fl3207.c` is indeed a standard I2C driver that's usable with sysfs.

The critical thing to watch out for is that the LED driver's probe function will fail without the required device properties. The driver requires a `issi,riset-ohms` property.  While syzkaller doesn't currently inject arbitrary device properties, I can keep an eye on if that changes.  Even if probing fails, it's still a *new* I2C driver which can lead to new paths being reached.

I also have to examine the changes to `drivers/i2c/i2c-mux.c`. This is *core* kernel logic, and any modifications here are definitely worth investigating. I'll need to confirm that the changes in `i2c-mux.c` are actually reachable. After checking, it looks like `i2c_mux_get_channel_swnode` called from `i2c_mux_add_adapter` is indeed reachable, especially on platforms that don't use device trees for I2C muxes (like software nodes).

So, although the `ptp_ocp.c` part is likely not reachable, the I2C mux change and the new LED driver itself are my main targets. Since the patch *doesn't* *only* contain changes to vendor-specific PCIe drivers, SmartNIC, or GPU drivers, it's worth fuzzing. The overall patch increases the attack surface with a new LED driver, and modification to core I2C mux logic.

My config options to enable should be `LEDS_IS32FL3207`.  `I2C` needs to be enabled, as well. Also, since this uses `led_classdev_mc` it *might* need `LEDS_CLASS_MULTICOLOR`, but I'll need to check the exact usage.

For fuzzing focus, I will list `i2c_mux_add_adapter`, as that's a central non-static entrypoint, then `is32fl3207_probe`, `is32fl3207_brightness_set`, and `is32fl3207_mc_brightness_set`, as they are the key functions in the new driver.



4/2 2026/08/11 05:54 tool grepper
0m Args:
{
  "Expression": "led_classdev_multicolor_register_ext",
  "PathPrefix": "include/linux/led-class-multicolor.h"
}

Results:
{
  "Output": "include/linux/led-class-multicolor.h=48=static inline struct led_classdev_mc *lcdev_to_mccdev(\n--\ninclude/linux/led-class-multicolor.h-54-/**\ninclude/linux/led-class-multicolor.h:55: * led_classdev_multicolor_register_ext - register a new object of led_classdev\ninclude/linux/led-class-multicolor.h-56- *\t\t\t\t      class with support for multicolor LEDs\n--\ninclude/linux/led-class-multicolor.h-62- */\ninclude/linux/led-class-multicolor.h:63:int led_classdev_multicolor_register_ext(struct device *parent,\ninclude/linux/led-class-multicolor.h-64-\t\t\t\t\t    struct led_classdev_mc *mcled_cdev,\n--\ninclude/linux/led-class-multicolor.h=85=int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev,\n--\ninclude/linux/led-class-multicolor.h-87-\ninclude/linux/led-class-multicolor.h:88:int devm_led_classdev_multicolor_register_ext(struct device *parent,\ninclude/linux/led-class-multicolor.h-89-\t\t\t\t\t  struct led_classdev_mc *mcled_cdev,\n--\ninclude/linux/led-class-multicolor.h=95=static inline int led_classdev_multicolor_register(struct device *parent,\n--\ninclude/linux/led-class-multicolor.h-97-{\ninclude/linux/led-class-multicolor.h:98:\treturn led_classdev_multicolor_register_ext(parent, mcled_cdev, NULL);\ninclude/linux/led-class-multicolor.h-99-}\n--\ninclude/linux/led-class-multicolor.h=101=static inline int devm_led_classdev_multicolor_register(struct device *parent,\n--\ninclude/linux/led-class-multicolor.h-103-{\ninclude/linux/led-class-multicolor.h:104:\treturn devm_led_classdev_multicolor_register_ext(parent, mcled_cdev,\ninclude/linux/led-class-multicolor.h-105-\t\t\t\t\t\t\t NULL);\n"
}

5/2 2026/08/11 05:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 32631
							output: 43
							thoughts: 43
						

Thoughts:
Okay, let's dive into this. Here's how I'd approach summarizing those thoughts, focusing on the core issue and framing it for a fellow expert:

**Investigating LED Class Driver Stubbing**

Alright, I'm digging into this `devm_led_classdev_multicolor_register_ext` function. My initial question is: when `CONFIG_LEDS_CLASS_MULTICOLOR` is disabled, are we properly stubbing out this function? In other words, are we preventing the inclusion of this function, when the config option is not set? Essentially, I need to make sure the build doesn't even *attempt* to link something that's only relevant when the multicolor LED support is actually *enabled*. I want to ensure that if a user has disabled the configuration option, the resulting code should work as if the function had never existed in the first place, or rather, that the relevant parts of the code are properly excluded during the build process. I'll need to check the preprocessor directives and conditional compilation paths. This is about keeping the code clean, efficient, and avoiding potential linking errors or unexpected behavior if the option isn't available.



6/2 2026/08/11 05:54 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "CONFIG_LEDS_CLASS_MULTICOLOR",
  "PathPrefix": "include/linux/led-class-multicolor.h"
}

Results:
{
  "error": "no matches"
}

7/2 2026/08/11 05:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 32979
							output: 32
							thoughts: 64
						

Thoughts:
**Investigating the LED Driver Compilation**

Okay, so I'm trying to understand the dependencies here. The code assumes that if no stubs are present, then `LEDS_CLASS_MULTICOLOR` must be explicitly selected, a dependency, or always available, right? Let me verify that assumption. The quickest way to figure this out is to look at the build process for `led-class-multicolor.o`. I'll dive into the `drivers/leds/Makefile` and see how this object file is compiled and if there are any conditional inclusions. This should reveal the build configuration.



8/2 2026/08/11 05:54 tool grepper
0m Args:
{
  "Expression": "led-class-multicolor",
  "PathPrefix": "drivers/leds/Makefile"
}

Results:
{
  "Output": "drivers/leds/Makefile=6=obj-$(CONFIG_LEDS_CLASS_FLASH)\t\t+= led-class-flash.o\ndrivers/leds/Makefile:7:obj-$(CONFIG_LEDS_CLASS_MULTICOLOR)\t+= led-class-multicolor.o\ndrivers/leds/Makefile-8-obj-$(CONFIG_LEDS_TRIGGERS)\t\t+= led-triggers.o\n"
}

9/2 2026/08/11 05:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 33310
							output: 40
							thoughts: 51
						

Thoughts:
Okay, here's my take on those thoughts, framed as my own:

**Investigating a Configuration Dependency**

Alright, so `LEDS_CLASS_MULTICOLOR` is showing up as a configuration option, and I need to figure out how it's being selected. First things first, I'm going to jump into the `drivers/leds/rgb/Kconfig` file. My instinct tells me that `LEDS_IS32FL3207` might be the trigger, that it's the option that ultimately *selects* `LEDS_CLASS_MULTICOLOR`. Time to confirm my hypothesis and see what the dependencies look like. I'm practically certain this driver will have a relationship with the multicolor class and I need to see if it's a direct or indirect dependency.



10/2 2026/08/11 05:54 tool grepper
0m Args:
{
  "Expression": "LEDS_IS32FL3207",
  "PathPrefix": "drivers/leds/rgb/Kconfig"
}

Results:
{
  "Output": "drivers/leds/rgb/Kconfig=5=config LEDS_GROUP_MULTICOLOR\n--\ndrivers/leds/rgb/Kconfig-16-\ndrivers/leds/rgb/Kconfig:17:config LEDS_IS32FL3207\ndrivers/leds/rgb/Kconfig-18-\ttristate \"LED support for ISSI IS32FL3207\"\n"
}

11/2 2026/08/11 05:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 33686
							output: 39
							thoughts: 231
						

Thoughts:
**Analyzing the LED Driver Configuration**

Okay, so I've been looking at this Kconfig diff for `drivers/leds/rgb/Kconfig`, specifically the addition for `LEDS_IS32FL3207`. I see that the configuration itself is already present, which is good. But wait, it doesn't explicitly *select* `LEDS_CLASS_MULTICOLOR`. My initial thought is that it might depend on it indirectly through another dependency, or maybe it doesn't strictly *need* `LEDS_CLASS_MULTICOLOR` enabled to function.

But, hang on, if `LEDS_IS32FL3207` is utilizing `devm_led_classdev_multicolor_register_ext` in the driver code, and `LEDS_CLASS_MULTICOLOR` *isn't* explicitly selected, that's a potential linking failure. The build will bomb out if that multicolor class isn't available.

To clarify, let's go directly to the source. I'm going to quickly check `include/linux/led-class-multicolor.h` for stub implementations. That'll tell me if there's a fall-back if `LEDS_CLASS_MULTICOLOR` is disabled or not.



12/2 2026/08/11 05:54 tool read-file
0m Args:
{
  "File": "include/linux/led-class-multicolor.h",
  "FirstLine": 1,
  "LineCount": 100
}

Results:
{
  "Contents": "   1:\t/* SPDX-License-Identifier: GPL-2.0 */\n   2:\t/* LED Multicolor class interface\n   3:\t * Copyright (C) 2019-20 Texas Instruments Incorporated - http://www.ti.com/\n   4:\t */\n   5:\t\n   6:\t#ifndef _LINUX_MULTICOLOR_LEDS_H_INCLUDED\n   7:\t#define _LINUX_MULTICOLOR_LEDS_H_INCLUDED\n   8:\t\n   9:\t#include \u003clinux/leds.h\u003e\n  10:\t#include \u003cdt-bindings/leds/common.h\u003e\n  11:\t\n  12:\t/**\n  13:\t * struct mc_subled - Color component description.\n  14:\t * @color_index: Color ID.\n  15:\t * @brightness: Scaled intensity.\n  16:\t * @intensity: Current intensity.\n  17:\t * @max_intensity: Maximum supported intensity value.\n  18:\t * @channel: Channel index.\n  19:\t *\n  20:\t * Describes a color component of a multicolor LED. Many multicolor LEDs\n  21:\t * do not support global brightness control in hardware, so they use\n  22:\t * the brightness field in connection with led_mc_calc_color_components()\n  23:\t * to perform the intensity scaling in software.\n  24:\t * Such drivers should set max_intensity to 0 to signal the multicolor LED core\n  25:\t * that the maximum global brightness of the LED class device should be used for\n  26:\t * limiting incoming intensity values.\n  27:\t *\n  28:\t * Multicolor LEDs that do support global brightness control in hardware\n  29:\t * should instead set max_intensity to the maximum intensity value supported\n  30:\t * by the hardware for a given color component.\n  31:\t */\n  32:\tstruct mc_subled {\n  33:\t\tunsigned int color_index;\n  34:\t\tunsigned int brightness;\n  35:\t\tunsigned int intensity;\n  36:\t\tunsigned int max_intensity;\n  37:\t\tunsigned int channel;\n  38:\t};\n  39:\t\n  40:\tstruct led_classdev_mc {\n  41:\t\t/* led class device */\n  42:\t\tstruct led_classdev led_cdev;\n  43:\t\tunsigned int num_colors;\n  44:\t\n  45:\t\tstruct mc_subled *subled_info;\n  46:\t};\n  47:\t\n  48:\tstatic inline struct led_classdev_mc *lcdev_to_mccdev(\n  49:\t\t\t\t\t\t\tstruct led_classdev *led_cdev)\n  50:\t{\n  51:\t\treturn container_of(led_cdev, struct led_classdev_mc, led_cdev);\n  52:\t}\n  53:\t\n  54:\t/**\n  55:\t * led_classdev_multicolor_register_ext - register a new object of led_classdev\n  56:\t *\t\t\t\t      class with support for multicolor LEDs\n  57:\t * @parent: the multicolor LED to register\n  58:\t * @mcled_cdev: the led_classdev_mc structure for this device\n  59:\t * @init_data: the LED class multicolor device initialization data\n  60:\t *\n  61:\t * Returns: 0 on success or negative error value on failure\n  62:\t */\n  63:\tint led_classdev_multicolor_register_ext(struct device *parent,\n  64:\t\t\t\t\t\t    struct led_classdev_mc *mcled_cdev,\n  65:\t\t\t\t\t\t    struct led_init_data *init_data);\n  66:\t\n  67:\t/**\n  68:\t * led_classdev_multicolor_unregister - unregisters an object of led_classdev\n  69:\t *\t\t\t\t\tclass with support for multicolor LEDs\n  70:\t * @mcled_cdev: the multicolor LED to unregister\n  71:\t *\n  72:\t * Unregister a previously registered via led_classdev_multicolor_register\n  73:\t * object\n  74:\t */\n  75:\tvoid led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev);\n  76:\t\n  77:\t/**\n  78:\t * led_mc_calc_color_components() - Calculates component brightness values of a LED cluster.\n  79:\t * @mcled_cdev - Multicolor LED class device of the LED cluster.\n  80:\t * @brightness - Global brightness of the LED cluster.\n  81:\t *\n  82:\t * Calculates the brightness values for each color component of a monochrome LED cluster,\n  83:\t * see Documentation/leds/leds-class-multicolor.rst for details.\n  84:\t */\n  85:\tint led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev,\n  86:\t\t\t\t\t enum led_brightness brightness);\n  87:\t\n  88:\tint devm_led_classdev_multicolor_register_ext(struct device *parent,\n  89:\t\t\t\t\t\t  struct led_classdev_mc *mcled_cdev,\n  90:\t\t\t\t\t\t  struct led_init_data *init_data);\n  91:\t\n  92:\tvoid devm_led_classdev_multicolor_unregister(struct device *parent,\n  93:\t\t\t\t\t\t    struct led_classdev_mc *mcled_cdev);\n  94:\t\n  95:\tstatic inline int led_classdev_multicolor_register(struct device *parent,\n  96:\t\t\t\t\t\t    struct led_classdev_mc *mcled_cdev)\n  97:\t{\n  98:\t\treturn led_classdev_multicolor_register_ext(parent, mcled_cdev, NULL);\n  99:\t}\n 100:\t\n"
}

13/2 2026/08/11 05:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 35731
							output: 35
							thoughts: 83
						

Thoughts:
**Debugging a Missing Symbol**

Okay, so I'm looking at this code and I've hit a snag. It seems like the linker is complaining about a missing symbol, specifically `devm_led_classdev_multicolor_register_ext`. Now, my initial thought is, "Wait a minute, where's the implementation?" But then it hits me: if `CONFIG_LEDS_CLASS_MULTICOLOR` is disabled in the kernel config, this function won't even be compiled! It will be missing, so linking will *absolutely* fail. I need to make sure this is actually what's happening. Time to double-check `drivers/leds/rgb/Kconfig` again to confirm that the conditional compilation is working as expected. This configuration issue probably explains the linker error.



14/2 2026/08/11 05:54 tool read-file
0m Args:
{
  "File": "drivers/leds/rgb/Kconfig",
  "FirstLine": 1,
  "LineCount": 50
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0\n   2:\t\n   3:\tif LEDS_CLASS_MULTICOLOR\n   4:\t\n   5:\tconfig LEDS_GROUP_MULTICOLOR\n   6:\t\ttristate \"LEDs group multi-color support\"\n   7:\t\tdepends on OF\n   8:\t\thelp\n   9:\t\t  This option enables support for monochrome LEDs that are grouped\n  10:\t\t  into multicolor LEDs which is useful in the case where LEDs of\n  11:\t\t  different colors are physically grouped in a single multi-color LED\n  12:\t\t  and driven by a controller that doesn't have multi-color support.\n  13:\t\n  14:\t\t  To compile this driver as a module, choose M here: the module\n  15:\t\t  will be called leds-group-multicolor.\n  16:\t\n  17:\tconfig LEDS_IS32FL3207\n  18:\t\ttristate \"LED support for ISSI IS32FL3207\"\n  19:\t\tdepends on I2C\n  20:\t\tselect REGMAP_I2C\n  21:\t\thelp\n  22:\t\t  Say Y here to include support for the Lumissil IS32FL3207\n  23:\t\t  18-channel I2C LED controller.\n  24:\t\n  25:\t\t  To compile this driver as a module, choose M here: the module will\n  26:\t\t  be called leds-is32fl3207.\n  27:\t\n  28:\tconfig LEDS_KTD202X\n  29:\t\ttristate \"LED support for KTD202x Chips\"\n  30:\t\tdepends on I2C\n  31:\t\tselect REGMAP_I2C\n  32:\t\thelp\n  33:\t\t  This option enables support for the Kinetic KTD2026/KTD2027\n  34:\t\t  RGB/White LED driver found in different BQ mobile phones.\n  35:\t\t  It is a 3 or 4 channel LED driver programmed via an I2C interface.\n  36:\t\n  37:\t\t  To compile this driver as a module, choose M here: the module\n  38:\t\t  will be called leds-ktd202x.\n  39:\t\n  40:\tconfig LEDS_LP5812\n  41:\t\ttristate \"LED support for Texas Instruments LP5812\"\n  42:\t\tdepends on I2C\n  43:\t\thelp\n  44:\t\t  If you say Y here you get support for TI LP5812 LED driver.\n  45:\t\t  The LP5812 is a 4x3 matrix RGB LED driver with autonomous\n  46:\t\t  animation engine control.\n  47:\t\n  48:\t\t  To compile this driver as a module, choose M here: the\n  49:\t\t  module will be called leds-lp5812.\n  50:\t\n"
}

15/2 2026/08/11 05:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 36783
							output: 32
							thoughts: 145
						

Thoughts:
**Kconfig Configuration Review**

Okay, so I'm looking at `drivers/leds/rgb/Kconfig`, and right off the bat, I see that `LEDS_CLASS_MULTICOLOR` is at the very top. This means that's the base option to enable multicolor LED support. Consequently, if that's not enabled then `LEDS_IS32FL3207` isn't going to be available. The Kconfig structure dictates that. So, to ensure proper configuration, I'll definitely need to include both `LEDS_CLASS_MULTICOLOR` and `LEDS_IS32FL3207` in any relevant configurations.

Hmm, but what about `I2C_MUX`? I need to check something related to `i2c-mux.c`. I should probably check `drivers/i2c/Makefile` to see if that source file is compiled based on `CONFIG_I2C_MUX`. That would give me more clarity on the interdependencies.



16/2 2026/08/11 05:54 tool grepper
0m Args:
{
  "Expression": "i2c-mux",
  "PathPrefix": "drivers/i2c/Makefile"
}

Results:
{
  "Output": "drivers/i2c/Makefile=15=obj-$(CONFIG_I2C_CHARDEV)\t+= i2c-dev.o\ndrivers/i2c/Makefile:16:obj-$(CONFIG_I2C_MUX)\t\t+= i2c-mux.o\ndrivers/i2c/Makefile-17-obj-$(CONFIG_I2C_ATR)\t\t+= i2c-atr.o\n"
}

17/2 2026/08/11 05:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 37291
							output: 39
							thoughts: 139
						

Thoughts:
**Checking I2C Mux Configuration and Patch Dependencies**

Okay, so I'm dealing with `I2C_MUX` for this. The configuration options involved are clearly: `LEDS_CLASS_MULTICOLOR`, `LEDS_IS32FL3207`, and, of course, `I2C_MUX`. My primary concern is whether I absolutely need `I2C_MUX` enabled to properly analyze the changes in `i2c-mux.c`, and the answer is a firm yes. I'm going to need to ensure that is working as expected.

Beyond the `i2c-mux.c` file, the patch also touches `drivers/ptp/ptp_ocp.c`. This means I need to also check the `drivers/ptp/Kconfig` file to see if anything is defined related to `PTP_1588_CLOCK_OCP`. This will require a bit more digging, but it's important to be thorough.



18/2 2026/08/11 05:54 tool grepper
0m Args:
{
  "Expression": "PTP_1588_CLOCK_OCP",
  "PathPrefix": "drivers/ptp/Kconfig"
}

Results:
{
  "Output": "drivers/ptp/Kconfig=198=config PTP_1588_CLOCK_VMW\n--\ndrivers/ptp/Kconfig-209-\ndrivers/ptp/Kconfig:210:config PTP_1588_CLOCK_OCP\ndrivers/ptp/Kconfig-211-\ttristate \"OpenCompute TimeCard as PTP clock\"\n"
}

19/2 2026/08/11 05:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 37792
							output: 40
							thoughts: 918
						

Thoughts:
**My Fuzzing Strategy for This Patch**

Okay, so I'm looking at this patch and figuring out the best way to fuzz it. The `ptp_ocp` driver is a PCI driver for hardware that QEMU doesn't emulate, so while fuzzing it directly isn't strictly necessary, it doesn't hurt to enable `PTP_1588_CLOCK_OCP` in the configs, just in case Syzkaller can somehow hit it. The instructions do say to return WorthFuzzing=false if the changes only involve drivers for specific hardware not emulated in QEMU. However, this patch also includes changes to `leds-is32fl3207.c` and `i2c-mux.c`, so I'm going to return WorthFuzzing=true. I'm going to add `PTP_1588_CLOCK_OCP` to the configurations just to be safe.

Now, let's get into the nitty-gritty of the fuzzing targets. I initially identified these functions as potential focus symbols: `i2c_mux_add_adapter`, `is32fl3207_probe`, `is32fl3207_brightness_set`, and `is32fl3207_mc_brightness_set`. But wait a second... `is32fl3207_brightness_set` and `is32fl3207_mc_brightness_set` are static! Hmm, are they inlined? No, they are passed as function pointers to `led_cdev->brightness_set_blocking`, so they are *not* inlined. Perfect! Those are good candidates for focus symbols.

I was considering including `is32fl3207_register_led` as well. But it is also static, and it is called from `is32fl3207_probe`. I'm going to stick with just `is32fl3207_probe` and it should be sufficient.

Moving on, I need to verify what else was modified in `i2c-mux.c`. I checked the changes and only `i2c_mux_add_adapter` and `i2c_mux_del_adapters` seem to be the primary changes – specifically, adding or removing software nodes. So, `i2c_mux_add_adapter` is the crucial function to target here.

Finally, I checked and confirmed that `I2C_MUX` is tristate, so that config option is correct.

My final configuration for fuzzing looks like this:

**Configs:**

*   `LEDS_CLASS_MULTICOLOR`
*   `LEDS_IS32FL3207`
*   `I2C_MUX`
*   `PTP_1588_CLOCK_OCP`

**FocusSymbols:**

*   `i2c_mux_add_adapter`
*   `is32fl3207_probe`
*   `is32fl3207_brightness_set`
*   `is32fl3207_mc_brightness_set`

I'll double-check to make sure `PTP_1588_CLOCK_OCP` doesn't have any dependencies before I submit this.



20/2 2026/08/11 05:55 tool grepper
0m Args:
{
  "Expression": "config PTP_1588_CLOCK_OCP",
  "PathPrefix": "drivers/ptp/Kconfig"
}

Results:
{
  "Output": "drivers/ptp/Kconfig=198=config PTP_1588_CLOCK_VMW\n--\ndrivers/ptp/Kconfig-209-\ndrivers/ptp/Kconfig:210:config PTP_1588_CLOCK_OCP\ndrivers/ptp/Kconfig-211-\ttristate \"OpenCompute TimeCard as PTP clock\"\n"
}

21/2 2026/08/11 05:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 39595
							output: 35
							thoughts: 23
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Analyzing PTP_1588_CLOCK_OCP Configuration**

Alright, let's dive into this `config PTP_1588_CLOCK_OCP` section. My immediate thought is, I need to understand its context. Where am I in the larger configuration structure? Is this related to a specific hardware platform or a more generic implementation? Based on the variable name, I'm guessing this might relate to an Open Compute Project (OCP) implementation of Precision Time Protocol (PTP) for a 1588 clock. The OCP connection suggests this could be part of a larger network fabric or perhaps even a data center environment.

Okay, now let's read the subsequent lines. I'm looking for clues: Does it define clock source selection? Is there any mention of different PTP profiles (e.g., Telecom, Power)? Are there details on the hardware abstraction layer (HAL) interface that this configuration interacts with? Understanding these specifics is critical. I'm trying to determine whether this is a low-level definition that interacts with hardware registers, or if it is a higher-level abstraction.

I want to know how the configuration impacts the behavior of the PTP clock. Does this config dictate the master/slave roles? What parameters are configurable at runtime, versus compile time? I'm hoping to see details about the time synchronization algorithms being used, and any associated error handling.

Ultimately, I am asking, how does this configuration option influence the overall accuracy, stability, and robustness of the PTP implementation in this particular environment? That's what I am trying to determine.



22/2 2026/08/11 05:55 tool read-file
0m Args:
{
  "File": "drivers/ptp/Kconfig",
  "FirstLine": 210,
  "LineCount": 15
}

Results:
{
  "Contents": " 210:\tconfig PTP_1588_CLOCK_OCP\n 211:\t\ttristate \"OpenCompute TimeCard as PTP clock\"\n 212:\t\tdepends on PTP_1588_CLOCK\n 213:\t\tdepends on HAS_IOMEM \u0026\u0026 PCI\n 214:\t\tdepends on I2C \u0026\u0026 MTD\n 215:\t\tdepends on SERIAL_8250\n 216:\t\tdepends on !S390\n 217:\t\tdepends on COMMON_CLK\n 218:\t\tselect NET_DEVLINK\n 219:\t\tselect CRC16\n 220:\t\tselect DPLL\n 221:\t\thelp\n 222:\t\t  This driver adds support for an OpenCompute time card.\n 223:\t\n 224:\t\t  The OpenCompute time card is an atomic clock along with\n"
}

23/2 2026/08/11 05:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 40238
							output: 37
							thoughts: 232
						

Thoughts:
**Kernel Configuration Conundrum**

Okay, so I'm taking a look at these kernel configurations and trying to figure out the right set of options. It looks like the inclusion of this particular feature hinges on a handful of standard dependencies: `PTP_1588_CLOCK`, `HAS_IOMEM`, `PCI`, `I2C`, `MTD`, `SERIAL_8250`, and `COMMON_CLK`. Pretty standard stuff, mostly. However, I'm just double-checking, and it strikes me that `MTD` and `SERIAL_8250` might not always be turned on by default, depending on the base configuration.

My initial thought was to just proactively include `MTD` and `SERIAL_8250` in the configs as a precaution. Or perhaps, `PTP_1588_CLOCK_OCP` would be sufficient, and the build system would sort out the rest. Then I remembered, no, the syzkaller config generator needs explicit dependencies if they are present.

On second thought, though, I think these are *usually* enabled in the syzkaller configs anyway, so I think I can safely list just `PTP_1588_CLOCK_OCP` as the relevant trigger.

Oh, and wait... I just had a brain fart. `LEDS_CLASS_MULTICOLOR`… where *is* that defined? Is that in `drivers/leds/rgb/Kconfig`? No, that's not right. It's actually just in `drivers/leds/Kconfig`. Right. That's a good reminder to stay sharp with these details.



24/2 2026/08/11 05:55 tool grepper
0m Args:
{
  "Expression": "config LEDS_CLASS_MULTICOLOR",
  "PathPrefix": "drivers/leds/Kconfig"
}

Results:
{
  "Output": "drivers/leds/Kconfig=29=config LEDS_CLASS_FLASH\n--\ndrivers/leds/Kconfig-38-\ndrivers/leds/Kconfig:39:config LEDS_CLASS_MULTICOLOR\ndrivers/leds/Kconfig-40-\ttristate \"LED Multicolor Class Support\"\n"
}

25/2 2026/08/11 05:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 40954
							output: 34
							thoughts: 16
						

Thoughts:
Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to a fellow expert:

**Dependencies and Assumptions**

Right, so I'm digging into this `LEDS_CLASS_MULTICOLOR` thing. First thought: does this specific class, `MULTICOLOR`, have any hidden dependencies or prerequisites? I need to know if it's relying on some underlying hardware architecture, some specific driver setup, or even another software component to function correctly. I can't just assume it's standalone; that's a recipe for disaster in complex systems. It could be built on other LED functionalities or even color management routines. I need to get to the bottom of this.



26/2 2026/08/11 05:55 tool read-file
0m Args:
{
  "File": "drivers/leds/Kconfig",
  "FirstLine": 39,
  "LineCount": 10
}

Results:
{
  "Contents": "  39:\tconfig LEDS_CLASS_MULTICOLOR\n  40:\t\ttristate \"LED Multicolor Class Support\"\n  41:\t\tdepends on LEDS_CLASS\n  42:\t\thelp\n  43:\t\t  This option enables the multicolor LED sysfs class in /sys/class/leds.\n  44:\t\t  It wraps LED class and adds multicolor LED specific sysfs attributes\n  45:\t\t  and kernel internal API to it. You'll need this to provide support\n  46:\t\t  for multicolor LEDs that are grouped together. This class is not\n  47:\t\t  intended for single color LEDs. It can be built as a module.\n  48:\t\n"
}

27/2 2026/08/11 05:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 41330
							output: 282
							thoughts: 315
						

Thoughts:
**My Assessment of the Patch's Fuzzing Potential**

Okay, so this patch's fuzzability hinges on `LEDS_CLASS` – and that's a standard, so we're good there. The configuration options involved are `LEDS_CLASS_MULTICOLOR`, `LEDS_IS32FL3207`, `I2C_MUX`, and `PTP_1588_CLOCK_OCP`. I need to focus on these symbols: `i2c_mux_add_adapter`, `is32fl3207_probe`, `is32fl3207_brightness_set`, and `is32fl3207_mc_brightness_set`.

Let's dissect this. We have a new I2C LED driver (`leds-is32fl3207.c`) and modifications to the core I2C mux (`i2c-mux.c`) to handle software nodes. The `ptp_ocp` PCI driver is also altered to set up an I2C topology using this new LED driver. While the `ptp_ocp` driver itself is targeted to specific hardware that isn't emulated in QEMU, the real meat here is that the new I2C LED driver and the core I2C mux changes are reachable. We can hit them through sysfs I2C device instantiation or by injecting software nodes. That makes these changes within reachable kernel logic and drivers functionally relevant and, therefore, a good candidate for fuzzing.

My conclusion is that this patch is definitely fuzzable. Time to execute `set-results`.



28/2 2026/08/11 05:55 tool set-results
0m Args:
{
  "EnableConfigs": [
    "LEDS_CLASS_MULTICOLOR",
    "LEDS_IS32FL3207",
    "I2C_MUX",
    "PTP_1588_CLOCK_OCP"
  ],
  "FocusSymbols": [
    "i2c_mux_add_adapter",
    "is32fl3207_probe",
    "is32fl3207_brightness_set",
    "is32fl3207_mc_brightness_set"
  ],
  "Reasoning": "The patch introduces a new I2C LED driver (leds-is32fl3207) and modifies the core I2C mux logic (i2c-mux.c) to support software nodes. It also modifies the ptp_ocp PCI driver to instantiate an I2C topology using this new LED driver. While the ptp_ocp driver itself is for specific hardware not emulated in QEMU, the new I2C LED driver and the core I2C mux changes are reachable and can be fuzzed via sysfs I2C device instantiation or software node injection. Therefore, the patch contains functional changes to reachable core kernel logic and drivers that are worth fuzzing.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "LEDS_CLASS_MULTICOLOR",
    "LEDS_IS32FL3207",
    "I2C_MUX",
    "PTP_1588_CLOCK_OCP"
  ],
  "FocusSymbols": [
    "i2c_mux_add_adapter",
    "is32fl3207_probe",
    "is32fl3207_brightness_set",
    "is32fl3207_mc_brightness_set"
  ],
  "Reasoning": "The patch introduces a new I2C LED driver (leds-is32fl3207) and modifies the core I2C mux logic (i2c-mux.c) to support software nodes. It also modifies the ptp_ocp PCI driver to instantiate an I2C topology using this new LED driver. While the ptp_ocp driver itself is for specific hardware not emulated in QEMU, the new I2C LED driver and the core I2C mux changes are reachable and can be fuzzed via sysfs I2C device instantiation or software node injection. Therefore, the patch contains functional changes to reachable core kernel logic and drivers that are worth fuzzing.",
  "WorthFuzzing": true
}

29/1 2026/08/11 05:55 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

30/2 2026/08/11 05:55 agent kmsan-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "KMSANReasoning": "The patch series introduces a new LED driver (`leds-is32fl3207.c`) and modifies the `ptp_ocp` driver to use it via I2C. It also adds software node support to `i2c-mux.c`. All memory allocations introduced by these changes use zero-initializing functions (`devm_kzalloc`, `devm_kcalloc`) or are explicitly zero-initialized on the stack (e.g., `u8 scaling[...] = { };`). The changes do not introduce any new kernel structures that are copied to user space, nor do they alter control flow or bounds checking in a way that could expose uninitialized memory. Any potential bugs introduced by these changes, such as out-of-bounds accesses or use-after-free, would be effectively caught by standard KASAN and other standard bug detectors. Therefore, a dedicated KMSAN fuzzing session is not justified.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit 3eeff6deacc895e05e8263c0f0fd7fddae5280ac
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Aug 11 05:53:56 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml b/Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml
new file mode 100644
index 0000000000000..4ee9db964eccc
--- /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 <ahmadexp@gmail.com>
+
+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) = 76.5 / RISET amperes.
+
+  "#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 state
+          cannot be preserved.
+        enum: ["on", "off"]
+
+      default-brightness:
+        maximum: 255
+        description: |
+          Brightness used when default-state is "on". This value must not
+          exceed max-brightness when max-brightness is specified.
+
+    anyOf:
+      - required:
+          - function
+      - required:
+          - color
+
+    required:
+      - reg
+      - led-max-microamp
+
+  "^multi-led@[0-9a-f]$":
+    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 OUT18).
+
+      "#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 state
+          cannot be preserved.
+        enum: ["on", "off"]
+
+      default-brightness:
+        maximum: 255
+        description: |
+          Brightness used when default-state is "on". This value must not
+          exceed max-brightness when max-brightness is specified.
+
+    patternProperties:
+      "^led@[0-9a-f]+$":
+        type: object
+        $ref: common.yaml#
+        unevaluatedProperties: false
+
+        properties:
+          reg:
+            minimum: 0
+            maximum: 17
+            description: |
+              Zero-based LED output number. Values 0 through 17 correspond to
+              OUT1 through OUT18.
+
+          led-max-microamp:
+            minimum: 1
+            maximum: 38250
+
+          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 <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/leds/common.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        led-controller@34 {
+            compatible = "issi,is32fl3207";
+            reg = <0x34>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            vcc-supply = <&led_3v3>;
+            enable-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>;
+            issi,riset-ohms = <4700>;
+
+            multi-led@0 {
+                reg = <0>;
+                color = <LED_COLOR_ID_RGB>;
+                function = LED_FUNCTION_STATUS;
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                led@0 {
+                    reg = <0>;
+                    color = <LED_COLOR_ID_RED>;
+                    led-max-microamp = <8100>;
+                };
+
+                led@1 {
+                    reg = <1>;
+                    color = <LED_COLOR_ID_GREEN>;
+                    led-max-microamp = <8100>;
+                };
+
+                led@2 {
+                    reg = <2>;
+                    color = <LED_COLOR_ID_BLUE>;
+                    led-max-microamp = <8100>;
+                };
+            };
+        };
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index a82b6ec9c5674..ce4132a754d74 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13822,6 +13822,13 @@ S:	Maintained
 F:	Documentation/filesystems/isofs.rst
 F:	fs/isofs/
 
+ISSI IS32FL3207 LED DRIVER
+M:	Ahmad Byagowi <ahmadexp@gmail.com>
+L:	linux-leds@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/leds/issi,is32fl3207.yaml
+F:	drivers/leds/rgb/leds-is32fl3207.c
+
 IT87 HARDWARE MONITORING DRIVER
 M:	Jean Delvare <jdelvare@suse.com>
 L:	linux-hwmon@vger.kernel.org
diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c
index 681a201c239ba..8b54bb13c613e 100644
--- a/drivers/i2c/i2c-mux.c
+++ b/drivers/i2c/i2c-mux.c
@@ -20,11 +20,13 @@
  */
 
 #include <linux/acpi.h>
+#include <linux/err.h>
 #include <linux/i2c.h>
 #include <linux/i2c-mux.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/of.h>
+#include <linux/property.h>
 #include <linux/slab.h>
 #include <linux/sysfs.h>
 
@@ -264,6 +266,55 @@ static const struct i2c_lock_operations i2c_parent_lock_ops = {
 	.unlock_bus =  i2c_parent_unlock_bus,
 };
 
+static struct fwnode_handle *
+i2c_mux_get_channel_swnode(struct i2c_mux_core *muxc, u32 chan_id)
+{
+	struct fwnode_handle *dev_node = dev_fwnode(muxc->dev);
+	struct fwnode_handle *mux_node, *child = 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 = dev_node->secondary;
+	}
+	if (!is_software_node(dev_node))
+		return NULL;
+
+	if (muxc->arbitrator)
+		mux_node = fwnode_get_named_child_node(dev_node, "i2c-arb");
+	else if (muxc->gate)
+		mux_node = fwnode_get_named_child_node(dev_node, "i2c-gate");
+	else
+		mux_node = 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", &reg)) {
+			fwnode_handle_put(mux_node);
+			mux_node = NULL;
+		}
+	}
+
+	if (!mux_node)
+		mux_node = fwnode_handle_get(dev_node);
+	else if (muxc->arbitrator || muxc->gate)
+		child = fwnode_handle_get(mux_node);
+
+	if (!child) {
+		fwnode_for_each_child_node(mux_node, child) {
+			if (fwnode_property_read_u32(child, "reg", &reg))
+				continue;
+			if (chan_id == reg)
+				break;
+		}
+	}
+
+	fwnode_handle_put(mux_node);
+	return child;
+}
+
 int i2c_mux_add_adapter(struct i2c_mux_core *muxc,
 			u32 force_nr, u32 chan_id)
 {
@@ -324,8 +375,8 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc,
 		priv->adap.lock_ops = &i2c_parent_lock_ops;
 
 	/*
-	 * 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 = muxc->dev->of_node;
@@ -364,6 +415,18 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc,
 
 		priv->adap.dev.of_node = child;
 		of_node_put(mux_node);
+	} else {
+		struct fwnode_handle *fwnode;
+		const struct software_node *swnode;
+
+		fwnode = i2c_mux_get_channel_swnode(muxc, chan_id);
+		if (fwnode) {
+			swnode = to_software_node(fwnode);
+			ret = device_add_software_node(&priv->adap.dev, swnode);
+			fwnode_handle_put(fwnode);
+			if (ret)
+				goto err_free_priv;
+		}
 	}
 
 	/*
@@ -408,6 +471,7 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc,
 	return 0;
 
 err_free_priv:
+	device_remove_software_node(&priv->adap.dev);
 	kfree(priv);
 	return ret;
 }
@@ -429,6 +493,7 @@ void i2c_mux_del_adapters(struct i2c_mux_core *muxc)
 		sysfs_remove_link(&muxc->dev->kobj, symlink_name);
 
 		sysfs_remove_link(&priv->adap.dev.kobj, "mux_device");
+		device_remove_software_node(&priv->adap.dev);
 		i2c_del_adapter(adap);
 		of_node_put(np);
 		kfree(priv);
diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig
index 6e9ab5f60714b..c896be1318dcc 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.
 
+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 cc0f2df662869..228923e8bb119 100644
--- a/drivers/leds/rgb/Makefile
+++ b/drivers/leds/rgb/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
 obj-$(CONFIG_LEDS_GROUP_MULTICOLOR)	+= leds-group-multicolor.o
+obj-$(CONFIG_LEDS_IS32FL3207)		+= leds-is32fl3207.o
 obj-$(CONFIG_LEDS_KTD202X)		+= leds-ktd202x.o
 obj-$(CONFIG_LEDS_LP5812)		+= leds-lp5812.o
 obj-$(CONFIG_LEDS_LP5860_CORE)		+= leds-lp5860-core.o
diff --git a/drivers/leds/rgb/leds-is32fl3207.c b/drivers/leds/rgb/leds-is32fl3207.c
new file mode 100644
index 0000000000000..c4e4a114a16e1
--- /dev/null
+++ b/drivers/leds/rgb/leds-is32fl3207.c
@@ -0,0 +1,687 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ISSI IS32FL3207 LED controller driver
+ *
+ * Copyright 2026 Ahmad Byagowi
+ */
+
+#include <linux/bitmap.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/string.h>
+
+#include <dt-bindings/leds/common.h>
+
+#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 = 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 = IS32FL3207_MAX_BRIGHTNESS;
+	if (fwnode_property_present(fwnode, "max-brightness")) {
+		ret = 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 = value;
+	}
+
+	value = *max_brightness;
+	if (fwnode_property_present(fwnode, "default-brightness")) {
+		ret = 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 = led_init_default_state_get(fwnode);
+	if (default_state == LEDS_DEFSTATE_KEEP)
+		return dev_err_probe(chip->dev, -EINVAL,
+				     "default state keep is not supported for %pfw\n",
+				     fwnode);
+
+	*brightness = default_state == 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[] = {
+		"default-brightness",
+		"default-state",
+		"max-brightness",
+		"retain-state-shutdown",
+	};
+	unsigned int i;
+
+	for (i = 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 = 0; i < num_channels; i++) {
+		ret = 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 = container_of(cdev, struct is32fl3207_led,
+						     cdev);
+	struct mc_subled subled = {
+		.brightness = brightness,
+		.channel = 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 = lcdev_to_mccdev(cdev);
+	struct is32fl3207_led *led = 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 = fwnode_property_read_u32(fwnode, "reg", &reg);
+	if (ret)
+		return dev_err_probe(chip->dev, ret,
+				     "failed to read channel for %pfw\n",
+				     fwnode);
+
+	if (reg >= 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 = 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 = 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 = min_t(u64, scaling, 0xff);
+
+	guard(mutex)(&chip->lock);
+	ret = regmap_write(chip->regmap, IS32FL3207_REG_SCALING(reg),
+			   (unsigned int)scaling);
+	if (ret)
+		return ret;
+
+	set_bit(reg, chip->channels);
+	*channel = 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 = {
+		.devicename = dev_name(chip->dev),
+		.devname_mandatory = true,
+		.fwnode = 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 = is32fl3207_parse_led_properties(chip, fwnode,
+					      &max_brightness, &brightness);
+	if (ret)
+		return ret;
+
+	if (fwnode_property_present(fwnode, "color")) {
+		ret = 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 >= LED_COLOR_ID_MAX || color == LED_COLOR_ID_MULTI ||
+		    color == LED_COLOR_ID_RGB)
+			return dev_err_probe(chip->dev, -EINVAL,
+					     "invalid single LED color %u\n",
+					     color);
+	}
+
+	ret = is32fl3207_configure_channel(chip, fwnode, &led->channel);
+	if (ret)
+		return ret;
+	led->chip = chip;
+	led->led_cdev = &led->cdev;
+	led->cdev.brightness = brightness;
+	led->cdev.max_brightness = max_brightness;
+	led->cdev.brightness_set_blocking = is32fl3207_brightness_set;
+
+	ret = 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 = {
+		.devicename = dev_name(chip->dev),
+		.devname_mandatory = true,
+		.fwnode = 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 = 0;
+	u32 group_color;
+	u32 group_reg;
+	unsigned int first_channel = IS32FL3207_NUM_CHANNELS;
+	int ret;
+
+	ret = is32fl3207_parse_led_properties(chip, fwnode,
+					      &max_brightness, &brightness);
+	if (ret)
+		return ret;
+
+	ret = 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 != LED_COLOR_ID_RGB &&
+	    group_color != LED_COLOR_ID_MULTI)
+		return dev_err_probe(chip->dev, -EINVAL,
+				     "invalid multicolor LED color %u\n",
+				     group_color);
+
+	ret = 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 = 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 = 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 = is32fl3207_validate_component(chip, child);
+		if (ret)
+			return ret;
+
+		ret = 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 >= LED_COLOR_ID_MAX || color == LED_COLOR_ID_MULTI ||
+		    color == 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 = is32fl3207_configure_channel(chip, child,
+						   &subleds[i].channel);
+		if (ret)
+			return ret;
+
+		subleds[i].color_index = color;
+		subleds[i].intensity = max_brightness;
+		subleds[i].max_intensity = 0;
+		first_channel = min(first_channel, subleds[i].channel);
+		i++;
+	}
+
+	if (group_reg != 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 = chip;
+	led->led_cdev = &led->mcdev.led_cdev;
+	led->mcdev.num_colors = count;
+	led->mcdev.subled_info = subleds;
+	led->mcdev.led_cdev.brightness = brightness;
+	led->mcdev.led_cdev.max_brightness = max_brightness;
+	led->mcdev.led_cdev.brightness_set_blocking =
+		is32fl3207_mc_brightness_set;
+
+	led_mc_calc_color_components(&led->mcdev, brightness);
+
+	ret = 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 = fwnode_get_child_node_count(fwnode);
+	bool has_color = fwnode_property_present(fwnode, "color");
+	u32 color = LED_COLOR_ID_MAX;
+	int ret;
+
+	if (has_color) {
+		ret = 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 == LED_COLOR_ID_RGB || color == 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] = { };
+	int ret;
+
+	ret = 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] = { };
+	u8 pwm[2 * IS32FL3207_NUM_CHANNELS] = { };
+	int disable_ret;
+	int ret;
+
+	/* Reset requires normal operation (SSD = 1). */
+	ret = regmap_write(chip->regmap, IS32FL3207_REG_CONTROL,
+			   IS32FL3207_CONTROL_ENABLE);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(chip->regmap, IS32FL3207_REG_RESET, 0);
+	if (ret)
+		return ret;
+	usleep_range(200, 300);
+
+	ret = regmap_write(chip->regmap, IS32FL3207_REG_CONTROL, 0);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(chip->regmap, IS32FL3207_REG_GLOBAL_CURRENT,
+			   IS32FL3207_GLOBAL_CURRENT_MAX);
+	if (ret)
+		return ret;
+
+	ret = regmap_bulk_write(chip->regmap, IS32FL3207_REG_SCALING(0),
+				scaling, sizeof(scaling));
+	if (ret)
+		return ret;
+
+	ret = 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 = regmap_write(chip->regmap, IS32FL3207_REG_CONTROL,
+			   IS32FL3207_CONTROL_ENABLE);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(chip->regmap, IS32FL3207_REG_PWM_UPDATE, 0);
+	disable_ret = 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 = regmap_write(chip->regmap, IS32FL3207_REG_CONTROL,
+			   IS32FL3207_CONTROL_ENABLE);
+	if (ret)
+		return ret;
+
+	ret = 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 = 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 = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = IS32FL3207_REG_RESET,
+};
+
+static int is32fl3207_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct is32fl3207 *ddata;
+	unsigned int count;
+	unsigned int i = 0;
+	u32 riset_ohms;
+	int ret;
+
+	count = 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 = devm_kzalloc(dev, struct_size(ddata, leds, count), GFP_KERNEL);
+	if (!ddata)
+		return -ENOMEM;
+
+	ddata->dev = dev;
+	ddata->num_leds = count;
+	i2c_set_clientdata(client, ddata);
+
+	ret = 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 = 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 = 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 = 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 = devm_mutex_init(dev, &ddata->lock);
+	if (ret)
+		return ret;
+
+	ret = devm_regulator_get_enable_optional(dev, "vcc");
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(dev, ret,
+				     "failed to enable VCC regulator\n");
+
+	ret = devm_add_action_or_reset(dev, is32fl3207_disable, ddata);
+	if (ret)
+		return ret;
+
+	/* Let VCC settle while SDB keeps the outputs disabled. */
+	usleep_range(1000, 2000);
+
+	ret = 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 = 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 = &ddata->leds[i];
+
+		ret = is32fl3207_register_led(ddata, child, led);
+		if (ret)
+			return ret;
+
+		i++;
+	}
+
+	ret = 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 = i2c_get_clientdata(client);
+	bool retain_state = false;
+	unsigned int i;
+
+	for (i = 0; i < chip->num_leds; i++)
+		if (chip->leds[i].led_cdev->flags & LED_RETAIN_AT_SHUTDOWN) {
+			retain_state = true;
+			break;
+		}
+
+	if (!retain_state) {
+		is32fl3207_disable(chip);
+		return;
+	}
+
+	for (i = 0; i < chip->num_leds; i++) {
+		struct led_classdev *cdev = 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[] = {
+	{ .compatible = "issi,is32fl3207" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, is32fl3207_of_match);
+
+static const struct i2c_device_id is32fl3207_id[] = {
+	{ .name = "is32fl3207" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, is32fl3207_id);
+
+static struct i2c_driver is32fl3207_driver = {
+	.driver = {
+		.name = "is32fl3207",
+		.of_match_table = is32fl3207_of_match,
+	},
+	.probe = is32fl3207_probe,
+	.shutdown = is32fl3207_shutdown,
+	.id_table = is32fl3207_id,
+};
+module_i2c_driver(is32fl3207_driver);
+
+MODULE_AUTHOR("Ahmad Byagowi <ahmadexp@gmail.com>");
+MODULE_DESCRIPTION("Lumissil IS32FL3207 LED controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c
index 3d26ec1f7b9ec..a843198796ab3 100644
--- a/drivers/ptp/ptp_ocp.c
+++ b/drivers/ptp/ptp_ocp.c
@@ -21,10 +21,14 @@
 #include <net/devlink.h>
 #include <linux/i2c.h>
 #include <linux/mtd/mtd.h>
+#include <linux/mutex.h>
 #include <linux/nvmem-consumer.h>
+#include <linux/property.h>
 #include <linux/crc16.h>
 #include <linux/dpll.h>
 
+#include <dt-bindings/leds/common.h>
+
 #define PCI_DEVICE_ID_META_TIMECARD		0x0400
 
 #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
 
+#define OCP_I2C_MUX_CHANNELS		4
+#define OCP_I2C_MAX_SENSOR_COUNT	5
+#define OCP_I2C_MAX_LED_COUNT		6
+#define OCP_I2C_MAX_LED_COMPONENT_COUNT	(3 * OCP_I2C_MAX_LED_COUNT)
+#define OCP_I2C_MUX_NAME_LEN		32
+#define OCP_I2C_MUX_COMPATIBLE		"nxp,pca9546"
+#define OCP_I2C_MUX_TYPE		"pca9546"
+#define OCP_I2C_MUX_ADDRESS		0x70
+#define OCP_I2C_RETRY_MAX		10
+#define OCP_I2C_RECOVERY_SECS		30
+#define OCP_I2C_MAX_NODE_COUNT		(1 + OCP_I2C_MUX_CHANNELS + \
+					 OCP_I2C_MAX_SENSOR_COUNT + 1 + \
+					 OCP_I2C_MAX_LED_COUNT + \
+					 OCP_I2C_MAX_LED_COMPONENT_COUNT)
+
+struct ptp_ocp_i2c_device {
+	const char *node_name;
+	const char *compatible;
+	const char *type;
+	u8 channel;
+	u8 address;
+};
+
+struct ptp_ocp_led {
+	const char *node_name;
+	const char *function;
+	u8 function_enumerator;
+	bool has_function_enumerator;
+	u8 channel[3];
+};
+
+struct ptp_ocp_i2c_profile {
+	const char *name;
+	const struct ptp_ocp_i2c_device *sensors;
+	unsigned int sensor_count;
+	const struct ptp_ocp_led *leds;
+	unsigned int led_count;
+	const char *led_node_name;
+	u8 led_mux_channel;
+	u8 led_address;
+	u32 led_riset_ohms;
+	u32 led_max_microamp;
+};
+
+struct ptp_ocp_i2c_topology {
+	char mux_name[OCP_I2C_MUX_NAME_LEN];
+	struct software_node mux_node;
+	struct property_entry mux_props[6];
+	struct software_node channel_nodes[OCP_I2C_MUX_CHANNELS];
+	struct property_entry channel_props[OCP_I2C_MUX_CHANNELS][4];
+	struct software_node sensor_nodes[OCP_I2C_MAX_SENSOR_COUNT];
+	struct property_entry sensor_props[OCP_I2C_MAX_SENSOR_COUNT][3];
+	struct software_node led_node;
+	struct property_entry led_props[6];
+	struct software_node led_nodes[OCP_I2C_MAX_LED_COUNT];
+	struct property_entry led_group_props[OCP_I2C_MAX_LED_COUNT][7];
+	struct software_node component_nodes[OCP_I2C_MAX_LED_COMPONENT_COUNT];
+	struct property_entry
+		component_props[OCP_I2C_MAX_LED_COMPONENT_COUNT][4];
+	const struct software_node *node_group[OCP_I2C_MAX_NODE_COUNT + 1];
+	const struct ptp_ocp_i2c_profile *profile;
+	bool registered;
+};
+
 enum {
 	PORT_GNSS,
 	PORT_GNSS2,
@@ -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 *bp, int gen,
 				      struct ptp_perout_request *req);
 static int ptp_ocp_signal_enable(void *priv, u32 req, bool enable);
 static int ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr);
+static int ptp_ocp_i2c_notifier_call(struct notifier_block *nb,
+				     unsigned long action, void *data);
 
 static int ptp_ocp_art_board_init(struct ptp_ocp *bp, struct ocp_resource *r);
 
@@ -488,6 +568,133 @@ static struct ptp_ocp_eeprom_map art_eeprom_map[] = {
 	{ }
 };
 
+/* 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[] = {
+	{ "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[] = {
+	{
+		.node_name = "multi-led@c",
+		.function = LED_FUNCTION_STATUS,
+		.channel = { 13, 12, 14 },
+	},
+	{
+		.node_name = "multi-led@6",
+		.function = LED_FUNCTION_INDICATOR,
+		.function_enumerator = 1,
+		.has_function_enumerator = true,
+		.channel = { 7, 6, 8 },
+	},
+	{
+		.node_name = "multi-led@9",
+		.function = LED_FUNCTION_INDICATOR,
+		.function_enumerator = 2,
+		.has_function_enumerator = true,
+		.channel = { 10, 9, 11 },
+	},
+	{
+		.node_name = "multi-led@0",
+		.function = LED_FUNCTION_INDICATOR,
+		.function_enumerator = 3,
+		.has_function_enumerator = true,
+		.channel = { 1, 0, 2 },
+	},
+	{
+		.node_name = "multi-led@3",
+		.function = LED_FUNCTION_INDICATOR,
+		.function_enumerator = 4,
+		.has_function_enumerator = true,
+		.channel = { 4, 3, 5 },
+	},
+};
+
+static const struct ptp_ocp_i2c_device ptp_ocp_v9_sensors[] = {
+	{ "pressure@76", "bosch,bme280", "bme280", 1, 0x76 },
+	{ "imu@29", "bosch,bno055", "bno055", 1, 0x29 },
+};
+
+static const struct ptp_ocp_led ptp_ocp_v9_leds[] = {
+	{
+		.node_name = "multi-led@0",
+		.function = LED_FUNCTION_STATUS,
+		.function_enumerator = 1,
+		.has_function_enumerator = true,
+		.channel = { 0, 1, 2 },
+	},
+	{
+		.node_name = "multi-led@3",
+		.function = LED_FUNCTION_STATUS,
+		.function_enumerator = 2,
+		.has_function_enumerator = true,
+		.channel = { 3, 4, 5 },
+	},
+	{
+		.node_name = "multi-led@6",
+		.function = LED_FUNCTION_INDICATOR,
+		.function_enumerator = 1,
+		.has_function_enumerator = true,
+		.channel = { 6, 7, 8 },
+	},
+	{
+		.node_name = "multi-led@9",
+		.function = LED_FUNCTION_INDICATOR,
+		.function_enumerator = 2,
+		.has_function_enumerator = true,
+		.channel = { 9, 10, 11 },
+	},
+	{
+		.node_name = "multi-led@c",
+		.function = LED_FUNCTION_INDICATOR,
+		.function_enumerator = 3,
+		.has_function_enumerator = true,
+		.channel = { 12, 13, 14 },
+	},
+	{
+		.node_name = "multi-led@f",
+		.function = LED_FUNCTION_INDICATOR,
+		.function_enumerator = 4,
+		.has_function_enumerator = true,
+		.channel = { 15, 16, 17 },
+	},
+};
+
+static_assert(ARRAY_SIZE(ptp_ocp_r4006_sensors) <=
+	      OCP_I2C_MAX_SENSOR_COUNT);
+static_assert(ARRAY_SIZE(ptp_ocp_r4006_leds) <= OCP_I2C_MAX_LED_COUNT);
+static_assert(ARRAY_SIZE(ptp_ocp_v9_sensors) <= OCP_I2C_MAX_SENSOR_COUNT);
+static_assert(ARRAY_SIZE(ptp_ocp_v9_leds) <= OCP_I2C_MAX_LED_COUNT);
+
+static const struct ptp_ocp_i2c_profile ptp_ocp_r4006_profile = {
+	.name = "r4006",
+	.sensors = ptp_ocp_r4006_sensors,
+	.sensor_count = ARRAY_SIZE(ptp_ocp_r4006_sensors),
+	.leds = ptp_ocp_r4006_leds,
+	.led_count = ARRAY_SIZE(ptp_ocp_r4006_leds),
+	.led_node_name = "led-controller@34",
+	.led_mux_channel = 1,
+	.led_address = 0x34,
+	.led_riset_ohms = 4700,
+	.led_max_microamp = 8150,
+};
+
+static const struct ptp_ocp_i2c_profile ptp_ocp_v9_profile = {
+	.name = "v9",
+	.sensors = ptp_ocp_v9_sensors,
+	.sensor_count = ARRAY_SIZE(ptp_ocp_v9_sensors),
+	.leds = ptp_ocp_v9_leds,
+	.led_count = ARRAY_SIZE(ptp_ocp_v9_leds),
+	.led_node_name = "led-controller@37",
+	.led_mux_channel = 1,
+	.led_address = 0x37,
+	.led_riset_ohms = 4700,
+	.led_max_microamp = 8100,
+};
+
 #define bp_assign_entry(bp, res, val) ({				\
 	uintptr_t addr = (uintptr_t)(bp) + (res)->bp_offset;		\
 	*(typeof(val) *)addr = val;					\
@@ -1969,19 +2176,29 @@ ptp_ocp_nvmem_device_put(struct nvmem_device **nvmemp)
 	*nvmemp = NULL;
 }
 
-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 = 0;
 
 	tag = NULL;
 	nvmem = NULL;
+	mutex_lock(&bp->eeprom_lock);
+	if (ptp_ocp_has_eeprom_data(bp))
+		goto out;
+	if (!bp->i2c_ctrl || !bp->eeprom_map) {
+		ret = -ENODEV;
+		goto out;
+	}
 
 	for (map = bp->eeprom_map; map->len; map++) {
 		if (map->tag != tag) {
@@ -1997,21 +2214,542 @@ ptp_ocp_read_eeprom(struct ptp_ocp *bp)
 		}
 		ret = nvmem_device_read(nvmem, map->off, map->len,
 					BP_MAP_ENTRY_ADDR(bp, map));
-		if (ret != map->len)
+		if (ret != map->len) {
+			if (ret >= 0)
+				ret = -EIO;
 			goto fail;
+		}
 	}
 
-	bp->has_eeprom_data = true;
+	/* Publish the EEPROM fields before readers observe valid data. */
+	smp_store_release(&bp->has_eeprom_data, true);
+	ret = 0;
 
 out:
 	ptp_ocp_nvmem_device_put(&nvmem);
-	return;
+	mutex_unlock(&bp->eeprom_lock);
+	return ret;
 
 fail:
-	dev_err(&bp->pdev->dev, "could not read eeprom: %d\n", ret);
 	goto out;
 }
 
+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 = device_find_child(&i2c_ctrl->dev, NULL,
+				ptp_ocp_i2c_adapter_match);
+	if (!dev)
+		return NULL;
+
+	adapter = 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 == PCI_VENDOR_ID_META &&
+		bp->pdev->device == PCI_DEVICE_ID_META_TIMECARD) ||
+	       (bp->pdev->vendor == PCI_VENDOR_ID_CELESTICA &&
+		bp->pdev->device == 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 = 0; len < OCP_BOARD_ID_LEN; len++)
+		if (board_id[len] < 0x20 || board_id[len] > 0x7e)
+			break;
+
+	if (!len)
+		return false;
+
+	*text_len = len;
+	for (; len < OCP_BOARD_ID_LEN; len++)
+		if (board_id[len] != 0 && board_id[len] != 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[] = "R4006";
+	static const char v9_id[] = "TIMECARD-V9";
+	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 >= sizeof(r4006_id) - 1 &&
+	    !memcmp(bp->board_id, r4006_id, sizeof(r4006_id) - 1))
+		return &ptp_ocp_r4006_profile;
+
+	/* Older revisions share V9's I2C devices but not its LED wiring. */
+	if (board_id_len == sizeof(v9_id) - 1 &&
+	    !memcmp(bp->board_id, v9_id, sizeof(v9_id) - 1))
+		return &ptp_ocp_v9_profile;
+
+	return NULL;
+}
+
+static int
+ptp_ocp_i2c_init_nodes(struct ptp_ocp *bp)
+{
+	static const char * const channel_names[] = {
+		"i2c@0", "i2c@1", "i2c@2", "i2c@3",
+	};
+	static const char * const output_names[] = {
+		"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[] = {
+		LED_COLOR_ID_RED, LED_COLOR_ID_GREEN, LED_COLOR_ID_BLUE,
+	};
+	const struct ptp_ocp_i2c_profile *profile = bp->i2c_profile;
+	struct device *dev = &bp->pdev->dev;
+	struct ptp_ocp_i2c_topology *topology;
+	unsigned int component = 0;
+	unsigned int node = 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 = 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 >= OCP_I2C_MUX_CHANNELS)
+		return -EINVAL;
+
+	for (i = 0; i < profile->sensor_count; i++)
+		if (profile->sensors[i].channel >= OCP_I2C_MUX_CHANNELS)
+			return -EINVAL;
+
+	for (i = 0; i < profile->led_count; i++)
+		for (j = 0; j < ARRAY_SIZE(profile->leds[i].channel); j++)
+			if (profile->leds[i].channel[j] >=
+			    ARRAY_SIZE(output_names))
+				return -EINVAL;
+
+	topology = devm_kzalloc(dev, sizeof(*topology), GFP_KERNEL);
+	if (!topology)
+		return -ENOMEM;
+
+	topology->profile = profile;
+	snprintf(topology->mux_name, sizeof(topology->mux_name),
+		 "ocp%d-%s-mux", bp->id, profile->name);
+	topology->mux_node.name = topology->mux_name;
+	topology->mux_node.properties = topology->mux_props;
+	topology->mux_props[0] =
+		PROPERTY_ENTRY_STRING("compatible", OCP_I2C_MUX_COMPATIBLE);
+	topology->mux_props[1] =
+		PROPERTY_ENTRY_U32("reg", OCP_I2C_MUX_ADDRESS);
+	topology->mux_props[2] =
+		PROPERTY_ENTRY_BOOL("i2c-mux-idle-disconnect");
+	topology->mux_props[3] = PROPERTY_ENTRY_U32("#address-cells", 1);
+	topology->mux_props[4] = PROPERTY_ENTRY_U32("#size-cells", 0);
+	topology->node_group[node++] = &topology->mux_node;
+
+	for (i = 0; i < OCP_I2C_MUX_CHANNELS; i++) {
+		topology->channel_nodes[i].name = channel_names[i];
+		topology->channel_nodes[i].parent = &topology->mux_node;
+		topology->channel_nodes[i].properties =
+			topology->channel_props[i];
+		topology->channel_props[i][0] = PROPERTY_ENTRY_U32("reg", i);
+		topology->channel_props[i][1] =
+			PROPERTY_ENTRY_U32("#address-cells", 1);
+		topology->channel_props[i][2] =
+			PROPERTY_ENTRY_U32("#size-cells", 0);
+		topology->node_group[node++] = &topology->channel_nodes[i];
+	}
+
+	for (i = 0; i < profile->sensor_count; i++) {
+		const struct ptp_ocp_i2c_device *sensor;
+
+		sensor = &profile->sensors[i];
+		topology->sensor_nodes[i].name = sensor->node_name;
+		topology->sensor_nodes[i].parent =
+			&topology->channel_nodes[sensor->channel];
+		topology->sensor_nodes[i].properties =
+			topology->sensor_props[i];
+		prop = 0;
+		if (sensor->compatible)
+			topology->sensor_props[i][prop++] =
+				PROPERTY_ENTRY_STRING("compatible",
+						      sensor->compatible);
+		topology->sensor_props[i][prop] =
+			PROPERTY_ENTRY_U32("reg", sensor->address);
+		topology->node_group[node++] = &topology->sensor_nodes[i];
+	}
+
+	topology->led_node.name = profile->led_node_name;
+	topology->led_node.parent =
+		&topology->channel_nodes[profile->led_mux_channel];
+	topology->led_node.properties = topology->led_props;
+	topology->led_props[0] =
+		PROPERTY_ENTRY_STRING("compatible", "issi,is32fl3207");
+	topology->led_props[1] =
+		PROPERTY_ENTRY_U32("reg", profile->led_address);
+	topology->led_props[2] =
+		PROPERTY_ENTRY_U32("issi,riset-ohms",
+				   profile->led_riset_ohms);
+	topology->led_props[3] = PROPERTY_ENTRY_U32("#address-cells", 1);
+	topology->led_props[4] = PROPERTY_ENTRY_U32("#size-cells", 0);
+	topology->node_group[node++] = &topology->led_node;
+
+	for (i = 0; i < profile->led_count; i++) {
+		const struct ptp_ocp_led *led = &profile->leds[i];
+		u32 group_reg;
+
+		group_reg = min3(led->channel[0], led->channel[1],
+				 led->channel[2]);
+		topology->led_nodes[i].name = led->node_name;
+		topology->led_nodes[i].parent = &topology->led_node;
+		topology->led_nodes[i].properties =
+			topology->led_group_props[i];
+		prop = 0;
+		topology->led_group_props[i][prop++] =
+			PROPERTY_ENTRY_U32("reg", group_reg);
+		topology->led_group_props[i][prop++] =
+			PROPERTY_ENTRY_U32("color", LED_COLOR_ID_RGB);
+		topology->led_group_props[i][prop++] =
+			PROPERTY_ENTRY_STRING("function", led->function);
+		if (led->has_function_enumerator)
+			topology->led_group_props[i][prop++] =
+				PROPERTY_ENTRY_U32("function-enumerator",
+						   led->function_enumerator);
+		topology->led_group_props[i][prop++] =
+			PROPERTY_ENTRY_U32("#address-cells", 1);
+		topology->led_group_props[i][prop] =
+			PROPERTY_ENTRY_U32("#size-cells", 0);
+		topology->node_group[node++] = &topology->led_nodes[i];
+
+		for (j = 0; j < ARRAY_SIZE(led->channel); j++, component++) {
+			u8 channel = led->channel[j];
+
+			topology->component_nodes[component].name =
+				output_names[channel];
+			topology->component_nodes[component].parent =
+				&topology->led_nodes[i];
+			topology->component_nodes[component].properties =
+				topology->component_props[component];
+			topology->component_props[component][0] =
+				PROPERTY_ENTRY_U32("reg", channel);
+			topology->component_props[component][1] =
+				PROPERTY_ENTRY_U32("color", colors[j]);
+			topology->component_props[component][2] =
+				PROPERTY_ENTRY_U32("led-max-microamp",
+						   profile->led_max_microamp);
+			topology->node_group[node++] =
+				&topology->component_nodes[component];
+		}
+	}
+
+	expected_nodes = 1 + OCP_I2C_MUX_CHANNELS +
+		profile->sensor_count + 1 + profile->led_count +
+		3 * profile->led_count;
+	if (WARN_ON(node != expected_nodes)) {
+		devm_kfree(dev, topology);
+		return -EINVAL;
+	}
+
+	bp->i2c_topology = topology;
+
+register_nodes:
+	ret = software_node_register_node_group(topology->node_group);
+	if (ret)
+		return ret;
+
+	topology->registered = 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 = software_node_fwnode(node);
+	struct i2c_board_info info = { };
+	struct i2c_client *client;
+
+	client = i2c_find_device_by_fwnode(fwnode);
+	if (client) {
+		put_device(&client->dev);
+		return 0;
+	}
+
+	strscpy(info.type, type, sizeof(info.type));
+	info.addr = address;
+	info.fwnode = fwnode;
+	client = 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 = bp->i2c_topology;
+	const struct ptp_ocp_i2c_profile *profile = topology->profile;
+	const struct software_node *node;
+	struct fwnode_handle *fwnode;
+	struct i2c_adapter *adapter;
+	unsigned int i;
+	int err, ret = 0;
+
+	fwnode = software_node_fwnode(&topology->channel_nodes[channel]);
+	adapter = i2c_get_adapter_by_fwnode(fwnode);
+	if (!adapter)
+		return -EAGAIN;
+
+	for (i = 0; i < profile->sensor_count; i++) {
+		const struct ptp_ocp_i2c_device *sensor;
+
+		sensor = &profile->sensors[i];
+		if (sensor->channel != channel)
+			continue;
+
+		node = &topology->sensor_nodes[i];
+		err = ptp_ocp_i2c_add_device(adapter, node, sensor->type,
+					     sensor->address);
+		if (err && !ret)
+			ret = err;
+	}
+
+	if (channel == profile->led_mux_channel) {
+		err = ptp_ocp_i2c_add_device(adapter, &topology->led_node,
+					     "is32fl3207",
+					     profile->led_address);
+		if (err && !ret)
+			ret = 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 = software_node_fwnode(&bp->i2c_topology->mux_node);
+	client = 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 = HZ;
+	int retries;
+
+	if (!READ_ONCE(bp->i2c_resources_ready))
+		return;
+	if (!READ_ONCE(bp->i2c_root_present))
+		return;
+
+	retries = atomic_inc_return(&bp->i2c_retry_count);
+	if (retries >= OCP_I2C_RETRY_MAX) {
+		if (retries == 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 = 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 = 0;
+
+	if (!READ_ONCE(bp->i2c_root_present))
+		return 0;
+	if (!ptp_ocp_i2c_supported(bp) || !bp->eeprom_map)
+		return 0;
+
+	adapter = ptp_ocp_i2c_root_adapter(i2c_ctrl);
+	if (!adapter)
+		return -EAGAIN;
+
+	if (!ptp_ocp_has_eeprom_data(bp)) {
+		ret = ptp_ocp_read_eeprom(bp);
+		if (ret)
+			goto out_put_adapter;
+	}
+	if (!ptp_ocp_has_eeprom_data(bp)) {
+		ret = -EAGAIN;
+		goto out_put_adapter;
+	}
+	if (!bp->i2c_profile)
+		bp->i2c_profile = ptp_ocp_i2c_select_profile(bp);
+	if (!bp->i2c_profile)
+		goto out_put_adapter;
+
+	ret = ptp_ocp_i2c_init_nodes(bp);
+	if (ret)
+		goto out_put_adapter;
+
+	node = &bp->i2c_topology->mux_node;
+	ret = ptp_ocp_i2c_add_device(adapter, node, OCP_I2C_MUX_TYPE,
+				     OCP_I2C_MUX_ADDRESS);
+	if (ret)
+		goto out_put_adapter;
+
+	for (channel = 0; channel < OCP_I2C_MUX_CHANNELS; channel++) {
+		err = ptp_ocp_i2c_populate_channel(bp, channel);
+		if (err && !ret)
+			ret = err;
+	}
+
+out_put_adapter:
+	i2c_put_adapter(adapter);
+	return ret;
+}
+
+static void
+ptp_ocp_i2c_work(struct work_struct *work)
+{
+	struct ptp_ocp *bp = container_of(work, struct ptp_ocp, i2c_work.work);
+	struct platform_device *i2c_ctrl;
+	struct device *i2c_ctrl_dev;
+	int retries, ret = 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 = READ_ONCE(bp->i2c_ctrl);
+	if (!i2c_ctrl) {
+		ret = -EAGAIN;
+		goto out_unlock;
+	}
+
+	i2c_ctrl_dev = get_device(&i2c_ctrl->dev);
+	ret = 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 = atomic_xchg(&bp->i2c_retry_count, 0);
+	if (retries >= 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 = bp->i2c_topology;
+	if (!topology) {
+		bp->i2c_profile = NULL;
+		goto out;
+	}
+
+	if (topology->registered) {
+		ptp_ocp_i2c_remove_mux(bp);
+		software_node_unregister_node_group(topology->node_group);
+		topology->registered = false;
+	}
+
+	bp->i2c_topology = NULL;
+	bp->i2c_profile = NULL;
+
+out:
+	mutex_unlock(&bp->i2c_topology_lock);
+}
+
 static struct device *
 ptp_ocp_find_flash(struct ptp_ocp *bp)
 {
@@ -2166,9 +2904,9 @@ ptp_ocp_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req,
 	if (err)
 		return err;
 
-	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;
 	}
 
@@ -3758,7 +4496,7 @@ serialnum_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
 	struct ptp_ocp *bp = dev_get_drvdata(dev);
 
-	if (!bp->has_eeprom_data)
+	if (!ptp_ocp_has_eeprom_data(bp))
 		ptp_ocp_read_eeprom(bp);
 
 	return sysfs_emit(buf, "%pM\n", bp->serial);
@@ -4729,6 +5467,8 @@ ptp_ocp_device_init(struct ptp_ocp *bp, struct pci_dev *pdev)
 
 	bp->ptp_info = ptp_ocp_clock_info;
 	spin_lock_init(&bp->lock);
+	mutex_init(&bp->eeprom_lock);
+	mutex_init(&bp->i2c_topology_lock);
 
 	for (i = 0; i < __PORT_COUNT; i++)
 		bp->port[i].line = -1;
@@ -4867,6 +5607,11 @@ ptp_ocp_detach(struct ptp_ocp *bp)
 {
 	int i;
 
+	if (bp->i2c_notifier_registered) {
+		bus_unregister_notifier(&i2c_bus_type, &bp->i2c_notifier);
+		bp->i2c_notifier_registered = 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 +5826,8 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 		goto out_disable;
 
 	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);
 
 	/* compat mode.
 	 * Older FPGA firmware only returns 2 irq's.
@@ -5095,9 +5842,21 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 	bp->n_irqs = err;
 	pci_set_master(pdev);
 
+	bp->i2c_notifier.notifier_call = ptp_ocp_i2c_notifier_call;
+	err = 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 = true;
+
 	err = 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);
 
 	bp->ptp = ptp_clock_register(&bp->ptp_info, &pdev->dev);
 	if (IS_ERR(bp->ptp)) {
@@ -5191,46 +5950,84 @@ static struct pci_driver ptp_ocp_driver = {
 	.shutdown	= ptp_ocp_remove,
 };
 
+static bool
+ptp_ocp_i2c_is_child(struct ptp_ocp *bp, struct device *child)
+{
+	struct device *dev = child;
+
+	while (dev) {
+		if (dev == &bp->pdev->dev)
+			return true;
+		dev = dev->parent;
+	}
+
+	return false;
+}
+
+static bool
+ptp_ocp_i2c_is_root(struct ptp_ocp *bp, struct i2c_adapter *adapter)
+{
+	struct device *parent = adapter->dev.parent;
+
+	if (!parent)
+		return false;
+	if (bp->i2c_ctrl && parent == &bp->i2c_ctrl->dev)
+		return true;
+
+	/* The adapter can notify before ptp_ocp_register_i2c() stores it. */
+	return parent->parent == &bp->pdev->dev;
+}
+
 static int
 ptp_ocp_i2c_notifier_call(struct notifier_block *nb,
 			  unsigned long action, void *data)
 {
-	struct device *dev, *child = data;
-	struct ptp_ocp *bp;
-	bool add;
+	struct i2c_adapter *adapter;
+	struct i2c_client *client;
+	struct device *child = data;
+	struct ptp_ocp *bp = container_of(nb, struct ptp_ocp, i2c_notifier);
+
+	if (!ptp_ocp_i2c_is_child(bp, child))
+		return 0;
 
 	switch (action) {
 	case BUS_NOTIFY_ADD_DEVICE:
-	case BUS_NOTIFY_DEL_DEVICE:
-		add = action == BUS_NOTIFY_ADD_DEVICE;
-		break;
-	default:
+		adapter = 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;
-	}
 
-	if (!i2c_verify_adapter(child))
+	case BUS_NOTIFY_DEL_DEVICE:
+		adapter = 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;
 
-	dev = child;
-	while ((dev = dev->parent))
-		if (dev->driver && !strcmp(dev->driver->name, KBUILD_MODNAME))
-			goto found;
-	return 0;
+	case BUS_NOTIFY_BOUND_DRIVER:
+		client = i2c_verify_client(child);
+		if (!client || client->addr != 0x50)
+			return 0;
+		if (!ptp_ocp_i2c_is_root(bp, client->adapter))
+			return 0;
+		ptp_ocp_i2c_kick(bp);
+		return 0;
 
-found:
-	bp = dev_get_drvdata(dev);
-	if (add)
-		ptp_ocp_symlink(bp, child, "i2c");
-	else
-		sysfs_remove_link(&bp->dev.kobj, "i2c");
+	default:
+		return 0;
+	}
 
 	return 0;
 }
 
-static struct notifier_block ptp_ocp_i2c_notifier = {
-	.notifier_call = ptp_ocp_i2c_notifier_call,
-};
-
 static int __init
 ptp_ocp_init(void)
 {
@@ -5244,21 +6041,14 @@ ptp_ocp_init(void)
 	if (err)
 		goto out;
 
-	what = "i2c notifier";
-	err = bus_register_notifier(&i2c_bus_type, &ptp_ocp_i2c_notifier);
-	if (err)
-		goto out_notifier;
-
 	what = "ptp_ocp driver";
 	err = pci_register_driver(&ptp_ocp_driver);
 	if (err)
-		goto out_register;
+		goto out_class;
 
 	return 0;
 
-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 +6059,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();

31/3 2026/08/11 05:55 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28107
							output: 220
							thoughts: 1734
						

Thoughts:
**Uninitialized Memory Risk Assessment**

Okay, let's break down this patch series. We're adding a new LED driver, `leds-is32fl3207.c`, and integrating it into `ptp_ocp.c` via I2C. We've also got some changes to `i2c-mux.c` to support software nodes. My primary concern here, as always, is potential uninitialized memory exposure. Let's see if we've got any issues.

First, `drivers/i2c/i2c-mux.c`. The new `i2c_mux_get_channel_swnode` function looks straightforward, and the modification to `i2c_mux_add_adapter` using it and `device_add_software_node` doesn't scream any red flags. I don't see anything that would introduce an uninitialized memory risk in this section.

Now for the main course: `drivers/leds/rgb/leds-is32fl3207.c`. This is where the IS32FL3207 driver resides. Alright, let's examine the structure allocations. `is32fl3207_probe` uses `devm_kzalloc` for `ddata`, which is good because it zero-initializes the memory. Then, `is32fl3207_register_multicolor` also employs `devm_kcalloc` to allocate `subleds`, so that's also initialized.  Looking into the `is32fl3207_hw_init` function, we have zero-initialized arrays `u8 scaling[IS32FL3207_NUM_CHANNELS] = { }` and `u8 pwm[2 * IS32FL3207_NUM_CHANNELS] = { }`. Also in `is32fl3207_prepare_reset` the `u8 scaling[IS32FL3207_NUM_CHANNELS] = { }` is zero initialized. All structures and variables used here are properly initialized. So far, so good.

Next, the `drivers/ptp/ptp_ocp.c` file. The function `ptp_ocp_i2c_init_nodes` allocates the `topology` structure using `devm_kzalloc`, which again, ensures zero-initialization. That's a huge relief. Then, the code populates `topology->mux_props`, `topology->channel_props`, etc. using macros. The `struct ptp_ocp_i2c_topology` itself seems extensive, but the allocation and the use of the `PROPERTY_ENTRY_U32` macro suggest careful handling of initialization. We aren't sending any of this raw struct memory to userspace which is great.

Let's quickly check `ptp_ocp_read_eeprom`. We are reading from a nvmem into `bp` fields. No uninitialized memory is being leaked. Next, the call to `ptp_ocp_read_eeprom` from `ptp_ocp_devlink_info_get` checks and calls to read the EEPROM data. Still, no issues with potentially uninitialized data being exposed. Also, `serialnum_show` reads the EEPROM data, and then prints `bp->serial`.

In conclusion, after this detailed review, I'm comfortable saying that this patch series does not introduce any uninitialized memory risks. All of the allocations are using `devm_kzalloc` or `devm_kcalloc` or initialized as local variables and all data sent to userspace is properly initialized, so I'm not seeing any issues. I will set the `NeedsKMSAN` flag to `false`.



32/3 2026/08/11 05:55 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch series introduces a new LED driver (`leds-is32fl3207.c`) and modifies the `ptp_ocp` driver to use it via I2C. It also adds software node support to `i2c-mux.c`. All memory allocations introduced by these changes use zero-initializing functions (`devm_kzalloc`, `devm_kcalloc`) or are explicitly zero-initialized on the stack (e.g., `u8 scaling[...] = { };`). The changes do not introduce any new kernel structures that are copied to user space, nor do they alter control flow or bounds checking in a way that could expose uninitialized memory. Any potential bugs introduced by these changes, such as out-of-bounds accesses or use-after-free, would be effectively caught by standard KASAN and other standard bug detectors. Therefore, a dedicated KMSAN fuzzing session is not justified.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch series introduces a new LED driver (`leds-is32fl3207.c`) and modifies the `ptp_ocp` driver to use it via I2C. It also adds software node support to `i2c-mux.c`. All memory allocations introduced by these changes use zero-initializing functions (`devm_kzalloc`, `devm_kcalloc`) or are explicitly zero-initialized on the stack (e.g., `u8 scaling[...] = { };`). The changes do not introduce any new kernel structures that are copied to user space, nor do they alter control flow or bounds checking in a way that could expose uninitialized memory. Any potential bugs introduced by these changes, such as out-of-bounds accesses or use-after-free, would be effectively caught by standard KASAN and other standard bug detectors. Therefore, a dedicated KMSAN fuzzing session is not justified.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)